diff --git a/.github/actions/setup-build-env/action.yml b/.github/actions/setup-build-env/action.yml index 86d7c21ec..8b3e2d769 100644 --- a/.github/actions/setup-build-env/action.yml +++ b/.github/actions/setup-build-env/action.yml @@ -56,7 +56,7 @@ runs: if: runner.os == 'macOS' shell: bash run: | - brew bundle + brew install bison flex gawk libffi git pkg-config python3 bash googletest tcl-tk llvm - name: Linux runtime environment if: runner.os == 'Linux' diff --git a/.github/workflows/extra-builds.yml b/.github/workflows/extra-builds.yml index 644529c9d..d041794d7 100644 --- a/.github/workflows/extra-builds.yml +++ b/.github/workflows/extra-builds.yml @@ -172,32 +172,12 @@ jobs: cmake -B build -DCMAKE_TOOLCHAIN_FILE=${WASI_SDK_PATH}/share/cmake/wasi-sdk-p1.cmake -DCMAKE_BUILD_TYPE=Release -DYOSYS_COMPILER_LAUNCHER=ccache . cmake --build build -j$(nproc) - nix-build: - name: "Build nix flake" - needs: pre_job - if: (github.event_name == 'merge_group' || github.event_name == 'workflow_dispatch') && needs.pre_job.outputs.should_skip != 'true' - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest] - fail-fast: false - steps: - - uses: actions/checkout@v7 - with: - submodules: true - persist-credentials: false - - uses: cachix/install-nix-action@v31 - with: - install_url: https://releases.nixos.org/nix/nix-2.30.0/install - - run: nix build -L - extra-builds-result: runs-on: ubuntu-latest needs: - vs-build - mingw-build - wasi-build - - nix-build if: always() steps: - name: Check results diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml new file mode 100644 index 000000000..120240bd6 --- /dev/null +++ b/.github/workflows/nix.yml @@ -0,0 +1,24 @@ +name: Test nix build + +on: + workflow_dispatch: + schedule: + - cron: '0 5 * * 6' + +jobs: + nix-build: + name: "Build nix flake" + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + fail-fast: false + steps: + - uses: actions/checkout@v7 + with: + submodules: true + persist-credentials: false + - uses: cachix/install-nix-action@v31 + with: + install_url: https://releases.nixos.org/nix/nix-2.30.0/install + - run: nix build -L diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 01a7e505c..2d641deec 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -49,7 +49,7 @@ jobs: - id: set_output run: | if [ "${{ github.event_name }}" = "merge_group" ]; then - echo "should_skip=false" >> $GITHUB_OUTPUT + echo "should_skip=true" >> $GITHUB_OUTPUT elif [ "${{ github.event_name }}" = "push" ]; then should_skip=false else diff --git a/abc b/abc index 648de7a21..bec0695f0 160000 --- a/abc +++ b/abc @@ -1 +1 @@ -Subproject commit 648de7a21751cfab01c253aca6d4751f642577df +Subproject commit bec0695f0de539e974d95ab845e581da326ea3d7 diff --git a/kernel/log.cc b/kernel/log.cc index 2f5a6350b..2c3e45c2b 100644 --- a/kernel/log.cc +++ b/kernel/log.cc @@ -388,12 +388,6 @@ void log_formatted_file_error(std::string_view filename, int lineno, std::string log_error_with_prefix(prefix, str); } -void logv_file_error(const string &filename, int lineno, - const char *format, va_list ap) -{ - log_formatted_file_error(filename, lineno, vstringf(format, ap)); -} - void log_experimental(const std::string &str) { if (log_experimentals_ignored.count(str) == 0 && log_experimentals.count(str) == 0) { diff --git a/kernel/log.h b/kernel/log.h index 673c27ce2..f320537ad 100644 --- a/kernel/log.h +++ b/kernel/log.h @@ -24,7 +24,6 @@ #include -#include #include #define YS_REGEX_COMPILE(param) std::regex(param, \ std::regex_constants::nosubs | \ @@ -44,20 +43,11 @@ # endif #endif -#if defined(_MSC_VER) -// At least this is not in MSVC++ 2013. -# define __PRETTY_FUNCTION__ __FUNCTION__ -#endif - // from libs/sha1/sha1.h class SHA1; YOSYS_NAMESPACE_BEGIN -#define S__LINE__sub2(x) #x -#define S__LINE__sub1(x) S__LINE__sub2(x) -#define S__LINE__ S__LINE__sub1(__LINE__) - // YS_DEBUGTRAP is a macro that is functionally equivalent to a breakpoint // if the platform provides such functionality, and does nothing otherwise. // If no debugger is attached, it starts a just-in-time debugger if available, @@ -120,9 +110,6 @@ extern int log_make_debug; extern int log_force_debug; extern int log_debug_suppressed; -[[deprecated]] -[[noreturn]] void logv_file_error(const string &filename, int lineno, const char *format, va_list ap); - void set_verific_logging(void (*cb)(int msg_type, const char *message_id, const char* file_path, unsigned int left_line, unsigned int left_col, unsigned int right_line, unsigned int right_col, const char *msg)); extern void (*log_verific_callback)(int msg_type, const char *message_id, const char* file_path, unsigned int left_line, unsigned int left_col, unsigned int right_line, unsigned int right_col, const char *msg); diff --git a/passes/opt/cut_region.h b/passes/opt/cut_region.h index 3387922ca..d5384b631 100644 --- a/passes/opt/cut_region.h +++ b/passes/opt/cut_region.h @@ -826,6 +826,20 @@ struct CutRegionWorker return sig; } + // Shared ConstEval for the whole matching phase. The netlist is not + // modified until rewrites are applied, so one instance (built once, at + // O(module) cost) serves every fingerprint eval instead of rebuilding + // one per candidate. Callers keep push/pop balanced, so the base state + // stays clean between uses (asserted on each hand-out). + std::unique_ptr shared_ce_ptr; + ConstEval &shared_ce() + { + if (!shared_ce_ptr) + shared_ce_ptr = std::make_unique(module); + log_assert(shared_ce_ptr->stack.empty()); + return *shared_ce_ptr; + } + // Evaluate `out_sig` under the given input assignments; returns false if // the cut does not fully determine the output. Charges the eval budget // by `cone_cells_estimate`. diff --git a/passes/opt/opt_dff.cc b/passes/opt/opt_dff.cc index 76fcb1125..a68184cae 100644 --- a/passes/opt/opt_dff.cc +++ b/passes/opt/opt_dff.cc @@ -43,6 +43,91 @@ struct OptDffOptions bool keepdc; }; +// Bit-parallel random simulation used as a cheap pre-filter for equivalence +struct BitSim { + Module *module; + SigMap &sigmap; + ModWalker &modwalker; + dict sim_vals; + uint64_t rng_state; + int max_depth; + int evals_left; + + BitSim(Module *m, SigMap &sm, ModWalker &mw) + : module(m), sigmap(sm), modwalker(mw), rng_state(1337) + { + max_depth = module->design->scratchpad_get_int("opt_dff.sim_depth", 10000); + evals_left = module->design->scratchpad_get_int("opt_dff.sim_evals", 1000000); + } + + uint64_t next_rand() { + uint32_t lo = mkhash_xorshift((uint32_t)rng_state); + uint32_t hi = mkhash_xorshift((uint32_t)(rng_state >> 32) ^ lo); + rng_state = ((uint64_t)hi << 32) | lo; + return rng_state; + } + + uint64_t eval_bit(SigBit b, int depth = 0) { + SigBit mapped = sigmap(b); + if (mapped == State::S0) return 0ULL; + if (mapped == State::S1) return ~0ULL; + if (mapped == State::Sx || mapped == State::Sz) return 0ULL; + + auto it = sim_vals.find(mapped); + if (it != sim_vals.end()) return it->second; + + // Failsafe for huge designs + if (depth >= max_depth || evals_left <= 0) { + uint64_t r = next_rand(); + sim_vals[mapped] = r; + return r; + } + evals_left--; + + sim_vals[mapped] = 0; + uint64_t res = 0; + + if (!modwalker.has_drivers(mapped)) { + res = next_rand(); + } else { + auto &drivers = modwalker.signal_drivers[mapped]; + if (drivers.empty()) { + res = next_rand(); + } else { + auto driver = *drivers.begin(); + Cell *cell = driver.cell; + + if (cell->is_builtin_ff()) { + res = next_rand(); + } else if (cell->type == ID($_AND_)) { + res = eval_bit(cell->getPort(ID::A)[0], depth+1) & eval_bit(cell->getPort(ID::B)[0], depth+1); + } else if (cell->type == ID($_OR_)) { + res = eval_bit(cell->getPort(ID::A)[0], depth+1) | eval_bit(cell->getPort(ID::B)[0], depth+1); + } else if (cell->type == ID($_XOR_)) { + res = eval_bit(cell->getPort(ID::A)[0], depth+1) ^ eval_bit(cell->getPort(ID::B)[0], depth+1); + } else if (cell->type == ID($_NOT_)) { + res = ~eval_bit(cell->getPort(ID::A)[0], depth+1); + } else if (cell->type == ID($_MUX_)) { + uint64_t s = eval_bit(cell->getPort(ID::S)[0], depth+1); + uint64_t a = eval_bit(cell->getPort(ID::A)[0], depth+1); + uint64_t b = eval_bit(cell->getPort(ID::B)[0], depth+1); + res = (a & ~s) | (b & s); + } else if (cell->type == ID($mux)) { + uint64_t s = eval_bit(cell->getPort(ID::S)[0], depth+1); + uint64_t a = eval_bit(cell->getPort(ID::A)[driver.offset], depth+1); + uint64_t b = eval_bit(cell->getPort(ID::B)[driver.offset], depth+1); + res = (a & ~s) | (b & s); + } else { + res = next_rand(); + } + } + } + + sim_vals[mapped] = res; + return res; + } +}; + struct OptDffWorker { const OptDffOptions &opt; @@ -921,6 +1006,397 @@ struct OptDffWorker return did_something; } + + struct EqBit { + Cell *cell; + int idx; + SigBit q; + }; + + // NOTE: This intentionally duplicates a subset of FfData, as flattening just the + // fields that matter for merging into a single comparable/hashable key is cheaper + struct SigKey { + enum Flag : uint16_t { + InitOne = 1u << 0, + InitX = 1u << 1, + PolClk = 1u << 2, + PolCe = 1u << 3, + PolSrst = 1u << 4, + PolArst = 1u << 5, + PolAload = 1u << 6, + PolClr = 1u << 7, + PolSet = 1u << 8, + CeOverSrst = 1u << 9, + }; + + SigBit clk, ce, srst, arst, aload, clr, set; + IdString cell_type; // for SR + uint16_t flags; + + bool operator==(const SigKey &o) const { + return flags == o.flags && clk == o.clk && ce == o.ce && srst == o.srst && arst == o.arst + && aload == o.aload && clr == o.clr && set == o.set && cell_type == o.cell_type; + } + + Hasher hash_into(Hasher h) const { + h.eat(flags); + h.eat(clk); + h.eat(ce); + h.eat(srst); + h.eat(arst); + h.eat(aload); + h.eat(clr); + h.eat(set); + h.eat(cell_type); + return h; + } + }; + + bool is_def(State s) { + // Concrete constant bit (0 or 1), as opposed to x/z + return s == State::S0 || s == State::S1; + } + + std::vector> gather_initial_eq_classes(std::vector &bits, dict &ff_for_cell) + { + std::vector keys; + + // Collect FF bits eligible for merging + for (auto cell : module->selected_cells()) { + if (!cell->is_builtin_ff()) + continue; + + FfData ff(&initvals, cell); + if (!ff.has_clk && !ff.has_gclk) + continue; + + ff_for_cell.emplace(cell, ff); + + for (int i = 0; i < ff.width; i++) { + // Skip bits whose reset value is undefined (x) + if (ff.has_srst && !is_def(ff.val_srst[i])) continue; + if (ff.has_arst && !is_def(ff.val_arst[i])) continue; + + // Class members are assumed equal in the current cycle and proven equal in the next, which needs + // a base case anchoring them to a common known value + bool def_init = is_def(ff.val_init[i]); + if (!def_init && !ff.has_srst && !ff.has_arst) + continue; + + SigKey k = {}; + + // Flags + if (def_init && ff.val_init[i] == State::S1) + k.flags |= SigKey::InitOne; + else if (!def_init) + k.flags |= SigKey::InitX; + + if (ff.has_clk) { + k.clk = ff.sig_clk; + if (ff.pol_clk) k.flags |= SigKey::PolClk; + } + if (ff.has_ce) { + k.ce = ff.sig_ce; + if (ff.pol_ce) k.flags |= SigKey::PolCe; + } + if (ff.has_srst) { + k.srst = ff.sig_srst; + if (ff.pol_srst) k.flags |= SigKey::PolSrst; + if (ff.ce_over_srst) k.flags |= SigKey::CeOverSrst; + } + if (ff.has_arst) { + k.arst = ff.sig_arst; + if (ff.pol_arst) k.flags |= SigKey::PolArst; + } + if (ff.has_aload) { + k.aload = ff.sig_aload; + if (ff.pol_aload) k.flags |= SigKey::PolAload; + } + if (ff.has_sr) { + k.clr = ff.sig_clr[i]; + k.set = ff.sig_set[i]; + k.cell_type = cell->type; + if (ff.pol_clr) k.flags |= SigKey::PolClr; + if (ff.pol_set) k.flags |= SigKey::PolSet; + } + + bits.push_back({cell, i, ff.sig_q[i]}); + keys.push_back(k); + } + } + + dict> buckets; + for (int i = 0; i < GetSize(bits); i++) + buckets[keys[i]].push_back(i); + + std::vector> classes; + for (auto &kv : buckets) + if (GetSize(kv.second) >= 2) + classes.push_back(std::move(kv.second)); + + return classes; + } + + std::vector> filter_classes_sim( + const std::vector> &classes, + const std::vector &bits, + const dict &ff_for_cell, + ModWalker &modwalker + ) { + BitSim sim(module, sigmap, modwalker); + + // Assume same class + for (auto &cls : classes) { + uint64_t class_q_val = sim.next_rand(); + for (int idx : cls) { + sim.sim_vals[sigmap(bits[idx].q)] = class_q_val; + } + } + + std::vector> refined_classes; + for (auto &cls : classes) { + dict> sim_buckets; + for (int idx : cls) { + const EqBit &eb = bits[idx]; + const FfData &ff = ff_for_cell.at(eb.cell); + uint64_t n_val = sim.eval_bit(ff.sig_d[eb.idx]); + + if (ff.has_aload) { + uint64_t al = sim.eval_bit(ff.sig_aload); + if (!ff.pol_aload) al = ~al; + uint64_t ad = sim.eval_bit(ff.sig_ad[eb.idx]); + n_val = (n_val & ~al) | (ad & al); + } + if (ff.has_arst) { + uint64_t ar = sim.eval_bit(ff.sig_arst); + if (!ff.pol_arst) ar = ~ar; + uint64_t ar_val = (ff.val_arst[eb.idx] == State::S1) ? ~0ULL : 0ULL; + n_val = (n_val & ~ar) | (ar_val & ar); + } + if (ff.has_sr) { + uint64_t clr = sim.eval_bit(ff.sig_clr[eb.idx]); + if (!ff.pol_clr) clr = ~clr; + uint64_t set = sim.eval_bit(ff.sig_set[eb.idx]); + if (!ff.pol_set) set = ~set; + n_val = ~clr & (set | n_val); + } + if (ff.has_srst) { + uint64_t srst = sim.eval_bit(ff.sig_srst); + if (!ff.pol_srst) srst = ~srst; + uint64_t srst_val = (ff.val_srst[eb.idx] == State::S1) ? ~0ULL : 0ULL; + n_val = (n_val & ~srst) | (srst_val & srst); + } + + sim_buckets[n_val].push_back(idx); + } + + for (auto &kv : sim_buckets) + if (GetSize(kv.second) >= 2) + refined_classes.push_back(std::move(kv.second)); + } + + return refined_classes; + } + + std::vector> filter_classes_sat( + std::vector> classes, + const std::vector &bits, + const dict &ff_for_cell, + ModWalker &modwalker + ) { + QuickConeSat qcsat(modwalker); + std::vector q_lit(bits.size(), -1); + std::vector n_lit(bits.size(), -1); + + // Build the next-state function n_lit[idx] of every candidate bit by + // folding the FF's control logic on top of the D input (-> next value) + + // Two bits are equivalent if their next states always agree whenever their + // current states (and those of every other candidate pair) agree + for (auto &cls : classes) { + for (int idx : cls) { + const EqBit &eb = bits[idx]; + const FfData &ff = ff_for_cell.at(eb.cell); + q_lit[idx] = qcsat.importSigBit(eb.q); + int n = qcsat.importSigBit(ff.sig_d[eb.idx]); + + if (ff.has_aload) { + int al = qcsat.importSigBit(ff.sig_aload); + if (!ff.pol_aload) al = qcsat.ez->NOT(al); + n = qcsat.ez->ITE(al, qcsat.importSigBit(ff.sig_ad[eb.idx]), n); + } + if (ff.has_arst) { + int ar = qcsat.importSigBit(ff.sig_arst); + if (!ff.pol_arst) ar = qcsat.ez->NOT(ar); + n = qcsat.ez->ITE(ar, qcsat.ez->value(ff.val_arst[eb.idx] == State::S1), n); + } + if (ff.has_sr) { + int clr = qcsat.importSigBit(ff.sig_clr[eb.idx]); + if (!ff.pol_clr) clr = qcsat.ez->NOT(clr); + int set = qcsat.importSigBit(ff.sig_set[eb.idx]); + if (!ff.pol_set) set = qcsat.ez->NOT(set); + n = qcsat.ez->AND(qcsat.ez->NOT(clr), qcsat.ez->OR(set, n)); + } + if (ff.has_srst) { + int srst = qcsat.importSigBit(ff.sig_srst); + if (!ff.pol_srst) srst = qcsat.ez->NOT(srst); + n = qcsat.ez->ITE(srst, qcsat.ez->value(ff.val_srst[eb.idx] == State::S1), n); + } + + n_lit[idx] = n; + } + } + + qcsat.prepare(); + + // Assume the induction hypo (that every current class is internally equal in the present cycle), and try + // to prove that the members of each class therefore also agree in the next cycle + + // A class survives only if no counterexample exists under that hypo, so combined with the common init/reset + // value that every class shares, this makes the equality an inductive invariant -> bits are eq and safe to merge + std::vector worklist; + std::vector in_worklist(GetSize(classes), true); + + for (int i = 0; i < GetSize(classes); i++) + worklist.push_back(i); + + while (!worklist.empty()) { + int cls_idx = worklist.back(); + worklist.pop_back(); + in_worklist[cls_idx] = false; + + auto &cls = classes[cls_idx]; + if (GetSize(cls) < 2) continue; + + // Induction hypo: assume every candidate class is equal + std::vector assumptions; + for (auto &c : classes) { + if (GetSize(c) < 2) continue; + int rep = c[0]; + for (int k = 1; k < GetSize(c); k++) + assumptions.push_back(qcsat.ez->IFF(q_lit[rep], q_lit[c[k]])); + } + + // Scan the class members against the representative and issue a query per pair, + // stopping early at the first counterexample, which is reused to split the entire + // class at once + int rep = cls[0]; + for (int i = 1; i < GetSize(cls); i++) { + if (n_lit[rep] == n_lit[cls[i]]) + continue; + + // Can the next state of the rep and this member ever differ? + int query = qcsat.ez->XOR(n_lit[rep], n_lit[cls[i]]); + // Capture every member's next-state value in that model so one counterexample + // partitions the whole class + std::vector modelExprs; + for (int b : cls) + modelExprs.push_back(n_lit[b]); + + std::vector modelVals; + assumptions.push_back(query); + + if (qcsat.ez->solve(modelExprs, modelVals, assumptions)) { + // SAT -> partition entire class + std::vector sub0; + std::vector sub1; + + for (size_t b_idx = 0; b_idx < cls.size(); b_idx++) { + if (modelVals[b_idx]) + sub1.push_back(cls[b_idx]); + else + sub0.push_back(cls[b_idx]); + } + + classes[cls_idx] = std::move(sub0); + classes.push_back(std::move(sub1)); + in_worklist.push_back(false); + + // Partition was split -> the induction hypo weakened + for (int j = 0; j < GetSize(classes); j++) { + if (GetSize(classes[j]) >= 2 && !in_worklist[j]) { + worklist.push_back(j); + in_worklist[j] = true; + } + } + + break; // Process new splits + } + + assumptions.pop_back(); // Remove query for the next pairwise check if UNSAT + } + } + + return classes; + } + + bool apply_eq_merges(const std::vector> &classes, const std::vector &bits, dict &ff_for_cell) + { + bool any_change = false; + dict> remove_bits; + + // Drive every non-rep Q from its class rep, drop merged bits from their FFs + for (auto &cls : classes) { + if (GetSize(cls) < 2) + continue; + SigBit rep_q = bits[cls[0]].q; + any_change = true; + for (int k = 1; k < GetSize(cls); k++) { + const EqBit &eb = bits[cls[k]]; + initvals.remove_init(eb.q); + module->connect(eb.q, rep_q); + remove_bits[eb.cell].insert(eb.idx); + } + } + + for (auto &kv : remove_bits) { + Cell *cell = kv.first; + const std::set &drop = kv.second; + FfData &ff = ff_for_cell.at(cell); + std::vector keep; + + for (int i = 0; i < ff.width; i++) + if (!drop.count(i)) + keep.push_back(i); + + if (keep.empty()) { + module->remove(cell); + } else { + FfData new_ff = ff.slice(keep); + new_ff.cell = cell; + new_ff.emit(); + } + } + + return any_change; + } + + bool run_eqbits() + { + if (!opt.sat) + return false; + + std::vector bits; + dict ff_for_cell; + + std::vector> classes = gather_initial_eq_classes(bits, ff_for_cell); + if (classes.empty()) + return false; + + ModWalker modwalker(module->design, module); + + // Simulation prepass + classes = filter_classes_sim(classes, bits, ff_for_cell, modwalker); + if (classes.empty()) + return false; + + // SAT prove + classes = filter_classes_sat(std::move(classes), bits, ff_for_cell, modwalker); + if (classes.empty()) + return false; + + return apply_eq_merges(classes, bits, ff_for_cell); + } }; struct OptDffPass : public Pass { @@ -950,7 +1426,9 @@ struct OptDffPass : public Pass { log("\n"); log(" -sat\n"); log(" additionally invoke SAT solver to detect and remove flip-flops (with\n"); - log(" non-constant inputs) that can also be replaced with a constant driver\n"); + log(" non-constant inputs) that can also be replaced with a constant driver,\n"); + log(" or merged with equivalent flip-flops. this reasons in 2-valued logic\n"); + log(" and may resolve don't-care bits, so it is incompatible with -keepdc.\n"); log("\n"); log(" -keepdc\n"); log(" some optimizations change the behavior of the circuit with respect to\n"); @@ -982,6 +1460,13 @@ struct OptDffPass : public Pass { } extra_args(args, argidx, design); + // The SAT engine reasons in 2-valued logic (a constant x is treated as + // 0), so it can resolve don't-care bits to concrete values -- exactly + // what -keepdc promises not to do. Refuse the combination rather than + // silently ignore -keepdc. + if (opt.sat && opt.keepdc) + log_cmd_error("The -sat and -keepdc options are mutually exclusive.\n"); + bool did_something = false; for (auto mod : design->selected_modules()) { OptDffWorker worker(opt, mod); @@ -989,6 +1474,8 @@ struct OptDffPass : public Pass { did_something = true; if (worker.run_constbits()) did_something = true; + if (worker.run_eqbits()) + did_something = true; } if (did_something) diff --git a/passes/opt/opt_priority_onehot.cc b/passes/opt/opt_priority_onehot.cc index 180a6cb33..eadc7615e 100644 --- a/passes/opt/opt_priority_onehot.cc +++ b/passes/opt/opt_priority_onehot.cc @@ -256,7 +256,7 @@ struct OptPriorityOnehotWorker : CutRegionWorker { bool fingerprint(const Candidate &cand, bool msb_first) { - ConstEval ce(module); + ConstEval &ce = shared_ce(); SigSpec out_sig = sigmap(cand.out_sig); SigSpec valid_sig = sigmap(cand.valid_sig); SigSpec field_sig = sigmap(cand.field_sig); diff --git a/passes/silimate/opt_compact_prefix.cc b/passes/silimate/opt_compact_prefix.cc index fdc902a64..c7b443682 100644 --- a/passes/silimate/opt_compact_prefix.cc +++ b/passes/silimate/opt_compact_prefix.cc @@ -236,7 +236,7 @@ struct OptCompactPrefixWorker : CutRegionWorker for (auto bit : en_cands) cand_bits.push_back(bit); - ConstEval ce(module); + ConstEval &ce = shared_ce(); SigSpec out_s = sigmap(root_sig); SigSpec data_s = sigmap(data_sig); SigSpec cand_s; @@ -415,7 +415,7 @@ struct OptCompactPrefixWorker : CutRegionWorker { if (width < 4) return false; - ConstEval ce(module); + ConstEval &ce = shared_ce(); SigSpec in_s = sigmap(in_sig); SigSpec out_s = sigmap(out_sig); @@ -511,7 +511,7 @@ struct OptCompactPrefixWorker : CutRegionWorker int wd = GetSize(dis_s); int wt = GetSize(data_s); bool self = sigmap(dis_s) == sigmap(data_s); - ConstEval ce(module); + ConstEval &ce = shared_ce(); for (int pol = 0; pol < 2; pol++) { // A self-pair (data == disable bus) can only be probed with all // lanes enabled AND data all-ones, i.e. enable-high polarity. @@ -546,7 +546,7 @@ struct OptCompactPrefixWorker : CutRegionWorker return false; if (lw > wd || lw > wt || wd > 62 || wt > 62) return false; - ConstEval ce(module); + ConstEval &ce = shared_ce(); SigSpec dis_s = sigmap(dis_sig); SigSpec data_s = sigmap(data_sig); SigSpec out_s = sigmap(out_sig); @@ -615,8 +615,14 @@ struct OptCompactPrefixWorker : CutRegionWorker return mask; } + // Evaluate the modulo vector set once and test all four + // (msb_first, offset) variants against the recorded outputs, in the + // original priority order with first-match tie-break. This replaces up + // to four full ConstEval sweeps (one per variant) with a single sweep; + // sets out_msb_first/out_offset on the first surviving variant. bool fingerprint_modulo(const SigSpec &en_sig, const SigSpec &n_sig, - const SigSpec &mask_sig, int width, bool msb_first, int offset) + const SigSpec &mask_sig, int width, + bool &out_msb_first, int &out_offset) { if (width <= 0 || width > 62) return false; @@ -624,7 +630,7 @@ struct OptCompactPrefixWorker : CutRegionWorker // also matches plain gating muxes; require a real counter range. if (GetSize(n_sig) < 2) return false; - ConstEval ce(module); + ConstEval &ce = shared_ce(); SigSpec en_s = sigmap(en_sig); SigSpec n_s = sigmap(n_sig); SigSpec mask_s = sigmap(mask_sig); @@ -653,6 +659,12 @@ struct OptCompactPrefixWorker : CutRegionWorker envals.push_back(lfsr & full); } + // Variants in the original check order (msb/lsb x offset 0/1). + static const struct { bool msb; int off; } variants[] = { + {true, 0}, {false, 0}, {false, 1}, {true, 1} + }; + bool alive[4] = {true, true, true, true}; + for (uint64_t nv : nvals) for (uint64_t ev : envals) { uint64_t actual; @@ -660,10 +672,27 @@ struct OptCompactPrefixWorker : CutRegionWorker {n_s, const_u64(nv, nw)}}, mask_s, actual)) return false; - if (actual != expected_modulo_mask(ev, nv, width, msb_first, offset)) + bool any = false; + for (int v = 0; v < 4; v++) { + if (!alive[v]) + continue; + if (actual != expected_modulo_mask(ev, nv, width, + variants[v].msb, variants[v].off)) + alive[v] = false; + else + any = true; + } + if (!any) return false; } - return true; + + for (int v = 0; v < 4; v++) + if (alive[v]) { + out_msb_first = variants[v].msb; + out_offset = variants[v].off; + return true; + } + return false; } // --- Forward gather/compress: out[k] = data[i_k] where i_k is the @@ -693,7 +722,7 @@ struct OptCompactPrefixWorker : CutRegionWorker { if (width < 4 || width > 62) return false; - ConstEval ce(module); + ConstEval &ce = shared_ce(); SigSpec en_s = sigmap(en_sig); SigSpec data_s = sigmap(data_sig); SigSpec out_s = sigmap(out_sig); @@ -1397,19 +1426,7 @@ struct OptCompactPrefixWorker : CutRegionWorker fp_attempts++; bool msb_first = false; int offset = -1; - if (fingerprint_modulo(en.sig, n.sig, root.sig, width, true, 0)) { - msb_first = true; - offset = 0; - } else if (fingerprint_modulo(en.sig, n.sig, root.sig, width, false, 0)) { - msb_first = false; - offset = 0; - } else if (fingerprint_modulo(en.sig, n.sig, root.sig, width, false, 1)) { - msb_first = false; - offset = 1; - } else if (fingerprint_modulo(en.sig, n.sig, root.sig, width, true, 1)) { - msb_first = true; - offset = 1; - } else { + if (!fingerprint_modulo(en.sig, n.sig, root.sig, width, msb_first, offset)) { log_debug(" mod %s/%s: fingerprint mismatch\n", en.name.c_str(), n.name.c_str()); continue; } diff --git a/tests/opt/opt_dff_eqbits.ys b/tests/opt/opt_dff_eqbits.ys new file mode 100644 index 000000000..f990fe71a --- /dev/null +++ b/tests/opt/opt_dff_eqbits.ys @@ -0,0 +1,89 @@ +# small test case +design -reset +read_verilog -sv opt_dff_eqbits_small.sv +hierarchy -top test_case +techmap +opt_dff -sat +synth +opt_dff -sat +opt_clean -purge + +select -assert-count 2 t:$_SDFF_PN0_ + +# equivalence +design -reset +read_verilog -sv opt_dff_eqbits_small.sv +hierarchy -top test_case +prep +design -save gold + +opt_dff -sat +design -save gate + +design -copy-from gold -as gold test_case +design -copy-from gate -as gate test_case +equiv_make gold gate equiv +equiv_induct equiv +equiv_status -assert + + +# large test case +design -reset +read_verilog -sv opt_dff_eqbits_large.sv +hierarchy -top test_case +techmap +opt_dff -sat +synth +opt_dff -sat +opt_clean -purge + +select -assert-count 6 t:$_SDFFE_PN0P_ + +# equivalence +design -reset +read_verilog -sv opt_dff_eqbits_large.sv +hierarchy -top test_case +prep +design -save gold + +opt_dff -sat +design -save gate + +design -copy-from gold -as gold test_case +design -copy-from gate -as gate test_case +equiv_make gold gate equiv +equiv_induct equiv +equiv_status -assert + +# verify keepdc exclusivity +design -reset +read_verilog -sv <