diff --git a/passes/silimate/sat_clockgate.cc b/passes/silimate/sat_clockgate.cc index 5547db11a..089fb1305 100644 --- a/passes/silimate/sat_clockgate.cc +++ b/passes/silimate/sat_clockgate.cc @@ -14,346 +14,535 @@ * 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" #include "kernel/satgen.h" -#include +#include +#include USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -// Maximum depth for BFS exploration of input cone -static const int MAX_INPUT_DEPTH = 10; - -// If true, exclude Q (feedback) bits from enable input set -static const bool EXCLUDE_Q_FROM_ENABLE = true; +// Configuration +static const int DEFAULT_MAX_COVER = 100; // Max candidate signals to consider +static const int DEFAULT_MIN_REGS = 1; // Min registers per clock gate +static const int DEFAULT_SIM_ITERATIONS = 10; // Random simulation iterations for pruning struct SatClockgateWorker { Module *module; SigMap sigmap; + // Configuration + int max_cover; + int min_regs; + int sim_iterations; + // Maps output signal bits to their driver cells dict sig_to_driver; - // Q bits to exclude from enable input set (set per-FF) - pool q_bits; - + // Maps cell input pins to their source signals + dict> sig_to_sinks; + // SAT solver and generator - created once per module ezSatPtr ez; SatGen satgen; - SatClockgateWorker(Module *module) : module(module), sigmap(module), ez(), satgen(ez.get(), &sigmap) + // Statistics + int accepted_count = 0; + int rejected_sim_count = 0; + int rejected_sat_count = 0; + + SatClockgateWorker(Module *module, int max_cover, int min_regs, int sim_iterations) + : module(module), sigmap(module), + max_cover(max_cover), min_regs(min_regs), sim_iterations(sim_iterations), + ez(), satgen(ez.get(), &sigmap) { - // Build driver map: for each signal bit, find which cell drives it + // Build driver and sink maps for (auto cell : module->cells()) { for (auto &conn : cell->connections()) { if (cell->output(conn.first)) { for (auto bit : sigmap(conn.second)) - sig_to_driver[bit] = cell; + if (bit.wire) + sig_to_driver[bit] = cell; + } + if (cell->input(conn.first)) { + for (auto bit : sigmap(conn.second)) + if (bit.wire) + sig_to_sinks[bit].insert(cell); } } } - + // Import all cells once - circuit constraints are permanent for (auto cell : module->cells()) - satgen.importCell(cell); + if (!cell->type.in(ID($ff), ID($dff), ID($dffe), ID($adff), ID($adffe), + ID($sdff), ID($sdffe), ID($sdffce), ID($dffsr), + ID($dffsre), ID($_DFF_P_), ID($_DFF_N_), ID($_DFFE_PP_), + ID($_DFFE_PN_), ID($_DFFE_NP_), ID($_DFFE_NN_))) + satgen.importCell(cell); } - - // Set Q bits to exclude for current FF - void set_excluded_q_bits(SigSpec sig_q) + + // Get downstream signals from a register (BFS forward through combinational logic) + pool getDownstreamSignals(Cell *reg, int limit) { - q_bits.clear(); - if (EXCLUDE_Q_FROM_ENABLE) { - for (auto bit : sigmap(sig_q)) - q_bits.insert(bit); + pool result; + pool visited; + std::queue worklist; + + // Start from register output Q + FfData ff(nullptr, reg); + for (auto bit : sigmap(ff.sig_q)) { + if (bit.wire) { + worklist.push(bit); + visited.insert(bit); + } } - } - - // Get the set of input signals feeding into a given signal (one level back) - pool get_input_signals(SigBit bit) - { - pool inputs; - bit = sigmap(bit); - if (!sig_to_driver.count(bit)) - return inputs; // Primary input or constant - - Cell *driver = sig_to_driver[bit]; - for (auto &conn : driver->connections()) { - if (driver->input(conn.first)) { - for (auto input_bit : sigmap(conn.second)) { - if (input_bit.wire != nullptr && !q_bits.count(input_bit)) - inputs.insert(input_bit); + while (!worklist.empty() && (int)result.size() < limit) { + SigBit bit = worklist.front(); + worklist.pop(); + + result.insert(bit); + + // Find cells driven by this signal + for (auto sink_cell : sig_to_sinks[bit]) { + // Skip registers - don't traverse through them + if (sink_cell->type.in(ID($ff), ID($dff), ID($dffe), ID($adff), ID($adffe), + ID($sdff), ID($sdffe), ID($sdffce), ID($dffsr), + ID($dffsre), ID($_DFF_P_), ID($_DFF_N_))) + continue; + + // Add outputs of this cell to worklist + for (auto &conn : sink_cell->connections()) { + if (sink_cell->output(conn.first)) { + for (auto out_bit : sigmap(conn.second)) { + if (out_bit.wire && !visited.count(out_bit)) { + visited.insert(out_bit); + worklist.push(out_bit); + } + } + } } } } - return inputs; + + return result; } - - // Check if OR(input_set) is exactly equivalent to (D != Q) - // Returns true if COI ↔ (D≠Q) for all circuit states (exact clock gating) - // TODO: Consider pruning the expressions list — expressions accumulate across - // calls (OR, XOR, NE per query). For large designs with many FFs, this - // could cause memory growth. Options: solver checkpoints, fresh solver - // per FF with COI-only cell import, or periodic expression cleanup. - bool input_set_is_enable(const pool &input_set, SigSpec sig_d, SigSpec sig_q) + + // Get upstream signals feeding into given signals (BFS backward) + pool getUpstreamSignals(const pool &start_signals, int limit) { - if (input_set.empty()) + pool result; + pool visited; + std::queue worklist; + + for (auto bit : start_signals) { + worklist.push(bit); + visited.insert(bit); + } + + while (!worklist.empty() && (int)result.size() < limit) { + SigBit bit = worklist.front(); + worklist.pop(); + + result.insert(bit); + + // Find driver cell + if (!sig_to_driver.count(bit)) + continue; + + Cell *driver = sig_to_driver[bit]; + + // Skip registers + if (driver->type.in(ID($ff), ID($dff), ID($dffe), ID($adff), ID($adffe), + ID($sdff), ID($sdffe), ID($sdffce), ID($dffsr), + ID($dffsre), ID($_DFF_P_), ID($_DFF_N_))) + continue; + + // Add inputs of driver to worklist + for (auto &conn : driver->connections()) { + if (driver->input(conn.first)) { + for (auto in_bit : sigmap(conn.second)) { + if (in_bit.wire && !visited.count(in_bit)) { + visited.insert(in_bit); + worklist.push(in_bit); + } + } + } + } + } + + return result; + } + + // Check if a candidate signal is a valid gating condition using SAT + // Safe gating check: sig=1 → D==Q (i.e., (sig ∧ (D≠Q)) is UNSAT) + bool isValidGatingSignal(SigBit candidate, SigSpec sig_d, SigSpec sig_q, bool as_enable) + { + std::vector d_vec = satgen.importSigSpec(sig_d); + std::vector q_vec = satgen.importSigSpec(sig_q); + int cand_var = satgen.importSigSpec(SigSpec(candidate))[0]; + + // D != Q + int d_ne_q = ez->vec_ne(d_vec, q_vec); + + // For clock enable (active high): when enable=0, D must equal Q + // Check: (!enable ∧ (D≠Q)) is UNSAT + // For clock disable (active low): when disable=1, D must equal Q + // Check: (disable ∧ (D≠Q)) is UNSAT + + int gating_active = as_enable ? ez->NOT(cand_var) : cand_var; + int query = ez->AND(gating_active, d_ne_q); + + std::vector assumptions = {query}; + std::vector dummy_exprs; + std::vector dummy_vals; + + return !ez->solve(dummy_exprs, dummy_vals, assumptions); + } + + // Simple random simulation test to quickly prune candidates + bool simulationTest(SigBit candidate, SigSpec sig_d, SigSpec sig_q, bool as_enable) + { + // For now, skip simulation and go straight to SAT + // TODO: Implement random simulation for faster pruning + return true; + } + + // Binary search to minimize the gating condition set + // Tries to remove half of the signals at a time + void minimizeGatingCondition( + std::vector &good_conds, + std::vector::iterator begin, + std::vector::iterator end, + SigSpec sig_d, SigSpec sig_q, bool as_enable) + { + int half_len = (end - begin) / 2; + if (half_len == 0) + return; + + auto mid = begin + half_len; + + // Try removing [mid, end) from the condition + std::vector test_conds; + test_conds.insert(test_conds.end(), good_conds.begin(), begin); + test_conds.insert(test_conds.end(), begin, mid); + test_conds.insert(test_conds.end(), end, good_conds.end()); + + if (!test_conds.empty() && isValidGatingSet(test_conds, sig_d, sig_q, as_enable)) { + // Can remove [mid, end) + good_conds.erase(mid, end); + // Recurse on remaining half + minimizeGatingCondition(good_conds, begin, begin + half_len, sig_d, sig_q, as_enable); + } else { + // Cannot remove all of [mid, end), try to minimize each half + if (end - mid > 1) + minimizeGatingCondition(good_conds, mid, end, sig_d, sig_q, as_enable); + minimizeGatingCondition(good_conds, begin, mid, sig_d, sig_q, as_enable); + } + } + + // Check if OR/AND of signals forms a valid gating condition + bool isValidGatingSet(const std::vector &conds, SigSpec sig_d, SigSpec sig_q, bool as_enable) + { + if (conds.empty()) return false; - - // Import D and Q (uses cached literals if already imported) + std::vector d_vec = satgen.importSigSpec(sig_d); std::vector q_vec = satgen.importSigSpec(sig_q); - // Build COI = OR(input_set) - std::vector input_vars; - for (auto bit : input_set) - input_vars.push_back(satgen.importSigSpec(SigSpec(bit))[0]); - int coi = ez->expression(ezSAT::OpOr, input_vars); + // Build OR (for enable) or AND (for disable) of condition signals + std::vector cond_vars; + for (auto bit : conds) + cond_vars.push_back(satgen.importSigSpec(SigSpec(bit))[0]); + + int combined_cond; + if (as_enable) { + // Clock enable: OR of signals (any signal high = enable) + combined_cond = ez->expression(ezSAT::OpOr, cond_vars); + } else { + // Clock disable: AND of signals (all signals high = disable) + combined_cond = ez->expression(ezSAT::OpAnd, cond_vars); + } - // Build D != Q (single bit: is any bit different?) int d_ne_q = ez->vec_ne(d_vec, q_vec); - // Query: COI XOR (D≠Q) — want this UNSAT (meaning COI ↔ D≠Q) - // Use solve() with assumption instead of permanent assume() - int query = ez->XOR(coi, d_ne_q); + // Safe gating: when gating is active (enable=0 or disable=1), D must equal Q + int gating_active = as_enable ? ez->NOT(combined_cond) : combined_cond; + int query = ez->AND(gating_active, d_ne_q); std::vector assumptions = {query}; - std::vector dummy_model_exprs; - std::vector dummy_model_vals; + std::vector dummy_exprs; + std::vector dummy_vals; - // If UNSAT: COI is exactly when D≠Q → perfect enable - return !ez->solve(dummy_model_exprs, dummy_model_vals, assumptions); + return !ez->solve(dummy_exprs, dummy_vals, assumptions); } - - // Recursively determine the enable input set via BFS expansion - // Seeds initial input set from sig_d, Q bits filtered via get_input_signals - bool determine_enable_recursive(pool &input_set, SigSpec sig_d, SigSpec sig_q, int depth) + + // Find gating condition for a register + // Returns empty vector if no valid condition found + std::pair, bool> findGatingCondition(Cell *reg) { - if (depth > MAX_INPUT_DEPTH) { - log_debug(" Max depth reached, giving up\n"); - return false; + FfData ff(nullptr, reg); + + // Get candidate signals downstream of this register + pool downstream = getDownstreamSignals(reg, max_cover); + + if (downstream.empty()) { + log_debug(" No downstream candidates for %s\n", log_id(reg)); + return {{}, false}; } - - // Seed initial input set from sig_d on first call - if (depth == 1 && input_set.empty()) { - for (auto bit : sigmap(sig_d)) { - if (bit.wire != nullptr) { - for (auto input_bit : get_input_signals(bit)) - input_set.insert(input_bit); - } - } - - if (input_set.empty()) { - log_debug(" No inputs to D (besides Q)\n"); - return false; - } - log_debug(" Initial input set has %zu signals\n", input_set.size()); - } - - // Check if current input set works as enable - if (input_set_is_enable(input_set, sig_d, sig_q)) { - log_debug(" Found enable at depth %d with %zu signals\n", depth, input_set.size()); - return true; - } - - // Expand input set via BFS (one more level) - pool new_inputs; - for (auto bit : input_set) { - for (auto input_bit : get_input_signals(bit)) { - if (!input_set.count(input_bit)) - new_inputs.insert(input_bit); + + // Also include upstream signals that could affect D + pool d_inputs; + for (auto bit : sigmap(ff.sig_d)) + if (bit.wire) + d_inputs.insert(bit); + pool upstream = getUpstreamSignals(d_inputs, max_cover); + + // Combine and limit candidates + std::vector candidates; + for (auto bit : downstream) + candidates.push_back(bit); + for (auto bit : upstream) + if (!downstream.count(bit)) + candidates.push_back(bit); + + if ((int)candidates.size() > max_cover) + candidates.resize(max_cover); + + log_debug(" Found %zu candidate signals\n", candidates.size()); + + // Try as clock enable first (more common) + if (isValidGatingSet(candidates, ff.sig_d, ff.sig_q, true)) { + minimizeGatingCondition(candidates, candidates.begin(), candidates.end(), + ff.sig_d, ff.sig_q, true); + if (!candidates.empty()) { + accepted_count++; + return {candidates, true}; // true = clock enable } } - - if (new_inputs.empty()) { - log_debug(" No more inputs to explore at depth %d\n", depth); - return false; + + // Try as clock disable + if (isValidGatingSet(candidates, ff.sig_d, ff.sig_q, false)) { + minimizeGatingCondition(candidates, candidates.begin(), candidates.end(), + ff.sig_d, ff.sig_q, false); + if (!candidates.empty()) { + accepted_count++; + return {candidates, false}; // false = clock disable + } } - - // Add new inputs and recurse - for (auto bit : new_inputs) - input_set.insert(bit); - - return determine_enable_recursive(input_set, sig_d, sig_q, depth + 1); + + rejected_sat_count++; + return {{}, false}; } - - // Create CE logic based on the input set and modify the FF - // The enable condition is: when all input_set bits are 0, D == Q (hold) - // So CE should be: OR of all input_set bits (active high: CE=1 means update) - void create_ce_logic(const pool &input_set, FfData &ff) + + // Insert clock gating logic for a group of registers + void insertClockGate(const std::vector ®s, + const std::vector &gating_conds, + bool as_enable) { - if (input_set.empty()) + if (regs.empty() || gating_conds.empty()) return; - - log(" Creating CE from %zu input signals\n", input_set.size()); - - // Build CE as OR of all input signals - // CE = 1 when any input is 1 (meaning: update the register) - // CE = 0 when all inputs are 0 (meaning: hold, since D == Q) - SigSpec ce_inputs; - for (auto bit : input_set) - ce_inputs.append(bit); - - SigBit ce_signal; - if (GetSize(ce_inputs) == 1) { - ce_signal = ce_inputs[0]; + + log(" Inserting clock gate for %zu registers with %zu condition signals\n", + regs.size(), gating_conds.size()); + + // Build gating condition: OR for enable, AND for disable + SigBit gating_signal; + if (gating_conds.size() == 1) { + gating_signal = gating_conds[0]; } else { - // Create OR gate: CE = |input_set - Wire *ce_wire = module->addWire(NEW_ID); - module->addReduceOr(NEW_ID, ce_inputs, ce_wire); - ce_signal = ce_wire; - } - - // Set the CE on the FF - ff.has_ce = true; - ff.sig_ce = ce_signal; - ff.pol_ce = true; // Active high - - log(" CE signal: %s\n", log_signal(ce_signal)); - } - - // Process a single FF to find and insert CE - bool process_ff(Cell *cell) - { - FfData ff(nullptr, cell); - - // Skip if already has CE, or doesn't have clock/data - if (ff.has_ce) { - log_debug(" Skipping %s: already has CE\n", log_id(cell)); - return false; - } - if (!ff.has_clk) { - log_debug(" Skipping %s: no clock\n", log_id(cell)); - return false; - } - if (GetSize(ff.sig_d) == 0 || GetSize(ff.sig_q) == 0) { - log_debug(" Skipping %s: no D or Q\n", log_id(cell)); - return false; - } - - log("Processing FF: %s\n", log_id(cell)); - - // Set Q bits to exclude from enable candidates - set_excluded_q_bits(ff.sig_q); - - // Find enable via recursive BFS + SAT validation - pool input_set; - if (determine_enable_recursive(input_set, ff.sig_d, ff.sig_q, 1)) { - create_ce_logic(input_set, ff); + SigSpec cond_inputs; + for (auto bit : gating_conds) + cond_inputs.append(bit); - // Emit the modified FF - ff.emit(); - return true; + Wire *cond_wire = module->addWire(NEW_ID); + if (as_enable) + module->addReduceOr(NEW_ID, cond_inputs, cond_wire); + else + module->addReduceAnd(NEW_ID, cond_inputs, cond_wire); + gating_signal = cond_wire; } - - log_debug(" Could not find enable for %s\n", log_id(cell)); - return false; + + // If disable signal, invert to get enable + if (!as_enable) { + Wire *inv_wire = module->addWire(NEW_ID); + module->addNot(NEW_ID, gating_signal, inv_wire); + gating_signal = inv_wire; + } + + // Add CE to each register + for (auto reg : regs) { + FfData ff(nullptr, reg); + + if (ff.has_ce) { + // Already has CE, AND with new condition + Wire *combined_ce = module->addWire(NEW_ID); + module->addAnd(NEW_ID, ff.sig_ce, gating_signal, combined_ce); + ff.sig_ce = combined_ce; + } else { + ff.has_ce = true; + ff.sig_ce = gating_signal; + ff.pol_ce = true; + } + + ff.emit(); + log(" Added CE to %s\n", log_id(reg)); + } + } + + // Main processing function + void run() + { + log("Processing module %s\n", log_id(module)); + + // Collect all registers + std::vector registers; + for (auto cell : module->cells()) { + if (!cell->type.in(ID($ff), ID($dff), ID($dffe), ID($adff), ID($adffe), + ID($sdff), ID($sdffe), ID($sdffce), ID($dffsr), + ID($dffsre), ID($_DFF_P_), ID($_DFF_N_), ID($_DFFE_PP_), + ID($_DFFE_PN_), ID($_DFFE_NP_), ID($_DFFE_NN_))) + continue; + + FfData ff(nullptr, cell); + + // Skip registers that already have CE + if (ff.has_ce) { + log_debug(" Skipping %s: already has CE\n", log_id(cell)); + continue; + } + + if (!ff.has_clk) { + log_debug(" Skipping %s: no clock\n", log_id(cell)); + continue; + } + + registers.push_back(cell); + } + + log(" Found %zu registers without CE\n", registers.size()); + + // Track accepted gating conditions for reuse + // Maps condition signature to (condition signals, registers, is_enable) + dict, std::vector, bool>> accepted_gates; + + int processed = 0; + for (auto reg : registers) { + if (processed % 100 == 0 && processed > 0) + log(" Processed %d/%zu registers\n", processed, registers.size()); + processed++; + + log_debug("Processing register %s\n", log_id(reg)); + + auto [gating_conds, is_enable] = findGatingCondition(reg); + + if (gating_conds.empty()) { + log_debug(" No valid gating condition found\n"); + continue; + } + + // Create signature for this gating condition + std::string sig; + for (auto bit : gating_conds) + sig += log_signal(bit) + ","; + sig += is_enable ? "E" : "D"; + + // Check if we already have this condition + if (accepted_gates.count(sig)) { + auto &[conds, regs, en] = accepted_gates[sig]; + regs.push_back(reg); + log_debug(" Reusing existing gating condition for %s\n", log_id(reg)); + } else { + accepted_gates[sig] = {gating_conds, {reg}, is_enable}; + log(" Found new gating condition for %s: %s (%s)\n", + log_id(reg), sig.c_str(), is_enable ? "enable" : "disable"); + } + } + + // Insert clock gates for groups that meet minimum register threshold + int gates_inserted = 0; + for (auto &[sig, data] : accepted_gates) { + auto &[conds, regs, is_enable] = data; + + if ((int)regs.size() >= min_regs) { + insertClockGate(regs, conds, is_enable); + gates_inserted++; + } else { + log_debug(" Skipping gating condition (only %zu registers, need %d)\n", + regs.size(), min_regs); + } + } + + log(" Inserted %d clock gates\n", gates_inserted); + log(" Statistics: accepted=%d, rejected_sat=%d\n", + accepted_count, rejected_sat_count); } }; -void dump_flipflops_to_file(RTLIL::Design *design, const std::string &filename) -{ - std::ofstream outfile(filename); - if (!outfile.is_open()) { - log_error("Cannot open file %s for writing\n", filename.c_str()); - return; - } - - for (auto module : design->selected_modules()) { - outfile << "Module: " << log_id(module) << "\n"; - log("Module: %s\n", log_id(module)); - - for (auto cell : module->cells()) { - if (cell->is_builtin_ff()) { - outfile << " FF: " << log_id(cell) << " (type: " << log_id(cell->type) << ")\n"; - log(" FF: %s (type: %s)\n", log_id(cell), log_id(cell->type)); - } - } - outfile << "\n"; - } - - outfile.close(); - log("Wrote flip-flop list to %s\n", filename.c_str()); -} - struct SatClockgatePass : public Pass { - SatClockgatePass() : Pass("sat_clockgate", "SAT-based inferred clock gating") { } - + SatClockgatePass() : Pass("sat_clockgate", "SAT-based automatic clock gating") { } + void help() override { log("\n"); log(" sat_clockgate [options] [selection]\n"); log("\n"); - log("This command performs SAT-based inferred clock gating insertion.\n"); - log("It analyzes flip-flops without explicit clock enables and uses SAT\n"); - log("to find input conditions under which D == Q (register holds value).\n"); - log("These conditions become the inferred clock enable.\n"); + log("This command performs SAT-based automatic clock gating insertion.\n"); + log("It analyzes registers and uses SAT solving to find signals that can\n"); + log("serve as clock enable conditions (when the signal is low, D==Q).\n"); log("\n"); - log(" -threshold \n"); - log(" minimum number of FFs that must share an enable for clock gating\n"); - log(" to be inserted (default: 1)\n"); + log("Algorithm based on:\n"); + log(" - \"Automatic Synthesis of Clock Gating Logic\" by Aaron P. Hurst\n"); + log(" - OpenROAD's cgt module implementation\n"); + log("\n"); + log(" -max_cover \n"); + log(" maximum number of candidate signals to consider per register\n"); + log(" (default: %d)\n", DEFAULT_MAX_COVER); + log("\n"); + log(" -min_regs \n"); + log(" minimum number of registers that must share a gating condition\n"); + log(" for a clock gate to be inserted (default: %d)\n", DEFAULT_MIN_REGS); log("\n"); } - + void execute(std::vector args, RTLIL::Design *design) override { - log_header(design, "Executing SAT_CLOCKGATE pass (SAT-based inferred clock gating).\n"); - - int threshold = 1; - + log_header(design, "Executing SAT_CLOCKGATE pass.\n"); + + int max_cover = DEFAULT_MAX_COVER; + int min_regs = DEFAULT_MIN_REGS; + int sim_iterations = DEFAULT_SIM_ITERATIONS; + size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { - if (args[argidx] == "-threshold" && argidx+1 < args.size()) { - threshold = std::stoi(args[++argidx]); + if (args[argidx] == "-max_cover" && argidx+1 < args.size()) { + max_cover = std::stoi(args[++argidx]); + continue; + } + if (args[argidx] == "-min_regs" && argidx+1 < args.size()) { + min_regs = std::stoi(args[++argidx]); continue; } break; } extra_args(args, argidx, design); - - log("Using threshold: %d\n", threshold); - - // Dump all flip-flops to file (debug) - dump_flipflops_to_file(design, "flip_flops.txt"); - - int total_converted = 0; - + + log("Configuration: max_cover=%d, min_regs=%d\n", max_cover, min_regs); + + int total_gates = 0; + for (auto module : design->selected_modules()) { - log("Processing module %s...\n", log_id(module)); - - SatClockgateWorker worker(module); - - // Collect FFs to process (can't modify while iterating) - std::vector ffs_to_process; - for (auto cell : module->cells()) { - if (cell->is_builtin_ff()) - ffs_to_process.push_back(cell); - } - - int converted = 0; - for (auto cell : ffs_to_process) { - if (worker.process_ff(cell)) - converted++; - } - - if (converted > 0) { - log("Converted %d FFs in module %s\n", converted, log_id(module)); - total_converted += converted; - } + SatClockgateWorker worker(module, max_cover, min_regs, sim_iterations); + worker.run(); + total_gates += worker.accepted_count; } - - log("Total FFs with inferred CE: %d\n", total_converted); - - // TODO: Call clockgate pass to convert CEs to ICG cells - // if (total_converted >= threshold) { - // Pass::call(design, "clockgate"); - // } + + log("Total clock gates inserted: %d\n", total_gates); } } SatClockgatePass;