From 1cf8e7c7db1be4c6b26bea796663581de21dd019 Mon Sep 17 00:00:00 2001 From: "N. Engelhardt" Date: Fri, 24 Jan 2025 18:48:09 +0100 Subject: [PATCH 1/5] add ioff inference for qlf_k6n10f --- techlibs/quicklogic/Makefile.inc | 3 +- techlibs/quicklogic/ql_ioff.cc | 82 +++++++++++++++++++++ techlibs/quicklogic/synth_quicklogic.cc | 13 +++- tests/arch/quicklogic/qlf_k6n10f/ioff.ys | 91 ++++++++++++++++++++++++ 4 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 techlibs/quicklogic/ql_ioff.cc create mode 100644 tests/arch/quicklogic/qlf_k6n10f/ioff.ys diff --git a/techlibs/quicklogic/Makefile.inc b/techlibs/quicklogic/Makefile.inc index ade1443713c..a54a7ec0324 100644 --- a/techlibs/quicklogic/Makefile.inc +++ b/techlibs/quicklogic/Makefile.inc @@ -6,6 +6,7 @@ OBJS += techlibs/quicklogic/ql_bram_merge.o OBJS += techlibs/quicklogic/ql_bram_types.o OBJS += techlibs/quicklogic/ql_dsp_simd.o OBJS += techlibs/quicklogic/ql_dsp_io_regs.o +OBJS += techlibs/quicklogic/ql_ioff.o # -------------------------------------- @@ -40,4 +41,4 @@ $(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf $(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/dsp_final_map.v)) $(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/TDP18K_FIFO.v)) $(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/ufifo_ctl.v)) -$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/sram1024x18_mem.v)) \ No newline at end of file +$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/sram1024x18_mem.v)) diff --git a/techlibs/quicklogic/ql_ioff.cc b/techlibs/quicklogic/ql_ioff.cc new file mode 100644 index 00000000000..c857fc35d73 --- /dev/null +++ b/techlibs/quicklogic/ql_ioff.cc @@ -0,0 +1,82 @@ +#include "kernel/log.h" +#include "kernel/modtools.h" +#include "kernel/register.h" +#include "kernel/rtlil.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct QlIoffPass : public Pass { + QlIoffPass() : Pass("ql_ioff", "Infer I/O FFs for qlf_k6n10f architecture") {} + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" ql_ioff [selection]\n"); + log("\n"); + log("This pass promotes qlf_k6n10f registers directly connected to a top-level I/O\n"); + log("port to I/O FFs.\n"); + log("\n"); + } + + void execute(std::vector, RTLIL::Design *design) override + { + log_header(design, "Executing QL_IOFF pass.\n"); + + ModWalker modwalker(design); + Module *module = design->top_module(); + if (!module) + return; + modwalker.setup(module); + pool cells_to_replace; + for (auto cell : module->selected_cells()) { + if (cell->type.in(ID(dffsre), ID(sdffsre))) { + bool e_const = cell->getPort(ID::E).is_fully_ones(); + bool r_const = cell->getPort(ID::R).is_fully_ones(); + bool s_const = cell->getPort(ID::S).is_fully_ones(); + + if (!(e_const && r_const && s_const)) + continue; + + auto d_sig = modwalker.sigmap(cell->getPort(ID::D)); + if (d_sig.is_wire() && d_sig.as_wire()->port_input) { + log_debug("Cell %s is potentially eligible for promotion to input IOFF.\n", cell->name.c_str()); + // check that d_sig has no other consumers + if (GetSize(d_sig) != 1) continue; + pool portbits; + modwalker.get_consumers(portbits, d_sig[0]); + if (GetSize(portbits) > 1) { + log_debug("not promoting: d_sig has other consumers\n"); + continue; + } + cells_to_replace.insert(cell); + continue; // no need to check Q if we already put it on the list + } + auto q_sig = modwalker.sigmap(cell->getPort(ID::Q)); + if (q_sig.is_wire() && q_sig.as_wire()->port_output) { + log_debug("Cell %s is potentially eligible for promotion to output IOFF.\n", cell->name.c_str()); + // check that q_sig has no other consumers + if (GetSize(q_sig) != 1) continue; + pool portbits; + modwalker.get_consumers(portbits, q_sig[0]); + if (GetSize(portbits) > 0) { + log_debug("not promoting: q_sig has other consumers\n"); + continue; + } + cells_to_replace.insert(cell); + } + } + } + + for (auto cell : cells_to_replace) { + log("Promoting register %s to IOFF.\n", log_signal(cell->getPort(ID::Q))); + cell->type = ID(dff); + cell->unsetPort(ID::E); + cell->unsetPort(ID::R); + cell->unsetPort(ID::S); + } + } +} QlIoffPass; + +PRIVATE_NAMESPACE_END diff --git a/techlibs/quicklogic/synth_quicklogic.cc b/techlibs/quicklogic/synth_quicklogic.cc index 76ef4457069..16f6609438e 100644 --- a/techlibs/quicklogic/synth_quicklogic.cc +++ b/techlibs/quicklogic/synth_quicklogic.cc @@ -78,7 +78,7 @@ struct SynthQuickLogicPass : public ScriptPass { } string top_opt, blif_file, edif_file, family, currmodule, verilog_file, lib_path; - bool abc9, inferAdder, nobram, bramTypes, dsp; + bool abc9, inferAdder, nobram, bramTypes, dsp, ioff; void clear_flags() override { @@ -94,6 +94,7 @@ struct SynthQuickLogicPass : public ScriptPass { bramTypes = false; lib_path = "+/quicklogic/"; dsp = true; + ioff = true; } void set_scratchpad_defaults(RTLIL::Design *design) { @@ -158,6 +159,10 @@ struct SynthQuickLogicPass : public ScriptPass { dsp = false; continue; } + if (args[argidx] == "-noioff") { + ioff = false; + continue; + } break; } extra_args(args, argidx, design); @@ -328,6 +333,12 @@ struct SynthQuickLogicPass : public ScriptPass { run("clean"); run("opt_lut"); } + + if (check_label("iomap", "(for qlf_k6n10f)") && (family == "qlf_k6n10f" || help_mode)) { + if (ioff || help_mode) { + run("ql_ioff", "(unless -noioff)"); + } + } if (check_label("check")) { run("autoname"); diff --git a/tests/arch/quicklogic/qlf_k6n10f/ioff.ys b/tests/arch/quicklogic/qlf_k6n10f/ioff.ys new file mode 100644 index 00000000000..bd69a28d400 --- /dev/null +++ b/tests/arch/quicklogic/qlf_k6n10f/ioff.ys @@ -0,0 +1,91 @@ +# test: acceptable for output IOFF promotion +read_verilog < Date: Fri, 24 Jan 2025 21:29:10 +0100 Subject: [PATCH 2/5] fix tests not expecting ioffs --- tests/arch/quicklogic/qlf_k6n10f/counter.ys | 2 +- tests/arch/quicklogic/qlf_k6n10f/dffs.ys | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/arch/quicklogic/qlf_k6n10f/counter.ys b/tests/arch/quicklogic/qlf_k6n10f/counter.ys index ebb6ce243eb..d1a9ec692d7 100644 --- a/tests/arch/quicklogic/qlf_k6n10f/counter.ys +++ b/tests/arch/quicklogic/qlf_k6n10f/counter.ys @@ -2,7 +2,7 @@ read_verilog ../../common/counter.v hierarchy -top top proc flatten -equiv_opt -assert -multiclock -map +/quicklogic/qlf_k6n10f/cells_sim.v -map +/quicklogic/common/cells_sim.v synth_quicklogic -family qlf_k6n10f # equivalency check +equiv_opt -assert -multiclock -map +/quicklogic/qlf_k6n10f/cells_sim.v -map +/quicklogic/common/cells_sim.v synth_quicklogic -family qlf_k6n10f -noioff # equivalency check design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) cd top # Constrain all select calls below inside the top module select -assert-count 4 t:$lut diff --git a/tests/arch/quicklogic/qlf_k6n10f/dffs.ys b/tests/arch/quicklogic/qlf_k6n10f/dffs.ys index 79a16c9412b..e12963ae6ac 100644 --- a/tests/arch/quicklogic/qlf_k6n10f/dffs.ys +++ b/tests/arch/quicklogic/qlf_k6n10f/dffs.ys @@ -5,7 +5,7 @@ design -save read hierarchy -top my_dff proc -equiv_opt -async2sync -assert -map +/quicklogic/qlf_k6n10f/cells_sim.v -map +/quicklogic/common/cells_sim.v synth_quicklogic -family qlf_k6n10f # equivalency check +equiv_opt -async2sync -assert -map +/quicklogic/qlf_k6n10f/cells_sim.v -map +/quicklogic/common/cells_sim.v synth_quicklogic -family qlf_k6n10f -noioff # equivalency check design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) cd my_dff # Constrain all select calls below inside the top module select -assert-count 1 t:sdffsre @@ -14,7 +14,7 @@ select -assert-none t:sdffsre %% t:* %D design -load read hierarchy -top my_dffe proc -equiv_opt -async2sync -assert -map +/quicklogic/qlf_k6n10f/cells_sim.v -map +/quicklogic/common/cells_sim.v synth_quicklogic -family qlf_k6n10f # equivalency check +equiv_opt -async2sync -assert -map +/quicklogic/qlf_k6n10f/cells_sim.v -map +/quicklogic/common/cells_sim.v synth_quicklogic -family qlf_k6n10f -noioff # equivalency check design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) cd my_dffe # Constrain all select calls below inside the top module select -assert-count 1 t:sdffsre From 9da4fe747e96e68a0c8ca8420659ff016cc8481b Mon Sep 17 00:00:00 2001 From: "N. Engelhardt" Date: Tue, 28 Jan 2025 11:23:36 +0100 Subject: [PATCH 3/5] fix bus ioff inference --- techlibs/quicklogic/ql_ioff.cc | 18 +++--- tests/arch/quicklogic/qlf_k6n10f/ioff.ys | 81 ++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 8 deletions(-) diff --git a/techlibs/quicklogic/ql_ioff.cc b/techlibs/quicklogic/ql_ioff.cc index c857fc35d73..e08850fb74c 100644 --- a/techlibs/quicklogic/ql_ioff.cc +++ b/techlibs/quicklogic/ql_ioff.cc @@ -39,13 +39,14 @@ struct QlIoffPass : public Pass { if (!(e_const && r_const && s_const)) continue; - auto d_sig = modwalker.sigmap(cell->getPort(ID::D)); - if (d_sig.is_wire() && d_sig.as_wire()->port_input) { + SigSpec d = cell->getPort(ID::D); + if (GetSize(d) != 1) continue; + SigBit d_sig = modwalker.sigmap(d[0]); + if (d_sig.is_wire() && d_sig.wire->port_input) { log_debug("Cell %s is potentially eligible for promotion to input IOFF.\n", cell->name.c_str()); // check that d_sig has no other consumers - if (GetSize(d_sig) != 1) continue; pool portbits; - modwalker.get_consumers(portbits, d_sig[0]); + modwalker.get_consumers(portbits, d_sig); if (GetSize(portbits) > 1) { log_debug("not promoting: d_sig has other consumers\n"); continue; @@ -53,13 +54,14 @@ struct QlIoffPass : public Pass { cells_to_replace.insert(cell); continue; // no need to check Q if we already put it on the list } - auto q_sig = modwalker.sigmap(cell->getPort(ID::Q)); - if (q_sig.is_wire() && q_sig.as_wire()->port_output) { + SigSpec q = cell->getPort(ID::Q); + if (GetSize(q) != 1) continue; + SigBit q_sig = modwalker.sigmap(q[0]); + if (q_sig.is_wire() && q_sig.wire->port_output) { log_debug("Cell %s is potentially eligible for promotion to output IOFF.\n", cell->name.c_str()); // check that q_sig has no other consumers - if (GetSize(q_sig) != 1) continue; pool portbits; - modwalker.get_consumers(portbits, q_sig[0]); + modwalker.get_consumers(portbits, q_sig); if (GetSize(portbits) > 0) { log_debug("not promoting: q_sig has other consumers\n"); continue; diff --git a/tests/arch/quicklogic/qlf_k6n10f/ioff.ys b/tests/arch/quicklogic/qlf_k6n10f/ioff.ys index bd69a28d400..3144eda133b 100644 --- a/tests/arch/quicklogic/qlf_k6n10f/ioff.ys +++ b/tests/arch/quicklogic/qlf_k6n10f/ioff.ys @@ -9,6 +9,18 @@ EOF synth_quicklogic -family qlf_k6n10f -top top select -assert-count 1 t:dff +design -reset +# test: acceptable for output IOFF promotion +read_verilog < Date: Tue, 28 Jan 2025 17:37:23 +0100 Subject: [PATCH 4/5] detect aliased I/O ports --- techlibs/quicklogic/ql_ioff.cc | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/techlibs/quicklogic/ql_ioff.cc b/techlibs/quicklogic/ql_ioff.cc index e08850fb74c..4cc9785d521 100644 --- a/techlibs/quicklogic/ql_ioff.cc +++ b/techlibs/quicklogic/ql_ioff.cc @@ -32,38 +32,39 @@ struct QlIoffPass : public Pass { pool cells_to_replace; for (auto cell : module->selected_cells()) { if (cell->type.in(ID(dffsre), ID(sdffsre))) { + log_debug("Checking cell %s.\n", cell->name.c_str()); bool e_const = cell->getPort(ID::E).is_fully_ones(); bool r_const = cell->getPort(ID::R).is_fully_ones(); bool s_const = cell->getPort(ID::S).is_fully_ones(); - if (!(e_const && r_const && s_const)) + if (!(e_const && r_const && s_const)) { + log_debug("not promoting: E, R, or S is used\n"); continue; + } SigSpec d = cell->getPort(ID::D); - if (GetSize(d) != 1) continue; - SigBit d_sig = modwalker.sigmap(d[0]); - if (d_sig.is_wire() && d_sig.wire->port_input) { + log_assert(GetSize(d) == 1); + if (modwalker.has_inputs(d)) { log_debug("Cell %s is potentially eligible for promotion to input IOFF.\n", cell->name.c_str()); // check that d_sig has no other consumers pool portbits; - modwalker.get_consumers(portbits, d_sig); + modwalker.get_consumers(portbits, d); if (GetSize(portbits) > 1) { - log_debug("not promoting: d_sig has other consumers\n"); + log_debug("not promoting: D has other consumers\n"); continue; } cells_to_replace.insert(cell); continue; // no need to check Q if we already put it on the list } SigSpec q = cell->getPort(ID::Q); - if (GetSize(q) != 1) continue; - SigBit q_sig = modwalker.sigmap(q[0]); - if (q_sig.is_wire() && q_sig.wire->port_output) { + log_assert(GetSize(q) == 1); + if (modwalker.has_outputs(q)) { log_debug("Cell %s is potentially eligible for promotion to output IOFF.\n", cell->name.c_str()); // check that q_sig has no other consumers pool portbits; - modwalker.get_consumers(portbits, q_sig); + modwalker.get_consumers(portbits, q); if (GetSize(portbits) > 0) { - log_debug("not promoting: q_sig has other consumers\n"); + log_debug("not promoting: Q has other consumers\n"); continue; } cells_to_replace.insert(cell); From 303a386ecc89bbff7cfbc0d4a64535087826a372 Mon Sep 17 00:00:00 2001 From: "N. Engelhardt" Date: Fri, 31 Jan 2025 11:28:57 +0100 Subject: [PATCH 5/5] create duplicate IOFFs if multiple output ports are connected to the same register --- techlibs/quicklogic/ql_ioff.cc | 66 +++++++++++++++++++----- techlibs/quicklogic/synth_quicklogic.cc | 5 +- tests/arch/quicklogic/qlf_k6n10f/ioff.ys | 37 +++++++++++++ 3 files changed, 93 insertions(+), 15 deletions(-) diff --git a/techlibs/quicklogic/ql_ioff.cc b/techlibs/quicklogic/ql_ioff.cc index 4cc9785d521..87b62e855f2 100644 --- a/techlibs/quicklogic/ql_ioff.cc +++ b/techlibs/quicklogic/ql_ioff.cc @@ -29,7 +29,17 @@ struct QlIoffPass : public Pass { if (!module) return; modwalker.setup(module); - pool cells_to_replace; + pool input_ffs; + dict> output_ffs; + dict> output_bit_aliases; + + for (Wire* wire : module->wires()) + if (wire->port_output) { + output_ffs[wire].resize(wire->width, nullptr); + for (SigBit bit : SigSpec(wire)) + output_bit_aliases[modwalker.sigmap(bit)].insert(bit); + } + for (auto cell : module->selected_cells()) { if (cell->type.in(ID(dffsre), ID(sdffsre))) { log_debug("Checking cell %s.\n", cell->name.c_str()); @@ -53,32 +63,62 @@ struct QlIoffPass : public Pass { log_debug("not promoting: D has other consumers\n"); continue; } - cells_to_replace.insert(cell); - continue; // no need to check Q if we already put it on the list + input_ffs.insert(cell); + continue; // prefer input FFs over output FFs } + SigSpec q = cell->getPort(ID::Q); log_assert(GetSize(q) == 1); - if (modwalker.has_outputs(q)) { + if (modwalker.has_outputs(q) && !modwalker.has_consumers(q)) { log_debug("Cell %s is potentially eligible for promotion to output IOFF.\n", cell->name.c_str()); - // check that q_sig has no other consumers - pool portbits; - modwalker.get_consumers(portbits, q); - if (GetSize(portbits) > 0) { - log_debug("not promoting: Q has other consumers\n"); - continue; + for (SigBit bit : output_bit_aliases[modwalker.sigmap(q)]) { + log_assert(bit.is_wire()); + output_ffs[bit.wire][bit.offset] = cell; } - cells_to_replace.insert(cell); + } } } - for (auto cell : cells_to_replace) { - log("Promoting register %s to IOFF.\n", log_signal(cell->getPort(ID::Q))); + for (auto cell : input_ffs) { + log("Promoting register %s to input IOFF.\n", log_signal(cell->getPort(ID::Q))); cell->type = ID(dff); cell->unsetPort(ID::E); cell->unsetPort(ID::R); cell->unsetPort(ID::S); } + for (auto & [old_port_output, ioff_cells] : output_ffs) { + if (std::any_of(ioff_cells.begin(), ioff_cells.end(), [](Cell * c) { return c != nullptr; })) + { + // create replacement output wire + RTLIL::Wire* new_port_output = module->addWire(NEW_ID, old_port_output->width); + new_port_output->start_offset = old_port_output->start_offset; + module->swap_names(old_port_output, new_port_output); + std::swap(old_port_output->port_id, new_port_output->port_id); + std::swap(old_port_output->port_input, new_port_output->port_input); + std::swap(old_port_output->port_output, new_port_output->port_output); + std::swap(old_port_output->upto, new_port_output->upto); + std::swap(old_port_output->is_signed, new_port_output->is_signed); + std::swap(old_port_output->attributes, new_port_output->attributes); + + // create new output FFs + SigSpec sig_o(old_port_output); + SigSpec sig_n(new_port_output); + for (int i = 0; i < new_port_output->width; i++) { + if (ioff_cells[i]) { + log("Promoting %s to output IOFF.\n", log_signal(sig_n[i])); + + RTLIL::Cell *new_cell = module->addCell(NEW_ID, ID(dff)); + new_cell->setPort(ID::C, ioff_cells[i]->getPort(ID::C)); + new_cell->setPort(ID::D, ioff_cells[i]->getPort(ID::D)); + new_cell->setPort(ID::Q, sig_n[i]); + new_cell->set_bool_attribute(ID::keep); + } else { + module->connect(sig_n[i], sig_o[i]); + } + } + } + } } } QlIoffPass; diff --git a/techlibs/quicklogic/synth_quicklogic.cc b/techlibs/quicklogic/synth_quicklogic.cc index 16f6609438e..07ec769b559 100644 --- a/techlibs/quicklogic/synth_quicklogic.cc +++ b/techlibs/quicklogic/synth_quicklogic.cc @@ -334,9 +334,10 @@ struct SynthQuickLogicPass : public ScriptPass { run("opt_lut"); } - if (check_label("iomap", "(for qlf_k6n10f)") && (family == "qlf_k6n10f" || help_mode)) { + if (check_label("iomap", "(for qlf_k6n10f, skip if -noioff)") && (family == "qlf_k6n10f" || help_mode)) { if (ioff || help_mode) { - run("ql_ioff", "(unless -noioff)"); + run("ql_ioff"); + run("opt_clean"); } } diff --git a/tests/arch/quicklogic/qlf_k6n10f/ioff.ys b/tests/arch/quicklogic/qlf_k6n10f/ioff.ys index 3144eda133b..da1fb2946d8 100644 --- a/tests/arch/quicklogic/qlf_k6n10f/ioff.ys +++ b/tests/arch/quicklogic/qlf_k6n10f/ioff.ys @@ -21,6 +21,21 @@ EOF synth_quicklogic -family qlf_k6n10f -top top select -assert-count 4 t:dff +design -reset +# test: acceptable for output IOFF promotion; duplicate output FF +read_verilog <