From cb3853fca0dd85639af790e4f2d7c565387511c7 Mon Sep 17 00:00:00 2001 From: Stan Lee Date: Fri, 6 Mar 2026 09:51:45 -0800 Subject: [PATCH 01/18] improvements to autoscope --- kernel/fstdata.cc | 61 +++++++++-------------------------- kernel/fstdata.h | 2 +- passes/sat/sim.cc | 5 +-- passes/silimate/reg_rename.cc | 5 +-- 4 files changed, 22 insertions(+), 51 deletions(-) diff --git a/kernel/fstdata.cc b/kernel/fstdata.cc index 899447773..dd88590b2 100644 --- a/kernel/fstdata.cc +++ b/kernel/fstdata.cc @@ -397,41 +397,11 @@ int FstData::getWidth(fstHandle signal) } // Auto-discover scope from FST by finding the top module -std::string FstData::autoScope(Module *topmod) { +std::vector FstData::autoScope(Module *topmod) { - log("Auto-discovering scope from file...\n"); + log("Auto-discovering scopes from file...\n"); std::string top = RTLIL::unescape_id(topmod->name); - log("Available scopes:\n"); - std::set unique_scopes; - for (const auto& var : vars) { - unique_scopes.insert(var.scope); - } - for (const auto& scope : unique_scopes) { - log(" %s\n", scope.c_str()); - } - - // Option 1 - Instance based scope matching - // Will fail if the DUT instance name != the top module name - log("Trying instance-based scope matching...\n"); - for (const auto& var : vars) { - // Check if this scope ends with our top module - log_debug("Checking scope: %s\n", var.scope.c_str()); - if (var.scope == top || - var.scope.find("." + top) != std::string::npos) { - // Extract the full path up to (and including) the top module - size_t pos = var.scope.find(top); - if (pos != std::string::npos) { - std::string scope = var.scope.substr(0, pos + top.length()); - return scope; - } - } - } - - // Option 2 - Port based scope matching - // Matches based on exact port name matching of the top module - log("Trying port-based scope matching...\n"); - // Map top module port name to their bit widths (RTL reference point) dict top2widths; for (auto wire : topmod->wires()) { @@ -456,7 +426,7 @@ std::string FstData::autoScope(Module *topmod) { if (last_dot != std::string::npos) { // no '.' means no scope/signal extraction is possible std::string scope = name.substr(0, last_dot); std::string signal_name = name.substr(last_dot + 1); - + // Check that signal is in the top module and width matches if (top2widths.count(signal_name)) { int signal_width = getWidth(handle); @@ -467,24 +437,23 @@ std::string FstData::autoScope(Module *topmod) { } } - // Find scopes with exact matches - // If there is a tie, return the longest scope - std::string result = ""; + // Find scopes with exact matches and add to array + std::vector results; for (const auto& entry : scopes2matches) { int num_matches = entry.second; if (num_matches == GetSize(top2widths)) { std::string scope = entry.first; - if (result.empty() || scope.length() > result.length()) { - result = scope; - } + results.push_back(scope); } } - if (!result.empty()) { - return result; + if (results.empty()) { + log_warning("Could not auto-discover scope for module '%s'...\n", + RTLIL::unescape_id(topmod->name).c_str()); + } else { + log("Found %d scopes for module '%s':\n", GetSize(results), RTLIL::unescape_id(topmod->name).c_str()); + for (const auto& scope : results) { + log(" %s\n", scope.c_str()); + } } - - // No match found - log_warning("Could not auto-discover scope for module '%s'...\n", - RTLIL::unescape_id(topmod->name).c_str()); - return ""; + return results; } diff --git a/kernel/fstdata.h b/kernel/fstdata.h index f95806cca..01e175691 100644 --- a/kernel/fstdata.h +++ b/kernel/fstdata.h @@ -58,7 +58,7 @@ class FstData double getTimescale() { return timescale; } const char *getTimescaleString() { return timescale_str.c_str(); } int getWidth(fstHandle signal); - std::string autoScope(Module *topmod); + std::vector autoScope(Module *topmod); private: void extractVarNames(); diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index 113a15bdd..eb3f39480 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -1475,11 +1475,12 @@ struct SimWorker : SimShared fst = new FstData(sim_filename); timescale = fst->getTimescaleString(); if (scope.empty()) { - scope = fst->autoScope(topmod); - if (scope.empty()) { + scopes = fst->autoScope(topmod); + if (scopes.empty()) { log_error("No scope found for module '%s'. Please specify -scope explicitly.\n", RTLIL::unescape_id(topmod->name).c_str()); } + scope = scopes[0]; // Use the first scope found by default } log("Using scope: \"%s\"\n", scope.c_str()); diff --git a/passes/silimate/reg_rename.cc b/passes/silimate/reg_rename.cc index 5635b6869..05e5200fb 100644 --- a/passes/silimate/reg_rename.cc +++ b/passes/silimate/reg_rename.cc @@ -200,11 +200,12 @@ struct RegRenamePass : public Pass { try { FstData fst(vcd_filename); if (scope.empty()) { - scope = fst.autoScope(topmod); - if (scope.empty()) { + scopes = fst->autoScope(topmod); + if (scopes.empty()) { log_error("No scope found for module '%s'. Please specify -scope explicitly.\n", RTLIL::unescape_id(topmod->name).c_str()); } + scope = scopes[0]; // Use the first scope found by default } log("Using scope: \"%s\"\n", scope.c_str()); for (auto &var : fst.getVars()) { From c3a9a6d90e62e972a5757be6645ddbc059a39ec5 Mon Sep 17 00:00:00 2001 From: Stan Lee Date: Fri, 6 Mar 2026 09:59:51 -0800 Subject: [PATCH 02/18] fix --- passes/sat/sim.cc | 2 +- passes/silimate/reg_rename.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index eb3f39480..00b144037 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -1475,7 +1475,7 @@ struct SimWorker : SimShared fst = new FstData(sim_filename); timescale = fst->getTimescaleString(); if (scope.empty()) { - scopes = fst->autoScope(topmod); + std::vector scopes = fst->autoScope(topmod); if (scopes.empty()) { log_error("No scope found for module '%s'. Please specify -scope explicitly.\n", RTLIL::unescape_id(topmod->name).c_str()); diff --git a/passes/silimate/reg_rename.cc b/passes/silimate/reg_rename.cc index 05e5200fb..e5c7ffca2 100644 --- a/passes/silimate/reg_rename.cc +++ b/passes/silimate/reg_rename.cc @@ -200,7 +200,7 @@ struct RegRenamePass : public Pass { try { FstData fst(vcd_filename); if (scope.empty()) { - scopes = fst->autoScope(topmod); + std::vector scopes = fst.autoScope(topmod); if (scopes.empty()) { log_error("No scope found for module '%s'. Please specify -scope explicitly.\n", RTLIL::unescape_id(topmod->name).c_str()); From 20c1f3212f15b6b7c4bc161f1ceffbfbe74ad051 Mon Sep 17 00:00:00 2001 From: Stan Lee Date: Fri, 6 Mar 2026 10:23:31 -0800 Subject: [PATCH 03/18] cleaner with warning --- kernel/fstdata.cc | 10 ++++++++-- kernel/fstdata.h | 2 +- passes/sat/sim.cc | 5 ++--- passes/silimate/reg_rename.cc | 5 ++--- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/kernel/fstdata.cc b/kernel/fstdata.cc index dd88590b2..f2e73e9ec 100644 --- a/kernel/fstdata.cc +++ b/kernel/fstdata.cc @@ -397,7 +397,7 @@ int FstData::getWidth(fstHandle signal) } // Auto-discover scope from FST by finding the top module -std::vector FstData::autoScope(Module *topmod) { +std::string FstData::autoScope(Module *topmod) { log("Auto-discovering scopes from file...\n"); std::string top = RTLIL::unescape_id(topmod->name); @@ -449,11 +449,17 @@ std::vector FstData::autoScope(Module *topmod) { if (results.empty()) { log_warning("Could not auto-discover scope for module '%s'...\n", RTLIL::unescape_id(topmod->name).c_str()); + return ""; } else { log("Found %d scopes for module '%s':\n", GetSize(results), RTLIL::unescape_id(topmod->name).c_str()); for (const auto& scope : results) { log(" %s\n", scope.c_str()); } + if (results.size() > 1) { + log_warning("Multiple scopes found for module '%s'. Using the first one.\n", + RTLIL::unescape_id(topmod->name).c_str()); + } + std::string scope = results[0]; } - return results; + return scope; } diff --git a/kernel/fstdata.h b/kernel/fstdata.h index 01e175691..f95806cca 100644 --- a/kernel/fstdata.h +++ b/kernel/fstdata.h @@ -58,7 +58,7 @@ class FstData double getTimescale() { return timescale; } const char *getTimescaleString() { return timescale_str.c_str(); } int getWidth(fstHandle signal); - std::vector autoScope(Module *topmod); + std::string autoScope(Module *topmod); private: void extractVarNames(); diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index 00b144037..113a15bdd 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -1475,12 +1475,11 @@ struct SimWorker : SimShared fst = new FstData(sim_filename); timescale = fst->getTimescaleString(); if (scope.empty()) { - std::vector scopes = fst->autoScope(topmod); - if (scopes.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()); } - scope = scopes[0]; // Use the first scope found by default } log("Using scope: \"%s\"\n", scope.c_str()); diff --git a/passes/silimate/reg_rename.cc b/passes/silimate/reg_rename.cc index e5c7ffca2..5635b6869 100644 --- a/passes/silimate/reg_rename.cc +++ b/passes/silimate/reg_rename.cc @@ -200,12 +200,11 @@ struct RegRenamePass : public Pass { try { FstData fst(vcd_filename); if (scope.empty()) { - std::vector scopes = fst.autoScope(topmod); - if (scopes.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()); } - scope = scopes[0]; // Use the first scope found by default } log("Using scope: \"%s\"\n", scope.c_str()); for (auto &var : fst.getVars()) { From c9330dc36f8de507ee471e8e4f42bc3341dd12ff Mon Sep 17 00:00:00 2001 From: Stan Lee Date: Fri, 6 Mar 2026 10:30:49 -0800 Subject: [PATCH 04/18] oop --- kernel/fstdata.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel/fstdata.cc b/kernel/fstdata.cc index f2e73e9ec..7edc03e83 100644 --- a/kernel/fstdata.cc +++ b/kernel/fstdata.cc @@ -401,6 +401,7 @@ std::string FstData::autoScope(Module *topmod) { log("Auto-discovering scopes from file...\n"); std::string top = RTLIL::unescape_id(topmod->name); + std::string scope = ""; // Map top module port name to their bit widths (RTL reference point) dict top2widths; From 69145403df415d1742b52e22ff1a8543badee6c7 Mon Sep 17 00:00:00 2001 From: Stan Lee Date: Fri, 6 Mar 2026 10:32:28 -0800 Subject: [PATCH 05/18] more updates --- kernel/fstdata.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kernel/fstdata.cc b/kernel/fstdata.cc index 7edc03e83..307c573e8 100644 --- a/kernel/fstdata.cc +++ b/kernel/fstdata.cc @@ -410,7 +410,7 @@ std::string FstData::autoScope(Module *topmod) { top2widths[RTLIL::unescape_id(wire->name)] = wire->width; } } - log("Extracted %d ports from top module\n", GetSize(top2widths)); + log("Extracted %d ports from module '%s'\n", GetSize(top2widths), top); // For each scope, track the number of matching ports dict scopes2matches; @@ -449,16 +449,16 @@ std::string FstData::autoScope(Module *topmod) { } if (results.empty()) { log_warning("Could not auto-discover scope for module '%s'...\n", - RTLIL::unescape_id(topmod->name).c_str()); + top); return ""; } else { - log("Found %d scopes for module '%s':\n", GetSize(results), RTLIL::unescape_id(topmod->name).c_str()); + log("Found %d scopes for module '%s':\n", GetSize(results), top); for (const auto& scope : results) { log(" %s\n", scope.c_str()); } if (results.size() > 1) { log_warning("Multiple scopes found for module '%s'. Using the first one.\n", - RTLIL::unescape_id(topmod->name).c_str()); + top); } std::string scope = results[0]; } From 1592125e7162eabe357ba477329deb30ba0670e6 Mon Sep 17 00:00:00 2001 From: Stan Lee Date: Fri, 6 Mar 2026 10:36:29 -0800 Subject: [PATCH 06/18] fix err --- kernel/fstdata.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/fstdata.cc b/kernel/fstdata.cc index 307c573e8..fd72ed4c5 100644 --- a/kernel/fstdata.cc +++ b/kernel/fstdata.cc @@ -460,7 +460,7 @@ std::string FstData::autoScope(Module *topmod) { log_warning("Multiple scopes found for module '%s'. Using the first one.\n", top); } - std::string scope = results[0]; + scope = results[0]; } return scope; } From b7984f12f8f812763ec4821c2a021c3d4b3d1357 Mon Sep 17 00:00:00 2001 From: Stan Lee Date: Fri, 6 Mar 2026 12:19:17 -0800 Subject: [PATCH 07/18] greptile --- kernel/fstdata.cc | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/kernel/fstdata.cc b/kernel/fstdata.cc index fd72ed4c5..c1d56e353 100644 --- a/kernel/fstdata.cc +++ b/kernel/fstdata.cc @@ -410,7 +410,7 @@ std::string FstData::autoScope(Module *topmod) { top2widths[RTLIL::unescape_id(wire->name)] = wire->width; } } - log("Extracted %d ports from module '%s'\n", GetSize(top2widths), top); + log("Extracted %d ports from module '%s'\n", GetSize(top2widths), top.c_str()); // For each scope, track the number of matching ports dict scopes2matches; @@ -449,18 +449,17 @@ std::string FstData::autoScope(Module *topmod) { } if (results.empty()) { log_warning("Could not auto-discover scope for module '%s'...\n", - top); + top.c_str()); return ""; } else { - log("Found %d scopes for module '%s':\n", GetSize(results), top); + log("Found %d scopes for module '%s':\n", GetSize(results), top.c_str()); for (const auto& scope : results) { log(" %s\n", scope.c_str()); } if (results.size() > 1) { log_warning("Multiple scopes found for module '%s'. Using the first one.\n", - top); + top.c_str()); } - scope = results[0]; + return results[0]; } - return scope; } From e394197bc5048e97effacb56b5e963fc64b1ab26 Mon Sep 17 00:00:00 2001 From: tondapusili Date: Fri, 6 Mar 2026 15:51:58 -0800 Subject: [PATCH 08/18] rtlil: parameterize SigSpec::is_mostly_const --- kernel/rtlil.cc | 4 ++-- kernel/rtlil.h | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 266aedfe0..9b3ee4cf3 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -5605,13 +5605,13 @@ bool RTLIL::SigSpec::is_chunk() const return ++it == cs.end(); } -bool RTLIL::SigSpec::is_mostly_const() const +bool RTLIL::SigSpec::is_mostly_const(double const_ratio_threshold) const { int constbits = 0; for (auto &chunk : chunks()) if (chunk.width > 0 && chunk.wire == NULL) constbits += chunk.width; - return (constbits > size()/2); + return (constbits > size() * const_ratio_threshold); } bool RTLIL::SigSpec::known_driver() const diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 9dfe0c570..7044e609a 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -1684,7 +1684,9 @@ public: bool known_driver() const; - bool is_mostly_const() const; + // const_ratio_threshold is expected in [0.0, 1.0] + // boundary is exclusive, returns true only if const bit ratio > const_ratio_threshold + bool is_mostly_const(double const_ratio_threshold = 0.5) const; bool is_fully_const() const; bool is_fully_zero() const; bool is_fully_ones() const; From bcf71dea85598fb29345cf5e800bb5b6b682e958 Mon Sep 17 00:00:00 2001 From: Stan Lee Date: Fri, 13 Mar 2026 11:15:46 -0700 Subject: [PATCH 09/18] dump the number of scopes/signals in the search space --- kernel/fstdata.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/fstdata.cc b/kernel/fstdata.cc index c1d56e353..69745bc61 100644 --- a/kernel/fstdata.cc +++ b/kernel/fstdata.cc @@ -399,7 +399,7 @@ int FstData::getWidth(fstHandle signal) // Auto-discover scope from FST by finding the top module std::string FstData::autoScope(Module *topmod) { - log("Auto-discovering scopes from file...\n"); + log("Auto-discovering scopes from %d candidates...\n", GetSize(handle_to_var)); std::string top = RTLIL::unescape_id(topmod->name); std::string scope = ""; From 5d25ae4db604464e4e9f57b081f5ea353e3a363e Mon Sep 17 00:00:00 2001 From: tondapusili Date: Fri, 13 Mar 2026 15:56:55 -0700 Subject: [PATCH 10/18] rtlil: add fast Cell accessors and SigSpec::const_ratio() --- kernel/rtlil.cc | 35 +++++++++++++++++++++++++++++++++-- kernel/rtlil.h | 12 ++++++++++-- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 9b3ee4cf3..9cf8715f4 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -4564,6 +4564,32 @@ const RTLIL::Const &RTLIL::Cell::getParam(RTLIL::IdString paramname) const throw std::out_of_range("Cell::getParam()"); } +// NOTE: as_int() silently truncates >32-bit values and reinterprets string-typed Const values +std::map RTLIL::Cell::getParamsAsInts() const +{ + std::map result; + for (auto ¶m : parameters) { + std::string key = param.first.str(); + if (key.size() > 0 && key[0] == '\\') + key = key.substr(1); + result[key] = param.second.as_int(); + } + return result; +} + +double RTLIL::Cell::maxInputConstRatio() const +{ + double max_ratio = 0.0; + for (auto &conn : connections_) { + if (input(conn.first)) { + double ratio = conn.second.const_ratio(); + if (ratio > max_ratio) + max_ratio = ratio; + } + } + return max_ratio; +} + void RTLIL::Cell::sort() { connections_.sort(sort_by_id_str()); @@ -5605,13 +5631,18 @@ bool RTLIL::SigSpec::is_chunk() const return ++it == cs.end(); } -bool RTLIL::SigSpec::is_mostly_const(double const_ratio_threshold) const +double RTLIL::SigSpec::const_ratio() const { int constbits = 0; for (auto &chunk : chunks()) if (chunk.width > 0 && chunk.wire == NULL) constbits += chunk.width; - return (constbits > size() * const_ratio_threshold); + return empty() ? 0.0 : static_cast(constbits) / size(); +} + +bool RTLIL::SigSpec::is_mostly_const(double const_ratio_threshold) const +{ + return (const_ratio() > const_ratio_threshold); } bool RTLIL::SigSpec::known_driver() const diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 7044e609a..3d2e54a1a 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -1684,8 +1684,9 @@ public: bool known_driver() const; - // const_ratio_threshold is expected in [0.0, 1.0] - // boundary is exclusive, returns true only if const bit ratio > const_ratio_threshold + // Constant bit ratio helpers: const_ratio() returns [0.0, 1.0], + // is_mostly_const() returns true if const_ratio() > threshold + double const_ratio() const; bool is_mostly_const(double const_ratio_threshold = 0.5) const; bool is_fully_const() const; bool is_fully_zero() const; @@ -2531,6 +2532,13 @@ public: void setParam(RTLIL::IdString paramname, RTLIL::Const value); const RTLIL::Const &getParam(RTLIL::IdString paramname) const; + // Primitive-type parameter accessors for efficient Python interop. + // NOTE: silently truncates wide (>32-bit) parameters and reinterprets string-typed Const values + std::map getParamsAsInts() const; + + // Returns the maximum const_ratio() across all input ports, 0.0 if no input ports + double maxInputConstRatio() const; + void sort(); void check(); void fixup_parameters(bool set_a_signed = false, bool set_b_signed = false); From 29b182fd9ba13c04944eba27deedc2a549bd5390 Mon Sep 17 00:00:00 2001 From: tondapusili Date: Mon, 16 Mar 2026 10:47:54 -0700 Subject: [PATCH 11/18] adding more fast Cell accessors and small refactoring to reduce code dup --- kernel/rtlil.cc | 28 +++++++++++++++++++++------- kernel/rtlil.h | 6 ++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 9cf8715f4..622c3d93c 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -4568,19 +4568,15 @@ const RTLIL::Const &RTLIL::Cell::getParam(RTLIL::IdString paramname) const std::map RTLIL::Cell::getParamsAsInts() const { std::map result; - for (auto ¶m : parameters) { - std::string key = param.first.str(); - if (key.size() > 0 && key[0] == '\\') - key = key.substr(1); - result[key] = param.second.as_int(); - } + for (const auto ¶m : parameters) + result[RTLIL::unescape_id(param.first)] = param.second.as_int(); return result; } double RTLIL::Cell::maxInputConstRatio() const { double max_ratio = 0.0; - for (auto &conn : connections_) { + for (const auto &conn : connections_) { if (input(conn.first)) { double ratio = conn.second.const_ratio(); if (ratio > max_ratio) @@ -4590,6 +4586,24 @@ double RTLIL::Cell::maxInputConstRatio() const return max_ratio; } +std::vector RTLIL::Cell::getOutputPortNames() const +{ + std::vector result; + for (const auto &conn : connections_) { + if (output(conn.first)) + result.push_back(RTLIL::unescape_id(conn.first)); + } + return result; +} + +std::map RTLIL::Cell::getConnectionSizes() const +{ + std::map result; + for (const auto &conn : connections_) + result[RTLIL::unescape_id(conn.first)] = conn.second.size(); + return result; +} + void RTLIL::Cell::sort() { connections_.sort(sort_by_id_str()); diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 3d2e54a1a..7964ac5a5 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -2539,6 +2539,12 @@ public: // Returns the maximum const_ratio() across all input ports, 0.0 if no input ports double maxInputConstRatio() const; + // Returns the names of all output ports (backslash-stripped) + std::vector getOutputPortNames() const; + + // Returns {port_name: sig.size()} for all connections, port names backslash-stripped + std::map getConnectionSizes() const; + void sort(); void check(); void fixup_parameters(bool set_a_signed = false, bool set_b_signed = false); From e5d3bb954efed672cda39569c795c356a12936da Mon Sep 17 00:00:00 2001 From: Stan Lee Date: Mon, 16 Mar 2026 12:05:27 -0700 Subject: [PATCH 12/18] correction --- kernel/fstdata.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/fstdata.cc b/kernel/fstdata.cc index 69745bc61..2e3c429a5 100644 --- a/kernel/fstdata.cc +++ b/kernel/fstdata.cc @@ -399,7 +399,7 @@ int FstData::getWidth(fstHandle signal) // Auto-discover scope from FST by finding the top module std::string FstData::autoScope(Module *topmod) { - log("Auto-discovering scopes from %d candidates...\n", GetSize(handle_to_var)); + log("Auto-discovering scopes from %d candidates...\n", GetSize(name_to_handle)); std::string top = RTLIL::unescape_id(topmod->name); std::string scope = ""; From 8268a79af547d716b597b670c9d82cd53ee458a2 Mon Sep 17 00:00:00 2001 From: Stan Lee Date: Wed, 18 Mar 2026 14:06:18 -0700 Subject: [PATCH 13/18] debug before assertion --- passes/sat/sim.cc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index 113a15bdd..d470d760f 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -444,6 +444,13 @@ struct SimInstance bool did_something = false; sig = sigmap(sig); + if (shared->debug) { + log("GetSize(sig) %s: %d, GetSize(value) %s: %d\n", + log_signal(sig), + GetSize(sig), + log_signal(value), + GetSize(value)); + } log_assert(GetSize(sig) <= GetSize(value)); for (int i = 0; i < GetSize(sig); i++) From 91739e79cb5fc34f32e29f0793558ff87e71ffd4 Mon Sep 17 00:00:00 2001 From: Akash Levy Date: Mon, 23 Mar 2026 03:30:55 -0700 Subject: [PATCH 14/18] Safe parameter extraction in mem_from_cell --- kernel/mem.cc | 52 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/kernel/mem.cc b/kernel/mem.cc index 02d12dea4..428d46741 100644 --- a/kernel/mem.cc +++ b/kernel/mem.cc @@ -753,6 +753,36 @@ namespace { int n_wr_ports = cell->parameters.at(ID::WR_PORTS).as_int(); Const rd_wide_continuation = is_compat ? Const(State::S0, n_rd_ports) : cell->parameters.at(ID::RD_WIDE_CONTINUATION); Const wr_wide_continuation = is_compat ? Const(State::S0, n_wr_ports) : cell->parameters.at(ID::WR_WIDE_CONTINUATION); + pool bad_params; + if (!is_compat) { + auto check_param_size = [&](IdString param_name, int expected_size) { + const Const ¶m = cell->parameters.at(param_name); + if (GetSize(param) != expected_size) { + log_warning("Memory cell %s.%s (%s) has inconsistent parameter %s: expected size %d, got %d\n", + log_id(cell->module), log_id(cell), log_id(res.memid), + log_id(param_name), expected_size, GetSize(param)); + bad_params.insert(param_name); + } + }; + check_param_size(ID::RD_WIDE_CONTINUATION, n_rd_ports); + check_param_size(ID::RD_CLK_ENABLE, n_rd_ports); + check_param_size(ID::RD_CLK_POLARITY, n_rd_ports); + check_param_size(ID::RD_CE_OVER_SRST, n_rd_ports); + check_param_size(ID::RD_ARST_VALUE, n_rd_ports * res.width); + check_param_size(ID::RD_SRST_VALUE, n_rd_ports * res.width); + check_param_size(ID::RD_INIT_VALUE, n_rd_ports * res.width); + check_param_size(ID::RD_TRANSPARENCY_MASK, n_rd_ports * n_wr_ports); + check_param_size(ID::RD_COLLISION_X_MASK, n_rd_ports * n_wr_ports); + check_param_size(ID::WR_WIDE_CONTINUATION, n_wr_ports); + check_param_size(ID::WR_CLK_ENABLE, n_wr_ports); + check_param_size(ID::WR_CLK_POLARITY, n_wr_ports); + check_param_size(ID::WR_PRIORITY_MASK, n_wr_ports * n_wr_ports); + } + auto safe_param_extract = [&](IdString param_name, int offset, int len) -> Const { + if (bad_params.count(param_name)) + return Const(State::S0, len); + return cell->parameters.at(param_name).extract(offset, len); + }; for (int i = 0, ni; i < n_rd_ports; i = ni) { ni = i + 1; while (ni < n_rd_ports && rd_wide_continuation[ni] == State::S1) @@ -760,8 +790,8 @@ namespace { MemRd mrd; mrd.wide_log2 = ceil_log2(ni - i); log_assert(ni - i == (1 << mrd.wide_log2)); - mrd.clk_enable = cell->parameters.at(ID::RD_CLK_ENABLE).extract(i, 1).as_bool(); - mrd.clk_polarity = cell->parameters.at(ID::RD_CLK_POLARITY).extract(i, 1).as_bool(); + mrd.clk_enable = safe_param_extract(ID::RD_CLK_ENABLE, i, 1).as_bool(); + mrd.clk_polarity = safe_param_extract(ID::RD_CLK_POLARITY, i, 1).as_bool(); mrd.clk = cell->getPort(ID::RD_CLK).extract(i, 1); mrd.en = cell->getPort(ID::RD_EN).extract(i, 1); mrd.addr = cell->getPort(ID::RD_ADDR).extract(i * abits, abits); @@ -774,16 +804,16 @@ namespace { mrd.arst = State::S0; mrd.srst = State::S0; } else { - mrd.ce_over_srst = cell->parameters.at(ID::RD_CE_OVER_SRST).extract(i, 1).as_bool(); - mrd.arst_value = cell->parameters.at(ID::RD_ARST_VALUE).extract(i * res.width, (ni - i) * res.width); - mrd.srst_value = cell->parameters.at(ID::RD_SRST_VALUE).extract(i * res.width, (ni - i) * res.width); - mrd.init_value = cell->parameters.at(ID::RD_INIT_VALUE).extract(i * res.width, (ni - i) * res.width); + mrd.ce_over_srst = safe_param_extract(ID::RD_CE_OVER_SRST, i, 1).as_bool(); + mrd.arst_value = safe_param_extract(ID::RD_ARST_VALUE, i * res.width, (ni - i) * res.width); + mrd.srst_value = safe_param_extract(ID::RD_SRST_VALUE, i * res.width, (ni - i) * res.width); + mrd.init_value = safe_param_extract(ID::RD_INIT_VALUE, i * res.width, (ni - i) * res.width); mrd.arst = cell->getPort(ID::RD_ARST).extract(i, 1); mrd.srst = cell->getPort(ID::RD_SRST).extract(i, 1); } if (!is_compat) { - Const transparency_mask = cell->parameters.at(ID::RD_TRANSPARENCY_MASK).extract(i * n_wr_ports, n_wr_ports); - Const collision_x_mask = cell->parameters.at(ID::RD_COLLISION_X_MASK).extract(i * n_wr_ports, n_wr_ports); + Const transparency_mask = safe_param_extract(ID::RD_TRANSPARENCY_MASK, i * n_wr_ports, n_wr_ports); + Const collision_x_mask = safe_param_extract(ID::RD_COLLISION_X_MASK, i * n_wr_ports, n_wr_ports); for (int j = 0; j < n_wr_ports; j++) if (wr_wide_continuation[j] != State::S1) { mrd.transparency_mask.push_back(transparency_mask[j] == State::S1); @@ -799,14 +829,14 @@ namespace { MemWr mwr; mwr.wide_log2 = ceil_log2(ni - i); log_assert(ni - i == (1 << mwr.wide_log2)); - mwr.clk_enable = cell->parameters.at(ID::WR_CLK_ENABLE).extract(i, 1).as_bool(); - mwr.clk_polarity = cell->parameters.at(ID::WR_CLK_POLARITY).extract(i, 1).as_bool(); + mwr.clk_enable = safe_param_extract(ID::WR_CLK_ENABLE, i, 1).as_bool(); + mwr.clk_polarity = safe_param_extract(ID::WR_CLK_POLARITY, i, 1).as_bool(); mwr.clk = cell->getPort(ID::WR_CLK).extract(i, 1); mwr.en = cell->getPort(ID::WR_EN).extract(i * res.width, (ni - i) * res.width); mwr.addr = cell->getPort(ID::WR_ADDR).extract(i * abits, abits); mwr.data = cell->getPort(ID::WR_DATA).extract(i * res.width, (ni - i) * res.width); if (!is_compat) { - Const priority_mask = cell->parameters.at(ID::WR_PRIORITY_MASK).extract(i * n_wr_ports, n_wr_ports); + Const priority_mask = safe_param_extract(ID::WR_PRIORITY_MASK, i * n_wr_ports, n_wr_ports); for (int j = 0; j < n_wr_ports; j++) if (wr_wide_continuation[j] != State::S1) mwr.priority_mask.push_back(priority_mask[j] == State::S1); From 0ed1f1bfa42731d13f3103ecdb23e26df7886f86 Mon Sep 17 00:00:00 2001 From: Akash Levy Date: Mon, 23 Mar 2026 03:51:49 -0700 Subject: [PATCH 15/18] Smallfixes --- kernel/mem.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/kernel/mem.cc b/kernel/mem.cc index 428d46741..f16f2082d 100644 --- a/kernel/mem.cc +++ b/kernel/mem.cc @@ -777,6 +777,10 @@ namespace { check_param_size(ID::WR_CLK_ENABLE, n_wr_ports); check_param_size(ID::WR_CLK_POLARITY, n_wr_ports); check_param_size(ID::WR_PRIORITY_MASK, n_wr_ports * n_wr_ports); + if (bad_params.count(ID::RD_WIDE_CONTINUATION)) + rd_wide_continuation = Const(State::S0, n_rd_ports); + if (bad_params.count(ID::WR_WIDE_CONTINUATION)) + wr_wide_continuation = Const(State::S0, n_wr_ports); } auto safe_param_extract = [&](IdString param_name, int offset, int len) -> Const { if (bad_params.count(param_name)) @@ -862,7 +866,7 @@ namespace { auto &port = res.rd_ports[i]; port.transparency_mask.resize(GetSize(res.wr_ports)); port.collision_x_mask.resize(GetSize(res.wr_ports)); - if (!cell->parameters.at(ID::RD_TRANSPARENT).extract(i, 1).as_bool()) + if (!cell->parameters.at(ID::RD_TRANSPARENT).extract(i, 1, State::S0).as_bool()) continue; if (!port.clk_enable) continue; From 510ef01b09a16e78454d27ca7480e9476d6241c5 Mon Sep 17 00:00:00 2001 From: Abhinav Tondapu Date: Wed, 25 Mar 2026 16:55:29 -0700 Subject: [PATCH 16/18] adding temp debug logs to fix runtime issue --- passes/silimate/negopt.cc | 48 ++++++++++++++++++++++---- passes/silimate/peepopt_manual2sub.pmg | 4 +-- passes/silimate/peepopt_muxneg.pmg | 4 +-- passes/silimate/peepopt_neg2sub.pmg | 4 +-- passes/silimate/peepopt_negexpand.pmg | 3 +- passes/silimate/peepopt_negmux.pmg | 3 +- passes/silimate/peepopt_negneg.pmg | 3 +- passes/silimate/peepopt_negrebuild.pmg | 4 +-- passes/silimate/peepopt_sub2neg.pmg | 4 +-- 9 files changed, 55 insertions(+), 22 deletions(-) diff --git a/passes/silimate/negopt.cc b/passes/silimate/negopt.cc index 28d047ed9..c658807b4 100644 --- a/passes/silimate/negopt.cc +++ b/passes/silimate/negopt.cc @@ -60,8 +60,6 @@ struct NegoptPass : public Pass { bool run_pre = false; bool run_post = false; - log_header(design, "Executing NEGOPT pass (optimize negation patterns).\n"); - size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { if (args[argidx] == "-pre") { @@ -76,28 +74,48 @@ struct NegoptPass : public Pass { } extra_args(args, argidx, design); - if (!run_pre && !run_post) { - run_pre = true; - run_post = true; - } + if (run_pre == run_post) + log_cmd_error("NEGOPT requires exactly one of -pre or -post.\n"); + + log_header(design, "Executing NEGOPT %s pass (optimize negation patterns).\n", + run_pre ? "PRE" : "POST"); constexpr int max_iterations = 100; for (auto module : design->selected_modules()) { + auto log_module_event = [&](const char *event) { + log("%s %s\n", event, log_id(module)); + log_flush(); + }; + + auto log_pass_event = [&](const char *event, const char *pass_name, int iter = -1) { + if (iter >= 0) + log(" %s %s pass (iter=%d)\n", event, pass_name, iter + 1); + else + log(" %s %s pass\n", event, pass_name); + log_flush(); + }; + if (run_pre) { + log_module_event("Processing Module:"); + // manual2sub and sub2neg only need to run once: no downstream // pre-subpass creates the patterns they match // separate pm instances so sub2neg sees the $sub cells manual2sub creates. { peepopt_pm pm(module); pm.setup(module->selected_cells()); + log_pass_event("Starting", "manual2sub"); pm.run_manual2sub(); + log_pass_event("Ending", "manual2sub"); log_flush(); } { peepopt_pm pm(module); pm.setup(module->selected_cells()); + log_pass_event("Starting", "sub2neg"); pm.run_sub2neg(); + log_pass_event("Ending", "sub2neg"); log_flush(); } @@ -108,33 +126,51 @@ struct NegoptPass : public Pass { peepopt_pm pm(module); pm.setup(module->selected_cells()); + log_pass_event("Starting", "negexpand", iter); pm.run_negexpand(); + log_pass_event("Ending", "negexpand", iter); log_flush(); + log_pass_event("Starting", "negneg", iter); pm.run_negneg(); + log_pass_event("Ending", "negneg", iter); log_flush(); + log_pass_event("Starting", "negmux", iter); pm.run_negmux(); + log_pass_event("Ending", "negmux", iter); log_flush(); } if (did_something) log_warning("NEGOPT pre reached max iterations (%d) in module %s without convergence.\n", max_iterations, log_id(module)); + + log_module_event("Finished Module:"); } if (run_post) { + log_module_event("Processing Module:"); + did_something = true; for (int iter = 0; iter < max_iterations && did_something; iter++) { did_something = false; peepopt_pm pm(module); pm.setup(module->selected_cells()); + log_pass_event("Starting", "negrebuild", iter); pm.run_negrebuild(); + log_pass_event("Ending", "negrebuild", iter); log_flush(); + log_pass_event("Starting", "muxneg", iter); pm.run_muxneg(); + log_pass_event("Ending", "muxneg", iter); log_flush(); + log_pass_event("Starting", "neg2sub", iter); pm.run_neg2sub(); + log_pass_event("Ending", "neg2sub", iter); log_flush(); } if (did_something) log_warning("NEGOPT post reached max iterations (%d) in module %s without convergence.\n", max_iterations, log_id(module)); + + log_module_event("Finished Module:"); } } } diff --git a/passes/silimate/peepopt_manual2sub.pmg b/passes/silimate/peepopt_manual2sub.pmg index b45a05308..257c93b86 100644 --- a/passes/silimate/peepopt_manual2sub.pmg +++ b/passes/silimate/peepopt_manual2sub.pmg @@ -94,7 +94,7 @@ code root_add inner_add_A not_gate_A subtrahend minuend result_sig is_signed subtrahend = port(not_gate_A, \A); - log("manual2sub in %s: Found (a + ~b) + 1 pattern, creating $sub for %s\n", log_id(module), log_signal(result_sig)); + log(" creating $sub for %s from (a + ~b) + 1\n", log_signal(result_sig)); Cell *cell = root_add; int width = GetSize(result_sig); int inner_width = GetSize(inner_y); @@ -196,7 +196,7 @@ code root_add inner_add_B not_gate_B minuend subtrahend result_sig is_signed else minuend = root_a; - log("manual2sub in %s: Found a + (~b + 1) pattern, creating $sub for %s\n", log_id(module), log_signal(result_sig)); + log(" creating $sub for %s from a + (~b + 1)\n", log_signal(result_sig)); Cell *cell = root_add; int width = GetSize(result_sig); int inner_width = GetSize(inner_add_B->getPort(ID::Y)); diff --git a/passes/silimate/peepopt_muxneg.pmg b/passes/silimate/peepopt_muxneg.pmg index e81fc7971..5d03ad7e9 100644 --- a/passes/silimate/peepopt_muxneg.pmg +++ b/passes/silimate/peepopt_muxneg.pmg @@ -60,8 +60,8 @@ code mux_a mux_b mux_s mux_y neg_a_in neg_a_y neg_b_in neg_b_y neg_a_signed neg_ neg_out_rs.extend_u0(GetSize(mux_y), neg_a_signed); module->connect(mux_y, neg_out_rs); - log("muxneg pattern in %s: mux=%s, neg_a=%s, neg_b=%s\n", - log_id(module), log_id(mux), log_id(neg_a), log_id(neg_b)); + log(" mux=%s, neg_a=%s, neg_b=%s\n", + log_id(mux), log_id(neg_a), log_id(neg_b)); new_mux->fixup_parameters(); new_neg->fixup_parameters(); diff --git a/passes/silimate/peepopt_neg2sub.pmg b/passes/silimate/peepopt_neg2sub.pmg index 9594e3b47..13656d9aa 100644 --- a/passes/silimate/peepopt_neg2sub.pmg +++ b/passes/silimate/peepopt_neg2sub.pmg @@ -62,8 +62,8 @@ code add_a add_b add_y neg_a neg_y neg_on_a add_a_signed add_b_signed neg_signed add->setPort(\Y, add_y); add->type = $sub; - log("neg2sub pattern in %s: add=%s, neg=%s (safe sub replacement)\n", - log_id(module), log_id(add), log_id(neg)); + log(" add=%s, neg=%s (safe sub replacement)\n", + log_id(add), log_id(neg)); add->fixup_parameters(); autoremove(neg); diff --git a/passes/silimate/peepopt_negexpand.pmg b/passes/silimate/peepopt_negexpand.pmg index 7ddc0b950..df7c90adc 100644 --- a/passes/silimate/peepopt_negexpand.pmg +++ b/passes/silimate/peepopt_negexpand.pmg @@ -49,8 +49,7 @@ code neg_a neg_y add_a add_b a_signed Cell *new_add = module->addAdd(NEW_ID2_SUFFIX("add"), neg_add_a, neg_add_b, neg_y, a_signed); - log("negexpand pattern in %s: neg=%s, add=%s\n", - log_id(module), log_id(neg), log_id(add)); + log(" neg=%s, add=%s\n", log_id(neg), log_id(add)); neg_a_cell->fixup_parameters(); neg_b_cell->fixup_parameters(); diff --git a/passes/silimate/peepopt_negmux.pmg b/passes/silimate/peepopt_negmux.pmg index d1918e3bf..fbedbf507 100644 --- a/passes/silimate/peepopt_negmux.pmg +++ b/passes/silimate/peepopt_negmux.pmg @@ -60,8 +60,7 @@ code neg_a neg_y mux_a mux_b mux_s mux_y a_signed Cell *new_mux = module->addMux(NEW_ID2_SUFFIX("mux"), neg_mux_a, neg_mux_b, mux_s, neg_y); - log("negmux pattern in %s: neg=%s, mux=%s\n", - log_id(module), log_id(neg), log_id(mux)); + log(" neg=%s, mux=%s\n", log_id(neg), log_id(mux)); neg_a_cell->fixup_parameters(); neg_b_cell->fixup_parameters(); diff --git a/passes/silimate/peepopt_negneg.pmg b/passes/silimate/peepopt_negneg.pmg index 166e641b0..09d7aa543 100644 --- a/passes/silimate/peepopt_negneg.pmg +++ b/passes/silimate/peepopt_negneg.pmg @@ -29,8 +29,7 @@ code neg1_a neg1_y neg2_a module->connect(neg1_y, neg2_a); - log("negneg pattern in %s: neg1=%s, neg2=%s\n", - log_id(module), log_id(neg1), log_id(neg2)); + log(" neg1=%s, neg2=%s\n", log_id(neg1), log_id(neg2)); autoremove(neg1); autoremove(neg2); diff --git a/passes/silimate/peepopt_negrebuild.pmg b/passes/silimate/peepopt_negrebuild.pmg index 37c8fba5e..49683f09f 100644 --- a/passes/silimate/peepopt_negrebuild.pmg +++ b/passes/silimate/peepopt_negrebuild.pmg @@ -100,8 +100,8 @@ code add_a add_b add_y neg1_a neg1_y neg2_a neg2_y add_signed add_b_signed neg1_ neg_out_rs.extend_u0(GetSize(add_y), add_signed); module->connect(add_y, neg_out_rs); - log("negrebuild pattern in %s: add=%s, neg1=%s, neg2=%s\n", - log_id(module), log_id(add), log_id(neg1), log_id(neg2)); + log(" add=%s, neg1=%s, neg2=%s\n", + log_id(add), log_id(neg1), log_id(neg2)); new_add->fixup_parameters(); new_neg->fixup_parameters(); diff --git a/passes/silimate/peepopt_sub2neg.pmg b/passes/silimate/peepopt_sub2neg.pmg index c0921c290..8b8aff9dd 100644 --- a/passes/silimate/peepopt_sub2neg.pmg +++ b/passes/silimate/peepopt_sub2neg.pmg @@ -43,8 +43,8 @@ code sub_a sub_b sub_y a_signed b_signed sub->setPort(\Y, sub_y); sub->type = $add; - log("sub2neg pattern in %s: sub=%s -> neg=%s, add=%s\n", - log_id(module), log_id(sub), log_id(neg), log_id(sub)); + log(" sub=%s -> neg=%s, add=%s\n", + log_id(sub), log_id(neg), log_id(sub)); sub->fixup_parameters(); neg->fixup_parameters(); From 5376dc27e124454c5133ca7e142320a7f1da27dd Mon Sep 17 00:00:00 2001 From: Akash Levy Date: Wed, 25 Mar 2026 22:49:17 -0700 Subject: [PATCH 17/18] Update Verific for bugfix --- verific | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/verific b/verific index 4feaf1f92..87cb8cf50 160000 --- a/verific +++ b/verific @@ -1 +1 @@ -Subproject commit 4feaf1f923157af0ed064eab8e302647bd5fa1b7 +Subproject commit 87cb8cf50448c4d28a1b2d57bd4afc119b6439af From f42a63941c5783cf0dcf57857eb0053ba3eadf6d Mon Sep 17 00:00:00 2001 From: AdvaySingh1 Date: Fri, 20 Mar 2026 15:00:17 -0700 Subject: [PATCH 18/18] Added initial clkmerge pass for multiple clock domains --- passes/silimate/clkmerge.cc | 136 ++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 passes/silimate/clkmerge.cc diff --git a/passes/silimate/clkmerge.cc b/passes/silimate/clkmerge.cc new file mode 100644 index 000000000..fc03ff4db --- /dev/null +++ b/passes/silimate/clkmerge.cc @@ -0,0 +1,136 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2026 Silimate Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/yosys.h" +#include "kernel/sigtools.h" +#include "kernel/ff.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct ClkMergePass : public Pass { + ClkMergePass() : Pass("clkmerge", "merge multiple clock domains into one") { } + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" clkmerge [-target ] [selection]\n"); + log("\n"); + log("This command rewrites all flip-flop clock ports in the selected modules to\n"); + log("use a single clock signal, effectively merging all clock domains into one.\n"); + log("\n"); + log("This is useful for miter-based equivalence checking where gold and gate\n"); + log("copies may use different clock signal names for what is logically the same\n"); + log("clock. Without merging, ABC's -dff mode will partition them into separate\n"); + log("clock domains, creating spurious cross-domain ports that degrade SAT\n"); + log("performance.\n"); + log("\n"); + log(" -target \n"); + log(" use the specified signal as the merged clock. if not given, the\n"); + log(" clock signal of the first FF encountered is used.\n"); + log("\n"); + } + void execute(std::vector args, RTLIL::Design *design) override + { + std::string target_clk_name; + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-target" && argidx + 1 < args.size()) { + target_clk_name = args[++argidx]; + continue; + } + break; + } + extra_args(args, argidx, design); + + for (auto module : design->selected_modules()) + { + SigMap sigmap(module); + + dict clk_domain_count; + std::vector> ff_clk_pairs; + + for (auto cell : module->selected_cells()) + { + if (!cell->is_builtin_ff()) + continue; + + FfData ff(nullptr, cell); + if (!ff.has_clk) + continue; + + SigSpec clk = sigmap(ff.sig_clk); + ff_clk_pairs.push_back({cell, clk}); + + for (auto bit : clk) + clk_domain_count[bit]++; + } + + if (ff_clk_pairs.empty()) + continue; + + // Determine target clock + SigSpec target_clk; + if (!target_clk_name.empty()) { + RTLIL::SigSpec sig; + if (!SigSpec::parse_sel(sig, design, module, target_clk_name)) + log_cmd_error("Failed to parse target clock expression '%s'.\n", target_clk_name.c_str()); + target_clk = sigmap(sig); + } else { + target_clk = ff_clk_pairs[0].second; + } + + // Collect distinct clocks + pool all_clks; + for (auto &pair : ff_clk_pairs) + all_clks.insert(pair.second); + + if (all_clks.size() <= 1) { + log("Module %s: only one clock domain found, nothing to merge.\n", log_id(module)); + continue; + } + + log("Module %s: merging %d clock domains into %s\n", + log_id(module), GetSize(all_clks), log_signal(target_clk)); + + int rewritten = 0; + for (auto &pair : ff_clk_pairs) + { + Cell *cell = pair.first; + SigSpec old_clk = pair.second; + + if (old_clk == target_clk) + continue; + + // Fine-grain cells use ID::C, coarse-grain use ID::CLK + if (cell->hasPort(ID::C)) + cell->setPort(ID::C, target_clk); + else if (cell->hasPort(ID::CLK)) + cell->setPort(ID::CLK, target_clk); + + rewritten++; + } + + log(" Rewrote clock on %d flip-flops.\n", rewritten); + } + } +} ClkMergePass; + +PRIVATE_NAMESPACE_END