verilator/test_regress/t/t_wrapper_clone.cpp
Yinan Xu b4b74d72f0
Add prepareClone and atClone APIs for Verilated models (#3503) (#4444)
This API is used if the user copies the process using `fork`
and similar OS-level mechanisms. The `at_clone` member function
ensures that all model-allocated resources are re-allocated, such
that the copied child process/model can simulate correctly.

A typical allocated resource is the thread pool, which every model
has its own pool.
2023-08-30 07:02:55 -04:00

90 lines
2.8 KiB
C++

//
// DESCRIPTION: Verilator: Verilog Test module for prepareClone/atClone APIs
//
// This file ONLY is placed into the Public Domain, for any use,
// without warranty, 2023 by Yinan Xu.
// SPDX-License-Identifier: CC0-1.0
#include <verilated.h>
#include <unistd.h>
#include <sys/wait.h>
// These require the above. Comment prevents clang-format moving them
#include "TestCheck.h"
#include VM_PREFIX_INCLUDE
double sc_time_stamp() { return 0; }
// Note: Since the pthread_atfork API accepts only function pointers,
// we are using a static variable for the TOP just for a simple example.
// Without using the pthread_atfork API, the user can instead manually call
// prepareClone and atClone before and after calling fork, and topp can be
// allocated dynamically.
static VM_PREFIX* topp = nullptr;
static auto prepareClone = []() { topp->prepareClone(); };
static auto atClone = []() { topp->atClone(); };
void single_cycle(VM_PREFIX* topp) {
topp->clock = 1;
topp->eval();
topp->clock = 0;
topp->eval();
}
int main(int argc, char** argv) {
// We disable the buffering for stdout in this test.
// Redirecting the stdout to files with buffering causes duplicated stdout
// outputs in both parent and child processes, even if they are actually
// called before the fork.
setvbuf(stdout, nullptr, _IONBF, 0);
VerilatedContext* contextp = new VerilatedContext;
topp = new VM_PREFIX{contextp};
// To avoid resource leaks, prepareClone must be called before fork to
// free all the allocated resources. Though this would bring performance
// overhead to the parent process, we believe that fork should not be
// called frequently, and the overhead is minor compared to simulation.
pthread_atfork(prepareClone, atClone, atClone);
// If you care about critical performance, prepareClone can be avoided,
// with atClone being called only at the child process, as follows.
// It has the same functionality as the previous one, but has memory leaks.
// According to the sanitizer, 288 bytes are leaked for one fork call.
// pthread_atfork(nullptr, nullptr, atClone);
topp->reset = 1;
topp->is_parent = 0;
for (int i = 0; i < 5; i++) { single_cycle(topp); }
topp->reset = 0;
while (!contextp->gotFinish()) {
single_cycle(topp);
if (topp->do_clone) {
const int pid = fork();
if (pid < 0) {
printf("fork failed\n");
} else if (pid == 0) {
printf("child: here we go\n");
} else {
while (wait(nullptr) > 0)
;
printf("parent: here we go\n");
topp->is_parent = 1;
}
}
}
topp->final();
VL_DO_DANGLING(delete topp, topp);
VL_DO_DANGLING(delete contextp, contextp);
return 0;
}