From 8a5a7c6fe67c806ddc8caa6a54a6b5d85cacec30 Mon Sep 17 00:00:00 2001 From: Chia-Hsiang Chang Date: Wed, 6 May 2026 17:26:42 -0700 Subject: [PATCH 1/9] feat: each instance simulates independently --- passes/sat/sim.cc | 63 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index 033fb9e8050..dddb8250748 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -128,6 +128,8 @@ struct SimShared bool serious_asserts = false; bool fst_noinit = false; bool initstate = true; + // when true, top SimInstance does not recurse into child instances + bool blackbox_children = false; }; void zinit(Const &v) @@ -309,6 +311,13 @@ struct SimInstance in_parent_drivers.emplace(sig[i], parent->sigmap(instance->getPort(wire->name)[i])); } } + + // With -bb, cut the input side of every non-top instance's boundary by sourcing port_input wires from VCD + if (shared->blackbox_children && shared->fst && parent != nullptr && wire->port_input) { + auto it = fst_handles.find(wire); + if (it != fst_handles.end()) + fst_inputs[wire] = it->second; + } } memories = Mem::get_all_memories(module); @@ -332,6 +341,20 @@ struct SimInstance dirty_children.insert(new SimInstance(shared, scope + "." + RTLIL::unescape_id(cell->name), mod, cell, this)); } + // With -bb, cut the child-output side of the boundary by sourcing each parent-side wire from VCD + if (mod != nullptr && shared->blackbox_children && shared->fst) { + for (auto &conn : cell->connections()) { + Wire *port = mod->wire(conn.first); + if (!port || !port->port_output) continue; + for (auto bit : sigmap(conn.second)) { + if (bit.wire == nullptr) continue; + auto it = fst_handles.find(bit.wire); + if (it != fst_handles.end()) + fst_inputs[bit.wire] = it->second; + } + } + } + for (auto &port : cell->connections()) { if (cell->input(port.first)) for (auto bit : sigmap(port.second)) { @@ -479,7 +502,7 @@ struct SimInstance log_assert(GetSize(sig) <= GetSize(value)); for (int i = 0; i < GetSize(sig); i++) - if (value[i] != State::Sa && state_nets.at(sig[i]) != value[i]) { + if (value[i] != State::Sa && state_nets.at(sig[i]) != value[i] && ) { state_nets.at(sig[i]) = value[i]; dirty_bits.insert(sig[i]); did_something = true; @@ -559,11 +582,14 @@ struct SimInstance if (children.count(cell)) { auto child = children.at(cell); - for (auto &conn: cell->connections()) - if (cell->input(conn.first) && GetSize(conn.second)) { - Const value = get_state(conn.second); - child->set_state(child->module->wire(conn.first), value); - } + // Under -bb the child's input ports are VCD-driven; skip the parent->child copy that would overwrite them + if (!shared->blackbox_children) { + for (auto &conn: cell->connections()) + if (cell->input(conn.first) && GetSize(conn.second)) { + Const value = get_state(conn.second); + child->set_state(child->module->wire(conn.first), value); + } + } dirty_children.insert(child); return; } @@ -667,6 +693,11 @@ struct SimInstance queue_cells.swap(dirty_cells); + // Under -bb the parent never dirties its children, so seed every child to keep the recursion alive + if (shared->blackbox_children) + for (auto &it : children) + dirty_children.insert(it.second); + while (1) { for (auto bit : dirty_bits) @@ -695,11 +726,13 @@ struct SimInstance update_memory(memid); dirty_memories.clear(); - for (auto wire : queue_outports) - if (instance->hasPort(wire->name)) { - Const value = get_state(wire); - parent->set_state(instance->getPort(wire->name), value); - } + // Under -bb the parent's wire is VCD-driven; skip the push-up that would overwrite it + if (!shared->blackbox_children) + for (auto wire : queue_outports) + if (instance->hasPort(wire->name)) { + Const value = get_state(wire); + parent->set_state(instance->getPort(wire->name), value); + } queue_outports.clear(); @@ -3048,6 +3081,10 @@ struct SimPass : public Pass { log(" -reg\n"); log(" overwrite register state from VCD file every cycle\n"); log("\n"); + log(" -bb\n"); + 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"); } @@ -3239,6 +3276,10 @@ struct SimPass : public Pass { reg_overwrite = true; continue; } + if (args[argidx] == "-bb") { + worker.blackbox_children = true; + continue; + } break; } extra_args(args, argidx, design); From a3d81a6d3f51f907ac160fff28f5906927068110 Mon Sep 17 00:00:00 2001 From: Chia-Hsiang Chang Date: Wed, 6 May 2026 18:27:57 -0700 Subject: [PATCH 2/9] refactor --- passes/sat/sim.cc | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index dddb8250748..661e95a2ffb 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -128,7 +128,6 @@ struct SimShared bool serious_asserts = false; bool fst_noinit = false; bool initstate = true; - // when true, top SimInstance does not recurse into child instances bool blackbox_children = false; }; @@ -312,7 +311,7 @@ struct SimInstance } } - // With -bb, cut the input side of every non-top instance's boundary by sourcing port_input wires from VCD + // With -bb, source port_input wires from VCD if (shared->blackbox_children && shared->fst && parent != nullptr && wire->port_input) { auto it = fst_handles.find(wire); if (it != fst_handles.end()) @@ -341,7 +340,7 @@ struct SimInstance dirty_children.insert(new SimInstance(shared, scope + "." + RTLIL::unescape_id(cell->name), mod, cell, this)); } - // With -bb, cut the child-output side of the boundary by sourcing each parent-side wire from VCD + // With -bb, source each parent-side child-output wire from VCD if (mod != nullptr && shared->blackbox_children && shared->fst) { for (auto &conn : cell->connections()) { Wire *port = mod->wire(conn.first); @@ -502,7 +501,7 @@ struct SimInstance log_assert(GetSize(sig) <= GetSize(value)); for (int i = 0; i < GetSize(sig); i++) - if (value[i] != State::Sa && state_nets.at(sig[i]) != value[i] && ) { + if (value[i] != State::Sa && state_nets.at(sig[i]) != value[i]) { state_nets.at(sig[i]) = value[i]; dirty_bits.insert(sig[i]); did_something = true; @@ -582,7 +581,7 @@ struct SimInstance if (children.count(cell)) { auto child = children.at(cell); - // Under -bb the child's input ports are VCD-driven; skip the parent->child copy that would overwrite them + // With -bb, skip the parent->child copy that would overwrite child's input ports if (!shared->blackbox_children) { for (auto &conn: cell->connections()) if (cell->input(conn.first) && GetSize(conn.second)) { @@ -693,7 +692,7 @@ struct SimInstance queue_cells.swap(dirty_cells); - // Under -bb the parent never dirties its children, so seed every child to keep the recursion alive + // With -bb, the parent never dirties its children, so add children to the queue manually if (shared->blackbox_children) for (auto &it : children) dirty_children.insert(it.second); @@ -726,7 +725,7 @@ struct SimInstance update_memory(memid); dirty_memories.clear(); - // Under -bb the parent's wire is VCD-driven; skip the push-up that would overwrite it + // With -bb, skip pushing up parent's wire that would overwrite it if (!shared->blackbox_children) for (auto wire : queue_outports) if (instance->hasPort(wire->name)) { From 57c3e484e37c8527febde8c2c2f9442e38cc47d6 Mon Sep 17 00:00:00 2001 From: Chia-Hsiang Chang Date: Fri, 8 May 2026 18:54:18 -0700 Subject: [PATCH 3/9] feat: parallel resim with chunks and bb --- passes/sat/sim.cc | 324 ++++++++++++++++++++++++++++++---------------- 1 file changed, 210 insertions(+), 114 deletions(-) diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index 661e95a2ffb..e34d3fb2c72 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -129,6 +129,8 @@ struct SimShared bool fst_noinit = false; bool initstate = true; bool blackbox_children = false; + pool 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 tops; + // Convenience alias kept in sync with tops.front() SimInstance *top = nullptr; + // instance entries: (module name, VCD scope) + std::vector> instance_specs; + // Resolved Module* per instance_specs entry + std::vector instance_modules; pool 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 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 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 (debug) - log("\n-- ph2 (initialize) --\n"); + if (!top->update_ph2(false, true)) + break; + } - if (!top->update_ph2(false, true)) - break; + if (debug) + log("\n-- ph3 (initialize) --\n"); + top->update_ph3(true); } - - if (debug) - log("\n-- ph3 (initialize) --\n"); - top->update_ph3(true); } void set_inports(pool 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 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); + // 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()); - for (auto wire : topmod->wires()) { + tops.push_back(new SimInstance(this, scope, topmod)); + top = tops.back(); - // 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))); + 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 &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 &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 :\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 \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 :: 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 ':' (got '%s')\n", spec.c_str()); + worker.instance_specs.emplace_back(spec.substr(0, pos), spec.substr(pos + 1)); + continue; + } + // -clk-period : 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 ).\n"); if (worker.sim_filename.empty()) worker.run(top_mod, cycle_width, numcycles); else { From 7b2e63ac5b9a8bad7b2e6a820dbf7afd708728c8 Mon Sep 17 00:00:00 2001 From: Chia-Hsiang Chang Date: Mon, 11 May 2026 16:09:02 -0700 Subject: [PATCH 4/9] refactor: style fix --- passes/sat/sim.cc | 82 ++++++++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index e34d3fb2c72..b27ef8d71d7 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -658,8 +658,11 @@ struct SimInstance if (cell->type == ID($print)) return; - if (shared->blackbox_children) - return; + if (shared->blackbox_children) { + Module *m = module->design->module(cell->type); + if (m && (m->get_blackbox_attribute(true) || shared->instance_root_modules.count(m->name))) + return; + } log_error("Unsupported cell type: %s (%s.%s)\n", log_id(cell->type), log_id(module), log_id(cell)); } @@ -1446,12 +1449,12 @@ struct SimWorker : SimShared } } - void register_output_step(int t) + void register_output_step(int time) { std::map data; - for (auto top : tops) - top->register_output_step_values(&data); - output_data.emplace_back(t, data); + for (auto t : tops) + t->register_output_step_values(&data); + output_data.emplace_back(time, data); } void write_output_files() @@ -1475,8 +1478,8 @@ struct SimWorker : SimShared if (writeback) { pool wbmods; - for (auto top : tops) - top->writeback(wbmods); + for (auto t : tops) + t->writeback(wbmods); } } @@ -1484,49 +1487,49 @@ struct SimWorker : SimShared { if (gclk) step += 1; - - for (auto top : tops) { + + for (auto t : tops) { while (1) { if (debug) log("\n-- ph1 --\n"); - top->update_ph1(); + t->update_ph1(); if (debug) log("\n-- ph2 --\n"); - if (!top->update_ph2(gclk)) + if (!t->update_ph2(gclk)) break; } if (debug) log("\n-- ph3 --\n"); - top->update_ph3(gclk); + t->update_ph3(gclk); } } void initialize_stable_past() { - for (auto top : tops) { + for (auto t : tops) { while (1) { if (debug) log("\n-- ph1 (initialize) --\n"); - top->update_ph1(); + t->update_ph1(); if (debug) log("\n-- ph2 (initialize) --\n"); - if (!top->update_ph2(false, true)) + if (!t->update_ph2(false, true)) break; } if (debug) log("\n-- ph3 (initialize) --\n"); - top->update_ph3(true); + t->update_ph3(true); } } @@ -1547,7 +1550,7 @@ struct SimWorker : SimShared { log_assert(tops.empty()); tops.push_back(new SimInstance(this, scope, topmod)); - top = tops.back(); + top = tops.front(); register_signals(); if (debug) @@ -1640,7 +1643,7 @@ struct SimWorker : SimShared log("Using scope: \"%s\"\n", scope.c_str()); tops.push_back(new SimInstance(this, scope, topmod)); - top = tops.back(); + top = tops.front(); for (auto portname : clock) { @@ -1748,11 +1751,13 @@ struct SimWorker : SimShared fst->getTimescaleString()); // Apply per-cycle FST values to every root bool did_something = false; - for (auto t : tops) if (t->setInputs()) did_something = true; + for (auto t : tops) + did_something |= t->setInputs(); if (initial) { if (!fst_noinit) - for (auto t : tops) if (t->setInitState()) did_something = true; + for (auto t : tops) + did_something |= t->setInitState(); initialize_stable_past(); initial = false; } @@ -1762,7 +1767,8 @@ struct SimWorker : SimShared // Override register state from VCD every cycle if (reg_overwrite) { bool diverged = false; - for (auto t : tops) if (t->setRegisters(time)) diverged = true; + for (auto t : tops) + diverged |= t->setRegisters(time); if (diverged) update(true); } @@ -1770,8 +1776,10 @@ struct SimWorker : SimShared last_time = time; bool status = false; - for (auto t : tops) status |= t->checkSignals(); - if (status) log_error("Signal difference\n"); + for (auto t : tops) + status |= t->checkSignals(); + if (status) + log_error("Signal difference\n"); cycle++; }); @@ -1808,7 +1816,7 @@ struct SimWorker : SimShared log_error("For multiclock witness there should be no clock signal.\n"); tops.push_back(new SimInstance(this, scope, topmod)); - top = tops.back(); + top = tops.front(); register_signals(); std::ifstream mf(map_filename); @@ -1963,7 +1971,7 @@ struct SimWorker : SimShared int state = 0; int cycle = 0; tops.push_back(new SimInstance(this, scope, topmod)); - top = tops.back(); + top = tops.front(); register_signals(); int prev_cycle = 0; int curr_cycle = 0; @@ -2214,7 +2222,7 @@ struct SimWorker : SimShared ReadWitness yw(sim_filename); tops.push_back(new SimInstance(this, scope, topmod)); - top = tops.back(); + top = tops.front(); register_signals(); YwHierarchy hierarchy = prepare_yw_hierarchy(yw); @@ -2556,7 +2564,7 @@ struct VCDWriter : public OutputWriter vcdfile << stringf("$timescale 1%s $end\n", worker->timescale); // VCD writer emits one root subtree per top - for (auto top : worker->tops) top->write_output_header( + for (auto t : worker->tops) t->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) { @@ -2750,10 +2758,10 @@ struct AnnotateActivity : public OutputWriter { double frequency = 1.0 / clk_period; std::stringstream ss; ss << std::setprecision(4) << real_timescale; - 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()); + for (auto t : worker->tops) { + t->module->set_string_attribute("$FREQUENCY", std::to_string(frequency)); + t->module->set_string_attribute("$DURATION", std::to_string(max_time)); + t->module->set_string_attribute("$TIMESCALE", ss.str()); } if (worker->debug) { log_debug("Max time: %d", max_time); @@ -2764,7 +2772,7 @@ struct AnnotateActivity : public OutputWriter { double totalDuty = 0.0f; // TODO make this debug code less messy and more readable. - for (auto top : worker->tops) top->write_output_header( + for (auto t : worker->tops) t->write_output_header( [&](IdString name) { if (worker->debug) log_debug("module %s", log_id(name)); @@ -2852,8 +2860,8 @@ struct FSTWriter : public OutputWriter fstWriterSetPackType(fstfile, FST_WR_PT_FASTLZ); fstWriterSetRepackOnClose(fstfile, 1); - - for (auto top : worker->tops) top->write_output_header( + + for (auto t : worker->tops) t->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) { @@ -3347,7 +3355,9 @@ struct SimPass : public Pass { worker.blackbox_children = true; continue; } - // -instance :: register a multi-root spec; resolved to Module* below + // -instance :: register a multi-root spec; resolved to Module* below. + // Split on the last ':' so that hierarchical scopes (which use '.' as separator) + // are kept intact in the scope half. if (args[argidx] == "-instance" && argidx+1 < args.size()) { std::string spec = args[++argidx]; size_t pos = spec.find_last_of(':'); From a00bb2b80beaed3f40297e2d583793185b202a10 Mon Sep 17 00:00:00 2001 From: Chia-Hsiang Chang Date: Mon, 11 May 2026 18:09:06 -0700 Subject: [PATCH 5/9] fix: don't recursively update children --- passes/sat/sim.cc | 45 +++++++++++++++------------------------------ 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index b27ef8d71d7..7533023d462 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -312,13 +312,6 @@ struct SimInstance in_parent_drivers.emplace(sig[i], parent->sigmap(instance->getPort(wire->name)[i])); } } - - // With -bb, source port_input wires from VCD - if (shared->blackbox_children && shared->fst && parent != nullptr && wire->port_input) { - auto it = fst_handles.find(wire); - if (it != fst_handles.end()) - fst_inputs[wire] = it->second; - } } memories = Mem::get_all_memories(module); @@ -338,9 +331,10 @@ struct SimInstance { Module *mod = module->design->module(cell->type); - // Skip recursion into blackbox children, and into cells whose type is also a multi-root top + // In -bb mode every parent<->child boundary is a cut, so don't recurse at all. if (mod != nullptr && !mod->get_blackbox_attribute(true) - && !shared->instance_root_modules.count(mod->name)) { + && !shared->instance_root_modules.count(mod->name) + && !shared->blackbox_children) { dirty_children.insert(new SimInstance(shared, scope + "." + RTLIL::unescape_id(cell->name), mod, cell, this)); } @@ -585,14 +579,11 @@ struct SimInstance if (children.count(cell)) { auto child = children.at(cell); - // With -bb, skip the parent->child copy that would overwrite child's input ports - if (!shared->blackbox_children) { - for (auto &conn: cell->connections()) - if (cell->input(conn.first) && GetSize(conn.second)) { - Const value = get_state(conn.second); - child->set_state(child->module->wire(conn.first), value); - } - } + for (auto &conn: cell->connections()) + if (cell->input(conn.first) && GetSize(conn.second)) { + Const value = get_state(conn.second); + child->set_state(child->module->wire(conn.first), value); + } dirty_children.insert(child); return; } @@ -658,9 +649,10 @@ struct SimInstance if (cell->type == ID($print)) return; + // If the cell is a blackbox child of an instance root module, skip it if (shared->blackbox_children) { Module *m = module->design->module(cell->type); - if (m && (m->get_blackbox_attribute(true) || shared->instance_root_modules.count(m->name))) + if (m) return; } @@ -702,11 +694,6 @@ struct SimInstance queue_cells.swap(dirty_cells); - // With -bb, the parent never dirties its children, so add children to the queue manually - if (shared->blackbox_children) - for (auto &it : children) - dirty_children.insert(it.second); - while (1) { for (auto bit : dirty_bits) @@ -735,13 +722,11 @@ struct SimInstance update_memory(memid); dirty_memories.clear(); - // With -bb, skip pushing up parent's wire that would overwrite it - if (!shared->blackbox_children) - for (auto wire : queue_outports) - if (instance->hasPort(wire->name)) { - Const value = get_state(wire); - parent->set_state(instance->getPort(wire->name), value); - } + for (auto wire : queue_outports) + if (instance->hasPort(wire->name)) { + Const value = get_state(wire); + parent->set_state(instance->getPort(wire->name), value); + } queue_outports.clear(); From 9e6d66d74e7ec1944be6f724d3fe28fd9ba14c62 Mon Sep 17 00:00:00 2001 From: Chia-Hsiang Chang Date: Tue, 12 May 2026 00:01:29 -0700 Subject: [PATCH 6/9] chore: log error when no scope found --- passes/sat/sim.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index 7533023d462..be689d2184d 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -1612,7 +1612,11 @@ struct SimWorker : SimShared 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; + if (id == 0) { + log_error("Can't find port '%s' on module '%s' in FST.\n", + (iscope + "." + RTLIL::unescape_id(wire->name)).c_str(), RTLIL::unescape_id(m->name).c_str()); + } + t->fst_inputs[wire] = id; } t->addAdditionalInputs(); } From eb83c40d2451cbd58a98d29f54d6e1b01654f283 Mon Sep 17 00:00:00 2001 From: Chia-Hsiang Chang Date: Tue, 12 May 2026 00:07:00 -0700 Subject: [PATCH 7/9] fix: check the cell is a child node before skipping --- passes/sat/sim.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index be689d2184d..ffa141d65e2 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -651,8 +651,8 @@ struct SimInstance // If the cell is a blackbox child of an instance root module, skip it if (shared->blackbox_children) { - Module *m = module->design->module(cell->type); - if (m) + Module *mod = module->design->module(cell->type); + if (shared->instance_root_modules.count(mod->name)) return; } @@ -1614,7 +1614,7 @@ struct SimWorker : SimShared fstHandle id = fst->getHandle(iscope + "." + RTLIL::unescape_id(wire->name)); if (id == 0) { log_error("Can't find port '%s' on module '%s' in FST.\n", - (iscope + "." + RTLIL::unescape_id(wire->name)).c_str(), RTLIL::unescape_id(m->name).c_str()); + (iscope + "." + RTLIL::unescape_id(wire->name)).c_str(), log_id(m)); } t->fst_inputs[wire] = id; } From 0dcfe5cd4bc3b211dcbde95e5a964ed07286f9d8 Mon Sep 17 00:00:00 2001 From: Chia-Hsiang Chang Date: Tue, 12 May 2026 00:21:25 -0700 Subject: [PATCH 8/9] chore: add comments and log --- passes/sat/sim.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index ffa141d65e2..a5941f2b3c4 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -1602,6 +1602,10 @@ struct SimWorker : SimShared // Multi-root mode: instance_modules was resolved in execute() alongside instance_specs if (!instance_modules.empty()) { + // In multi-root mode, each instance has its own clock pin and we drive every port_input + // from FST, so we can't honor user-supplied clock names here. + if (!clock.empty() || !clockn.empty()) + log_warning("-clock/-clockn are ignored with -instance; clocks are driven directly from the FST sample stream.\n"); for (size_t i = 0; i < instance_modules.size(); i++) { const std::string &iscope = instance_specs[i].second; Module *m = instance_modules[i]; From 67373542aeabbe59ce83e041f2bdbc611ffb5dff Mon Sep 17 00:00:00 2001 From: Chia-Hsiang Chang Date: Tue, 12 May 2026 00:32:45 -0700 Subject: [PATCH 9/9] fix: add guard to avoid crash on null-pointer dereference --- passes/sat/sim.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index a5941f2b3c4..8ab3efa0920 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -652,7 +652,7 @@ struct SimInstance // If the cell is a blackbox child of an instance root module, skip it if (shared->blackbox_children) { Module *mod = module->design->module(cell->type); - if (shared->instance_root_modules.count(mod->name)) + if (mod && shared->instance_root_modules.count(mod->name)) return; }