From 57c3e484e37c8527febde8c2c2f9442e38cc47d6 Mon Sep 17 00:00:00 2001 From: Chia-Hsiang Chang Date: Fri, 8 May 2026 18:54:18 -0700 Subject: [PATCH] feat: parallel resim with chunks and bb --- passes/sat/sim.cc | 328 ++++++++++++++++++++++++++++++---------------- 1 file changed, 212 insertions(+), 116 deletions(-) diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index 661e95a2f..e34d3fb2c 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 (!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 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); - - 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 &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 {