feat: parallel resim with chunks and bb

This commit is contained in:
Chia-Hsiang Chang 2026-05-08 18:54:18 -07:00
parent a3d81a6d3f
commit 57c3e484e3
1 changed files with 212 additions and 116 deletions

View File

@ -129,6 +129,8 @@ struct SimShared
bool fst_noinit = false;
bool initstate = true;
bool blackbox_children = false;
pool<IdString> instance_root_modules;
double clk_period_override = 0.0;
};
void zinit(Const &v)
@ -336,7 +338,9 @@ struct SimInstance
{
Module *mod = module->design->module(cell->type);
if (mod != nullptr) {
// Skip recursion into blackbox children, and into cells whose type is also a multi-root top
if (mod != nullptr && !mod->get_blackbox_attribute(true)
&& !shared->instance_root_modules.count(mod->name)) {
dirty_children.insert(new SimInstance(shared, scope + "." + RTLIL::unescape_id(cell->name), mod, cell, this));
}
@ -654,6 +658,9 @@ struct SimInstance
if (cell->type == ID($print))
return;
if (shared->blackbox_children)
return;
log_error("Unsupported cell type: %s (%s.%s)\n", log_id(cell->type), log_id(module), log_id(cell));
}
@ -1409,7 +1416,13 @@ struct SimInstance
struct SimWorker : SimShared
{
std::vector<SimInstance *> tops;
// Convenience alias kept in sync with tops.front()
SimInstance *top = nullptr;
// instance entries: (module name, VCD scope)
std::vector<std::pair<std::string, std::string>> instance_specs;
// Resolved Module* per instance_specs entry
std::vector<RTLIL::Module *> instance_modules;
pool<IdString> clock, clockn, reset, resetn;
std::string timescale;
std::string sim_filename;
@ -1421,20 +1434,23 @@ struct SimWorker : SimShared
~SimWorker()
{
outputfiles.clear();
delete top;
for (auto t : tops) delete t;
}
void register_signals()
{
next_output_id = 1;
top->register_signals(top->shared->next_output_id);
top->build_registers();
for (auto t : tops) {
t->register_signals(t->shared->next_output_id);
t->build_registers();
}
}
void register_output_step(int t)
{
std::map<int,Const> data;
top->register_output_step_values(&data);
for (auto top : tops)
top->register_output_step_values(&data);
output_data.emplace_back(t, data);
}
@ -1456,10 +1472,11 @@ struct SimWorker : SimShared
}
for(auto& writer : outputfiles)
writer->write(use_signal);
if (writeback) {
pool<Module*> wbmods;
top->writeback(wbmods);
for (auto top : tops)
top->writeback(wbmods);
}
}
@ -1467,47 +1484,50 @@ struct SimWorker : SimShared
{
if (gclk)
step += 1;
for (auto top : tops) {
while (1)
{
if (debug)
log("\n-- ph1 --\n");
while (1)
{
if (debug)
log("\n-- ph1 --\n");
top->update_ph1();
top->update_ph1();
if (debug)
log("\n-- ph2 --\n");
if (!top->update_ph2(gclk))
break;
}
if (debug)
log("\n-- ph2 --\n");
log("\n-- ph3 --\n");
if (!top->update_ph2(gclk))
break;
top->update_ph3(gclk);
}
if (debug)
log("\n-- ph3 --\n");
top->update_ph3(gclk);
}
void initialize_stable_past()
{
for (auto top : tops) {
while (1)
{
if (debug)
log("\n-- ph1 (initialize) --\n");
while (1)
{
if (debug)
log("\n-- ph1 (initialize) --\n");
top->update_ph1();
top->update_ph1();
if (debug)
log("\n-- ph2 (initialize) --\n");
if (!top->update_ph2(false, true))
break;
}
if (debug)
log("\n-- ph2 (initialize) --\n");
if (!top->update_ph2(false, true))
break;
log("\n-- ph3 (initialize) --\n");
top->update_ph3(true);
}
if (debug)
log("\n-- ph3 (initialize) --\n");
top->update_ph3(true);
}
void set_inports(pool<IdString> ports, State value)
@ -1525,8 +1545,9 @@ struct SimWorker : SimShared
void run(Module *topmod, int cycle_width, int numcycles)
{
log_assert(top == nullptr);
top = new SimInstance(this, scope, topmod);
log_assert(tops.empty());
tops.push_back(new SimInstance(this, scope, topmod));
top = tops.back();
register_signals();
if (debug)
@ -1585,66 +1606,86 @@ struct SimWorker : SimShared
void run_cosim_fst(Module *topmod, int numcycles, int log_interval)
{
log_assert(top == nullptr);
log_assert(tops.empty());
fst = new FstData(sim_filename);
timescale = fst->getTimescaleString();
if (scope.empty()) {
scope = fst->autoScope(topmod);
if (scope.empty()) {
log_error("No scope found for module '%s'. Please specify -scope explicitly.\n",
RTLIL::unescape_id(topmod->name).c_str());
}
}
log("Using scope: \"%s\"\n", scope.c_str());
top = new SimInstance(this, scope, topmod);
register_signals();
std::vector<fstHandle> fst_clock;
for (auto portname : clock)
{
Wire *w = topmod->wire(portname);
if (!w)
log_error("Can't find port %s on module %s.\n", log_id(portname), log_id(top->module));
if (!w->port_input)
log_error("Clock port %s on module %s is not input.\n", log_id(portname), log_id(top->module));
fstHandle id = fst->getHandle(scope + "." + RTLIL::unescape_id(portname));
if (id==0)
log_error("Can't find port %s.%s in FST.\n", scope, log_id(portname));
fst_clock.push_back(id);
}
for (auto portname : clockn)
{
Wire *w = topmod->wire(portname);
if (!w)
log_error("Can't find port %s on module %s.\n", log_id(portname), log_id(top->module));
if (!w->port_input)
log_error("Clock port %s on module %s is not input.\n", log_id(portname), log_id(top->module));
fstHandle id = fst->getHandle(scope + "." + RTLIL::unescape_id(portname));
if (id==0)
log_error("Can't find port %s.%s in FST.\n", scope, log_id(portname));
fst_clock.push_back(id);
}
SigMap sigmap(topmod);
for (auto wire : topmod->wires()) {
// Populate fst_inputs for input ports
if (wire->port_input) {
fstHandle id = fst->getHandle(scope + "." + RTLIL::unescape_id(wire->name));
if (id != 0) {
// Case of a regular wire/reg
top->fst_inputs[wire] = id;
} else {
// Not found
log_error("Unable to find required '%s' signal in file\n",(scope + "." + RTLIL::unescape_id(wire->name)));
// Multi-root mode: instance_modules was resolved in execute() alongside instance_specs
if (!instance_modules.empty()) {
for (size_t i = 0; i < instance_modules.size(); i++) {
const std::string &iscope = instance_specs[i].second;
Module *m = instance_modules[i];
log("Using -instance %s at scope \"%s\"\n", instance_specs[i].first.c_str(), iscope.c_str());
SimInstance *t = new SimInstance(this, iscope, m);
tops.push_back(t);
// Drive every port_input from the FST
for (auto wire : m->wires()) {
if (!wire->port_input) continue;
fstHandle id = fst->getHandle(iscope + "." + RTLIL::unescape_id(wire->name));
if (id != 0) t->fst_inputs[wire] = id;
}
t->addAdditionalInputs();
}
top = tops.front();
} else {
if (scope.empty()) {
scope = fst->autoScope(topmod);
if (scope.empty()) {
log_error("No scope found for module '%s'. Please specify -scope explicitly.\n",
RTLIL::unescape_id(topmod->name).c_str());
}
}
log("Using scope: \"%s\"\n", scope.c_str());
tops.push_back(new SimInstance(this, scope, topmod));
top = tops.back();
for (auto portname : clock)
{
Wire *w = topmod->wire(portname);
if (!w)
log_error("Can't find port %s on module %s.\n", log_id(portname), log_id(top->module));
if (!w->port_input)
log_error("Clock port %s on module %s is not input.\n", log_id(portname), log_id(top->module));
fstHandle id = fst->getHandle(scope + "." + RTLIL::unescape_id(portname));
if (id==0)
log_error("Can't find port %s.%s in FST.\n", scope, log_id(portname));
fst_clock.push_back(id);
}
for (auto portname : clockn)
{
Wire *w = topmod->wire(portname);
if (!w)
log_error("Can't find port %s on module %s.\n", log_id(portname), log_id(top->module));
if (!w->port_input)
log_error("Clock port %s on module %s is not input.\n", log_id(portname), log_id(top->module));
fstHandle id = fst->getHandle(scope + "." + RTLIL::unescape_id(portname));
if (id==0)
log_error("Can't find port %s.%s in FST.\n", scope, log_id(portname));
fst_clock.push_back(id);
}
for (auto wire : topmod->wires()) {
// Populate fst_inputs for input ports
if (wire->port_input) {
fstHandle id = fst->getHandle(scope + "." + RTLIL::unescape_id(wire->name));
if (id != 0) {
// Case of a regular wire/reg
top->fst_inputs[wire] = id;
} else {
// Not found
log_error("Unable to find required '%s' signal in file\n",(scope + "." + RTLIL::unescape_id(wire->name)));
}
}
}
top->addAdditionalInputs();
}
top->addAdditionalInputs();
register_signals();
uint64_t startCount = 0;
uint64_t stopCount = 0;
@ -1705,10 +1746,13 @@ struct SimWorker : SimShared
cycle,
(unsigned long)time,
fst->getTimescaleString());
bool did_something = top->setInputs();
// Apply per-cycle FST values to every root
bool did_something = false;
for (auto t : tops) if (t->setInputs()) did_something = true;
if (initial) {
if (!fst_noinit) did_something |= top->setInitState();
if (!fst_noinit)
for (auto t : tops) if (t->setInitState()) did_something = true;
initialize_stable_past();
initial = false;
}
@ -1716,15 +1760,18 @@ struct SimWorker : SimShared
update(true);
// Override register state from VCD every cycle
if (reg_overwrite && top->setRegisters(time))
update(true);
if (reg_overwrite) {
bool diverged = false;
for (auto t : tops) if (t->setRegisters(time)) diverged = true;
if (diverged) update(true);
}
register_output_step(time);
last_time = time;
bool status = top->checkSignals();
if (status)
log_error("Signal difference\n");
bool status = false;
for (auto t : tops) status |= t->checkSignals();
if (status) log_error("Signal difference\n");
cycle++;
});
@ -1754,13 +1801,14 @@ struct SimWorker : SimShared
void run_cosim_aiger_witness(Module *topmod, int cycle_width)
{
log_assert(top == nullptr);
log_assert(tops.empty());
if (!multiclock && (clock.size()+clockn.size())==0)
log_error("Clock signal must be specified.\n");
if (multiclock && (clock.size()+clockn.size())>0)
log_error("For multiclock witness there should be no clock signal.\n");
top = new SimInstance(this, scope, topmod);
tops.push_back(new SimInstance(this, scope, topmod));
top = tops.back();
register_signals();
std::ifstream mf(map_filename);
@ -1902,7 +1950,7 @@ struct SimWorker : SimShared
void run_cosim_btor2_witness(Module *topmod, int cycle_width)
{
log_assert(top == nullptr);
log_assert(tops.empty());
if (!multiclock && (clock.size()+clockn.size())==0)
log_error("Clock signal must be specified.\n");
if (multiclock && (clock.size()+clockn.size())>0)
@ -1914,7 +1962,8 @@ struct SimWorker : SimShared
int state = 0;
int cycle = 0;
top = new SimInstance(this, scope, topmod);
tops.push_back(new SimInstance(this, scope, topmod));
top = tops.back();
register_signals();
int prev_cycle = 0;
int curr_cycle = 0;
@ -2164,7 +2213,8 @@ struct SimWorker : SimShared
ReadWitness yw(sim_filename);
top = new SimInstance(this, scope, topmod);
tops.push_back(new SimInstance(this, scope, topmod));
top = tops.back();
register_signals();
YwHierarchy hierarchy = prepare_yw_hierarchy(yw);
@ -2295,9 +2345,9 @@ struct SimWorker : SimShared
{
Wire *w = topmod->wire(portname);
if (!w)
log_error("Can't find port %s on module %s.\n", log_id(portname), log_id(top->module));
log_error("Can't find port %s on module %s.\n", log_id(portname), log_id(topmod));
if (!w->port_input)
log_error("Clock port %s on module %s is not input.\n", log_id(portname), log_id(top->module));
log_error("Clock port %s on module %s is not input.\n", log_id(portname), log_id(topmod));
fstHandle id = fst->getHandle(scope + "." + RTLIL::unescape_id(portname));
if (id==0)
log_error("Can't find port %s.%s in FST.\n", scope, log_id(portname));
@ -2308,9 +2358,9 @@ struct SimWorker : SimShared
{
Wire *w = topmod->wire(portname);
if (!w)
log_error("Can't find port %s on module %s.\n", log_id(portname), log_id(top->module));
log_error("Can't find port %s on module %s.\n", log_id(portname), log_id(topmod));
if (!w->port_input)
log_error("Clock port %s on module %s is not input.\n", log_id(portname), log_id(top->module));
log_error("Clock port %s on module %s is not input.\n", log_id(portname), log_id(topmod));
fstHandle id = fst->getHandle(scope + "." + RTLIL::unescape_id(portname));
if (id==0)
log_error("Can't find port %s.%s in FST.\n", scope, log_id(portname));
@ -2505,7 +2555,8 @@ struct VCDWriter : public OutputWriter
if (!worker->timescale.empty())
vcdfile << stringf("$timescale 1%s $end\n", worker->timescale);
worker->top->write_output_header(
// VCD writer emits one root subtree per top
for (auto top : worker->tops) top->write_output_header(
[this](IdString name) { vcdfile << stringf("$scope module %s $end\n", log_id(name)); },
[this]() { vcdfile << stringf("$upscope $end\n");},
[this,&use_signal](const char *name, int size, Wire *w, int id, bool is_reg) {
@ -2679,24 +2730,31 @@ struct AnnotateActivity : public OutputWriter {
log_debug("Timescale %e seconds extracted from converted VCD file", real_timescale);
}
// Compute clock period, find the highest toggling signal and compute its average period
// Compute clock period: prefer the user override (-clk-period), else auto-detect
// from the highest-toggling signal
double clk_period;
SignalActivityDataMap::iterator itr = dataMap.find(clk);
if (itr == dataMap.end()) { // if clock signal can't be identified, set frequency to 1GHz
log_warning("Clock signal not found, setting frequency to 1GHz...\n");
clk_period = 1.0 / 1.0e9;
if (worker->clk_period_override > 0) {
clk_period = worker->clk_period_override;
} else {
std::vector<double_t> &clktoggleCounts = itr->second.toggleCounts;
clk_period = real_timescale * (double)max_time / (clktoggleCounts[0] / 2.0);
SignalActivityDataMap::iterator itr = dataMap.find(clk);
if (itr == dataMap.end()) { // if clock signal can't be identified, set frequency to 1GHz
log_warning("Clock signal not found, setting frequency to 1GHz...\n");
clk_period = 1.0 / 1.0e9;
} else {
std::vector<double_t> &clktoggleCounts = itr->second.toggleCounts;
clk_period = real_timescale * (double)max_time / (clktoggleCounts[0] / 2.0);
}
}
log_flush();
double frequency = 1.0 / clk_period;
worker->top->module->set_string_attribute("$FREQUENCY", std::to_string(frequency));
worker->top->module->set_string_attribute("$DURATION", std::to_string(max_time));
std::stringstream ss;
ss << std::setprecision(4) << real_timescale;
worker->top->module->set_string_attribute("$TIMESCALE", ss.str());
for (auto top : worker->tops) {
top->module->set_string_attribute("$FREQUENCY", std::to_string(frequency));
top->module->set_string_attribute("$DURATION", std::to_string(max_time));
top->module->set_string_attribute("$TIMESCALE", ss.str());
}
if (worker->debug) {
log_debug("Max time: %d", max_time);
log_debug("Clock period: %f", clk_period);
@ -2706,7 +2764,7 @@ struct AnnotateActivity : public OutputWriter {
double totalDuty = 0.0f;
// TODO make this debug code less messy and more readable.
worker->top->write_output_header(
for (auto top : worker->tops) top->write_output_header(
[&](IdString name) {
if (worker->debug)
log_debug("module %s", log_id(name));
@ -2795,7 +2853,7 @@ struct FSTWriter : public OutputWriter
fstWriterSetPackType(fstfile, FST_WR_PT_FASTLZ);
fstWriterSetRepackOnClose(fstfile, 1);
worker->top->write_output_header(
for (auto top : worker->tops) top->write_output_header(
[this](IdString name) { fstWriterSetScope(fstfile, FST_ST_VCD_MODULE, stringf("%s",log_id(name)).c_str(), nullptr); },
[this]() { fstWriterSetUpscope(fstfile); },
[this,&use_signal](const char *name, int size, Wire *w, int id, bool is_reg) {
@ -3084,6 +3142,16 @@ struct SimPass : public Pass {
log(" cut every parent<->child boundary in the hierarchy and source both sides from the FST\n");
log(" (each instance simulates its own logic only; boundary signals come from VCD)\n");
log("\n");
log(" -instance <module_name>:<scope>\n");
log(" repeatable; each entry roots a SimInstance at the named module with the given\n");
log(" VCD scope. Skips traversal from design top; combine with -bb so each root simulates\n");
log(" only its own logic with all boundaries sourced from the FST.\n");
log("\n");
log(" -clk-period <seconds>\n");
log(" override the activity-factor clock period (default: auto-detect from highest-\n");
log(" toggling signal). Useful when each parallel worker sees only a partial design and\n");
log(" the local highest-toggling signal isn't the system clock.\n");
log("\n");
}
@ -3279,6 +3347,22 @@ struct SimPass : public Pass {
worker.blackbox_children = true;
continue;
}
// -instance <module>:<scope>: register a multi-root spec; resolved to Module* below
if (args[argidx] == "-instance" && argidx+1 < args.size()) {
std::string spec = args[++argidx];
size_t pos = spec.find_last_of(':');
if (pos == std::string::npos)
log_cmd_error("-instance expects '<module>:<scope>' (got '%s')\n", spec.c_str());
worker.instance_specs.emplace_back(spec.substr(0, pos), spec.substr(pos + 1));
continue;
}
// -clk-period <seconds>: bypass the highest-toggling-signal heuristic
if (args[argidx] == "-clk-period" && argidx+1 < args.size()) {
worker.clk_period_override = atof(args[++argidx].c_str());
if (worker.clk_period_override <= 0)
log_cmd_error("-clk-period expects a positive number of seconds.\n");
continue;
}
break;
}
extra_args(args, argidx, design);
@ -3289,7 +3373,16 @@ struct SimPass : public Pass {
Module *top_mod = nullptr;
if (design->full_selection()) {
// Resolve every -instance module
if (!worker.instance_specs.empty()) {
for (auto &spec : worker.instance_specs) {
Module *m = design->module(RTLIL::escape_id(spec.first));
if (!m)
log_cmd_error("Module '%s' (from -instance) not found in design.\n", spec.first.c_str());
worker.instance_modules.push_back(m);
worker.instance_root_modules.insert(m->name);
}
} else if (design->full_selection()) {
top_mod = design->top_module();
if (!top_mod)
@ -3302,6 +3395,9 @@ struct SimPass : public Pass {
}
worker.reg_overwrite = reg_overwrite;
// Multi-root (-instance) is only supported in FST/VCD cosim
if (!worker.instance_specs.empty() && worker.sim_filename.empty())
log_cmd_error("-instance requires FST/VCD cosim (-r <file.vcd|.fst>).\n");
if (worker.sim_filename.empty())
worker.run(top_mod, cycle_width, numcycles);
else {