From 1f3328a711808f81000a402082458a4e22c817f6 Mon Sep 17 00:00:00 2001 From: George Rennie Date: Wed, 20 Nov 2024 13:52:25 +0100 Subject: [PATCH 01/17] kernel: define $barrier cell * This acts as an optimization barrier, limiting the rewriting allowed * For now this forbids all optimization, but attributes can be added to opt in to specific optimizations as required --- kernel/cellaigs.cc | 2 +- kernel/celledges.cc | 4 ++-- kernel/celltypes.h | 2 ++ kernel/qcsat.cc | 2 +- kernel/rtlil.cc | 9 +++++---- kernel/rtlil.h | 18 ++++++++++-------- kernel/satgen.cc | 6 +++--- techlibs/common/simlib.v | 23 +++++++++++++++++++++++ 8 files changed, 47 insertions(+), 19 deletions(-) diff --git a/kernel/cellaigs.cc b/kernel/cellaigs.cc index fd3c7bb67cd..be16b65ea63 100644 --- a/kernel/cellaigs.cc +++ b/kernel/cellaigs.cc @@ -291,7 +291,7 @@ Aig::Aig(Cell *cell) } } - if (cell->type.in(ID($not), ID($_NOT_), ID($pos), ID($buf), ID($_BUF_))) + if (cell->type.in(ID($not), ID($_NOT_), ID($pos), ID($buf), ID($barrier), ID($_BUF_))) { for (int i = 0; i < GetSize(cell->getPort(ID::Y)); i++) { int A = mk.inport(ID::A, i); diff --git a/kernel/celledges.cc b/kernel/celledges.cc index 8e52d0380df..0588e311af5 100644 --- a/kernel/celledges.cc +++ b/kernel/celledges.cc @@ -24,7 +24,7 @@ PRIVATE_NAMESPACE_BEGIN void bitwise_unary_op(AbstractCellEdgesDatabase *db, RTLIL::Cell *cell) { - bool is_signed = (cell->type != ID($buf)) && cell->getParam(ID::A_SIGNED).as_bool(); + bool is_signed = !cell->type.in(ID($buf), ID($barrier)) && cell->getParam(ID::A_SIGNED).as_bool(); int a_width = GetSize(cell->getPort(ID::A)); int y_width = GetSize(cell->getPort(ID::Y)); @@ -392,7 +392,7 @@ PRIVATE_NAMESPACE_END bool YOSYS_NAMESPACE_PREFIX AbstractCellEdgesDatabase::add_edges_from_cell(RTLIL::Cell *cell) { - if (cell->type.in(ID($not), ID($pos), ID($buf))) { + if (cell->type.in(ID($not), ID($pos), ID($buf), ID($barrier))) { bitwise_unary_op(this, cell); return true; } diff --git a/kernel/celltypes.h b/kernel/celltypes.h index 0ce5db54d46..1f34d50e2de 100644 --- a/kernel/celltypes.h +++ b/kernel/celltypes.h @@ -87,6 +87,8 @@ struct CellTypes { setup_internals_eval(); + setup_type(ID($barrier), {ID::A}, {ID::Y}); + setup_type(ID($tribuf), {ID::A, ID::EN}, {ID::Y}, true); setup_type(ID($assert), {ID::A, ID::EN}, pool(), true); diff --git a/kernel/qcsat.cc b/kernel/qcsat.cc index c9d00efa1cd..630368bedeb 100644 --- a/kernel/qcsat.cc +++ b/kernel/qcsat.cc @@ -77,7 +77,7 @@ void QuickConeSat::prepare() int QuickConeSat::cell_complexity(RTLIL::Cell *cell) { - if (cell->type.in(ID($concat), ID($slice), ID($pos), ID($buf), ID($_BUF_))) + if (cell->type.in(ID($concat), ID($slice), ID($pos), ID($buf), ID($barrier), ID($_BUF_))) return 0; if (cell->type.in(ID($not), ID($and), ID($or), ID($xor), ID($xnor), ID($reduce_and), ID($reduce_or), ID($reduce_xor), diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 8a0080dbf66..b21fd0209c3 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -1466,7 +1466,7 @@ namespace { cell->type.begins_with("$verific$") || cell->type.begins_with("$array:") || cell->type.begins_with("$extern:")) return; - if (cell->type == ID($buf)) { + if (cell->type.in(ID($buf), ID($barrier))) { port(ID::A, param(ID::WIDTH)); port(ID::Y, param(ID::WIDTH)); check_expected(); @@ -2979,7 +2979,8 @@ DEF_METHOD(LogicNot, 1, ID($logic_not)) add ## _func(name, sig_a, sig_y, is_signed, src); \ return sig_y; \ } -DEF_METHOD(Buf, sig_a.size(), ID($buf)) +DEF_METHOD(Buf, sig_a.size(), ID($buf)) +DEF_METHOD(Barrier, sig_a.size(), ID($barrier)) #undef DEF_METHOD #define DEF_METHOD(_func, _y_size, _type) \ @@ -4281,9 +4282,9 @@ void RTLIL::Cell::fixup_parameters(bool set_a_signed, bool set_b_signed) type.begins_with("$verific$") || type.begins_with("$array:") || type.begins_with("$extern:")) return; - if (type == ID($buf) || type == ID($mux) || type == ID($pmux) || type == ID($bmux)) { + if (type.in(ID($buf), ID($barrier), ID($mux), ID($pmux), ID($bmux))) { parameters[ID::WIDTH] = GetSize(connections_[ID::Y]); - if (type != ID($buf) && type != ID($mux)) + if (!type.in(ID($buf), ID($barrier), ID($mux))) parameters[ID::S_WIDTH] = GetSize(connections_[ID::S]); check(); return; diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 504fa0062d9..15da16b9773 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -1613,10 +1613,11 @@ struct RTLIL::Module : public RTLIL::NamedObject // The add* methods create a cell and return the created cell. All signals must exist in advance. - RTLIL::Cell* addNot (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); - RTLIL::Cell* addPos (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); - RTLIL::Cell* addBuf (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); - RTLIL::Cell* addNeg (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); + RTLIL::Cell* addNot (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); + RTLIL::Cell* addPos (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); + RTLIL::Cell* addBuf (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); + RTLIL::Cell* addBarrier (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); + RTLIL::Cell* addNeg (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); RTLIL::Cell* addAnd (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); RTLIL::Cell* addOr (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); @@ -1748,10 +1749,11 @@ struct RTLIL::Module : public RTLIL::NamedObject // The methods without the add* prefix create a cell and an output signal. They return the newly created output signal. - RTLIL::SigSpec Not (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, bool is_signed = false, const std::string &src = ""); - RTLIL::SigSpec Pos (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, bool is_signed = false, const std::string &src = ""); - RTLIL::SigSpec Buf (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, bool is_signed = false, const std::string &src = ""); - RTLIL::SigSpec Neg (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, bool is_signed = false, const std::string &src = ""); + RTLIL::SigSpec Not (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, bool is_signed = false, const std::string &src = ""); + RTLIL::SigSpec Pos (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, bool is_signed = false, const std::string &src = ""); + RTLIL::SigSpec Buf (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, bool is_signed = false, const std::string &src = ""); + RTLIL::SigSpec Barrier (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, bool is_signed = false, const std::string &src = ""); + RTLIL::SigSpec Neg (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, bool is_signed = false, const std::string &src = ""); RTLIL::SigSpec And (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, bool is_signed = false, const std::string &src = ""); RTLIL::SigSpec Or (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, bool is_signed = false, const std::string &src = ""); diff --git a/kernel/satgen.cc b/kernel/satgen.cc index 7885eccf84b..4454cf9ea5b 100644 --- a/kernel/satgen.cc +++ b/kernel/satgen.cc @@ -430,7 +430,7 @@ bool SatGen::importCell(RTLIL::Cell *cell, int timestep) return true; } - if (cell->type.in(ID($pos), ID($buf), ID($neg))) + if (cell->type.in(ID($pos), ID($buf), ID($barrier), ID($neg))) { std::vector a = importDefSigSpec(cell->getPort(ID::A), timestep); std::vector y = importDefSigSpec(cell->getPort(ID::Y), timestep); @@ -438,7 +438,7 @@ bool SatGen::importCell(RTLIL::Cell *cell, int timestep) std::vector yy = model_undef ? ez->vec_var(y.size()) : y; - if (cell->type.in(ID($pos), ID($buf))) { + if (cell->type.in(ID($pos), ID($buf), ID($barrier))) { ez->assume(ez->vec_eq(a, yy)); } else { std::vector zero(a.size(), ez->CONST_FALSE); @@ -451,7 +451,7 @@ bool SatGen::importCell(RTLIL::Cell *cell, int timestep) std::vector undef_y = importUndefSigSpec(cell->getPort(ID::Y), timestep); extendSignalWidthUnary(undef_a, undef_y, cell); - if (cell->type.in(ID($pos), ID($buf))) { + if (cell->type.in(ID($pos), ID($buf), ID($barrier))) { ez->assume(ez->vec_eq(undef_a, undef_y)); } else { int undef_any_a = ez->expression(ezSAT::OpOr, undef_a); diff --git a/techlibs/common/simlib.v b/techlibs/common/simlib.v index 6e39aa60afe..c96557fdcc1 100644 --- a/techlibs/common/simlib.v +++ b/techlibs/common/simlib.v @@ -108,6 +108,29 @@ endmodule // -------------------------------------------------------- +// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| +//- +//- $barrier (A, Y) +//* group unary +//- +//- A coarse-grain buffer cell type that acts as a barrier for optimizations. +//- Optimization passes are forbidden from rewriting patterns that include +//- this cell (by merging, constant propagation etc) with the exception of +//- opt_clean that can remove it if the output is unused. +//- +module \$barrier (A, Y); + +parameter WIDTH = 0; + +input [WIDTH-1:0] A; +output [WIDTH-1:0] Y; + +assign Y = A; + +endmodule + +// -------------------------------------------------------- + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| //- //- $neg (A, Y) From c0576de28f5b86fb866c64382691b1c8b046e2cf Mon Sep 17 00:00:00 2001 From: George Rennie Date: Wed, 20 Nov 2024 13:54:54 +0100 Subject: [PATCH 02/17] opt_merge: don't merge $barrier cells --- passes/opt/opt_merge.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/passes/opt/opt_merge.cc b/passes/opt/opt_merge.cc index b519d33d6bf..3401f0d1d48 100644 --- a/passes/opt/opt_merge.cc +++ b/passes/opt/opt_merge.cc @@ -246,6 +246,7 @@ struct OptMergeWorker ct.cell_types.erase(ID($anyconst)); ct.cell_types.erase(ID($allseq)); ct.cell_types.erase(ID($allconst)); + ct.cell_types.erase(ID($barrier)); log("Finding identical cells in module `%s'.\n", module->name.c_str()); assign_map.set(module); From ff848ba7a98cf26a9b4b979ac397d7040e242951 Mon Sep 17 00:00:00 2001 From: George Rennie Date: Wed, 20 Nov 2024 13:56:46 +0100 Subject: [PATCH 03/17] write_aiger2: support $barrier --- backends/aiger2/aiger.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backends/aiger2/aiger.cc b/backends/aiger2/aiger.cc index c7ed3b81f0f..2e13062bd78 100644 --- a/backends/aiger2/aiger.cc +++ b/backends/aiger2/aiger.cc @@ -28,8 +28,8 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -#define BITWISE_OPS ID($buf), ID($not), ID($mux), ID($and), ID($or), ID($xor), ID($xnor), ID($fa), \ - ID($bwmux) +#define BITWISE_OPS ID($buf), ID($barrier), ID($not), ID($mux), ID($and), ID($or), ID($xor), ID($xnor), \ + ID($fa), ID($bwmux) #define REDUCE_OPS ID($reduce_and), ID($reduce_or), ID($reduce_xor), ID($reduce_xnor), ID($reduce_bool) @@ -332,7 +332,7 @@ struct Index { a = CFALSE; } - if (cell->type.in(ID($buf), ID($pos), ID($_BUF_))) { + if (cell->type.in(ID($buf), ID($barrier), ID($pos), ID($_BUF_))) { return a; } else if (cell->type.in(ID($not), ID($_NOT_))) { return NOT(a); From fc35002ccee492c37bcca9d679d3bb7582d03130 Mon Sep 17 00:00:00 2001 From: George Rennie Date: Wed, 20 Nov 2024 13:57:29 +0100 Subject: [PATCH 04/17] write_btor: support $barrier, $buf and $_BUF_ --- backends/btor/btor.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backends/btor/btor.cc b/backends/btor/btor.cc index bfd29355711..87fb570e6a8 100644 --- a/backends/btor/btor.cc +++ b/backends/btor/btor.cc @@ -509,7 +509,7 @@ struct BtorWorker goto okay; } - if (cell->type.in(ID($not), ID($neg), ID($_NOT_), ID($pos))) + if (cell->type.in(ID($not), ID($neg), ID($_NOT_), ID($pos), ID($buf), ID($_BUF_), ID($barrier))) { string btor_op; if (cell->type.in(ID($not), ID($_NOT_))) btor_op = "not"; @@ -521,9 +521,9 @@ struct BtorWorker int nid_a = get_sig_nid(cell->getPort(ID::A), width, a_signed); SigSpec sig = sigmap(cell->getPort(ID::Y)); - // the $pos cell just passes through, all other cells need an actual operation applied + // buffer cells just pass through, all other cells need an actual operation applied int nid = nid_a; - if (cell->type != ID($pos)) + if (!cell->type.in(ID($pos), ID($buf), ID($_BUF_), ID($barrier))) { log_assert(!btor_op.empty()); int sid = get_bv_sid(width); From 0c488dc6ca7dbab44a2164334cfa59c2878d2db5 Mon Sep 17 00:00:00 2001 From: George Rennie Date: Wed, 20 Nov 2024 13:58:03 +0100 Subject: [PATCH 05/17] write_firrtl: support $buf and $barrier --- backends/firrtl/firrtl.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/firrtl/firrtl.cc b/backends/firrtl/firrtl.cc index ceb805dcb0a..a1e30a25db9 100644 --- a/backends/firrtl/firrtl.cc +++ b/backends/firrtl/firrtl.cc @@ -966,7 +966,7 @@ struct FirrtlWorker register_reverse_wire_map(y_id, cell->getPort(ID::Y)); continue; } - if (cell->type == ID($pos)) { + if (cell->type.in(ID($pos), ID($buf), ID($barrier))) { // assign y = a; // printCell(cell); string a_expr = make_expr(cell->getPort(ID::A)); From b1ff31c27402c4c6f097c23b11c14397542011e4 Mon Sep 17 00:00:00 2001 From: George Rennie Date: Wed, 20 Nov 2024 13:58:35 +0100 Subject: [PATCH 06/17] write_smt2: support $buf and $barrier --- backends/smt2/smt2.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/smt2/smt2.cc b/backends/smt2/smt2.cc index 87f5a08c83e..bbfb68d1dfa 100644 --- a/backends/smt2/smt2.cc +++ b/backends/smt2/smt2.cc @@ -678,7 +678,7 @@ struct Smt2Worker if (cell->type == ID($eqx)) return export_bvop(cell, "(= A B)", 'b'); if (cell->type == ID($not)) return export_bvop(cell, "(bvnot A)"); - if (cell->type == ID($pos)) return export_bvop(cell, "A"); + if (cell->type.in(ID($pos), ID($buf), ID($barrier))) return export_bvop(cell, "A"); if (cell->type == ID($neg)) return export_bvop(cell, "(bvneg A)"); if (cell->type == ID($add)) return export_bvop(cell, "(bvadd A B)"); From 7813f5320048e06720268a181b6a60377915b635 Mon Sep 17 00:00:00 2001 From: George Rennie Date: Wed, 20 Nov 2024 14:00:18 +0100 Subject: [PATCH 07/17] write_verilog: support $barrier --- backends/verilog/verilog_backend.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc index 1cef7be6092..8d54e2e5c22 100644 --- a/backends/verilog/verilog_backend.cc +++ b/backends/verilog/verilog_backend.cc @@ -1096,7 +1096,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) return true; } - if (cell->type.in(ID($_BUF_), ID($buf))) { + if (cell->type.in(ID($_BUF_), ID($buf), ID($barrier))) { f << stringf("%s" "assign ", indent.c_str()); dump_sigspec(f, cell->getPort(ID::Y)); f << stringf(" = "); From 6be0d85e1ad891c73bc9fa1015e6bdd9f5595401 Mon Sep 17 00:00:00 2001 From: George Rennie Date: Wed, 20 Nov 2024 14:00:43 +0100 Subject: [PATCH 08/17] flatten: add -barriers flag * This uses $barrier optimization barriers to connect wires into the flattened module instead of connections --- passes/techmap/flatten.cc | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/passes/techmap/flatten.cc b/passes/techmap/flatten.cc index 6363b34321e..dbf661eae23 100644 --- a/passes/techmap/flatten.cc +++ b/passes/techmap/flatten.cc @@ -61,6 +61,7 @@ struct FlattenWorker bool create_scopeinfo = true; bool create_scopename = false; std::string separator = "."; + bool barriers = false; template void map_attributes(RTLIL::Cell *cell, T *object, IdString orig_object_name) @@ -247,7 +248,27 @@ struct FlattenWorker log_error("Cell port %s.%s.%s is driving constant bits: %s <= %s\n", log_id(module), log_id(cell), log_id(port_it.first), log_signal(new_conn.first), log_signal(new_conn.second)); - module->connect(new_conn); + if (barriers) { + // Drive public output wires with barriers and the rest with + // connections + RTLIL::SigSig skip_conn, barrier_conn; + + for (int i = 0; i < GetSize(new_conn.first); i++) { + const auto lhs = new_conn.first[i], rhs = new_conn.second[i]; + auto& sigsig = !lhs.is_wire() || !lhs.wire->name.isPublic() ? skip_conn : barrier_conn; + sigsig.first.append(lhs); + sigsig.second.append(rhs); + } + + if (!skip_conn.first.empty()) + module->connect(skip_conn); + + if (!barrier_conn.first.empty()) + module->addBarrier(NEW_ID, barrier_conn.second, barrier_conn.first); + } else { + module->connect(new_conn); + } + sigmap.add(new_conn.first, new_conn.second); } @@ -353,6 +374,10 @@ struct FlattenPass : public Pass { log(" Don't remove unused submodules, leave a flattened version of each\n"); log(" submodule in the design.\n"); log("\n"); + log(" -barriers\n"); + log(" Introduce an optimization barrier (a $barrier cell) when a flattened\n"); + log(" module drives a public wire.\n"); + log("\n"); } void execute(std::vector args, RTLIL::Design *design) override { @@ -386,6 +411,9 @@ struct FlattenPass : public Pass { } if (args[argidx] == "-nocleanup") { cleanup = false; + } + if (args[argidx] == "-barriers") { + worker.barriers = true; continue; } break; From 268394bf05c5653d2b34f844edf0d7a6424a2d27 Mon Sep 17 00:00:00 2001 From: George Rennie Date: Wed, 20 Nov 2024 17:16:37 +0100 Subject: [PATCH 09/17] optbarriers: add command to add/remove optimization barriers * This can optionally ignore rewriting the outputs of cells or processes * This by default rewrites drivers of wires with public names but can also optionally rewrite drivers of wires with private names * A -remove flag allows cleaning up the design by replacing barriers with connections --- passes/cmds/Makefile.inc | 1 + passes/cmds/optbarriers.cc | 228 +++++++++++++++++++++++++++++++++++++ 2 files changed, 229 insertions(+) create mode 100644 passes/cmds/optbarriers.cc diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index 4ecaea7dd1c..e3b87b5dc20 100644 --- a/passes/cmds/Makefile.inc +++ b/passes/cmds/Makefile.inc @@ -56,3 +56,4 @@ OBJS += passes/cmds/setenv.o OBJS += passes/cmds/abstract.o OBJS += passes/cmds/test_select.o OBJS += passes/cmds/timeest.o +OBJS += passes/cmds/optbarriers.o diff --git a/passes/cmds/optbarriers.cc b/passes/cmds/optbarriers.cc new file mode 100644 index 00000000000..a899bae4988 --- /dev/null +++ b/passes/cmds/optbarriers.cc @@ -0,0 +1,228 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 George Rennie + * + * 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" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +// Standard visitor helper +template +struct overloaded : Ts... { using Ts::operator()...; }; +template +overloaded(Ts...) -> overloaded; + +struct OptBarriersPass : public Pass { + OptBarriersPass() : Pass("optbarriers", "insert optimization barriers") {} + + void help() override { + log("\n"); + log(" optbarriers [options] [selection]\n"); + log("\n"); + log("Insert optimization barriers to drivers of selected public wires.\n"); + log("\n"); + log("\n"); + log(" -nocells\n"); + log(" don't add optimization barriers to the outputs of cells\n"); + log("\n"); + log(" -noprocs\n"); + log(" don't add optimization barriers to the outputs of processes\n"); + log("\n"); + log(" -private\n"); + log(" also add optimization barriers to private wires\n"); + log("\n"); + log(" -remove\n"); + log(" replace selected optimization barriers with connections\n"); + log("\n"); + } + + void execute(std::vector args, RTLIL::Design *design) override { + log_header(design, "Executing OPTBARRIERS pass (insert optimization barriers).\n"); + + bool nocells_mode = false; + bool noprocs_mode = false; + bool private_mode = false; + bool remove_mode = false; + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + std::string arg = args[argidx]; + if (arg == "-nocells") { + nocells_mode = true; + continue; + } + if (arg == "-noprocs") { + noprocs_mode = true; + continue; + } + if (arg == "-private") { + private_mode = true; + continue; + } + if (arg == "-remove") { + remove_mode = true; + continue; + } + break; + } + extra_args(args, argidx, design); + + if (remove_mode) { + log("Replacing optimization barriers with connections.\n"); + remove_barriers(design); + return; + } + + for (auto* module : design->selected_modules()) { + // We can't just sigmap and iterate through wires for rewriting as + // we want to maintain the structure in connections, and sigmap + // will just return a canonical wire which does not have to be one + // that is directly driving the wire. Therefore for each type of + // object that could be driving the wires (cells, processes, + // connections) we rewrite the sigspecs. + + // Keep track of which wires we have allocated new wires for + dict new_wires; + // Keep track of bit pairs we need to construct barriers for from + // Y to A + dict new_barriers; + + // Skip constants, unselected wires and private wires when not in + // private mode. This works for SigChunk or SigBit input. + const auto skip = [&](const auto& chunk) { + if (!chunk.is_wire()) + return true; + + if (!design->selected(module, chunk.wire)) + return true; + + if (!private_mode && !chunk.wire->name.isPublic()) + return true; + + return false; + }; + + const auto rewrite_sigspec = [&](const SigSpec& sig) { + RTLIL::SigSpec new_output; + for (const auto& chunk : sig.chunks()) { + if (skip(chunk)) { + new_output.append(chunk); + continue; + } + + // Add a wire to drive if one does not already exist + auto* new_wire = new_wires.at(chunk.wire, nullptr); + if (!new_wire) { + new_wire = module->addWire(NEW_ID, GetSize(chunk.wire)); + new_wires.emplace(chunk.wire, new_wire); + } + + RTLIL::SigChunk new_chunk = chunk; + new_chunk.wire = new_wire; + + // Rewrite output to drive new wire, and schedule adding + // barrier bits from new wire to original + new_output.append(new_chunk); + for (int i = 0; i < GetSize(chunk); i++) + new_barriers.emplace(chunk[i], new_chunk[i]); + } + + return new_output; + }; + + // Rewrite cell outputs + if (!nocells_mode) + for (auto* cell : module->cells()) + if (cell->type != ID($barrier)) + for (const auto& [name, sig] : cell->connections()) + if (cell->output(name)) + cell->setPort(name, rewrite_sigspec(sig)); + + // Rewrite connections in processes + if (!noprocs_mode) { + const auto proc_rewriter = overloaded{ + // Don't do anything for input sigspecs + [&](const SigSpec&) {}, + // Rewrite connections to drive barrier if needed + [&](SigSpec& lhs, const SigSpec&) { + lhs = rewrite_sigspec(lhs); + } + }; + + for (auto& proc : module->processes) + proc.second->rewrite_sigspecs2(proc_rewriter); + } + + // Add all the scheduled barriers. To minimize the number of cells, + // first construct a sigspec of all bits, then sort and unify before + // creating barriers + SigSpec barrier_y; + for (const auto&[y_bit, _] : new_barriers) + barrier_y.append(y_bit); + barrier_y.sort_and_unify(); + + for (const auto& sig_y : barrier_y.chunks()) { + log_assert(sig_y.is_wire()); + SigSpec sig_a; + for (int i = 0; i < GetSize(sig_y); i++) + sig_a.append(new_barriers[sig_y[i]]); + module->addBarrier(NEW_ID, sig_a, sig_y); + } + + // Rewrite connections + std::vector new_connections; + for (const auto& conn : module->connections()) { + RTLIL::SigSig skip_conn, barrier_conn; + + for (int i = 0; i < GetSize(conn.first); i++) { + auto& sigsig = skip(conn.first[i]) ? skip_conn : barrier_conn; + sigsig.first.append(conn.first[i]); + sigsig.second.append(conn.second[i]); + } + + if (!skip_conn.first.empty()) + new_connections.emplace_back(std::move(skip_conn)); + + if (!barrier_conn.first.empty()) + module->addBarrier(NEW_ID, barrier_conn.second, barrier_conn.first); + } + module->new_connections(new_connections); + } + } + + void remove_barriers(RTLIL::Design* design) { + for (auto* module : design->selected_modules()) { + std::vector barriers; + + for (auto* cell : module->selected_cells()) + if (cell->type == ID($barrier)) + barriers.emplace_back(cell); + + for (auto* cell : barriers) { + const auto lhs = cell->getPort(ID::Y), rhs = cell->getPort(ID::A); + module->connect(lhs, rhs); + module->remove(cell); + } + } + } + +} OptBarriersPass; + +PRIVATE_NAMESPACE_END From 2663d31ce4f1ec03a7bd92f2ba643bf8e1ec23e1 Mon Sep 17 00:00:00 2001 From: George Rennie Date: Wed, 20 Nov 2024 17:40:18 +0100 Subject: [PATCH 10/17] prep: add -barriers flag * This uses optbarriers and flatten -barriers to insert barriers on public wires in the design, limiting the optimization that can affect them --- techlibs/common/prep.cc | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/techlibs/common/prep.cc b/techlibs/common/prep.cc index e9176304d48..92bac230895 100644 --- a/techlibs/common/prep.cc +++ b/techlibs/common/prep.cc @@ -67,6 +67,11 @@ struct PrepPass : public ScriptPass log(" -nokeepdc\n"); log(" do not call opt_* with -keepdc\n"); log("\n"); + log(" -barriers\n"); + log(" add optimization barriers to all public wires to preserve their structure.\n"); + log(" this limits the optimizations that can be applied to the design to only\n"); + log(" those involving private wires.\n"); + log("\n"); log(" -run [:]\n"); log(" only run the commands between the labels (see below). an empty\n"); log(" from label is synonymous to 'begin', and empty to label is\n"); @@ -79,7 +84,7 @@ struct PrepPass : public ScriptPass } string top_module, fsm_opts; - bool autotop, flatten, ifxmode, memxmode, nomemmode, nokeepdc, rdff; + bool autotop, flatten, ifxmode, memxmode, nomemmode, nokeepdc, rdff, barriers; void clear_flags() override { @@ -92,6 +97,7 @@ struct PrepPass : public ScriptPass nomemmode = false; nokeepdc = false; rdff = false; + barriers = false; } void execute(std::vector args, RTLIL::Design *design) override @@ -148,6 +154,10 @@ struct PrepPass : public ScriptPass nokeepdc = true; continue; } + if (args[argidx] == "-barriers") { + barriers = true; + continue; + } break; } extra_args(args, argidx, design); @@ -183,12 +193,16 @@ struct PrepPass : public ScriptPass if (check_label("coarse")) { + if (help_mode || barriers) + run("optbarriers", "(if -barriers)"); if (help_mode) run("proc [-ifx]"); else run(ifxmode ? "proc -ifx" : "proc"); - if (help_mode || flatten) - run("flatten", "(if -flatten)"); + if (help_mode) + run("flatten [-barriers]", "(if -flatten)"); + else if (flatten) + run(barriers ? "flatten -barriers" : "flatten"); run("future"); run(nokeepdc ? "opt_expr" : "opt_expr -keepdc"); run("opt_clean"); From 6de8141605783af5222c926ae97384444ef32fba Mon Sep 17 00:00:00 2001 From: George Rennie Date: Mon, 2 Dec 2024 15:41:40 +0100 Subject: [PATCH 11/17] optbarriers: add -noconns option --- passes/cmds/optbarriers.cc | 39 ++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/passes/cmds/optbarriers.cc b/passes/cmds/optbarriers.cc index a899bae4988..6c04c4c2c72 100644 --- a/passes/cmds/optbarriers.cc +++ b/passes/cmds/optbarriers.cc @@ -33,6 +33,7 @@ struct OptBarriersPass : public Pass { OptBarriersPass() : Pass("optbarriers", "insert optimization barriers") {} void help() override { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" optbarriers [options] [selection]\n"); log("\n"); @@ -45,6 +46,9 @@ struct OptBarriersPass : public Pass { log(" -noprocs\n"); log(" don't add optimization barriers to the outputs of processes\n"); log("\n"); + log(" -noconns\n"); + log(" don't add optimization barriers to the left hand sides of connections\n"); + log("\n"); log(" -private\n"); log(" also add optimization barriers to private wires\n"); log("\n"); @@ -58,6 +62,7 @@ struct OptBarriersPass : public Pass { bool nocells_mode = false; bool noprocs_mode = false; + bool noconns_mode = false; bool private_mode = false; bool remove_mode = false; @@ -72,6 +77,10 @@ struct OptBarriersPass : public Pass { noprocs_mode = true; continue; } + if (arg == "-noconns") { + noconns_mode = true; + continue; + } if (arg == "-private") { private_mode = true; continue; @@ -187,23 +196,25 @@ struct OptBarriersPass : public Pass { } // Rewrite connections - std::vector new_connections; - for (const auto& conn : module->connections()) { - RTLIL::SigSig skip_conn, barrier_conn; - - for (int i = 0; i < GetSize(conn.first); i++) { - auto& sigsig = skip(conn.first[i]) ? skip_conn : barrier_conn; - sigsig.first.append(conn.first[i]); - sigsig.second.append(conn.second[i]); - } + if (!noconns_mode) { + std::vector new_connections; + for (const auto& conn : module->connections()) { + RTLIL::SigSig skip_conn, barrier_conn; + + for (int i = 0; i < GetSize(conn.first); i++) { + auto& sigsig = skip(conn.first[i]) ? skip_conn : barrier_conn; + sigsig.first.append(conn.first[i]); + sigsig.second.append(conn.second[i]); + } - if (!skip_conn.first.empty()) - new_connections.emplace_back(std::move(skip_conn)); + if (!skip_conn.first.empty()) + new_connections.emplace_back(std::move(skip_conn)); - if (!barrier_conn.first.empty()) - module->addBarrier(NEW_ID, barrier_conn.second, barrier_conn.first); + if (!barrier_conn.first.empty()) + module->addBarrier(NEW_ID, barrier_conn.second, barrier_conn.first); + } + module->new_connections(new_connections); } - module->new_connections(new_connections); } } From d191c410905a95f8fa7c2a55e69b0a736c65fa9d Mon Sep 17 00:00:00 2001 From: George Rennie Date: Mon, 2 Dec 2024 20:12:09 +0100 Subject: [PATCH 12/17] optbarriers: preserve SCCs in processes --- passes/cmds/optbarriers.cc | 272 ++++++++++++++++++++++++++++++++++--- 1 file changed, 256 insertions(+), 16 deletions(-) diff --git a/passes/cmds/optbarriers.cc b/passes/cmds/optbarriers.cc index 6c04c4c2c72..ecc5a231892 100644 --- a/passes/cmds/optbarriers.cc +++ b/passes/cmds/optbarriers.cc @@ -19,6 +19,7 @@ #include "kernel/yosys.h" #include "kernel/sigtools.h" +#include USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN @@ -29,6 +30,121 @@ struct overloaded : Ts... { using Ts::operator()...; }; template overloaded(Ts...) -> overloaded; +// This computes a graph of assignment dependencies in a process, which is used +// to preserve SCCs in the process which are useful for DFF inference +struct ProcessDependencyWorker { + ProcessDependencyWorker(const RTLIL::Process& proc) { + add_process(proc); + } + + void add_process(const RTLIL::Process& proc) { + add_caserule(proc.root_case); + + for (const auto* sync : proc.syncs) + add_syncrule(*sync); + } + + void add_syncrule(const RTLIL::SyncRule& sync) { + for (const auto& sigsig : sync.actions) + add_sigsig(sigsig); + } + + void add_caserule(const RTLIL::CaseRule& caserule) { + for (const auto& sigsig : caserule.actions) + add_sigsig(sigsig); + + for (const auto* rule : caserule.switches) + add_switchrule(*rule); + } + + void add_switchrule(const RTLIL::SwitchRule& switchrule) { + for (const auto* rule : switchrule.cases) + add_caserule(*rule); + } + + void add_sigsig(const RTLIL::SigSig& sigsig) { + for (int i = 0; i < GetSize(sigsig.first); i++) { + const auto lhs = sigsig.first[i], rhs = sigsig.second[i]; + if (rhs.is_wire()) + dependencies[lhs].emplace(rhs); + } + } + + // Returns the set of nodes that appear in an SCC with this bit + pool scc_nodes(const SigBit bit) { + pool scc_nodes; + + // This uses a DFS to iterate through the graph stopping when it detects + // SCCs + struct StackElem { + const SigBit lhs; + pool::const_iterator current_rhs; + const pool::const_iterator end; + + StackElem( + const SigBit lhs, + pool::const_iterator current_rhs, + const pool::const_iterator end + ) : lhs{lhs}, current_rhs{current_rhs}, end{end} {} + }; + std::vector node_stack; + + // Try to add a new node to the stack. Returns false if it is already + // in the stack (we have found an SCC) or doesn't exist in the dependency + // map (has no children), otherwise true + const auto try_add_node = [&](const SigBit node) { + const auto stack_it = std::find_if( + node_stack.cbegin(), node_stack.cend(), + [&](const auto& elem){ return elem.lhs == node; } + ); + + if (stack_it != node_stack.cend()) + return false; + + const auto it = dependencies.find(node); + + if (it == dependencies.end()) + return false; + + node_stack.emplace_back(node, it->second.begin(), it->second.end()); + return true; + }; + + try_add_node(bit); + + while (!node_stack.empty()) { + auto& top = node_stack.back(); + + // If we have explored all children of this node backtrack + if (top.current_rhs == top.end) { + node_stack.pop_back(); + if (!node_stack.empty()) + ++node_stack.back().current_rhs; + continue; + } + + // Not yet at an SCC so try to add this top node to the stack. If + // it doesn't form an SCC and has children, carry on (with the new top node) + if (try_add_node(*top.current_rhs)) + continue; + + // We have found an SCC or a node without children. If it loops back + // to the starting bit, add the whole stack as it is all in the SCC + // being searched for + if (*top.current_rhs == bit) + for (const auto& elem : node_stack) + scc_nodes.emplace(elem.lhs); + + // Increment the iterator to keep going + ++top.current_rhs; + } + + return scc_nodes; + } + + dict> dependencies; +}; + struct OptBarriersPass : public Pass { OptBarriersPass() : Pass("optbarriers", "insert optimization barriers") {} @@ -139,7 +255,7 @@ struct OptBarriersPass : public Pass { // Add a wire to drive if one does not already exist auto* new_wire = new_wires.at(chunk.wire, nullptr); if (!new_wire) { - new_wire = module->addWire(NEW_ID, GetSize(chunk.wire)); + new_wire = module->addWire(NEW_ID_SUFFIX(chunk.wire->name.str()), GetSize(chunk.wire)); new_wires.emplace(chunk.wire, new_wire); } @@ -156,29 +272,153 @@ struct OptBarriersPass : public Pass { return new_output; }; - // Rewrite cell outputs - if (!nocells_mode) - for (auto* cell : module->cells()) - if (cell->type != ID($barrier)) - for (const auto& [name, sig] : cell->connections()) - if (cell->output(name)) - cell->setPort(name, rewrite_sigspec(sig)); + // Rewrite processes. It is not as simple as changing all LHS + // expressions to drive barriers if required, as this prevents + // proc passes from optimizing self feedback which is important to + // prevent false comb paths when generating FFs. We only care about + // the assignments/connections within processes, and want to maintain + // the property that if a bit that is to be rewritten to use a + // barrier can be assigned transitively to itself, the value that is + // assigned should be the value before the barrier. + // + // To do this we first enumerate the assignment dependency graph + // for the process - marking which bits drive any other bit. We + // then look for strongly connected components containing barrier + // bits. These correspond to potential paths where the bit can be + // driven by itself and so should see the pre-barrier value. For + // each of the bits on this path we want to construct a parallel + // version that appears in all the same process assignments but is + // driven originally by the pre-barrier value. + // + // For example, consider the following set of assignments appear + // somewhere in the process and we want to add a barrier to b: + // a <- b + // b <- a + // There is a path from b to itself through $a, so we add wire b\b to + // represent the pre-barrier version of b and a\b to represent + // the version of a that sees a pre-barrier version of b. We then + // correspondingly add these paths to the assignments and a barrier: + // {a\b, a} <- {b\b, b} + // b\b <- a\b + // b\b -$barrier> b + // + // This has retained that b\b is transitively driven by itself, but + // a is still driven by b, the post-barrier version of b + if (!noprocs_mode) + for (const auto& proc : module->processes) { + // A map from each bit driven by the original process to the + // set of variants required for it. If a bit doesn't appear in + // variants it is only needed in the original form it appears + // in the process. + // + // To get bit a\b from the above example you would index + // variants[a][b] + dict> variants; + + { + // We want to minimize the number of wires we have to create + // for each bit in variants, so for variants[a][b] we create + // a unique wire with size GetSize(a.wire) for each tuple + // of a.wire, b.wire, a.offset - b.offset rather than for + // every pair (a, b). + using IdxTuple = std::tuple; + dict variant_wires; + + // Collect all assignment dependencies in the process + ProcessDependencyWorker dep_worker(*proc.second); + + // Enumerate driven bits that need barriers added + for (const auto& [variant_bit, _] : dep_worker.dependencies) { + if (skip(variant_bit)) + continue; + + // Collect the bits that are in an SCC with this bit and + // thus need to have variants constructed that see the + // pre-barrier value + for (const auto& lhs_bit : dep_worker.scc_nodes(variant_bit)) { + // Don't add a new wire for the variant_bit itself as this + // should be driven by a wire generated by rewrite_sigspec below + if (lhs_bit == variant_bit) + continue; + + const int offset = lhs_bit.offset - variant_bit.offset; + const IdxTuple idx{lhs_bit.wire, variant_bit.wire, offset}; + auto it = variant_wires.find(idx); + + // Create a new wire to represent this offset combination + // if needed + if (it == variant_wires.end()) { + const auto name = NEW_ID_SUFFIX(lhs_bit.wire->name.str()); + auto* new_wire = module->addWire(name, GetSize(lhs_bit.wire)); + it = variant_wires.emplace(idx, new_wire).first; + } + + variants[lhs_bit].emplace(variant_bit, SigBit(it->second, lhs_bit.offset)); + } + + // Even if a bit doesn't appear in any SCCs we want to rewrite it if it + // isn't skipped + variants[variant_bit].emplace(variant_bit, rewrite_sigspec(variant_bit)); + } + } + + const auto variant = [&](const SigBit a, const SigBit b) { + const auto it1 = variants.find(a); + // There are no variants of a required, so a definitely isn't + // transitively driven by b + if (it1 == variants.end()) + return a; + + const auto it2 = it1->second.find(b); + // a isn't be transitively driven by b + if (it2 == it1->second.end()) + return a; + + // a can be transitively driven by b so return the variant + // of a that sees pre-barrier b + return it2->second; + }; - // Rewrite connections in processes - if (!noprocs_mode) { const auto proc_rewriter = overloaded{ - // Don't do anything for input sigspecs + // Don't do anything for input sigspecs, these are not connections [&](const SigSpec&) {}, - // Rewrite connections to drive barrier if needed - [&](SigSpec& lhs, const SigSpec&) { - lhs = rewrite_sigspec(lhs); + // Rewrite connections to drive barrier and see pre-barrier + // values if needed + [&](SigSpec& lhs, SigSpec& rhs) { + for (int i = 0; i < GetSize(lhs); i++) { + const auto lhs_bit = lhs[i], rhs_bit = rhs[i]; + + for (const auto& [variant_bit, variant_lhs_bit] : variants[lhs_bit]) { + // For the existing connections, update the lhs to be pre-barrier + // variant_lhs_bit and rhs to be the variant of itself that sees + // the pre-barrier version of variant_bit + if (variant_bit == lhs_bit) { + lhs[i] = variant_lhs_bit; + rhs[i] = variant(rhs_bit, variant_bit); + continue; + } + + // For new variants of lhs_bit that we need to exist, set the + // rhs bit to the variant of rhs_bit that sees the pre_barrier + // version of variant_bit + lhs.append(variant_lhs_bit); + rhs.append(variant(rhs_bit, variant_bit)); + } + } } }; - for (auto& proc : module->processes) - proc.second->rewrite_sigspecs2(proc_rewriter); + proc.second->rewrite_sigspecs2(proc_rewriter); } + // Rewrite cell outputs + if (!nocells_mode) + for (auto* cell : module->cells()) + if (cell->type != ID($barrier)) + for (const auto& [name, sig] : cell->connections()) + if (cell->output(name)) + cell->setPort(name, rewrite_sigspec(sig)); + // Add all the scheduled barriers. To minimize the number of cells, // first construct a sigspec of all bits, then sort and unify before // creating barriers From 5f26e66fc718ca33a42827366c48a656da2d07b9 Mon Sep 17 00:00:00 2001 From: George Rennie Date: Mon, 2 Dec 2024 20:17:20 +0100 Subject: [PATCH 13/17] optbarriers: comment typo --- passes/cmds/optbarriers.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/passes/cmds/optbarriers.cc b/passes/cmds/optbarriers.cc index ecc5a231892..b76a635e789 100644 --- a/passes/cmds/optbarriers.cc +++ b/passes/cmds/optbarriers.cc @@ -294,7 +294,7 @@ struct OptBarriersPass : public Pass { // somewhere in the process and we want to add a barrier to b: // a <- b // b <- a - // There is a path from b to itself through $a, so we add wire b\b to + // There is a path from b to itself through a, so we add wire b\b to // represent the pre-barrier version of b and a\b to represent // the version of a that sees a pre-barrier version of b. We then // correspondingly add these paths to the assignments and a barrier: From d1eeae5e067d77227c7ec5977868a7c75093159d Mon Sep 17 00:00:00 2001 From: George Rennie Date: Tue, 15 Jul 2025 12:24:55 +0100 Subject: [PATCH 14/17] write_smt2: only take sign of cell if fit has one * this is needed to handle $barrier cells which otherwise are similar to $buf but do not have a sign parameter --- backends/smt2/smt2.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/smt2/smt2.cc b/backends/smt2/smt2.cc index bbfb68d1dfa..23dfd503b35 100644 --- a/backends/smt2/smt2.cc +++ b/backends/smt2/smt2.cc @@ -460,7 +460,7 @@ struct Smt2Worker { RTLIL::SigSpec sig_a, sig_b; RTLIL::SigSpec sig_y = sigmap(cell->getPort(ID::Y)); - bool is_signed = type == 'U' ? false : cell->getParam(ID::A_SIGNED).as_bool(); + bool is_signed = type == 'U' ? false : cell->hasParam(ID::A_SIGNED) && cell->getParam(ID::A_SIGNED).as_bool(); int width = GetSize(sig_y); if (type == 's' || type == 'S' || type == 'd' || type == 'b') { From 5b06807bfea51a042acc2134de6fa093a7bbed71 Mon Sep 17 00:00:00 2001 From: George Rennie Date: Tue, 15 Jul 2025 12:26:19 +0100 Subject: [PATCH 15/17] kernel: remove $barrier from cellaigs and satgen * this was added earlier in the PR with the intention of making equiv_opt work, but it means barriers are lost through passes like aigmap and can potentially be used in optimization passes so it is better to remove it --- kernel/cellaigs.cc | 2 +- kernel/satgen.cc | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/kernel/cellaigs.cc b/kernel/cellaigs.cc index be16b65ea63..fd3c7bb67cd 100644 --- a/kernel/cellaigs.cc +++ b/kernel/cellaigs.cc @@ -291,7 +291,7 @@ Aig::Aig(Cell *cell) } } - if (cell->type.in(ID($not), ID($_NOT_), ID($pos), ID($buf), ID($barrier), ID($_BUF_))) + if (cell->type.in(ID($not), ID($_NOT_), ID($pos), ID($buf), ID($_BUF_))) { for (int i = 0; i < GetSize(cell->getPort(ID::Y)); i++) { int A = mk.inport(ID::A, i); diff --git a/kernel/satgen.cc b/kernel/satgen.cc index 4454cf9ea5b..7885eccf84b 100644 --- a/kernel/satgen.cc +++ b/kernel/satgen.cc @@ -430,7 +430,7 @@ bool SatGen::importCell(RTLIL::Cell *cell, int timestep) return true; } - if (cell->type.in(ID($pos), ID($buf), ID($barrier), ID($neg))) + if (cell->type.in(ID($pos), ID($buf), ID($neg))) { std::vector a = importDefSigSpec(cell->getPort(ID::A), timestep); std::vector y = importDefSigSpec(cell->getPort(ID::Y), timestep); @@ -438,7 +438,7 @@ bool SatGen::importCell(RTLIL::Cell *cell, int timestep) std::vector yy = model_undef ? ez->vec_var(y.size()) : y; - if (cell->type.in(ID($pos), ID($buf), ID($barrier))) { + if (cell->type.in(ID($pos), ID($buf))) { ez->assume(ez->vec_eq(a, yy)); } else { std::vector zero(a.size(), ez->CONST_FALSE); @@ -451,7 +451,7 @@ bool SatGen::importCell(RTLIL::Cell *cell, int timestep) std::vector undef_y = importUndefSigSpec(cell->getPort(ID::Y), timestep); extendSignalWidthUnary(undef_a, undef_y, cell); - if (cell->type.in(ID($pos), ID($buf), ID($barrier))) { + if (cell->type.in(ID($pos), ID($buf))) { ez->assume(ez->vec_eq(undef_a, undef_y)); } else { int undef_any_a = ez->expression(ezSAT::OpOr, undef_a); From 2b9926e03c44adf067f8496249248b8819a06b65 Mon Sep 17 00:00:00 2001 From: George Rennie Date: Tue, 15 Jul 2025 12:44:56 +0100 Subject: [PATCH 16/17] flatten: assert output isn't a constant when flattening with barriers --- passes/techmap/flatten.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/passes/techmap/flatten.cc b/passes/techmap/flatten.cc index dbf661eae23..8e94f210408 100644 --- a/passes/techmap/flatten.cc +++ b/passes/techmap/flatten.cc @@ -255,7 +255,8 @@ struct FlattenWorker for (int i = 0; i < GetSize(new_conn.first); i++) { const auto lhs = new_conn.first[i], rhs = new_conn.second[i]; - auto& sigsig = !lhs.is_wire() || !lhs.wire->name.isPublic() ? skip_conn : barrier_conn; + log_assert(lhs.is_wire()); + auto& sigsig = !lhs.wire->name.isPublic() ? skip_conn : barrier_conn; sigsig.first.append(lhs); sigsig.second.append(rhs); } From 24a9cbe7616ada3fdcd298da6d63882f6b83dee4 Mon Sep 17 00:00:00 2001 From: George Rennie Date: Tue, 15 Jul 2025 16:34:20 +0100 Subject: [PATCH 17/17] tests: add optbarriers tests --- tests/various/optbarriers.ys | 168 +++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 tests/various/optbarriers.ys diff --git a/tests/various/optbarriers.ys b/tests/various/optbarriers.ys new file mode 100644 index 00000000000..3c1ce8db231 --- /dev/null +++ b/tests/various/optbarriers.ys @@ -0,0 +1,168 @@ +# Examples from #3426 + +read_verilog <