From e1a59ba80b123bf28f2185c91b051895903493ce Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Mon, 22 Jan 2024 18:32:23 +0100 Subject: [PATCH] async2sync, clk2fflogic: Add support for $check and $print cells --- passes/sat/async2sync.cc | 73 +++++++++- passes/sat/clk2fflogic.cc | 191 +++++++++++++++++--------- tests/various/chformal_coverenable.ys | 2 + 3 files changed, 197 insertions(+), 69 deletions(-) diff --git a/passes/sat/async2sync.cc b/passes/sat/async2sync.cc index 6fdf470b1..93c7e96c8 100644 --- a/passes/sat/async2sync.cc +++ b/passes/sat/async2sync.cc @@ -41,31 +41,88 @@ struct Async2syncPass : public Pass { log("reset value in the next cycle regardless of the data-in value at the time of\n"); log("the clock edge.\n"); log("\n"); + log(" -nolower\n"); + log(" Do not automatically run 'chformal -lower' to lower $check cells.\n"); + log("\n"); } void execute(std::vector args, RTLIL::Design *design) override { - // bool flag_noinit = false; + bool flag_nolower = false; log_header(design, "Executing ASYNC2SYNC pass.\n"); size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { - // if (args[argidx] == "-noinit") { - // flag_noinit = true; - // continue; - // } + if (args[argidx] == "-nolower") { + flag_nolower = true; + continue; + } break; } extra_args(args, argidx, design); + bool have_check_cells = false; + for (auto module : design->selected_modules()) { SigMap sigmap(module); FfInitVals initvals(&sigmap, module); + SigBit initstate; + for (auto cell : vector(module->selected_cells())) { + if (cell->type.in(ID($print), ID($check))) + { + if (cell->type == ID($check)) + have_check_cells = true; + + bool trg_enable = cell->getParam(ID(TRG_ENABLE)).as_bool(); + if (!trg_enable) + continue; + + int trg_width = cell->getParam(ID(TRG_WIDTH)).as_int(); + + if (trg_width > 1) + log_error("$check cell %s with TRG_WIDTH > 1 is not support by async2sync, use clk2fflogic.\n", log_id(cell)); + + if (trg_width == 0) { + if (initstate == State::S0) + initstate = module->Initstate(NEW_ID); + + SigBit sig_en = cell->getPort(ID::EN); + cell->setPort(ID::EN, module->And(NEW_ID, sig_en, initstate)); + } else { + SigBit sig_en = cell->getPort(ID::EN); + SigSpec sig_args = cell->getPort(ID::ARGS); + bool trg_polarity = cell->getParam(ID(TRG_POLARITY)).as_bool(); + SigBit sig_trg = cell->getPort(ID::TRG); + Wire *sig_en_q = module->addWire(NEW_ID); + Wire *sig_args_q = module->addWire(NEW_ID, GetSize(sig_args)); + sig_en_q->attributes.emplace(ID::init, State::S0); + module->addDff(NEW_ID, sig_trg, sig_en, sig_en_q, trg_polarity, cell->get_src_attribute()); + module->addDff(NEW_ID, sig_trg, sig_args, sig_args_q, trg_polarity, cell->get_src_attribute()); + cell->setPort(ID::EN, sig_en_q); + cell->setPort(ID::ARGS, sig_args_q); + if (cell->type == ID($check)) { + SigBit sig_a = cell->getPort(ID::A); + Wire *sig_a_q = module->addWire(NEW_ID); + sig_a_q->attributes.emplace(ID::init, State::S1); + module->addDff(NEW_ID, sig_trg, sig_a, sig_a_q, trg_polarity, cell->get_src_attribute()); + cell->setPort(ID::A, sig_a_q); + } + } + + cell->setPort(ID::TRG, SigSpec()); + + cell->setParam(ID::TRG_ENABLE, false); + cell->setParam(ID::TRG_WIDTH, 0); + cell->setParam(ID::TRG_POLARITY, false); + cell->set_bool_attribute(ID(trg_on_gclk)); + continue; + } + if (!RTLIL::builtin_ff_cell_types().count(cell->type)) continue; @@ -273,6 +330,12 @@ struct Async2syncPass : public Pass { ff.emit(); } } + + if (have_check_cells && !flag_nolower) { + log_push(); + Pass::call(design, "chformal -lower"); + log_pop(); + } } } Async2syncPass; diff --git a/passes/sat/clk2fflogic.cc b/passes/sat/clk2fflogic.cc index 3dc96ecce..2c0e13f85 100644 --- a/passes/sat/clk2fflogic.cc +++ b/passes/sat/clk2fflogic.cc @@ -48,6 +48,9 @@ struct Clk2fflogicPass : public Pass { log("reset value in the next cycle regardless of the data-in value at the time of\n"); log("the clock edge.\n"); log("\n"); + log(" -nolower\n"); + log(" Do not automatically run 'chformal -lower' to lower $check cells.\n"); + log("\n"); } // Active-high sampled and current value of a level-triggered control signal. Initial sampled values is low/non-asserted. SampledSig sample_control(Module *module, SigSpec sig, bool polarity, bool is_fine) { @@ -117,21 +120,23 @@ struct Clk2fflogicPass : public Pass { } void execute(std::vector args, RTLIL::Design *design) override { - // bool flag_noinit = false; + bool flag_nolower = false; log_header(design, "Executing CLK2FFLOGIC pass (convert clocked FFs to generic $ff cells).\n"); size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { - // if (args[argidx] == "-noinit") { - // flag_noinit = true; - // continue; - // } + if (args[argidx] == "-nolower") { + flag_nolower = true; + continue; + } break; } extra_args(args, argidx, design); + bool have_check_cells = false; + for (auto module : design->selected_modules()) { SigMap sigmap(module); @@ -194,79 +199,137 @@ struct Clk2fflogicPass : public Pass { mem.emit(); } + SigBit initstate; + for (auto cell : vector(module->selected_cells())) { - SigSpec qval; - if (RTLIL::builtin_ff_cell_types().count(cell->type)) { - FfData ff(&initvals, cell); + if (cell->type.in(ID($print), ID($check))) + { + if (cell->type == ID($check)) + have_check_cells = true; - if (ff.has_gclk) { - // Already a $ff or $_FF_ cell. + bool trg_enable = cell->getParam(ID(TRG_ENABLE)).as_bool(); + if (!trg_enable) continue; - } - if (ff.has_clk) { - log("Replacing %s.%s (%s): CLK=%s, D=%s, Q=%s\n", - log_id(module), log_id(cell), log_id(cell->type), - log_signal(ff.sig_clk), log_signal(ff.sig_d), log_signal(ff.sig_q)); - } else if (ff.has_aload) { - log("Replacing %s.%s (%s): EN=%s, D=%s, Q=%s\n", - log_id(module), log_id(cell), log_id(cell->type), - log_signal(ff.sig_aload), log_signal(ff.sig_ad), log_signal(ff.sig_q)); + int trg_width = cell->getParam(ID(TRG_WIDTH)).as_int(); + + if (trg_width == 0) { + if (initstate == State::S0) + initstate = module->Initstate(NEW_ID); + + SigBit sig_en = cell->getPort(ID::EN); + cell->setPort(ID::EN, module->And(NEW_ID, sig_en, initstate)); } else { - // $sr. - log("Replacing %s.%s (%s): SET=%s, CLR=%s, Q=%s\n", - log_id(module), log_id(cell), log_id(cell->type), - log_signal(ff.sig_set), log_signal(ff.sig_clr), log_signal(ff.sig_q)); + SigBit sig_en = cell->getPort(ID::EN); + SigSpec sig_args = cell->getPort(ID::ARGS); + Const trg_polarity = cell->getParam(ID(TRG_POLARITY)); + SigSpec sig_trg = cell->getPort(ID::TRG); + + SigSpec sig_trg_sampled; + + for (auto const &bit : sig_trg) + sig_trg_sampled.append(sample_control_edge(module, bit, trg_polarity[GetSize(sig_trg_sampled)] == State::S1, false)); + SigSpec sig_args_sampled = sample_data(module, sig_args, Const(State::S0, GetSize(sig_args)), false, false).sampled; + SigBit sig_en_sampled = sample_data(module, sig_en, State::S0, false, false).sampled; + + SigBit sig_trg_combined = module->ReduceOr(NEW_ID, sig_trg_sampled); + + cell->setPort(ID::EN, module->And(NEW_ID, sig_en_sampled, sig_trg_combined)); + cell->setPort(ID::ARGS, sig_args_sampled); + if (cell->type == ID($check)) { + SigBit sig_a_sampled = sample_data(module, sig_en, State::S1, false, false).sampled; + cell->setPort(ID::A, sig_a_sampled); + } } - ff.remove(); + cell->setPort(ID::TRG, SigSpec()); - if (ff.has_clk) - ff.unmap_ce_srst(); + cell->setParam(ID::TRG_ENABLE, false); + cell->setParam(ID::TRG_WIDTH, 0); + cell->setParam(ID::TRG_POLARITY, false); + cell->set_bool_attribute(ID(trg_on_gclk)); - auto next_q = sample_data(module, ff.sig_q, ff.val_init, ff.is_fine, true).sampled; - - if (ff.has_clk) { - // The init value for the sampled d is never used, so we can set it to fixed zero, reducing uninit'd FFs - auto sampled_d = sample_data(module, ff.sig_d, RTLIL::Const(State::S0, ff.width), ff.is_fine); - auto clk_edge = sample_control_edge(module, ff.sig_clk, ff.pol_clk, ff.is_fine); - next_q = mux(module, next_q, sampled_d.sampled, clk_edge, ff.is_fine); - } - - SampledSig sampled_aload, sampled_ad, sampled_set, sampled_clr, sampled_arst; - // The check for a constant sig_aload is also done by opt_dff, but when using verific and running - // clk2fflogic before opt_dff (which does more and possibly unwanted optimizations) this check avoids - // generating a lot of extra logic. - bool has_nonconst_aload = ff.has_aload && ff.sig_aload != (ff.pol_aload ? State::S0 : State::S1); - if (has_nonconst_aload) { - sampled_aload = sample_control(module, ff.sig_aload, ff.pol_aload, ff.is_fine); - // The init value for the sampled ad is never used, so we can set it to fixed zero, reducing uninit'd FFs - sampled_ad = sample_data(module, ff.sig_ad, RTLIL::Const(State::S0, ff.width), ff.is_fine); - } - if (ff.has_sr) { - sampled_set = sample_control(module, ff.sig_set, ff.pol_set, ff.is_fine); - sampled_clr = sample_control(module, ff.sig_clr, ff.pol_clr, ff.is_fine); - } - if (ff.has_arst) - sampled_arst = sample_control(module, ff.sig_arst, ff.pol_arst, ff.is_fine); - - // First perform updates using _only_ sampled values, then again using _only_ current values. Unlike the previous - // implementation, this approach correctly handles all the cases of multiple signals changing simultaneously. - for (int current = 0; current < 2; current++) { - if (has_nonconst_aload) - next_q = mux(module, next_q, sampled_ad[current], sampled_aload[current], ff.is_fine); - if (ff.has_sr) - next_q = bitwise_sr(module, next_q, sampled_set[current], sampled_clr[current], ff.is_fine); - if (ff.has_arst) - next_q = mux(module, next_q, ff.val_arst, sampled_arst[current], ff.is_fine); - } - - module->connect(ff.sig_q, next_q); + continue; } + + if (!RTLIL::builtin_ff_cell_types().count(cell->type)) + continue; + + FfData ff(&initvals, cell); + + if (ff.has_gclk) { + // Already a $ff or $_FF_ cell. + continue; + } + + if (ff.has_clk) { + log("Replacing %s.%s (%s): CLK=%s, D=%s, Q=%s\n", + log_id(module), log_id(cell), log_id(cell->type), + log_signal(ff.sig_clk), log_signal(ff.sig_d), log_signal(ff.sig_q)); + } else if (ff.has_aload) { + log("Replacing %s.%s (%s): EN=%s, D=%s, Q=%s\n", + log_id(module), log_id(cell), log_id(cell->type), + log_signal(ff.sig_aload), log_signal(ff.sig_ad), log_signal(ff.sig_q)); + } else { + // $sr. + log("Replacing %s.%s (%s): SET=%s, CLR=%s, Q=%s\n", + log_id(module), log_id(cell), log_id(cell->type), + log_signal(ff.sig_set), log_signal(ff.sig_clr), log_signal(ff.sig_q)); + } + + ff.remove(); + + if (ff.has_clk) + ff.unmap_ce_srst(); + + auto next_q = sample_data(module, ff.sig_q, ff.val_init, ff.is_fine, true).sampled; + + if (ff.has_clk) { + // The init value for the sampled d is never used, so we can set it to fixed zero, reducing uninit'd FFs + auto sampled_d = sample_data(module, ff.sig_d, RTLIL::Const(State::S0, ff.width), ff.is_fine); + auto clk_edge = sample_control_edge(module, ff.sig_clk, ff.pol_clk, ff.is_fine); + next_q = mux(module, next_q, sampled_d.sampled, clk_edge, ff.is_fine); + } + + SampledSig sampled_aload, sampled_ad, sampled_set, sampled_clr, sampled_arst; + // The check for a constant sig_aload is also done by opt_dff, but when using verific and running + // clk2fflogic before opt_dff (which does more and possibly unwanted optimizations) this check avoids + // generating a lot of extra logic. + bool has_nonconst_aload = ff.has_aload && ff.sig_aload != (ff.pol_aload ? State::S0 : State::S1); + if (has_nonconst_aload) { + sampled_aload = sample_control(module, ff.sig_aload, ff.pol_aload, ff.is_fine); + // The init value for the sampled ad is never used, so we can set it to fixed zero, reducing uninit'd FFs + sampled_ad = sample_data(module, ff.sig_ad, RTLIL::Const(State::S0, ff.width), ff.is_fine); + } + if (ff.has_sr) { + sampled_set = sample_control(module, ff.sig_set, ff.pol_set, ff.is_fine); + sampled_clr = sample_control(module, ff.sig_clr, ff.pol_clr, ff.is_fine); + } + if (ff.has_arst) + sampled_arst = sample_control(module, ff.sig_arst, ff.pol_arst, ff.is_fine); + + // First perform updates using _only_ sampled values, then again using _only_ current values. Unlike the previous + // implementation, this approach correctly handles all the cases of multiple signals changing simultaneously. + for (int current = 0; current < 2; current++) { + if (has_nonconst_aload) + next_q = mux(module, next_q, sampled_ad[current], sampled_aload[current], ff.is_fine); + if (ff.has_sr) + next_q = bitwise_sr(module, next_q, sampled_set[current], sampled_clr[current], ff.is_fine); + if (ff.has_arst) + next_q = mux(module, next_q, ff.val_arst, sampled_arst[current], ff.is_fine); + } + + module->connect(ff.sig_q, next_q); + } } + if (have_check_cells && !flag_nolower) { + log_push(); + Pass::call(design, "chformal -lower"); + log_pop(); + } } } Clk2fflogicPass; diff --git a/tests/various/chformal_coverenable.ys b/tests/various/chformal_coverenable.ys index c052f6dba..efb197773 100644 --- a/tests/various/chformal_coverenable.ys +++ b/tests/various/chformal_coverenable.ys @@ -13,6 +13,8 @@ EOT prep -top top +design -save prep + async2sync select -assert-count 1 t:$cover