diff options
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | backends/btor/btor.cc | 1 | ||||
-rw-r--r-- | backends/firrtl/firrtl.cc | 1 | ||||
-rw-r--r-- | backends/smt2/smt2.cc | 1 | ||||
-rw-r--r-- | backends/smv/smv.cc | 1 | ||||
-rw-r--r-- | backends/verilog/verilog_backend.cc | 49 | ||||
-rw-r--r-- | kernel/calc.cc | 23 | ||||
-rw-r--r-- | kernel/celltypes.h | 12 | ||||
-rw-r--r-- | kernel/rtlil.cc | 36 | ||||
-rw-r--r-- | kernel/rtlil.h | 9 | ||||
-rw-r--r-- | kernel/satgen.cc | 59 | ||||
-rw-r--r-- | passes/cmds/Makefile.inc | 1 | ||||
-rw-r--r-- | passes/cmds/xprop.cc | 1198 | ||||
-rw-r--r-- | passes/opt/opt_expr.cc | 183 | ||||
-rw-r--r-- | passes/sat/formalff.cc | 2 | ||||
-rw-r--r-- | passes/sat/miter.cc | 14 | ||||
-rw-r--r-- | passes/sat/sim.cc | 27 | ||||
-rw-r--r-- | passes/techmap/Makefile.inc | 1 | ||||
-rw-r--r-- | passes/techmap/bwmuxmap.cc | 70 | ||||
-rw-r--r-- | passes/techmap/simplemap.cc | 46 | ||||
-rw-r--r-- | passes/techmap/simplemap.h | 1 | ||||
-rw-r--r-- | techlibs/common/simlib.v | 68 | ||||
-rw-r--r-- | techlibs/common/techmap.v | 2 | ||||
-rw-r--r-- | tests/opt/opt_expr_xnor.ys | 2 | ||||
-rw-r--r-- | tests/opt/opt_expr_xor.ys | 7 | ||||
-rw-r--r-- | tests/xprop/.gitignore | 2 | ||||
-rw-r--r-- | tests/xprop/generate.py | 278 | ||||
-rwxr-xr-x | tests/xprop/run-test.sh | 5 | ||||
-rw-r--r-- | tests/xprop/test.py | 516 |
29 files changed, 2537 insertions, 79 deletions
@@ -874,6 +874,7 @@ test: $(TARGETS) $(EXTRA_TARGETS) +cd tests/rpc && bash run-test.sh +cd tests/memfile && bash run-test.sh +cd tests/verilog && bash run-test.sh + +cd tests/xprop && bash run-test.sh $(SEEDOPT) @echo "" @echo " Passed \"make test\"." @echo "" diff --git a/backends/btor/btor.cc b/backends/btor/btor.cc index 06de71018..8368ab82d 100644 --- a/backends/btor/btor.cc +++ b/backends/btor/btor.cc @@ -1417,6 +1417,7 @@ struct BtorBackend : public Backend { log_push(); Pass::call(design, "bmuxmap"); Pass::call(design, "demuxmap"); + Pass::call(design, "bwmuxmap"); log_pop(); size_t argidx; diff --git a/backends/firrtl/firrtl.cc b/backends/firrtl/firrtl.cc index 76ba77abb..e483117d1 100644 --- a/backends/firrtl/firrtl.cc +++ b/backends/firrtl/firrtl.cc @@ -1215,6 +1215,7 @@ struct FirrtlBackend : public Backend { Pass::call(design, "pmuxtree"); Pass::call(design, "bmuxmap"); Pass::call(design, "demuxmap"); + Pass::call(design, "bwmuxmap"); namecache.clear(); autoid_counter = 0; diff --git a/backends/smt2/smt2.cc b/backends/smt2/smt2.cc index 7434b13da..fe50ca7f6 100644 --- a/backends/smt2/smt2.cc +++ b/backends/smt2/smt2.cc @@ -1744,6 +1744,7 @@ struct Smt2Backend : public Backend { log_push(); Pass::call(design, "bmuxmap"); Pass::call(design, "demuxmap"); + Pass::call(design, "bwmuxmap"); log_pop(); size_t argidx; diff --git a/backends/smv/smv.cc b/backends/smv/smv.cc index 7d4f94adc..49c2cc7a6 100644 --- a/backends/smv/smv.cc +++ b/backends/smv/smv.cc @@ -744,6 +744,7 @@ struct SmvBackend : public Backend { log_push(); Pass::call(design, "bmuxmap"); Pass::call(design, "demuxmap"); + Pass::call(design, "bwmuxmap"); log_pop(); size_t argidx; diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc index e60ebc70e..0a9c0590e 100644 --- a/backends/verilog/verilog_backend.cc +++ b/backends/verilog/verilog_backend.cc @@ -35,7 +35,7 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -bool verbose, norename, noattr, attr2comment, noexpr, nodec, nohex, nostr, extmem, defparam, decimal, siminit, systemverilog, simple_lhs; +bool verbose, norename, noattr, attr2comment, noexpr, nodec, nohex, nostr, extmem, defparam, decimal, siminit, systemverilog, simple_lhs, noparallelcase; int auto_name_counter, auto_name_offset, auto_name_digits, extmem_counter; std::map<RTLIL::IdString, int> auto_name_map; std::set<RTLIL::IdString> reg_wires; @@ -1209,7 +1209,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) if (cell->type == ID($modfloor)) { // wire truncated = $signed(A) % $signed(B); - // assign Y = (A[-1] == B[-1]) || truncated == 0 ? truncated : $signed(B) + $signed(truncated); + // assign Y = (A[-1] == B[-1]) || truncated == 0 ? $signed(truncated) : $signed(B) + $signed(truncated); if (cell->getParam(ID::A_SIGNED).as_bool() && cell->getParam(ID::B_SIGNED).as_bool()) { SigSpec sig_a = cell->getPort(ID::A); @@ -1229,7 +1229,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) dump_sigspec(f, sig_a.extract(sig_a.size()-1)); f << stringf(" == "); dump_sigspec(f, sig_b.extract(sig_b.size()-1)); - f << stringf(") || %s == 0 ? %s : ", temp_id.c_str(), temp_id.c_str()); + f << stringf(") || %s == 0 ? $signed(%s) : ", temp_id.c_str(), temp_id.c_str()); dump_cell_expr_port(f, cell, "B", true); f << stringf(" + $signed(%s);\n", temp_id.c_str()); return true; @@ -1314,24 +1314,38 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) f << stringf("%s" " input [%d:0] s;\n", indent.c_str(), s_width-1); dump_attributes(f, indent + " ", cell->attributes); - if (!noattr) - f << stringf("%s" " (* parallel_case *)\n", indent.c_str()); - f << stringf("%s" " casez (s)", indent.c_str()); - f << stringf(noattr ? " // synopsys parallel_case\n" : "\n"); + if (noparallelcase) + f << stringf("%s" " case (s)\n", indent.c_str()); + else { + if (!noattr) + f << stringf("%s" " (* parallel_case *)\n", indent.c_str()); + f << stringf("%s" " casez (s)", indent.c_str()); + f << stringf(noattr ? " // synopsys parallel_case\n" : "\n"); + } for (int i = 0; i < s_width; i++) { f << stringf("%s" " %d'b", indent.c_str(), s_width); for (int j = s_width-1; j >= 0; j--) - f << stringf("%c", j == i ? '1' : '?'); + f << stringf("%c", j == i ? '1' : noparallelcase ? '0' : '?'); f << stringf(":\n"); f << stringf("%s" " %s = b[%d:%d];\n", indent.c_str(), func_name.c_str(), (i+1)*width-1, i*width); } - f << stringf("%s" " default:\n", indent.c_str()); + if (noparallelcase) { + f << stringf("%s" " %d'b", indent.c_str(), s_width); + for (int j = s_width-1; j >= 0; j--) + f << '0'; + f << stringf(":\n"); + } else + f << stringf("%s" " default:\n", indent.c_str()); f << stringf("%s" " %s = a;\n", indent.c_str(), func_name.c_str()); + if (noparallelcase) { + f << stringf("%s" " default:\n", indent.c_str()); + f << stringf("%s" " %s = %d'bx;\n", indent.c_str(), func_name.c_str(), width); + } f << stringf("%s" " endcase\n", indent.c_str()); f << stringf("%s" "endfunction\n", indent.c_str()); @@ -2136,6 +2150,11 @@ struct VerilogBackend : public Backend { log(" without this option all internal cells are converted to Verilog\n"); log(" expressions.\n"); log("\n"); + log(" -noparallelcase\n"); + log(" With this option no parallel_case attributes are used. Instead, a case\n"); + log(" statement that assigns don't-care values for priority dependent inputs\n"); + log(" is generated.\n"); + log("\n"); log(" -siminit\n"); log(" add initial statements with hierarchical refs to initialize FFs when\n"); log(" in -noexpr mode.\n"); @@ -2211,6 +2230,7 @@ struct VerilogBackend : public Backend { decimal = false; siminit = false; simple_lhs = false; + noparallelcase = false; auto_prefix = ""; bool blackboxes = false; @@ -2246,6 +2266,10 @@ struct VerilogBackend : public Backend { noexpr = true; continue; } + if (arg == "-noparallelcase") { + noparallelcase = true; + continue; + } if (arg == "-nodec") { nodec = true; continue; @@ -2302,8 +2326,11 @@ struct VerilogBackend : public Backend { } log_push(); - Pass::call(design, "bmuxmap"); - Pass::call(design, "demuxmap"); + if (!noexpr) { + Pass::call(design, "bmuxmap"); + Pass::call(design, "demuxmap"); + Pass::call(design, "bwmuxmap"); + } Pass::call(design, "clean_zerowidth"); log_pop(); diff --git a/kernel/calc.cc b/kernel/calc.cc index 32e8a49fe..9b02a6e30 100644 --- a/kernel/calc.cc +++ b/kernel/calc.cc @@ -690,5 +690,28 @@ RTLIL::Const RTLIL::const_demux(const RTLIL::Const &arg1, const RTLIL::Const &ar return res; } +RTLIL::Const RTLIL::const_bweqx(const RTLIL::Const &arg1, const RTLIL::Const &arg2) +{ + log_assert(arg2.size() == arg1.size()); + RTLIL::Const result(RTLIL::State::S0, arg1.size()); + for (int i = 0; i < arg1.size(); i++) + result[i] = arg1[i] == arg2[i] ? State::S1 : State::S0; + + return result; +} + +RTLIL::Const RTLIL::const_bwmux(const RTLIL::Const &arg1, const RTLIL::Const &arg2, const RTLIL::Const &arg3) +{ + log_assert(arg2.size() == arg1.size()); + log_assert(arg3.size() == arg1.size()); + RTLIL::Const result(RTLIL::State::Sx, arg1.size()); + for (int i = 0; i < arg1.size(); i++) { + if (arg3[i] != State::Sx || arg1[i] == arg2[i]) + result[i] = arg3[i] == State::S1 ? arg2[i] : arg1[i]; + } + + return result; +} + YOSYS_NAMESPACE_END diff --git a/kernel/celltypes.h b/kernel/celltypes.h index e617b4603..63e7408c1 100644 --- a/kernel/celltypes.h +++ b/kernel/celltypes.h @@ -116,7 +116,8 @@ struct CellTypes ID($shl), ID($shr), ID($sshl), ID($sshr), ID($shift), ID($shiftx), ID($lt), ID($le), ID($eq), ID($ne), ID($eqx), ID($nex), ID($ge), ID($gt), ID($add), ID($sub), ID($mul), ID($div), ID($mod), ID($divfloor), ID($modfloor), ID($pow), - ID($logic_and), ID($logic_or), ID($concat), ID($macc) + ID($logic_and), ID($logic_or), ID($concat), ID($macc), + ID($bweqx) }; for (auto type : unary_ops) @@ -125,7 +126,7 @@ struct CellTypes for (auto type : binary_ops) setup_type(type, {ID::A, ID::B}, {ID::Y}, true); - for (auto type : std::vector<RTLIL::IdString>({ID($mux), ID($pmux)})) + for (auto type : std::vector<RTLIL::IdString>({ID($mux), ID($pmux), ID($bwmux)})) setup_type(type, {ID::A, ID::B, ID::S}, {ID::Y}, true); for (auto type : std::vector<RTLIL::IdString>({ID($bmux), ID($demux)})) @@ -430,6 +431,11 @@ struct CellTypes return const_demux(arg1, arg2); } + if (cell->type == ID($bweqx)) + { + return const_bweqx(arg1, arg2); + } + if (cell->type == ID($lut)) { int width = cell->parameters.at(ID::WIDTH).as_int(); @@ -490,6 +496,8 @@ struct CellTypes { if (cell->type.in(ID($mux), ID($_MUX_))) return const_mux(arg1, arg2, arg3); + if (cell->type == ID($bwmux)) + return const_bwmux(arg1, arg2, arg3); if (cell->type == ID($pmux)) return const_pmux(arg1, arg2, arg3); if (cell->type == ID($_AOI3_)) diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 5211c3b3f..eee014c54 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -1613,6 +1613,23 @@ namespace { return; } + if (cell->type == ID($bweqx)) { + port(ID::A, param(ID::WIDTH)); + port(ID::B, param(ID::WIDTH)); + port(ID::Y, param(ID::WIDTH)); + check_expected(); + return; + } + + if (cell->type == ID($bwmux)) { + port(ID::A, param(ID::WIDTH)); + port(ID::B, param(ID::WIDTH)); + port(ID::S, param(ID::WIDTH)); + port(ID::Y, param(ID::WIDTH)); + check_expected(); + return; + } + if (cell->type.in(ID($assert), ID($assume), ID($live), ID($fair), ID($cover))) { port(ID::A, 1); port(ID::EN, 1); @@ -2466,6 +2483,7 @@ DEF_METHOD(Sshr, sig_a.size(), ID($sshr)) return sig_y; \ } DEF_METHOD(Mux, ID($mux), 0) +DEF_METHOD(Bwmux, ID($bwmux), 0) DEF_METHOD(Pmux, ID($pmux), 1) #undef DEF_METHOD @@ -2489,6 +2507,24 @@ DEF_METHOD(Bmux, ID($bmux), 0) DEF_METHOD(Demux, ID($demux), 1) #undef DEF_METHOD +#define DEF_METHOD(_func, _type) \ + RTLIL::Cell* RTLIL::Module::add ## _func(RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, const RTLIL::SigSpec &sig_y, const std::string &src) { \ + RTLIL::Cell *cell = addCell(name, _type); \ + cell->parameters[ID::WIDTH] = sig_a.size(); \ + cell->setPort(ID::A, sig_a); \ + cell->setPort(ID::B, sig_b); \ + cell->setPort(ID::Y, sig_y); \ + cell->set_src_attribute(src); \ + return cell; \ + } \ + RTLIL::SigSpec RTLIL::Module::_func(RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_s, const std::string &src) { \ + RTLIL::SigSpec sig_y = addWire(NEW_ID, sig_a.size()); \ + add ## _func(name, sig_a, sig_s, sig_y, src); \ + return sig_y; \ + } +DEF_METHOD(Bweqx, ID($bweqx)) +#undef DEF_METHOD + #define DEF_METHOD_2(_func, _type, _P1, _P2) \ RTLIL::Cell* RTLIL::Module::add ## _func(RTLIL::IdString name, const RTLIL::SigBit &sig1, const RTLIL::SigBit &sig2, const std::string &src) { \ RTLIL::Cell *cell = addCell(name, _type); \ diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 755abf534..42bb66da8 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -505,6 +505,9 @@ namespace RTLIL RTLIL::Const const_bmux (const RTLIL::Const &arg1, const RTLIL::Const &arg2); RTLIL::Const const_demux (const RTLIL::Const &arg1, const RTLIL::Const &arg2); + RTLIL::Const const_bweqx (const RTLIL::Const &arg1, const RTLIL::Const &arg2); + RTLIL::Const const_bwmux (const RTLIL::Const &arg1, const RTLIL::Const &arg2, const RTLIL::Const &arg3); + // This iterator-range-pair is used for Design::modules(), Module::wires() and Module::cells(). // It maintains a reference counter that is used to make sure that the container is not modified while being iterated over. @@ -1303,6 +1306,9 @@ public: RTLIL::Cell* addBmux (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_s, const RTLIL::SigSpec &sig_y, const std::string &src = ""); RTLIL::Cell* addDemux (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_s, const RTLIL::SigSpec &sig_y, const std::string &src = ""); + RTLIL::Cell* addBweqx (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, const RTLIL::SigSpec &sig_y, const std::string &src = ""); + RTLIL::Cell* addBwmux (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, const RTLIL::SigSpec &sig_s, const RTLIL::SigSpec &sig_y, const std::string &src = ""); + RTLIL::Cell* addSlice (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_y, RTLIL::Const offset, const std::string &src = ""); RTLIL::Cell* addConcat (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, const RTLIL::SigSpec &sig_y, const std::string &src = ""); RTLIL::Cell* addLut (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_y, RTLIL::Const lut, const std::string &src = ""); @@ -1432,6 +1438,9 @@ public: RTLIL::SigSpec Bmux (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_s, const std::string &src = ""); RTLIL::SigSpec Demux (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_s, const std::string &src = ""); + RTLIL::SigSpec Bweqx (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, const std::string &src = ""); + RTLIL::SigSpec Bwmux (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, const RTLIL::SigSpec &sig_s, const std::string &src = ""); + RTLIL::SigBit BufGate (RTLIL::IdString name, const RTLIL::SigBit &sig_a, const std::string &src = ""); RTLIL::SigBit NotGate (RTLIL::IdString name, const RTLIL::SigBit &sig_a, const std::string &src = ""); RTLIL::SigBit AndGate (RTLIL::IdString name, const RTLIL::SigBit &sig_a, const RTLIL::SigBit &sig_b, const std::string &src = ""); diff --git a/kernel/satgen.cc b/kernel/satgen.cc index 2a1fd1711..3a2fa4735 100644 --- a/kernel/satgen.cc +++ b/kernel/satgen.cc @@ -223,7 +223,33 @@ bool SatGen::importCell(RTLIL::Cell *cell, int timestep) return true; } - if (cell->type.in(ID($_MUX_), ID($mux), ID($_NMUX_))) + if (cell->type == ID($bweqx)) + { + std::vector<int> a = importDefSigSpec(cell->getPort(ID::A), timestep); + std::vector<int> b = importDefSigSpec(cell->getPort(ID::B), timestep); + std::vector<int> y = importDefSigSpec(cell->getPort(ID::Y), timestep); + + std::vector<int> bweqx = ez->vec_not(ez->vec_xor(a, b)); + + if (model_undef) + { + std::vector<int> undef_a = importUndefSigSpec(cell->getPort(ID::A), timestep); + std::vector<int> undef_b = importUndefSigSpec(cell->getPort(ID::B), timestep); + std::vector<int> undef_y = importUndefSigSpec(cell->getPort(ID::Y), timestep); + + std::vector<int> both_undef = ez->vec_and(undef_a, undef_b); + std::vector<int> both_def = ez->vec_and(ez->vec_not(undef_a), ez->vec_not(undef_b)); + + bweqx = ez->vec_or(both_undef, ez->vec_and(both_def, bweqx)); + + for (int yx : undef_y) + ez->assume(ez->NOT(yx)); + } + ez->assume(ez->vec_eq(bweqx, y)); + return true; + } + + if (cell->type.in(ID($_MUX_), ID($mux), ID($_NMUX_), ID($bwmux))) { std::vector<int> a = importDefSigSpec(cell->getPort(ID::A), timestep); std::vector<int> b = importDefSigSpec(cell->getPort(ID::B), timestep); @@ -233,6 +259,8 @@ bool SatGen::importCell(RTLIL::Cell *cell, int timestep) std::vector<int> yy = model_undef ? ez->vec_var(y.size()) : y; if (cell->type == ID($_NMUX_)) ez->assume(ez->vec_eq(ez->vec_not(ez->vec_ite(s.at(0), b, a)), yy)); + else if (cell->type == ID($bwmux)) + ez->assume(ez->vec_eq(ez->vec_ite(s, b, a), yy)); else ez->assume(ez->vec_eq(ez->vec_ite(s.at(0), b, a), yy)); @@ -245,7 +273,11 @@ bool SatGen::importCell(RTLIL::Cell *cell, int timestep) std::vector<int> unequal_ab = ez->vec_not(ez->vec_iff(a, b)); std::vector<int> undef_ab = ez->vec_or(unequal_ab, ez->vec_or(undef_a, undef_b)); - std::vector<int> yX = ez->vec_ite(undef_s.at(0), undef_ab, ez->vec_ite(s.at(0), undef_b, undef_a)); + std::vector<int> yX; + if (cell->type == ID($bwmux)) + yX = ez->vec_ite(undef_s, undef_ab, ez->vec_ite(s, undef_b, undef_a)); + else + yX = ez->vec_ite(undef_s.at(0), undef_ab, ez->vec_ite(s.at(0), undef_b, undef_a)); ez->assume(ez->vec_eq(yX, undef_y)); undefGating(y, yy, undef_y); } @@ -375,29 +407,24 @@ bool SatGen::importCell(RTLIL::Cell *cell, int timestep) std::vector<int> undef_s = importUndefSigSpec(cell->getPort(ID::S), timestep); std::vector<int> undef_y = importUndefSigSpec(cell->getPort(ID::Y), timestep); - int maybe_a = ez->CONST_TRUE; + int all_undef = ez->CONST_FALSE; + int found_active = ez->CONST_FALSE; - std::vector<int> bits_set = std::vector<int>(undef_y.size(), ez->CONST_FALSE); - std::vector<int> bits_clr = std::vector<int>(undef_y.size(), ez->CONST_FALSE); + std::vector<int> undef_tmp = undef_a; for (size_t i = 0; i < s.size(); i++) { - std::vector<int> part_of_b(b.begin()+i*a.size(), b.begin()+(i+1)*a.size()); std::vector<int> part_of_undef_b(undef_b.begin()+i*a.size(), undef_b.begin()+(i+1)*a.size()); - int maybe_s = ez->OR(s.at(i), undef_s.at(i)); - int sure_s = ez->AND(s.at(i), ez->NOT(undef_s.at(i))); - - maybe_a = ez->AND(maybe_a, ez->NOT(sure_s)); - - bits_set = ez->vec_ite(maybe_s, ez->vec_or(bits_set, ez->vec_or(part_of_b, part_of_undef_b)), bits_set); - bits_clr = ez->vec_ite(maybe_s, ez->vec_or(bits_clr, ez->vec_or(ez->vec_not(part_of_b), part_of_undef_b)), bits_clr); + undef_tmp = ez->vec_ite(s.at(i), part_of_undef_b, undef_tmp); + all_undef = ez->OR(all_undef, undef_s.at(i)); + all_undef = ez->OR(all_undef, ez->AND(s.at(i), found_active)); + found_active = ez->OR(found_active, s.at(i)); } - bits_set = ez->vec_ite(maybe_a, ez->vec_or(bits_set, ez->vec_or(bits_set, ez->vec_or(a, undef_a))), bits_set); - bits_clr = ez->vec_ite(maybe_a, ez->vec_or(bits_clr, ez->vec_or(bits_clr, ez->vec_or(ez->vec_not(a), undef_a))), bits_clr); + undef_tmp = ez->vec_or(undef_tmp, std::vector<int>(a.size(), all_undef)); - ez->assume(ez->vec_eq(ez->vec_not(ez->vec_xor(bits_set, bits_clr)), undef_y)); + ez->assume(ez->vec_eq(undef_tmp, undef_y)); undefGating(y, yy, undef_y); } return true; diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index 16a38b511..8c7a18d02 100644 --- a/passes/cmds/Makefile.inc +++ b/passes/cmds/Makefile.inc @@ -43,3 +43,4 @@ OBJS += passes/cmds/logger.o OBJS += passes/cmds/printattrs.o OBJS += passes/cmds/sta.o OBJS += passes/cmds/clean_zerowidth.o +OBJS += passes/cmds/xprop.o diff --git a/passes/cmds/xprop.cc b/passes/cmds/xprop.cc new file mode 100644 index 000000000..c2a1b5c44 --- /dev/null +++ b/passes/cmds/xprop.cc @@ -0,0 +1,1198 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2022 Jannis Harder <jix@yosyshq.com> <me@jix.one> + * + * 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/celltypes.h" +#include "kernel/ffinit.h" +#include "kernel/ff.h" +#include "kernel/modtools.h" +#include "kernel/sigtools.h" +#include "kernel/utils.h" +#include "kernel/yosys.h" +#include <deque> + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct XpropOptions +{ + bool split_inputs = false; + bool split_outputs = false; + bool assume_encoding = false; + bool assert_encoding = false; + bool assume_def_inputs = false; + bool required = false; + bool formal = false; + bool debug_asserts = false; +}; + +struct XpropWorker +{ + struct EncodedBit { + SigBit is_0, is_1, is_x; + bool driven; + }; + + struct EncodedSig { + SigSpec is_0, is_1, is_x; + Module *module; + + void invert() { std::swap(is_0, is_1); } + void auto_0() { connect_0(module->Not(NEW_ID, module->Or(NEW_ID, is_1, is_x))); } + void auto_1() { connect_1(module->Not(NEW_ID, module->Or(NEW_ID, is_0, is_x))); } + void auto_x() { connect_x(module->Not(NEW_ID, module->Or(NEW_ID, is_0, is_1))); } + + void connect_0(SigSpec sig) { module->connect(is_0, sig); } + void connect_1(SigSpec sig) { module->connect(is_1, sig); } + void connect_x(SigSpec sig) { module->connect(is_x, sig); } + + void connect_1_under_x(SigSpec sig) { connect_1(module->And(NEW_ID, sig, module->Not(NEW_ID, is_x))); } + void connect_0_under_x(SigSpec sig) { connect_0(module->And(NEW_ID, sig, module->Not(NEW_ID, is_x))); } + + void connect_x_under_0(SigSpec sig) { connect_x(module->And(NEW_ID, sig, module->Not(NEW_ID, is_0))); } + + void connect_as_bool() { + int width = GetSize(is_0); + if (width <= 1) + return; + module->connect(is_0.extract(1, width - 1), Const(State::S1, width - 1)); + module->connect(is_1.extract(1, width - 1), Const(State::S0, width - 1)); + module->connect(is_x.extract(1, width - 1), Const(State::S0, width - 1)); + is_0 = is_0[0]; + is_1 = is_1[0]; + is_x = is_x[0]; + } + + int size() const { return is_0.size(); } + }; + + Module *module; + XpropOptions options; + ModWalker modwalker; + SigMap &sigmap; + FfInitVals initvals; + + pool<SigBit> maybe_x_bits; + dict<SigBit, EncodedBit> encoded_bits; + + pool<Cell *> pending_cells; + std::deque<Cell *> pending_cell_queue; + + XpropWorker(Module *module, XpropOptions options) : + module(module), options(options), + modwalker(module->design), sigmap(modwalker.sigmap) + { + modwalker.setup(module); + initvals.set(&modwalker.sigmap, module); + + maybe_x_bits.insert(State::Sx); + + for (auto cell : module->cells()) { + pending_cells.insert(cell); + pending_cell_queue.push_back(cell); + } + + if (!options.assume_def_inputs) { + for (auto port : module->ports) { + auto wire = module->wire(port); + if (wire->port_input) + mark_maybe_x(SigSpec(wire)); + } + } + } + + bool maybe_x(SigBit bit) + { + return maybe_x_bits.count(sigmap(bit)); + } + + bool maybe_x(const SigSpec &sig) + { + for (auto bit : sig) + if (maybe_x(bit)) return true; + return false; + } + + bool ports_maybe_x(Cell *cell) + { + for (auto &conn : cell->connections()) + if (maybe_x(conn.second)) + return true; + return false; + } + + bool inputs_maybe_x(Cell *cell) + { + for (auto &conn : cell->connections()) + if (cell->input(conn.first) && maybe_x(conn.second)) + return true; + return false; + } + + void mark_maybe_x(SigBit bit) + { + sigmap.apply(bit); + if (!maybe_x_bits.insert(bit).second) + return; + auto it = modwalker.signal_consumers.find(bit); + if (it == modwalker.signal_consumers.end()) + return; + for (auto &consumer : it->second) + if (pending_cells.insert(consumer.cell).second) + pending_cell_queue.push_back(consumer.cell); + } + + void mark_maybe_x(const SigSpec &sig) + { + for (auto bit : sig) + mark_maybe_x(bit); + } + + void mark_outputs_maybe_x(Cell *cell) + { + for (auto &conn : cell->connections()) + if (cell->output(conn.first)) + mark_maybe_x(conn.second); + } + + EncodedSig encoded(SigSpec sig, bool driving = false) + { + EncodedSig result; + SigSpec invert; + + if (driving) + result.module = module; + + int new_bits = 0; + + sigmap.apply(sig); + + for (auto bit : sig) { + if (!bit.is_wire()) + continue; + else if (!maybe_x(bit) && !driving) + invert.append(bit); + else if (!encoded_bits.count(bit)) { + new_bits += 1; + encoded_bits.emplace(bit, { + State::Sm, State::Sm, State::Sm, false + }); + } + } + + if (!invert.empty() && !driving) + invert = module->Not(NEW_ID, invert); + + EncodedSig new_sigs; + if (new_bits > 0) { + new_sigs.is_0 = module->addWire(NEW_ID, new_bits); + new_sigs.is_1 = module->addWire(NEW_ID, new_bits); + new_sigs.is_x = module->addWire(NEW_ID, new_bits); + } + + int invert_pos = 0; + int new_pos = 0; + + SigSpec driven_orig; + EncodedSig driven_enc; + SigSig driven_never_x; + + for (auto bit : sig) + { + if (!bit.is_wire()) { + result.is_0.append(bit == State::S0 ? State::S1 : State::S0); + result.is_1.append(bit == State::S1 ? State::S1 : State::S0); + result.is_x.append(bit == State::Sx ? State::S1 : State::S0); + continue; + } else if (!maybe_x(bit) && !driving) { + result.is_0.append(invert[invert_pos++]); + result.is_1.append(bit); + result.is_x.append(State::S0); + continue; + } + auto &enc = encoded_bits.at(bit); + if (enc.is_0 == State::Sm) { + enc.is_0 = new_sigs.is_0[new_pos]; + enc.is_1 = new_sigs.is_1[new_pos]; + enc.is_x = new_sigs.is_x[new_pos]; + new_pos++; + } + if (driving) { + log_assert(!enc.driven); + enc.driven = true; + if (maybe_x(bit)) { + driven_orig.append(bit); + driven_enc.is_0.append(enc.is_0); + driven_enc.is_1.append(enc.is_1); + driven_enc.is_x.append(enc.is_x); + } else { + driven_never_x.first.append(bit); + driven_never_x.second.append(enc.is_1); + } + } + result.is_0.append(enc.is_0); + result.is_1.append(enc.is_1); + result.is_x.append(enc.is_x); + } + + if (!driven_orig.empty()) { + module->addBwmux(NEW_ID, driven_enc.is_1, Const(State::Sx, GetSize(driven_orig)), driven_enc.is_x, driven_orig); + } + if (!driven_never_x.first.empty()) { + module->connect(driven_never_x); + } + + if (driving && (options.assert_encoding || options.assume_encoding)) { + auto not_0 = module->Not(NEW_ID, result.is_0); + auto not_1 = module->Not(NEW_ID, result.is_1); + auto not_x = module->Not(NEW_ID, result.is_x); + auto valid = module->ReduceAnd(NEW_ID, { + module->Eq(NEW_ID, result.is_0, module->And(NEW_ID, not_1, not_x)), + module->Eq(NEW_ID, result.is_1, module->And(NEW_ID, not_0, not_x)), + module->Eq(NEW_ID, result.is_x, module->And(NEW_ID, not_0, not_1)), + }); + if (options.assert_encoding) + module->addAssert(NEW_ID_SUFFIX("xprop_enc"), valid, State::S1); + else + module->addAssume(NEW_ID_SUFFIX("xprop_enc"), valid, State::S1); + if (options.debug_asserts) { + auto bad_bits = module->Bweqx(NEW_ID, {result.is_0, result.is_1, result.is_x}, Const(State::Sx, GetSize(result) * 3)); + module->addAssert(NEW_ID_SUFFIX("xprop_debug"), module->LogicNot(NEW_ID, bad_bits), State::S1); + } + } + + return result; + } + + void mark_all_maybe_x() + { + while (!pending_cell_queue.empty()) { + Cell *cell = pending_cell_queue.front(); + pending_cell_queue.pop_front(); + pending_cells.erase(cell); + + mark_maybe_x(cell); + } + } + + void mark_maybe_x(Cell *cell) { + if (cell->type.in(ID($bweqx), ID($eqx), ID($nex), ID($initstate), ID($assert), ID($assume), ID($cover), ID($anyseq), ID($anyconst))) + return; + + if (cell->type.in(ID($pmux))) { + mark_outputs_maybe_x(cell); + return; + } + + if (RTLIL::builtin_ff_cell_types().count(cell->type) || cell->type == ID($anyinit)) { + FfData ff(&initvals, cell); + + if (cell->type != ID($anyinit)) + for (int i = 0; i < ff.width; i++) + if (ff.val_init[i] == State::Sx) + mark_maybe_x(ff.sig_q[i]); + + for (int i = 0; i < ff.width; i++) + if (maybe_x(ff.sig_d[i])) + mark_maybe_x(ff.sig_q[i]); + + if ((ff.has_clk || ff.has_gclk) && !ff.has_ce && !ff.has_aload && !ff.has_srst && !ff.has_arst && !ff.has_sr) + return; + } + + if (cell->type == ID($not)) { + auto &sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); sig_a.extend_u0(GetSize(sig_y), cell->getParam(ID::A_SIGNED).as_bool()); + for (int i = 0; i < GetSize(sig_y); i++) + if (maybe_x(sig_a[i])) + mark_maybe_x(sig_y[i]); + return; + } + + if (cell->type.in(ID($and), ID($or), ID($xor), ID($xnor))) { + auto &sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); sig_a.extend_u0(GetSize(sig_y), cell->getParam(ID::A_SIGNED).as_bool()); + auto sig_b = cell->getPort(ID::B); sig_b.extend_u0(GetSize(sig_y), cell->getParam(ID::B_SIGNED).as_bool()); + for (int i = 0; i < GetSize(sig_y); i++) + if (maybe_x(sig_a[i]) || maybe_x(sig_b[i])) + mark_maybe_x(sig_y[i]); + return; + } + + if (cell->type.in(ID($bwmux))) { + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + auto &sig_b = cell->getPort(ID::B); + auto &sig_s = cell->getPort(ID::S); + for (int i = 0; i < GetSize(sig_y); i++) + if (maybe_x(sig_a[i]) || maybe_x(sig_b[i]) || maybe_x(sig_s[i])) + mark_maybe_x(sig_y[i]); + return; + } + + if (cell->type.in(ID($_MUX_), ID($mux), ID($bmux))) { + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + auto &sig_b = cell->getPort(ID::B); + auto &sig_s = cell->getPort(ID::S); + if (maybe_x(sig_s)) { + mark_maybe_x(sig_y); + return; + } + for (int i = 0; i < GetSize(sig_y); i++) { + if (maybe_x(sig_a[i])) { + mark_maybe_x(sig_y[i]); + continue; + } + for (int j = i; j < GetSize(sig_b); j += GetSize(sig_y)) { + if (maybe_x(sig_b[j])) { + mark_maybe_x(sig_y[i]); + break; + } + } + } + return; + } + + if (cell->type.in(ID($demux))) { + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + auto &sig_s = cell->getPort(ID::S); + if (maybe_x(sig_s)) { + mark_maybe_x(sig_y); + return; + } + for (int i = 0; i < GetSize(sig_a); i++) + if (maybe_x(sig_a[i])) + for (int j = i; j < GetSize(sig_y); j += GetSize(sig_a)) + mark_maybe_x(sig_y[j]); + return; + } + + if (cell->type.in(ID($shl), ID($shr), ID($sshl), ID($sshr), ID($shift))) { + auto &sig_b = cell->getPort(ID::B); + auto &sig_y = cell->getPort(ID::Y); + + if (maybe_x(sig_b)) { + mark_maybe_x(sig_y); + return; + } + + auto &sig_a = cell->getPort(ID::A); + + if (maybe_x(sig_a)) { + // We could be more precise for shifts, but that's not required + // for correctness, so let's keep it simple + mark_maybe_x(sig_y); + return; + } + return; + } + + if (cell->type.in(ID($shiftx))) { + auto &sig_b = cell->getPort(ID::B); + auto &sig_y = cell->getPort(ID::Y); + + if (cell->getParam(ID::B_SIGNED).as_bool() || GetSize(sig_b) >= 30) { + mark_maybe_x(sig_y); + } else { + int max_shift = (1 << GetSize(sig_b)) - 1; + + auto &sig_a = cell->getPort(ID::A); + + for (int i = 0; i < GetSize(sig_y); i++) + if (i + max_shift >= GetSize(sig_a)) + mark_maybe_x(sig_y[i]); + } + + if (maybe_x(sig_b)) { + mark_maybe_x(sig_y); + return; + } + + auto &sig_a = cell->getPort(ID::A); + if (maybe_x(sig_a)) { + // We could be more precise for shifts, but that's not required + // for correctness, so let's keep it simple + mark_maybe_x(sig_y); + return; + } + return; + } + + if (cell->type.in(ID($add), ID($sub), ID($mul), ID($neg))) { + if (inputs_maybe_x(cell)) + mark_outputs_maybe_x(cell); + return; + } + + if (cell->type.in(ID($div), ID($mod), ID($divfloor), ID($modfloor))) { + mark_outputs_maybe_x(cell); + return; + } + + if (cell->type.in( + ID($le), ID($lt), ID($ge), ID($gt), + ID($reduce_and), ID($reduce_or), ID($reduce_xor), ID($reduce_xnor), + ID($reduce_bool), ID($logic_not), ID($logic_or), ID($logic_and), + ID($eq), ID($ne), + + ID($_NOT_), ID($_AND_), ID($_NAND_), ID($_ANDNOT_), ID($_OR_), ID($_NOR_), ID($_ORNOT_), ID($_XOR_), ID($_XNOR_) + )) { + auto &sig_y = cell->getPort(ID::Y); + if (inputs_maybe_x(cell)) + mark_maybe_x(sig_y[0]); + return; + } + + log_warning("Unhandled cell %s (%s) during maybe-x marking\n", log_id(cell), log_id(cell->type)); + mark_outputs_maybe_x(cell); + } + + void process_cells() + { + for (auto cell : module->selected_cells()) + process_cell(cell); + } + + void process_cell(Cell *cell) + { + if (!ports_maybe_x(cell)) { + + if (cell->type == ID($bweq)) { + auto sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); + auto sig_b = cell->getPort(ID::B); + + auto name = cell->name; + module->remove(cell); + module->addXnor(name, sig_a, sig_b, sig_y); + return; + } + + if (cell->type.in(ID($nex), ID($eqx))) { + auto sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); + auto sig_b = cell->getPort(ID::B); + + auto name = cell->name; + module->remove(cell); + if (cell->type == ID($eqx)) + module->addEq(name, sig_a, sig_b, sig_y); + else + module->addNe(name, sig_a, sig_b, sig_y); + return; + } + + return; + } + + if (cell->type.in(ID($not), ID($_NOT_))) { + auto &sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); + if (cell->type == ID($not)) + sig_a.extend_u0(GetSize(sig_y), cell->getParam(ID::A_SIGNED).as_bool()); + + auto enc_a = encoded(sig_a); + auto enc_y = encoded(sig_y, true); + + enc_y.connect_x(enc_a.is_x); + enc_y.connect_0(enc_a.is_1); + enc_y.connect_1(enc_a.is_0); + + module->remove(cell); + return; + } + + if (cell->type.in(ID($and), ID($or), ID($_AND_), ID($_OR_), ID($_NAND_), ID($_NOR_), ID($_ANDNOT_), ID($_ORNOT_))) { + auto &sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); + auto sig_b = cell->getPort(ID::B); + if (cell->type.in(ID($and), ID($or))) { + sig_a.extend_u0(GetSize(sig_y), cell->getParam(ID::A_SIGNED).as_bool()); + sig_b.extend_u0(GetSize(sig_y), cell->getParam(ID::B_SIGNED).as_bool()); + } + + auto enc_a = encoded(sig_a); + auto enc_b = encoded(sig_b); + auto enc_y = encoded(sig_y, true); + + if (cell->type.in(ID($or), ID($_OR_))) + enc_a.invert(), enc_b.invert(), enc_y.invert(); + if (cell->type.in(ID($_NAND_), ID($_NOR_))) + enc_y.invert(); + if (cell->type.in(ID($_ANDNOT_), ID($_ORNOT_))) + enc_b.invert(); + + enc_y.connect_0(module->Or(NEW_ID, enc_a.is_0, enc_b.is_0)); + enc_y.connect_1(module->And(NEW_ID, enc_a.is_1, enc_b.is_1)); + enc_y.auto_x(); + module->remove(cell); + return; + } + + if (cell->type.in(ID($reduce_and), ID($reduce_or), ID($reduce_bool), ID($logic_not))) { + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + + auto enc_a = encoded(sig_a); + auto enc_y = encoded(sig_y, true); + + enc_y.connect_as_bool(); + + if (cell->type.in(ID($reduce_or), ID($reduce_bool))) + enc_a.invert(), enc_y.invert(); + if (cell->type == ID($logic_not)) + enc_a.invert(); + + enc_y.connect_0(module->ReduceOr(NEW_ID, enc_a.is_0)); + enc_y.connect_1(module->ReduceAnd(NEW_ID, enc_a.is_1)); + enc_y.auto_x(); + module->remove(cell); + + return; + } + + if (cell->type.in(ID($reduce_xor), ID($reduce_xnor))) { + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + + auto enc_a = encoded(sig_a); + auto enc_y = encoded(sig_y, true); + + enc_y.connect_as_bool(); + if (cell->type == ID($reduce_xnor)) + enc_y.invert(); + + + enc_y.connect_x(module->ReduceOr(NEW_ID, enc_a.is_x)); + enc_y.connect_1_under_x(module->ReduceXor(NEW_ID, enc_a.is_1)); + enc_y.auto_0(); + module->remove(cell); + + return; + } + + if (cell->type.in(ID($logic_and), ID($logic_or))) { + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + auto &sig_b = cell->getPort(ID::B); + + auto enc_a = encoded(sig_a); + auto enc_b = encoded(sig_b); + auto enc_y = encoded(sig_y, true); + + enc_y.connect_as_bool(); + + auto a_is_1 = module->ReduceOr(NEW_ID, enc_a.is_1); + auto a_is_0 = module->ReduceAnd(NEW_ID, enc_a.is_0); + auto b_is_1 = module->ReduceOr(NEW_ID, enc_b.is_1); + auto b_is_0 = module->ReduceAnd(NEW_ID, enc_b.is_0); + + if (cell->type == ID($logic_or)) + enc_y.invert(), std::swap(a_is_0, a_is_1), std::swap(b_is_0, b_is_1); + + enc_y.connect_0(module->Or(NEW_ID, a_is_0, b_is_0)); + enc_y.connect_1(module->And(NEW_ID, a_is_1, b_is_1)); + enc_y.auto_x(); + module->remove(cell); + return; + } + + if (cell->type.in(ID($xor), ID($xnor), ID($_XOR_), ID($_XNOR_))) { + auto &sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); + auto sig_b = cell->getPort(ID::B); + if (cell->type.in(ID($xor), ID($xnor))) { + sig_a.extend_u0(GetSize(sig_y), cell->getParam(ID::A_SIGNED).as_bool()); + sig_b.extend_u0(GetSize(sig_y), cell->getParam(ID::B_SIGNED).as_bool()); + } + + auto enc_a = encoded(sig_a); + auto enc_b = encoded(sig_b); + auto enc_y = encoded(sig_y, true); + + if (cell->type.in(ID($xnor), ID($_XNOR_))) + enc_y.invert(); + + enc_y.connect_x(module->Or(NEW_ID, enc_a.is_x, enc_b.is_x)); + enc_y.connect_1_under_x(module->Xor(NEW_ID, enc_a.is_1, enc_b.is_1)); + enc_y.auto_0(); + module->remove(cell); + return; + } + + if (cell->type.in(ID($eq), ID($ne))) { + auto &sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); + auto sig_b = cell->getPort(ID::B); + int width = std::max(GetSize(sig_a), GetSize(sig_b)); + sig_a.extend_u0(width, cell->getParam(ID::A_SIGNED).as_bool()); + sig_b.extend_u0(width, cell->getParam(ID::B_SIGNED).as_bool()); + + auto enc_a = encoded(sig_a); + auto enc_b = encoded(sig_b); + auto enc_y = encoded(sig_y, true); + enc_y.connect_as_bool(); + + if (cell->type == ID($ne)) + enc_y.invert(); + + auto delta = module->Xor(NEW_ID, enc_a.is_1, enc_b.is_1); + auto xpos = module->Or(NEW_ID, enc_a.is_x, enc_b.is_x); + + enc_y.connect_0(module->ReduceOr(NEW_ID, module->And(NEW_ID, delta, module->Not(NEW_ID, xpos)))); + enc_y.connect_x_under_0(module->ReduceOr(NEW_ID, xpos)); + enc_y.auto_1(); + module->remove(cell); + return; + } + + if (cell->type.in(ID($eqx), ID($nex))) { + auto &sig_y = cell->getPort(ID::Y); + auto sig_a = cell->getPort(ID::A); + auto sig_b = cell->getPort(ID::B); + int width = std::max(GetSize(sig_a), GetSize(sig_b)); + sig_a.extend_u0(width, cell->getParam(ID::A_SIGNED).as_bool()); + sig_b.extend_u0(width, cell->getParam(ID::B_SIGNED).as_bool()); + + auto enc_a = encoded(sig_a); + auto enc_b = encoded(sig_b); + + auto delta_0 = module->Xnor(NEW_ID, enc_a.is_0, enc_b.is_0); + auto delta_1 = module->Xnor(NEW_ID, enc_a.is_1, enc_b.is_1); + + auto eq = module->ReduceAnd(NEW_ID, {delta_0, delta_1}); + + auto res = cell->type == ID($nex) ? module->Not(NEW_ID, eq) : eq; + + module->connect(sig_y[0], res); + if (GetSize(sig_y) > 1) + module->connect(sig_y.extract(1, GetSize(sig_y) - 1), Const(State::S0, GetSize(sig_y) - 1)); + module->remove(cell); + return; + } + + if (cell->type.in(ID($bweqx))) { + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + auto &sig_b = cell->getPort(ID::B); + + auto enc_a = encoded(sig_a); + auto enc_b = encoded(sig_b); + + auto delta_0 = module->Xnor(NEW_ID, enc_a.is_0, enc_b.is_0); + auto delta_1 = module->Xnor(NEW_ID, enc_a.is_1, enc_b.is_1); + module->addAnd(NEW_ID, delta_0, delta_1, sig_y); + module->remove(cell); + return; + } + + if (cell->type.in(ID($_MUX_), ID($mux), ID($bwmux))) { + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + auto &sig_b = cell->getPort(ID::B); + auto sig_s = cell->getPort(ID::S); + + if (cell->type == ID($mux)) + sig_s = SigSpec(sig_s[0], GetSize(sig_y)); + + auto enc_a = encoded(sig_a); + auto enc_b = encoded(sig_b); + auto enc_s = encoded(sig_s); + auto enc_y = encoded(sig_y, true); + + enc_y.connect_1(module->And(NEW_ID, + module->Or(NEW_ID, enc_a.is_1, enc_s.is_1), + module->Or(NEW_ID, enc_b.is_1, enc_s.is_0))); + enc_y.connect_0(module->And(NEW_ID, + module->Or(NEW_ID, enc_a.is_0, enc_s.is_1), + module->Or(NEW_ID, enc_b.is_0, enc_s.is_0))); + enc_y.auto_x(); + module->remove(cell); + return; + } + + if (cell->type.in(ID($pmux))) { + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + auto &sig_b = cell->getPort(ID::B); + auto &sig_s = cell->getPort(ID::S); + + auto enc_a = encoded(sig_a); + auto enc_b = encoded(sig_b); + auto enc_s = encoded(sig_s); + auto enc_y = encoded(sig_y, true); + + int width = GetSize(enc_y); + + auto all_x = module->ReduceOr(NEW_ID, { + enc_s.is_x, + module->And(NEW_ID, enc_s.is_1, module->Sub(NEW_ID, enc_s.is_1, Const(1, width))) + }); + + auto selected = enc_a; + + for (int i = 0; i < GetSize(enc_s); i++) { + auto sel_bit = enc_s.is_1[i]; + selected.is_0 = module->Mux(NEW_ID, selected.is_0, enc_b.is_0.extract(i * width, width), sel_bit); + selected.is_1 = module->Mux(NEW_ID, selected.is_1, enc_b.is_1.extract(i * width, width), sel_bit); + selected.is_x = module->Mux(NEW_ID, selected.is_x, enc_b.is_x.extract(i * width, width), sel_bit); + } + + enc_y.connect_0(module->Mux(NEW_ID, selected.is_0, Const(State::S0, width), all_x)); + enc_y.connect_1(module->Mux(NEW_ID, selected.is_1, Const(State::S0, width), all_x)); + enc_y.connect_x(module->Mux(NEW_ID, selected.is_x, Const(State::S1, width), all_x)); + + module->remove(cell); + return; + } + + if (cell->type.in(ID($shl), ID($shr), ID($sshl), ID($sshr), ID($shift), ID($shiftx))) { + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + auto &sig_b = cell->getPort(ID::B); + + auto enc_a = encoded(sig_a); + auto enc_b = encoded(sig_b); + auto enc_y = encoded(sig_y, true); + + auto all_x = module->ReduceOr(NEW_ID, enc_b.is_x)[0]; + auto not_all_x = module->Not(NEW_ID, all_x)[0]; + + SigSpec y_not_0 = module->addWire(NEW_ID, GetSize(sig_y)); + SigSpec y_1 = module->addWire(NEW_ID, GetSize(sig_y)); + SigSpec y_x = module->addWire(NEW_ID, GetSize(sig_y)); + + auto encoded_type = cell->type == ID($shiftx) ? ID($shift) : cell->type; + + if (cell->type == ID($shiftx)) { + std::swap(enc_a.is_0, enc_a.is_x); + } + + auto shift_0 = module->addCell(NEW_ID, encoded_type); + shift_0->parameters = cell->parameters; + shift_0->setPort(ID::A, module->Not(NEW_ID, enc_a.is_0)); + shift_0->setPort(ID::B, enc_b.is_1); + shift_0->setPort(ID::Y, y_not_0); + + auto shift_1 = module->addCell(NEW_ID, encoded_type); + shift_1->parameters = cell->parameters; + shift_1->setPort(ID::A, enc_a.is_1); + shift_1->setPort(ID::B, enc_b.is_1); + shift_1->setPort(ID::Y, y_1); + + auto shift_x = module->addCell(NEW_ID, encoded_type); + shift_x->parameters = cell->parameters; + shift_x->setPort(ID::A, enc_a.is_x); + shift_x->setPort(ID::B, enc_b.is_1); + shift_x->setPort(ID::Y, y_x); + + SigSpec y_0 = module->Not(NEW_ID, y_not_0); + + if (cell->type == ID($shiftx)) + std::swap(y_0, y_x); + + enc_y.connect_0(module->And(NEW_ID, y_0, SigSpec(not_all_x, GetSize(sig_y)))); + enc_y.connect_1(module->And(NEW_ID, y_1, SigSpec(not_all_x, GetSize(sig_y)))); + enc_y.connect_x(module->Or(NEW_ID, y_x, SigSpec(all_x, GetSize(sig_y)))); + + module->remove(cell); + return; + } + + if (cell->type.in(ID($ff))) { + auto &sig_d = cell->getPort(ID::D); + auto &sig_q = cell->getPort(ID::Q); + + auto init_q = initvals(sig_q); + auto init_q_is_1 = init_q; + auto init_q_is_x = init_q; + + for (auto &bit : init_q_is_1) + bit = bit == State::S1 ? State::S1 : State::S0; + for (auto &bit : init_q_is_x) + bit = bit == State::Sx ? State::S1 : State::S0; + + initvals.remove_init(sig_q); + + auto enc_d = encoded(sig_d); + auto enc_q = encoded(sig_q, true); + + auto data_q = module->addWire(NEW_ID, GetSize(sig_q)); + + module->addFf(NEW_ID, enc_d.is_1, data_q); + module->addFf(NEW_ID, enc_d.is_x, enc_q.is_x); + + initvals.set_init(data_q, init_q_is_1); + initvals.set_init(enc_q.is_x, init_q_is_x); + + enc_q.connect_1_under_x(data_q); + enc_q.auto_0(); + + module->remove(cell); + return; + } + + if (RTLIL::builtin_ff_cell_types().count(cell->type) || cell->type == ID($anyinit)) { + FfData ff(&initvals, cell); + + if ((ff.has_clk || ff.has_gclk) && !ff.has_ce && !ff.has_aload && !ff.has_srst && !ff.has_arst && !ff.has_sr) { + if (ff.has_clk && maybe_x(ff.sig_clk)) { + log_warning("Only non-x CLK inputs are currently supported for %s (%s)\n", log_id(cell), log_id(cell->type)); + } else { + auto init_q = ff.val_init; + auto init_q_is_1 = init_q; + auto init_q_is_x = init_q; + + if (ff.is_anyinit) { + for (auto &bit : init_q_is_1) + bit = State::Sx; + for (auto &bit : init_q_is_x) + bit = State::S0; + } else { + for (auto &bit : init_q_is_1) + bit = bit == State::S1 ? State::S1 : State::S0; + for (auto &bit : init_q_is_x) + bit = bit == State::Sx ? State::S1 : State::S0; + } + + ff.remove(); + + auto enc_d = encoded(ff.sig_d); + auto enc_q = encoded(ff.sig_q, true); + + auto data_q = module->addWire(NEW_ID, GetSize(ff.sig_q)); + + ff.sig_d = enc_d.is_1; + ff.sig_q = data_q; + ff.val_init = init_q_is_1; + ff.emit(); + + ff.name = NEW_ID; + ff.cell = nullptr; + ff.sig_d = enc_d.is_x; + ff.sig_q = enc_q.is_x; + ff.is_anyinit = false; + ff.val_init = init_q_is_x; + ff.emit(); + + + enc_q.connect_1_under_x(data_q); + enc_q.auto_0(); + + return; + } + } else { + log_warning("Unhandled FF-cell %s (%s), consider running clk2fflogic, async2sync and/or dffunmap\n", log_id(cell), log_id(cell->type)); + } + } + + // Celltypes where any input x bit makes the whole output x + if (cell->type.in( + ID($neg), + ID($le), ID($lt), ID($ge), ID($gt), + ID($add), ID($sub), ID($mul), ID($div), ID($mod), ID($divfloor), ID($modfloor) + )) { + + SigSpec inbits_x; + for (auto &conn : cell->connections()) { + if (cell->input(conn.first)) { + auto enc_port = encoded(conn.second); + inbits_x.append(enc_port.is_x); + cell->setPort(conn.first, enc_port.is_1); + } + } + + if (cell->type.in(ID($div), ID($mod), ID($divfloor), ID($modfloor))) { + auto sig_b = cell->getPort(ID::B); + auto invalid = module->LogicNot(NEW_ID, sig_b); + inbits_x.append(invalid); + sig_b[0] = module->Or(NEW_ID, sig_b[0], invalid); + cell->setPort(ID::B, sig_b); + } + + SigBit outbits_x = (GetSize(inbits_x) == 1 ? inbits_x : module->ReduceOr(NEW_ID, inbits_x)); + + bool bool_out = cell->type.in(ID($le), ID($lt), ID($ge), ID($gt)); + + for (auto &conn : cell->connections()) { + if (cell->output(conn.first)) { + auto enc_port = encoded(conn.second, true); + if (bool_out) + enc_port.connect_as_bool(); + + SigSpec new_output = module->addWire(NEW_ID, GetSize(conn.second)); + + enc_port.connect_1_under_x(bool_out ? new_output.extract(0) : new_output); + enc_port.connect_x(SigSpec(outbits_x, GetSize(enc_port))); + enc_port.auto_0(); + + cell->setPort(conn.first, new_output); + } + } + + return; + } + + if (cell->type == ID($bmux)) // TODO might want to support bmux natively anyway + log("Running 'bmuxmap' preserves x-propagation and can be run before 'xprop'.\n"); + if (cell->type == ID($demux)) // TODO might want to support demux natively anyway + log("Running 'demuxmap' preserves x-propagation and can be run before 'xprop'.\n"); + + if (options.required) + log_error("Unhandled cell %s (%s)\n", log_id(cell), log_id(cell->type)); + else + log_warning("Unhandled cell %s (%s)\n", log_id(cell), log_id(cell->type)); + } + + void split_ports() + { + if (!options.split_inputs && !options.split_outputs) + return; + + vector<IdString> new_ports; + + for (auto port : module->ports) { + auto wire = module->wire(port); + if (module->design->selected(module, wire)) { + if (wire->port_input == wire->port_output) { + log_warning("Port %s not an input or an output port which is not supported by xprop\n", log_id(wire)); + } else if ((options.split_inputs && !options.assume_def_inputs && wire->port_input) || (options.split_outputs && wire->port_output)) { + auto port_d = module->uniquify(stringf("%s_d", port.c_str())); + auto port_x = module->uniquify(stringf("%s_x", port.c_str())); + + auto wire_d = module->addWire(port_d, GetSize(wire)); + auto wire_x = module->addWire(port_x, GetSize(wire)); + + wire_d->port_input = wire->port_input; + wire_d->port_output = wire->port_output; + wire_d->port_id = GetSize(new_ports) + 1; + + wire_x->port_input = wire->port_input; + wire_x->port_output = wire->port_output; + wire_x->port_id = GetSize(new_ports) + 2; + + if (wire->port_output) { + auto enc = encoded(wire); + module->connect(wire_d, enc.is_1); + module->connect(wire_x, enc.is_x); + } else { + auto enc = encoded(wire, true); + + enc.connect_x(wire_x); + enc.connect_1_under_x(wire_d); + enc.auto_0(); + } + + wire->port_input = wire->port_output = false; + wire->port_id = 0; + + new_ports.push_back(port_d); + new_ports.push_back(port_x); + + continue; + } + } + wire->port_id = GetSize(new_ports) + 1; + new_ports.push_back(port); + } + + module->ports = new_ports; + + module->fixup_ports(); + } + + void encode_remaining() + { + pool<Wire *> enc_undriven_wires; + + for (auto &enc_bit : encoded_bits) { + if (!enc_bit.second.driven) { + log_assert(enc_bit.first.is_wire()); + enc_undriven_wires.insert(enc_bit.first.wire); + } + } + + if (options.formal && !enc_undriven_wires.empty()) { + for (auto &bit : enc_undriven_wires) + log_warning("Found encoded wire %s having a non-encoded driver\n", log_signal(bit)); + + log_error("Found encoded wires having a non-encoded driver, not allowed in -formal mode\n"); + } + + + for (auto wire : enc_undriven_wires) { + SigSpec sig(sigmap(wire)); + + SigSpec orig; + EncodedSig enc; + + for (auto bit : sig) { + auto it = encoded_bits.find(bit); + if (it == encoded_bits.end() || it->second.driven) + continue; + orig.append(bit); + enc.is_0.append(it->second.is_0); + enc.is_1.append(it->second.is_1); + enc.is_x.append(it->second.is_x); + it->second.driven = true; + } + + module->addBweqx(NEW_ID, orig, Const(State::S0, GetSize(orig)), enc.is_0); + module->addBweqx(NEW_ID, orig, Const(State::S1, GetSize(orig)), enc.is_1); + module->addBweqx(NEW_ID, orig, Const(State::Sx, GetSize(orig)), enc.is_x); + } + } +}; + +struct XpropPass : public Pass { + XpropPass() : Pass("xprop", "formal x propagation") {} + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" xprop [options] [selection]\n"); + log("\n"); + log("This pass transforms the circuit into an equivalent circuit that explicitly\n"); + log("encodes the propagation of x values using purely 2-valued logic. On the\n"); + log("interface between xprop-transformed and non-transformed parts of the design,\n"); + log("appropriate conversions are inserted automatically.\n"); + log("\n"); + log(" -split-inputs\n"); + log(" -split-outputs\n"); + log(" -split-ports\n"); + log(" Replace each input/output/port with two new ports, one carrying the\n"); + log(" defined values (named <portname>_d) and one carrying the mask of which\n"); + log(" bits are x (named <portname>_x). When a bit in the <portname>_x is set\n"); + log(" the corresponding bit in <portname>_d is ignored for inputs and\n"); + log(" guaranteed to be 0 for outputs.\n"); + log("\n"); + log(" -assume-encoding\n"); + log(" Add encoding invariants as assumptions. This can speed up formal\n"); + log(" verification tasks.\n"); + log("\n"); + log(" -assert-encoding\n"); + log(" Add encoding invariants as assertions. Used for testing the xprop\n"); + log(" pass itself.\n"); + log("\n"); + log(" -assume-def-inputs\n"); + log(" Assume all inputs are fully defined. This adds corresponding\n"); + log(" assumptions to the design and uses these assumptions to optimize the\n"); + log(" xprop encoding.\n"); + log("\n"); + log(" -required\n"); + log(" Produce a runtime error if any encountered cell could not be encoded.\n"); + log("\n"); + log(" -formal\n"); + log(" Produce a runtime error if any encoded cell uses a signal that is\n"); + log(" neither known to be non-x nor driven by another encoded cell.\n"); + log("\n"); + log(" -debug-asserts\n"); + log(" Add assertions checking that the encoding used by this pass never\n"); + log(" produces x values within the encoded signals.\n"); + log("\n"); + } + void execute(std::vector<std::string> args, RTLIL::Design *design) override + { + XpropOptions options; + + log_header(design, "Executing XPROP pass.\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-split-ports") { + options.split_inputs = true; + options.split_outputs = true; + continue; + } + if (args[argidx] == "-split-inputs") { + options.split_inputs = true; + continue; + } + if (args[argidx] == "-split-outputs") { + options.split_outputs = true; + continue; + } + if (args[argidx] == "-assume-encoding") { + options.assume_encoding = true; + continue; + } + if (args[argidx] == "-assert-encoding") { + options.assert_encoding = true; + continue; + } + if (args[argidx] == "-assume-def-inputs") { + options.assume_def_inputs = true; + continue; + } + if (args[argidx] == "-required") { + options.required = true; // TODO check more + continue; + } + if (args[argidx] == "-formal") { + options.formal = true; + options.required = true; + continue; + } + if (args[argidx] == "-debug-asserts") { // TODO documented + options.debug_asserts = true; + options.assert_encoding = true; + continue; + } + break; + } + + if (options.assert_encoding && options.assume_encoding) + log_cmd_error("The options -assert-encoding and -assume-encoding are exclusive.\n"); + + extra_args(args, argidx, design); + + log_push(); + Pass::call(design, "bmuxmap"); + Pass::call(design, "demuxmap"); + log_pop(); + + for (auto module : design->selected_modules()) { + if (options.assume_def_inputs) { + for (auto port : module->ports) { + auto wire = module->wire(port); + if (!module->design->selected(module, wire)) + continue; + + if (wire->port_input) { + module->addAssume(NEW_ID, module->Not(NEW_ID, module->ReduceOr(NEW_ID, module->Bweqx(NEW_ID, wire, Const(State::Sx, GetSize(wire))))), State::S1); + } + } + } + + XpropWorker worker(module, options); + log_debug("Marking all x-bits.\n"); + worker.mark_all_maybe_x(); + log_debug("Repalcing cells.\n"); + worker.process_cells(); + log_debug("Splitting ports.\n"); + worker.split_ports(); + log_debug("Encode remaining signals.\n"); + worker.encode_remaining(); + + } + } +} XpropPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/opt/opt_expr.cc b/passes/opt/opt_expr.cc index 9cc0170dc..9d5ca4ef9 100644 --- a/passes/opt/opt_expr.cc +++ b/passes/opt/opt_expr.cc @@ -643,6 +643,148 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons goto next_cell; } + if (cell->type.in(ID($and), ID($or), ID($xor), ID($xnor))) + { + RTLIL::SigSpec sig_a = assign_map(cell->getPort(ID::A)); + RTLIL::SigSpec sig_b = assign_map(cell->getPort(ID::B)); + + bool a_fully_const = (sig_a.is_fully_const() && (!keepdc || !sig_a.is_fully_undef())); + bool b_fully_const = (sig_b.is_fully_const() && (!keepdc || !sig_b.is_fully_undef())); + + if (a_fully_const != b_fully_const) + { + cover("opt.opt_expr.bitwise_logic_one_const"); + log_debug("Replacing %s cell `%s' in module `%s' having one fully constant input\n", + log_id(cell->type), log_id(cell->name), log_id(module)); + RTLIL::SigSpec sig_y = assign_map(cell->getPort(ID::Y)); + + int width = GetSize(cell->getPort(ID::Y)); + + sig_a.extend_u0(width, cell->getParam(ID::A_SIGNED).as_bool()); + sig_b.extend_u0(width, cell->getParam(ID::B_SIGNED).as_bool()); + + if (!a_fully_const) + std::swap(sig_a, sig_b); + + RTLIL::SigSpec b_group_0, b_group_1, b_group_x; + RTLIL::SigSpec y_group_0, y_group_1, y_group_x; + + for (int i = 0; i < width; i++) { + auto bit_a = sig_a[i].data; + if (bit_a == State::S0) b_group_0.append(sig_b[i]), y_group_0.append(sig_y[i]); + if (bit_a == State::S1) b_group_1.append(sig_b[i]), y_group_1.append(sig_y[i]); + if (bit_a == State::Sx) b_group_x.append(sig_b[i]), y_group_x.append(sig_y[i]); + } + + if (cell->type == ID($xnor)) { + std::swap(b_group_0, b_group_1); + std::swap(y_group_0, y_group_1); + } + + RTLIL::SigSpec y_new_0, y_new_1, y_new_x; + + if (cell->type == ID($and)) { + if (!y_group_0.empty()) y_new_0 = Const(State::S0, GetSize(y_group_0)); + if (!y_group_1.empty()) y_new_1 = b_group_1; + if (!y_group_x.empty()) { + if (keepdc) + y_new_x = module->And(NEW_ID, Const(State::Sx, GetSize(y_group_x)), b_group_x); + else + y_new_x = Const(State::S0, GetSize(y_group_x)); + } + } else if (cell->type == ID($or)) { + if (!y_group_0.empty()) y_new_0 = b_group_0; + if (!y_group_1.empty()) y_new_1 = Const(State::S1, GetSize(y_group_1)); + if (!y_group_x.empty()) { + if (keepdc) + y_new_x = module->Or(NEW_ID, Const(State::Sx, GetSize(y_group_x)), b_group_x); + else + y_new_x = Const(State::S1, GetSize(y_group_x)); + } + } else if (cell->type.in(ID($xor), ID($xnor))) { + if (!y_group_0.empty()) y_new_0 = b_group_0; + if (!y_group_1.empty()) y_new_1 = module->Not(NEW_ID, b_group_1); + if (!y_group_x.empty()) { + if (keepdc) + y_new_x = module->Xor(NEW_ID, Const(State::Sx, GetSize(y_group_x)), b_group_x); + else // This should be fine even with keepdc, but opt_expr_xor.ys wants to keep the xor + y_new_x = Const(State::Sx, GetSize(y_group_x)); + } + } else { + log_abort(); + } + + assign_map.add(y_group_0, y_new_0); module->connect(y_group_0, y_new_0); + assign_map.add(y_group_1, y_new_1); module->connect(y_group_1, y_new_1); + assign_map.add(y_group_x, y_new_x); module->connect(y_group_x, y_new_x); + + module->remove(cell); + did_something = true; + goto next_cell; + } + } + + if (cell->type == ID($bwmux)) + { + RTLIL::SigSpec sig_a = assign_map(cell->getPort(ID::A)); + RTLIL::SigSpec sig_b = assign_map(cell->getPort(ID::B)); + RTLIL::SigSpec sig_s = assign_map(cell->getPort(ID::S)); + RTLIL::SigSpec sig_y = assign_map(cell->getPort(ID::Y)); + int width = GetSize(cell->getPort(ID::Y)); + + if (sig_s.is_fully_def()) + { + RTLIL::SigSpec a_group_0, b_group_1; + RTLIL::SigSpec y_group_0, y_group_1; + for (int i = 0; i < width; i++) { + if (sig_s[i].data == State::S1) + y_group_1.append(sig_y[i]), b_group_1.append(sig_b[i]); + else + y_group_0.append(sig_y[i]), a_group_0.append(sig_a[i]); + } + + assign_map.add(y_group_0, a_group_0); module->connect(y_group_0, a_group_0); + assign_map.add(y_group_1, b_group_1); module->connect(y_group_1, b_group_1); + + module->remove(cell); + did_something = true; + goto next_cell; + } + else if (sig_a.is_fully_def() || sig_b.is_fully_def()) + { + bool flip = !sig_a.is_fully_def(); + if (flip) + std::swap(sig_a, sig_b); + + RTLIL::SigSpec b_group_0, b_group_1; + RTLIL::SigSpec s_group_0, s_group_1; + RTLIL::SigSpec y_group_0, y_group_1; + for (int i = 0; i < width; i++) { + if (sig_a[i].data == State::S1) + y_group_1.append(sig_y[i]), b_group_1.append(sig_b[i]), s_group_1.append(sig_s[i]); + else + y_group_0.append(sig_y[i]), b_group_0.append(sig_b[i]), s_group_0.append(sig_s[i]); + } + + RTLIL::SigSpec y_new_0, y_new_1; + + if (flip) { + if (!y_group_0.empty()) y_new_0 = module->And(NEW_ID, b_group_0, module->Not(NEW_ID, s_group_0)); + if (!y_group_1.empty()) y_new_1 = module->Or(NEW_ID, b_group_1, s_group_1); + } else { + if (!y_group_0.empty()) y_new_0 = module->And(NEW_ID, b_group_0, s_group_0); + if (!y_group_1.empty()) y_new_1 = module->Or(NEW_ID, b_group_1, module->Not(NEW_ID, s_group_1)); + } + + module->connect(y_group_0, y_new_0); + module->connect(y_group_1, y_new_1); + + module->remove(cell); + did_something = true; + goto next_cell; + } + } + if (do_fine) { if (cell->type.in(ID($not), ID($pos), ID($and), ID($or), ID($xor), ID($xnor))) @@ -905,7 +1047,7 @@ skip_fine_alu: } } - if (cell->type.in(ID($shiftx), ID($shift))) { + if (cell->type.in(ID($shiftx), ID($shift)) && (cell->type == ID($shiftx) || !cell->getParam(ID::A_SIGNED).as_bool())) { SigSpec sig_a = assign_map(cell->getPort(ID::A)); int width; bool trim_x = cell->type == ID($shiftx) || !keepdc; @@ -1152,7 +1294,7 @@ skip_fine_alu: goto next_cell; } - if (cell->type.in(ID($shl), ID($shr), ID($sshl), ID($sshr), ID($shift), ID($shiftx)) && assign_map(cell->getPort(ID::B)).is_fully_const()) + if (cell->type.in(ID($shl), ID($shr), ID($sshl), ID($sshr), ID($shift), ID($shiftx)) && (keepdc ? assign_map(cell->getPort(ID::B)).is_fully_def() : assign_map(cell->getPort(ID::B)).is_fully_const())) { bool sign_ext = cell->type == ID($sshr) && cell->getParam(ID::A_SIGNED).as_bool(); int shift_bits = assign_map(cell->getPort(ID::B)).as_int(cell->type.in(ID($shift), ID($shiftx)) && cell->getParam(ID::B_SIGNED).as_bool()); @@ -1163,7 +1305,7 @@ skip_fine_alu: RTLIL::SigSpec sig_a = assign_map(cell->getPort(ID::A)); RTLIL::SigSpec sig_y(cell->type == ID($shiftx) ? RTLIL::State::Sx : RTLIL::State::S0, cell->getParam(ID::Y_WIDTH).as_int()); - if (GetSize(sig_a) < GetSize(sig_y)) + if (cell->type != ID($shiftx) && GetSize(sig_a) < GetSize(sig_y)) sig_a.extend_u0(GetSize(sig_y), cell->getParam(ID::A_SIGNED).as_bool()); for (int i = 0; i < GetSize(sig_y); i++) { @@ -1446,6 +1588,31 @@ skip_identity: goto next_cell; \ } \ } +#define FOLD_2ARG_SIMPLE_CELL(_t, B_ID) \ + if (cell->type == ID($##_t)) { \ + RTLIL::SigSpec a = cell->getPort(ID::A); \ + RTLIL::SigSpec b = cell->getPort(B_ID); \ + assign_map.apply(a), assign_map.apply(b); \ + if (a.is_fully_const() && b.is_fully_const()) { \ + RTLIL::SigSpec y(RTLIL::const_ ## _t(a.as_const(), b.as_const())); \ + cover("opt.opt_expr.const.$" #_t); \ + replace_cell(assign_map, module, cell, stringf("%s, %s", log_signal(a), log_signal(b)), ID::Y, y); \ + goto next_cell; \ + } \ + } +#define FOLD_MUX_CELL(_t) \ + if (cell->type == ID($##_t)) { \ + RTLIL::SigSpec a = cell->getPort(ID::A); \ + RTLIL::SigSpec b = cell->getPort(ID::B); \ + RTLIL::SigSpec s = cell->getPort(ID::S); \ + assign_map.apply(a), assign_map.apply(b), assign_map.apply(s); \ + if (a.is_fully_const() && b.is_fully_const() && s.is_fully_const()) { \ + RTLIL::SigSpec y(RTLIL::const_ ## _t(a.as_const(), b.as_const(), s.as_const())); \ + cover("opt.opt_expr.const.$" #_t); \ + replace_cell(assign_map, module, cell, stringf("%s, %s, %s", log_signal(a), log_signal(b), log_signal(s)), ID::Y, y); \ + goto next_cell; \ + } \ + } FOLD_1ARG_CELL(not) FOLD_2ARG_CELL(and) @@ -1477,6 +1644,9 @@ skip_identity: FOLD_2ARG_CELL(gt) FOLD_2ARG_CELL(ge) + FOLD_2ARG_CELL(eqx) + FOLD_2ARG_CELL(nex) + FOLD_2ARG_CELL(add) FOLD_2ARG_CELL(sub) FOLD_2ARG_CELL(mul) @@ -1489,6 +1659,13 @@ skip_identity: FOLD_1ARG_CELL(pos) FOLD_1ARG_CELL(neg) + FOLD_MUX_CELL(mux); + FOLD_MUX_CELL(pmux); + FOLD_2ARG_SIMPLE_CELL(bmux, ID::S); + FOLD_2ARG_SIMPLE_CELL(demux, ID::S); + FOLD_2ARG_SIMPLE_CELL(bweqx, ID::B); + FOLD_MUX_CELL(bwmux); + // be very conservative with optimizing $mux cells as we do not want to break mux trees if (cell->type == ID($mux)) { RTLIL::SigSpec input = assign_map(cell->getPort(ID::S)); diff --git a/passes/sat/formalff.cc b/passes/sat/formalff.cc index 962e350a1..e36379096 100644 --- a/passes/sat/formalff.cc +++ b/passes/sat/formalff.cc @@ -532,12 +532,14 @@ struct FormalFfPass : public Pass { if ((int)bits.size() == ff.val_init.size()) { // This check is only to make the private names more helpful for debugging ff.is_anyinit = true; + ff.is_fine = false; emit = true; break; } auto slice = ff.slice(bits); slice.is_anyinit = is_anyinit; + slice.is_fine = false; slice.emit(); } } diff --git a/passes/sat/miter.cc b/passes/sat/miter.cc index bf33c9ce3..1f64c0216 100644 --- a/passes/sat/miter.cc +++ b/passes/sat/miter.cc @@ -30,6 +30,7 @@ void create_miter_equiv(struct Pass *that, std::vector<std::string> args, RTLIL: bool flag_make_outputs = false; bool flag_make_outcmp = false; bool flag_make_assert = false; + bool flag_make_cover = false; bool flag_flatten = false; bool flag_cross = false; @@ -54,6 +55,10 @@ void create_miter_equiv(struct Pass *that, std::vector<std::string> args, RTLIL: flag_make_assert = true; continue; } + if (args[argidx] == "-make_cover") { + flag_make_cover = true; + continue; + } if (args[argidx] == "-flatten") { flag_flatten = true; continue; @@ -237,6 +242,12 @@ void create_miter_equiv(struct Pass *that, std::vector<std::string> args, RTLIL: miter_module->connect(RTLIL::SigSig(w_cmp, this_condition)); } + if (flag_make_cover) + { + auto cover_condition = miter_module->Not(NEW_ID, this_condition); + miter_module->addCover("\\cover_" + RTLIL::unescape_id(gold_wire->name), cover_condition, State::S1); + } + all_conditions.append(this_condition); } } @@ -402,6 +413,9 @@ struct MiterPass : public Pass { log(" -make_assert\n"); log(" also create an 'assert' cell that checks if trigger is always low.\n"); log("\n"); + log(" -make_cover\n"); + log(" also create a 'cover' cell for each gold/gate output pair.\n"); + log("\n"); log(" -flatten\n"); log(" call 'flatten -wb; opt_expr -keepdc -undriven;;' on the miter circuit.\n"); log("\n"); diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index 1cd1ebc71..e8dda4c45 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -509,7 +509,7 @@ struct SimInstance } } - bool update_ph2() + bool update_ph2(bool gclk) { bool did_something = false; @@ -567,7 +567,8 @@ struct SimInstance } if (ff_data.has_gclk) { // $ff - current_q = ff.past_d; + if (gclk) + current_q = ff.past_d; } if (set_state(ff_data.sig_q, current_q)) did_something = true; @@ -616,7 +617,7 @@ struct SimInstance } for (auto it : children) - if (it.second->update_ph2()) { + if (it.second->update_ph2(gclk)) { dirty_children.insert(it.second); did_something = true; } @@ -985,7 +986,7 @@ struct SimWorker : SimShared writer->write(use_signal); } - void update() + void update(bool gclk) { while (1) { @@ -997,7 +998,7 @@ struct SimWorker : SimShared if (debug) log("\n-- ph2 --\n"); - if (!top->update_ph2()) + if (!top->update_ph2(gclk)) break; } @@ -1047,7 +1048,7 @@ struct SimWorker : SimShared set_inports(clock, State::Sx); set_inports(clockn, State::Sx); - update(); + update(false); register_output_step(0); @@ -1060,7 +1061,7 @@ struct SimWorker : SimShared set_inports(clock, State::S0); set_inports(clockn, State::S1); - update(); + update(true); register_output_step(10*cycle + 5); if (debug) @@ -1076,7 +1077,7 @@ struct SimWorker : SimShared set_inports(resetn, State::S1); } - update(); + update(true); register_output_step(10*cycle + 10); } @@ -1193,7 +1194,7 @@ struct SimWorker : SimShared initial = false; } if (did_something) - update(); + update(true); register_output_step(time); bool status = top->checkSignals(); @@ -1342,12 +1343,12 @@ struct SimWorker : SimShared set_inports(clock, State::S0); set_inports(clockn, State::S1); } - update(); + update(true); register_output_step(10*cycle); if (!multiclock && cycle) { set_inports(clock, State::S0); set_inports(clockn, State::S1); - update(); + update(true); register_output_step(10*cycle + 5); } cycle++; @@ -1419,12 +1420,12 @@ struct SimWorker : SimShared log("Simulating cycle %d.\n", cycle); set_inports(clock, State::S1); set_inports(clockn, State::S0); - update(); + update(true); register_output_step(10*cycle+0); if (!multiclock) { set_inports(clock, State::S0); set_inports(clockn, State::S1); - update(); + update(true); register_output_step(10*cycle+5); } cycle++; diff --git a/passes/techmap/Makefile.inc b/passes/techmap/Makefile.inc index 98ccfc303..1b834fabc 100644 --- a/passes/techmap/Makefile.inc +++ b/passes/techmap/Makefile.inc @@ -31,6 +31,7 @@ OBJS += passes/techmap/dffinit.o OBJS += passes/techmap/pmuxtree.o OBJS += passes/techmap/bmuxmap.o OBJS += passes/techmap/demuxmap.o +OBJS += passes/techmap/bwmuxmap.o OBJS += passes/techmap/muxcover.o OBJS += passes/techmap/aigmap.o OBJS += passes/techmap/tribuf.o diff --git a/passes/techmap/bwmuxmap.cc b/passes/techmap/bwmuxmap.cc new file mode 100644 index 000000000..7fe1cded7 --- /dev/null +++ b/passes/techmap/bwmuxmap.cc @@ -0,0 +1,70 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2022 Jannis Harder <jix@yosyshq.com> <me@jix.one> + * + * 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" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct BwmuxmapPass : public Pass { + BwmuxmapPass() : Pass("bwmuxmap", "replace $bwmux cells with equivalent logic") {} + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" bwmxumap [options] [selection]\n"); + log("\n"); + log("This pass replaces $bwmux cells with equivalent logic\n"); + log("\n"); + } + void execute(std::vector<std::string> args, RTLIL::Design *design) override + { + log_header(design, "Executing BWMUXMAP pass.\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + // if (args[argidx] == "-arg") { + // continue; + // } + break; + } + + extra_args(args, argidx, design); + + for (auto module : design->selected_modules()) + for (auto cell : module->selected_cells()) + { + if (cell->type != ID($bwmux)) + continue; + auto &sig_y = cell->getPort(ID::Y); + auto &sig_a = cell->getPort(ID::A); + auto &sig_b = cell->getPort(ID::B); + auto &sig_s = cell->getPort(ID::S); + + auto not_s = module->Not(NEW_ID, sig_s); + auto masked_b = module->And(NEW_ID, sig_s, sig_b); + auto masked_a = module->And(NEW_ID, not_s, sig_a); + module->addOr(NEW_ID, masked_a, masked_b, sig_y); + + module->remove(cell); + } + } +} BwmuxmapPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/techmap/simplemap.cc b/passes/techmap/simplemap.cc index f75b82919..11692b715 100644 --- a/passes/techmap/simplemap.cc +++ b/passes/techmap/simplemap.cc @@ -58,28 +58,17 @@ void simplemap_bitop(RTLIL::Module *module, RTLIL::Cell *cell) RTLIL::SigSpec sig_b = cell->getPort(ID::B); RTLIL::SigSpec sig_y = cell->getPort(ID::Y); - sig_a.extend_u0(GetSize(sig_y), cell->parameters.at(ID::A_SIGNED).as_bool()); - sig_b.extend_u0(GetSize(sig_y), cell->parameters.at(ID::B_SIGNED).as_bool()); - - if (cell->type == ID($xnor)) - { - RTLIL::SigSpec sig_t = module->addWire(NEW_ID, GetSize(sig_y)); - - for (int i = 0; i < GetSize(sig_y); i++) { - RTLIL::Cell *gate = module->addCell(NEW_ID, ID($_NOT_)); - gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); - gate->setPort(ID::A, sig_t[i]); - gate->setPort(ID::Y, sig_y[i]); - } - - sig_y = sig_t; + if (cell->type != ID($bweqx)) { + sig_a.extend_u0(GetSize(sig_y), cell->parameters.at(ID::A_SIGNED).as_bool()); + sig_b.extend_u0(GetSize(sig_y), cell->parameters.at(ID::B_SIGNED).as_bool()); } IdString gate_type; - if (cell->type == ID($and)) gate_type = ID($_AND_); - if (cell->type == ID($or)) gate_type = ID($_OR_); - if (cell->type == ID($xor)) gate_type = ID($_XOR_); - if (cell->type == ID($xnor)) gate_type = ID($_XOR_); + if (cell->type == ID($and)) gate_type = ID($_AND_); + if (cell->type == ID($or)) gate_type = ID($_OR_); + if (cell->type == ID($xor)) gate_type = ID($_XOR_); + if (cell->type == ID($xnor)) gate_type = ID($_XNOR_); + if (cell->type == ID($bweqx)) gate_type = ID($_XNOR_); log_assert(!gate_type.empty()); for (int i = 0; i < GetSize(sig_y); i++) { @@ -284,6 +273,23 @@ void simplemap_mux(RTLIL::Module *module, RTLIL::Cell *cell) } } +void simplemap_bwmux(RTLIL::Module *module, RTLIL::Cell *cell) +{ + RTLIL::SigSpec sig_a = cell->getPort(ID::A); + RTLIL::SigSpec sig_b = cell->getPort(ID::B); + RTLIL::SigSpec sig_s = cell->getPort(ID::S); + RTLIL::SigSpec sig_y = cell->getPort(ID::Y); + + for (int i = 0; i < GetSize(sig_y); i++) { + RTLIL::Cell *gate = module->addCell(NEW_ID, ID($_MUX_)); + gate->add_strpool_attribute(ID::src, cell->get_strpool_attribute(ID::src)); + gate->setPort(ID::A, sig_a[i]); + gate->setPort(ID::B, sig_b[i]); + gate->setPort(ID::S, sig_s[i]); + gate->setPort(ID::Y, sig_y[i]); + } +} + void simplemap_tribuf(RTLIL::Module *module, RTLIL::Cell *cell) { RTLIL::SigSpec sig_a = cell->getPort(ID::A); @@ -409,6 +415,7 @@ void simplemap_get_mappers(dict<IdString, void(*)(RTLIL::Module*, RTLIL::Cell*)> mappers[ID($or)] = simplemap_bitop; mappers[ID($xor)] = simplemap_bitop; mappers[ID($xnor)] = simplemap_bitop; + mappers[ID($bweqx)] = simplemap_bitop; mappers[ID($reduce_and)] = simplemap_reduce; mappers[ID($reduce_or)] = simplemap_reduce; mappers[ID($reduce_xor)] = simplemap_reduce; @@ -422,6 +429,7 @@ void simplemap_get_mappers(dict<IdString, void(*)(RTLIL::Module*, RTLIL::Cell*)> mappers[ID($ne)] = simplemap_eqne; mappers[ID($nex)] = simplemap_eqne; mappers[ID($mux)] = simplemap_mux; + mappers[ID($bwmux)] = simplemap_bwmux; mappers[ID($tribuf)] = simplemap_tribuf; mappers[ID($bmux)] = simplemap_bmux; mappers[ID($lut)] = simplemap_lut; diff --git a/passes/techmap/simplemap.h b/passes/techmap/simplemap.h index c7654f68c..30cc1ccfe 100644 --- a/passes/techmap/simplemap.h +++ b/passes/techmap/simplemap.h @@ -31,6 +31,7 @@ extern void simplemap_reduce(RTLIL::Module *module, RTLIL::Cell *cell); extern void simplemap_lognot(RTLIL::Module *module, RTLIL::Cell *cell); extern void simplemap_logbin(RTLIL::Module *module, RTLIL::Cell *cell); extern void simplemap_mux(RTLIL::Module *module, RTLIL::Cell *cell); +extern void simplemap_bwmux(RTLIL::Module *module, RTLIL::Cell *cell); extern void simplemap_lut(RTLIL::Module *module, RTLIL::Cell *cell); extern void simplemap_slice(RTLIL::Module *module, RTLIL::Cell *cell); extern void simplemap_concat(RTLIL::Module *module, RTLIL::Cell *cell); diff --git a/techlibs/common/simlib.v b/techlibs/common/simlib.v index e64697efb..9cb68e725 100644 --- a/techlibs/common/simlib.v +++ b/techlibs/common/simlib.v @@ -1300,11 +1300,11 @@ wire [WIDTH-1:0] bm0_out, bm1_out; generate if (S_WIDTH > 1) begin:muxlogic - \$bmux #(.WIDTH(WIDTH), .S_WIDTH(S_WIDTH-1)) bm0 (.A(A), .S(S[S_WIDTH-2:0]), .Y(bm0_out)); + \$bmux #(.WIDTH(WIDTH), .S_WIDTH(S_WIDTH-1)) bm0 (.A(A[(WIDTH << (S_WIDTH - 1))-1:0]), .S(S[S_WIDTH-2:0]), .Y(bm0_out)); \$bmux #(.WIDTH(WIDTH), .S_WIDTH(S_WIDTH-1)) bm1 (.A(A[(WIDTH << S_WIDTH)-1:WIDTH << (S_WIDTH - 1)]), .S(S[S_WIDTH-2:0]), .Y(bm1_out)); assign Y = S[S_WIDTH-1] ? bm1_out : bm0_out; end else if (S_WIDTH == 1) begin:simple - assign Y = S ? A[1] : A[0]; + assign Y = S ? A[2*WIDTH-1:WIDTH] : A[WIDTH-1:0]; end else begin:passthru assign Y = A; end @@ -1331,10 +1331,17 @@ always @* begin Y = A; found_active_sel_bit = 0; for (i = 0; i < S_WIDTH; i = i+1) - if (S[i]) begin - Y = found_active_sel_bit ? 'bx : B >> (WIDTH*i); - found_active_sel_bit = 1; - end + case (S[i]) + 1'b1: begin + Y = found_active_sel_bit ? 'bx : B >> (WIDTH*i); + found_active_sel_bit = 1; + end + 1'b0: ; + 1'bx: begin + Y = 'bx; + found_active_sel_bit = 'bx; + end + endcase end endmodule @@ -1370,7 +1377,7 @@ parameter LUT = 0; input [WIDTH-1:0] A; output Y; -\$bmux #(.WIDTH(1), .S_WIDTH(WIDTH)) mux(.A(LUT), .S(A), .Y(Y)); +\$bmux #(.WIDTH(1), .S_WIDTH(WIDTH)) mux(.A(LUT[(1<<WIDTH)-1:0]), .S(A), .Y(Y)); endmodule @@ -1594,6 +1601,43 @@ endmodule // -------------------------------------------------------- +module \$bweqx (A, B, Y); + +parameter WIDTH = 0; + +input [WIDTH-1:0] A, B; +output [WIDTH-1:0] Y; + +genvar i; +generate + for (i = 0; i < WIDTH; i = i + 1) begin:slices + assign Y[i] = A[i] === B[i]; + end +endgenerate + +endmodule + +// -------------------------------------------------------- + +module \$bwmux (A, B, S, Y); + +parameter WIDTH = 0; + +input [WIDTH-1:0] A, B; +input [WIDTH-1:0] S; +output [WIDTH-1:0] Y; + +genvar i; +generate + for (i = 0; i < WIDTH; i = i + 1) begin:slices + assign Y[i] = S[i] ? B[i] : A[i]; + end +endgenerate + +endmodule + +// -------------------------------------------------------- + module \$assert (A, EN); input A, EN; @@ -1693,6 +1737,9 @@ endmodule // -------------------------------------------------------- `ifdef SIMLIB_FF +`ifndef SIMLIB_GLOBAL_CLOCK +`define SIMLIB_GLOBAL_CLOCK $global_clk +`endif module \$anyinit (D, Q); parameter WIDTH = 0; @@ -1702,7 +1749,7 @@ output reg [WIDTH-1:0] Q; initial Q <= 'bx; -always @($global_clk) begin +always @(`SIMLIB_GLOBAL_CLOCK) begin Q <= D; end @@ -1783,6 +1830,9 @@ endmodule `endif // -------------------------------------------------------- `ifdef SIMLIB_FF +`ifndef SIMLIB_GLOBAL_CLOCK +`define SIMLIB_GLOBAL_CLOCK $global_clk +`endif module \$ff (D, Q); @@ -1791,7 +1841,7 @@ parameter WIDTH = 0; input [WIDTH-1:0] D; output reg [WIDTH-1:0] Q; -always @($global_clk) begin +always @(`SIMLIB_GLOBAL_CLOCK) begin Q <= D; end diff --git a/techlibs/common/techmap.v b/techlibs/common/techmap.v index 91d385b80..7fb8173b0 100644 --- a/techlibs/common/techmap.v +++ b/techlibs/common/techmap.v @@ -59,7 +59,7 @@ module _90_simplemap_compare_ops; endmodule (* techmap_simplemap *) -(* techmap_celltype = "$pos $slice $concat $mux $tribuf $bmux" *) +(* techmap_celltype = "$pos $slice $concat $mux $tribuf $bmux $bwmux $bweqx" *) module _90_simplemap_various; endmodule diff --git a/tests/opt/opt_expr_xnor.ys b/tests/opt/opt_expr_xnor.ys index f8ef0d352..225df7076 100644 --- a/tests/opt/opt_expr_xnor.ys +++ b/tests/opt/opt_expr_xnor.ys @@ -32,7 +32,7 @@ select -assert-count 1 c:* cd fine_keepdc simplemap opt_expr -keepdc -select -assert-count 1 t:$_XOR_ +select -assert-count 1 t:$_XNOR_ cd miter -equiv -flatten -make_assert -make_outputs gold coarse_keepdc miter3 diff --git a/tests/opt/opt_expr_xor.ys b/tests/opt/opt_expr_xor.ys index 8874f2775..9edec35d6 100644 --- a/tests/opt/opt_expr_xor.ys +++ b/tests/opt/opt_expr_xor.ys @@ -22,9 +22,8 @@ simplemap equiv_opt -assert opt_expr design -load postopt select -assert-none t:$_XOR_ -select -assert-none t:$_XNOR_ # NB: simplemap does $xnor -> $_XOR_+$_NOT_ -select -assert-count 3 t:$_NOT_ - +select -assert-none t:$_XNOR_ +select -assert-count 2 t:$_NOT_ design -reset read_verilog -icells <<EOT @@ -36,7 +35,7 @@ EOT select -assert-count 2 t:$_XNOR_ equiv_opt -assert opt_expr design -load postopt -select -assert-none t:$_XNOR_ # NB: simplemap does $xnor -> $_XOR_+$_NOT_ +select -assert-none t:$_XNOR_ select -assert-count 1 t:$_NOT_ diff --git a/tests/xprop/.gitignore b/tests/xprop/.gitignore new file mode 100644 index 000000000..ff0b65b54 --- /dev/null +++ b/tests/xprop/.gitignore @@ -0,0 +1,2 @@ +/xprop_* +/run-test.mk diff --git a/tests/xprop/generate.py b/tests/xprop/generate.py new file mode 100644 index 000000000..eef8dc36e --- /dev/null +++ b/tests/xprop/generate.py @@ -0,0 +1,278 @@ +import os +import re +import sys +import random +import argparse + +parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) +parser.add_argument('-S', '--seed', type=int, help='seed for PRNG') +parser.add_argument('-c', '--count', type=int, default=32, help='number of random patterns to test') +parser.add_argument('-f', '--filter', default='', help='regular expression to filter tests to generate') +args = parser.parse_args() + +if args.seed is None: + args.seed = random.randrange(1 << 32) + +print(f"xprop PRNG seed: {args.seed}") + +makefile = open("run-test.mk", "w") + +def add_test(name, src, seq=False): + if not re.search(args.filter, name): + return + workdir = f"xprop_{name}" + + os.makedirs(workdir, exist_ok=True) + with open(f"{workdir}/uut.v", "w") as uut: + print(src, file=uut) + print(f"all: {workdir}", file=makefile) + print(f".PHONY: {workdir}", file=makefile) + print(f"{workdir}:", file=makefile) + seq_arg = " -s" if seq else "" + print( + f"\t@cd {workdir} && python3 -u ../test.py -S {args.seed} -c {args.count}{seq_arg} > test.log 2>&1 || echo {workdir}: failed > status\n" + f"\t@cat {workdir}/status\n" + # f"\t@grep '^.*: ok' {workdir}/status\n" + , + file=makefile, + ) + +def cell_test(name, cell, inputs, outputs, params, initial={}, defclock=False, seq=False): + ports = [] + port_conns = [] + for inport, width in inputs.items(): + ports.append(f"input [{width-1}:0] {inport}") + if defclock and inport in ["C", "CLK"]: + port_conns.append(f".{inport}({inport} !== 0)") + else: + port_conns.append(f".{inport}({inport})") + for outport, width in outputs.items(): + reg = " reg" if outport in initial else "" + ports.append(f"output{reg} [{width-1}:0] {outport}") + port_conns.append(f".{outport}({outport})") + param_defs = [] + for param, value in params.items(): + param_defs.append(f".{param}({value})") + initials = [] + # for port, value in initial.items(): + # initials.append(f"initial {port} = {value};\n") + add_test(name, + f"module uut({', '.join(ports)});\n" + f"\\${cell} #({', '.join(param_defs)}) cell ({', '.join(port_conns)});\n" + f"{''.join(initials)}" + "endmodule", + seq=seq, + ) + +def unary_test(cell, width, signed, out_width): + add_test( + f"{cell}_{width}{'us'[signed]}_{out_width}", + f"module uut(input [{width-1}:0] A, output [{out_width}-1:0] Y);\n" + f"\\${cell} #(.A_WIDTH({width}), .A_SIGNED({int(signed)}), .Y_WIDTH({out_width}))" + " cell (.A(A), .Y(Y));\n" + "endmodule", + ) + +def binary_test(cell, a_width, b_width, signed, out_width): + add_test( + f"{cell}_{a_width}{'us'[signed]}{b_width}_{out_width}", + f"module uut(input [{a_width-1}:0] A, input [{b_width-1}:0] B, output [{out_width}-1:0] Y);\n" + f"\\${cell} #(.A_WIDTH({a_width}), .A_SIGNED({int(signed)}), .B_WIDTH({b_width}), .B_SIGNED({int(signed)}), .Y_WIDTH({out_width}))" + " cell (.A(A), .B(B), .Y(Y));\n" + "endmodule", + ) + +def shift_test(cell, a_width, b_width, a_signed, b_signed, out_width): + add_test( + f"{cell}_{a_width}{'us'[a_signed]}{b_width}{'us'[b_signed]}_{out_width}", + f"module uut(input [{a_width-1}:0] A, input [{b_width-1}:0] B, output [{out_width}-1:0] Y);\n" + f"\\${cell} #(.A_WIDTH({a_width}), .A_SIGNED({int(a_signed)}), .B_WIDTH({b_width}), .B_SIGNED({int(b_signed)}), .Y_WIDTH({out_width}))" + " cell (.A(A), .B(B), .Y(Y));\n" + "endmodule", + ) + +def mux_test(width): + cell_test(f"mux_{width}", 'mux', {"A": width, "B": width, "S": 1}, {"Y": width}, {"WIDTH": width}) + +def bmux_test(width, s_width): + cell_test(f"bmux_{width}_{s_width}", 'bmux', {"A": width << s_width, "S": s_width}, {"Y": width}, {"WIDTH": width, "S_WIDTH": s_width}) + +def demux_test(width, s_width): + cell_test(f"demux_{width}_{s_width}", 'demux', {"A": width, "S": s_width}, {"Y": width << s_width}, {"WIDTH": width, "S_WIDTH": s_width}) + +def pmux_test(width, s_width): + cell_test(f"pmux_{width}_{s_width}", 'pmux', {"A": width, "B": width * s_width, "S": s_width}, {"Y": width}, {"WIDTH": width, "S_WIDTH": s_width}) + +def bwmux_test(width): + cell_test(f"bwmux_{width}", 'bwmux', {"A": width, "B": width, "S": width}, {"Y": width}, {"WIDTH": width}) + +def bweqx_test(width): + cell_test(f"bweqx_{width}", 'bweqx', {"A": width, "B": width}, {"Y": width}, {"WIDTH": width}) + +def ff_test(width): + cell_test(f"ff_{width}", 'ff', {"D": width}, {"Q": width}, {"WIDTH": width}, seq=True) + +def dff_test(width, pol, defclock): + cell_test(f"dff_{width}{'np'[pol]}{'xd'[defclock]}", 'dff', {"CLK": 1, "D": width}, {"Q": width}, {"WIDTH": width, "CLK_POLARITY": int(pol)}, defclock=defclock, seq=True) + +def dffe_test(width, pol, enpol, defclock): + cell_test(f"dffe_{width}{'np'[pol]}{'np'[enpol]}{'xd'[defclock]}", 'dffe', {"CLK": 1, "EN": 1, "D": width}, {"Q": width}, {"WIDTH": width, "CLK_POLARITY": int(pol), "EN_POLARITY": int(enpol)}, defclock=defclock, seq=True) + + +print(".PHONY: all", file=makefile) +print("all:\n\t@echo done\n", file=makefile) + +for cell in ["not", "pos", "neg"]: + unary_test(cell, 1, False, 1) + unary_test(cell, 3, False, 3) + unary_test(cell, 3, True, 3) + unary_test(cell, 3, True, 1) + unary_test(cell, 3, False, 5) + unary_test(cell, 3, True, 5) + +for cell in ["and", "or", "xor", "xnor"]: + binary_test(cell, 1, 1, False, 1) + binary_test(cell, 1, 1, True, 2) + binary_test(cell, 2, 2, False, 2) + binary_test(cell, 2, 2, False, 1) + binary_test(cell, 2, 1, False, 2) + binary_test(cell, 2, 1, False, 1) + +# [, "pow"] are not implemented yet +for cell in ["add", "sub", "mul", "div", "mod", "divfloor", "modfloor"]: + binary_test(cell, 1, 1, False, 1) + binary_test(cell, 1, 1, False, 2) + binary_test(cell, 3, 3, False, 1) + binary_test(cell, 3, 3, False, 3) + binary_test(cell, 3, 3, False, 6) + binary_test(cell, 3, 3, True, 1) + binary_test(cell, 3, 3, True, 3) + binary_test(cell, 3, 3, True, 6) + binary_test(cell, 5, 3, False, 3) + binary_test(cell, 5, 3, True, 3) + +for cell in ["lt", "le", "eq", "ne", "eqx", "nex", "ge", "gt"]: + binary_test(cell, 1, 1, False, 1) + binary_test(cell, 1, 1, False, 2) + binary_test(cell, 3, 3, False, 1) + binary_test(cell, 3, 3, False, 2) + binary_test(cell, 3, 3, True, 1) + binary_test(cell, 3, 3, True, 2) + binary_test(cell, 5, 3, False, 1) + binary_test(cell, 5, 3, True, 1) + binary_test(cell, 5, 3, False, 2) + binary_test(cell, 5, 3, True, 2) + +for cell in ["reduce_and", "reduce_or", "reduce_xor", "reduce_xnor"]: + unary_test(cell, 1, False, 1) + unary_test(cell, 3, False, 1) + unary_test(cell, 3, True, 1) + unary_test(cell, 3, False, 3) + unary_test(cell, 3, True, 3) + +for cell in ["reduce_bool", "logic_not"]: + unary_test(cell, 1, False, 1) + unary_test(cell, 3, False, 3) + unary_test(cell, 3, True, 3) + unary_test(cell, 3, True, 1) + +for cell in ["logic_and", "logic_or"]: + binary_test(cell, 1, 1, False, 1) + binary_test(cell, 3, 3, False, 3) + binary_test(cell, 3, 3, True, 3) + binary_test(cell, 3, 3, True, 1) + +for cell in ["shl", "shr", "sshl", "sshr", "shift"]: + shift_test(cell, 2, 1, False, False, 2) + shift_test(cell, 2, 1, True, False, 2) + shift_test(cell, 2, 1, False, False, 4) + shift_test(cell, 2, 1, True, False, 4) + shift_test(cell, 4, 2, False, False, 4) + shift_test(cell, 4, 2, True, False, 4) + shift_test(cell, 4, 2, False, False, 8) + shift_test(cell, 4, 2, True, False, 8) + shift_test(cell, 4, 3, False, False, 3) + shift_test(cell, 4, 3, True, False, 3) + +for cell in ["shift"]: + shift_test(cell, 2, 1, False, True, 2) + shift_test(cell, 2, 1, True, True, 2) + shift_test(cell, 2, 1, False, True, 4) + shift_test(cell, 2, 1, True, True, 4) + shift_test(cell, 4, 2, False, True, 4) + shift_test(cell, 4, 2, True, True, 4) + shift_test(cell, 4, 2, False, True, 8) + shift_test(cell, 4, 2, True, True, 8) + shift_test(cell, 4, 3, False, True, 3) + shift_test(cell, 4, 3, True, True, 3) + +for cell in ["shiftx"]: + shift_test(cell, 2, 1, False, True, 2) + shift_test(cell, 2, 1, False, True, 4) + shift_test(cell, 4, 2, False, True, 4) + shift_test(cell, 4, 2, False, True, 8) + shift_test(cell, 4, 3, False, True, 3) + +mux_test(1) +mux_test(3) + +bmux_test(1, 2) +bmux_test(2, 2) +bmux_test(3, 1) + +demux_test(1, 2) +demux_test(2, 2) +demux_test(3, 1) + +pmux_test(1, 4) +pmux_test(2, 2) +pmux_test(3, 1) +pmux_test(4, 4) + +bwmux_test(1) +bwmux_test(3) + +bweqx_test(1) +bweqx_test(3) + +ff_test(1) +ff_test(3) + +dff_test(1, True, True) +dff_test(1, False, True) +dff_test(3, True, True) +dff_test(3, False, True) + +# dff_test(1, True, False) # TODO support x clocks +# dff_test(1, False, False) # TODO support x clocks +# dff_test(3, True, False) # TODO support x clocks +# dff_test(3, False, False) # TODO support x clocks + +dffe_test(1, True, False, True) +dffe_test(1, False, False, True) +dffe_test(3, True, False, True) +dffe_test(3, False, False, True) +dffe_test(1, True, True, True) +dffe_test(1, False, True, True) +dffe_test(3, True, True, True) +dffe_test(3, False, True, True) + + + +# TODO "shift", "shiftx" + +# TODO "fa", "lcu", "alu", "macc", "lut", "sop" + +# TODO "slice", "concat" + +# TODO "tribuf", "specify2", "specify3", "specrule" + +# TODO "assert", "assume", "live", "fair", "cover", "initstate", "anyconst", "anyseq", "anyinit", "allconst", "allseq", "equiv", + +# TODO "bweqx", "bwmux" + +# TODO "sr", "ff", "dff", "dffe", "dffsr", "sffsre", "adff", "aldff", "sdff", "adffe", "aldffe", "sdffe", "sdffce", "dlatch", "adlatch", "dlatchsr" + +# TODO "fsm" + +# TODO "memrd", "memrd_v2", "memwr", "memwr_v2", "meminit", "meminit_v2", "mem", "mem_v2" diff --git a/tests/xprop/run-test.sh b/tests/xprop/run-test.sh new file mode 100755 index 000000000..1fc7e10b6 --- /dev/null +++ b/tests/xprop/run-test.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -e + +python3 generate.py $@ +make -f run-test.mk diff --git a/tests/xprop/test.py b/tests/xprop/test.py new file mode 100644 index 000000000..84ad0a1f4 --- /dev/null +++ b/tests/xprop/test.py @@ -0,0 +1,516 @@ +import os +import re +import subprocess +import itertools +import random +import argparse +from pathlib import Path + +parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) +parser.add_argument('-S', '--seed', type=int, help='seed for PRNG') +parser.add_argument('-c', '--count', type=int, default=32, help='number of random patterns to test') +parser.add_argument('-s', '--seq', action='store_true', help='run a sequential test') +parser.add_argument('steps', nargs='*', help='steps to run') +args = parser.parse_args() + +if args.seed is None: + args.seed = random.randrange(1 << 32) + +print(f"PRNG seed: {args.seed}") +random.seed(args.seed) + +steps = args.steps + +all_outputs = [ + "sim", + "sim_xprop", + "vsim_expr", + "vsim_expr_xprop", + "vsim_noexpr", + "vsim_noexpr_xprop", + "sat", + "sat_xprop", +] + +if not args.seq: + all_outputs += ["opt_expr", "opt_expr_xprop"] + +if not steps: + steps = ["clean", "prepare", "verify", *all_outputs, "compare"] + +if "clean" in steps: + for output in all_outputs: + try: + os.unlink(f"{output}.out") + except FileNotFoundError: + pass + + +def yosys(command): + subprocess.check_call(["yosys", "-Qp", command]) + +def remove(file): + try: + os.unlink(file) + except FileNotFoundError: + pass + + +def vcdextract(signals, on_change, file, output, limit=None): + scope = [] + ids = {} + prefix = [] + + for line in file: + line = prefix + line.split() + if line[-1] != "$end": + prefix = line + continue + prefix = [] + + if not line: + continue + if line[0] == "$scope": + scope.append(line[2].lstrip("\\")) + elif line[0] == "$upscope": + scope.pop() + elif line[0] == "$var": + ids[".".join(scope + [line[4].lstrip("\\")])] = line[3] + elif line[0] == "$enddefinitions": + break + elif line[0] in ["$version", "$date", "$comment"]: + continue + else: + raise RuntimeError(f"unexpected input in vcd {line}") + + dump_pos = {} + + for i, sig in enumerate(signals + on_change): + dump_pos[ids[sig]] = i + + values = [None] * len(signals + on_change) + + last_values = [] + + counter = 0 + + for line in file: + if line.startswith("#"): + if None in values: + continue + + if values != last_values: + last_values = list(values) + if limit is None or counter < limit: + print(*values[:len(signals)], file=output) + counter += 1 + continue + + if line.startswith("b"): + value, id = line[1:].split() + else: + value, id = line[:1], line[1:] + + pos = dump_pos.get(id) + if pos is None: + continue + + values[pos] = value + + if values != last_values: + if limit is None or counter < limit: + print(*values[:len(signals)], file=output) + + +share = Path(__file__).parent / ".." / ".." / "share" + +simlibs = [str(share / "simlib.v"), str(share / "simcells.v")] + +if "prepare" in steps: + yosys( + """ + read_verilog -icells uut.v + hierarchy -top uut; proc -noopt + write_rtlil uut.il + dump -o ports.list uut/x:* + """ + ) + +inputs = [] +outputs = [] + +with open("ports.list") as ports_file: + for line in ports_file: + line = line.split() + if not line or line[0] != "wire": + continue + line = line[1:] + width = 1 + if line[0] == "width": + width = int(line[1]) + line = line[2:] + direction, seq, name = line + assert name.startswith("\\") + name = name[1:] + seq = int(seq) + + if direction == "input": + inputs.append((seq, name, width)) + else: + outputs.append((seq, name, width)) + +inputs.sort() +outputs.sort() + +input_width = sum(width for seq, name, width in inputs) +output_width = sum(width for seq, name, width in outputs) + +if "prepare" in steps: + if args.seq: + patterns = [''.join(random.choices('01x', k=input_width)) for i in range(args.count)] + else: + if 3**input_width <= args.count * 2: + patterns = ["".join(bits) for bits in itertools.product(*["01x"] * input_width)] + else: + patterns = set() + + for bit in '01x': + patterns.add(bit * input_width) + + for bits in itertools.combinations('01x', 2): + for bit1, bit2 in itertools.permutations(bits): + for i in range(input_width): + pattern = [bit1] * input_width + pattern[i] = bit2 + patterns.add(''.join(pattern)) + + for i, j in itertools.combinations(range(input_width), 2): + pattern = [bit1] * input_width + pattern[i] = bit2 + pattern[j] = bit2 + patterns.add(''.join(pattern)) + + for bit1, bit2, bit3 in itertools.permutations('01x'): + for i, j in itertools.combinations(range(input_width), 2): + pattern = [bit1] * input_width + pattern[i] = bit2 + pattern[j] = bit3 + patterns.add(''.join(pattern)) + + if len(patterns) > args.count // 2: + patterns = sorted(patterns) + random.shuffle(patterns) + patterns = set(patterns[:args.count // 2]) + + while len(patterns) < args.count: + pattern = ''.join(random.choices('01x', k=input_width)) + patterns.add(pattern) + + patterns = sorted(patterns) + with open("patterns.list", "w") as f: + for pattern in patterns: + print(pattern, file=f) +else: + with open("patterns.list") as f: + patterns = [pattern.strip() for pattern in f] + + +if "prepare" in steps: + with open("wrapper.v", "w") as wrapper_file: + print( + "module wrapper(" + f"input [{input_width-1}:0] A, " + f"output [{output_width-1}:0] Y);", + file=wrapper_file, + ) + + connections = [] + pos = 0 + for seq, name, width in inputs: + connections.append(f".{name}(A[{pos+width-1}:{pos}])") + pos += width + pos = 0 + for seq, name, width in outputs: + connections.append(f".{name}(Y[{pos+width-1}:{pos}])") + pos += width + + print(f"uut uut({', '.join(connections)});", file=wrapper_file) + print("endmodule", file=wrapper_file) + + yosys( + """ + read_rtlil uut.il + read_verilog wrapper.v + hierarchy -top wrapper; proc -noopt + flatten; clean; + rename wrapper uut + write_rtlil wrapped.il + """ + ) + + try: + yosys( + """ + read_rtlil wrapped.il + dffunmap + xprop -required + check -assert + write_rtlil wrapped_xprop.il + """ + ) + except subprocess.CalledProcessError: + remove("wrapped_xprop.il") + + with open("verilog_sim_tb.v", "w") as tb_file: + print("module top();", file=tb_file) + print(f"reg gclk;", file=tb_file) + print(f"reg [{input_width-1}:0] A;", file=tb_file) + print(f"wire [{output_width-1}:0] Y;", file=tb_file) + print(f"uut uut(.A(A), .Y(Y));", file=tb_file) + print("initial begin", file=tb_file) + + for pattern in patterns: + print( + f' gclk = 1; #0; A[0] = 1\'b{pattern[-1]}; #0; A = {input_width}\'b{pattern}; #5; gclk = 0; $display("%b %b", A, Y); #5', + file=tb_file, + ) + + print(" $finish;", file=tb_file) + print("end", file=tb_file) + print("endmodule", file=tb_file) + + with open("synth_tb.v", "w") as tb_file: + print("module top(input clk);", file=tb_file) + print(f"reg [{len(patterns).bit_length() - 1}:0] counter = 0;", file=tb_file) + print(f"reg [{input_width-1}:0] A;", file=tb_file) + print(f"(* gclk *) reg gclk;", file=tb_file) + print(f"wire [{output_width-1}:0] Y;", file=tb_file) + print(f"uut uut(.A(A), .Y(Y));", file=tb_file) + print(f"always @(posedge gclk) counter <= counter + 1;", file=tb_file) + print(f"always @* case (counter)", file=tb_file) + for i, pattern in enumerate(patterns): + print(f" {i:7} : A = {input_width}'b{pattern};", file=tb_file) + print(f" default : A = {input_width}'b{'x' * input_width};", file=tb_file) + print(f"endcase", file=tb_file) + print("endmodule", file=tb_file) + + with open("const_tb.v", "w") as tb_file: + print("module top();", file=tb_file) + for i, pattern in enumerate(patterns): + print( + f"(* keep *) wire [{output_width-1}:0] Y_{i}; " + f"uut uut_{i}(.A({input_width}'b{pattern}), .Y(Y_{i}));", + file=tb_file, + ) + print("endmodule", file=tb_file) + +if "verify" in steps: + try: + seq_args = " -tempinduct -set-init-undef" if args.seq else "" + yosys( + f""" + read_rtlil wrapped.il + copy uut gold + rename uut gate + design -push-copy + dffunmap + xprop -formal -split-inputs -required -debug-asserts gate + clk2fflogic + sat{seq_args} -enable_undef -set-def-inputs -prove-asserts -verify -show-all gate + design -pop + + dffunmap + xprop -required -assume-encoding gate + miter -equiv -make_assert -flatten gold gate miter + clk2fflogic + sat{seq_args} -enable_undef -set-assumes -prove-asserts -verify -show-all miter + """ + ) + with open("verified", "w") as f: + pass + except subprocess.CalledProcessError: + remove("verified") + + +for mode in ["", "_xprop"]: + if not Path(f"wrapped{mode}.il").is_file(): + for expr in [f"expr{mode}", f"noexpr{mode}"]: + remove(f"vsim_{expr}.out") + continue + + if "prepare" in steps: + yosys( + f""" + read_rtlil wrapped{mode}.il + chformal -remove + dffunmap + write_verilog -noparallelcase vsim_expr{mode}.v + write_verilog -noexpr vsim_noexpr{mode}.v + """ + ) + + for expr in [f"expr{mode}", f"noexpr{mode}"]: + if f"vsim_{expr}" in steps: + subprocess.check_call( + [ + "iverilog", + "-DSIMLIB_FF", + "-DSIMLIB_GLOBAL_CLOCK=top.gclk", + f"-DDUMPFILE=\"vsim_{expr}.vcd\"", + "verilog_sim_tb.v", + f"vsim_{expr}.v", + *simlibs, + "-o", + f"vsim_{expr}", + ] + ) + with open(f"vsim_{expr}.out", "w") as f: + subprocess.check_call([f"./vsim_{expr}"], stdout=f) + +for mode in ["", "_xprop"]: + if f"sim{mode}" not in steps: + continue + if not Path(f"wrapped{mode}.il").is_file(): + remove(f"sim{mode}.out") + continue + yosys( + f""" + read_verilog synth_tb.v + read_rtlil wrapped{mode}.il + hierarchy -top top; proc -noopt + dffunmap + sim -clock clk -n {len(patterns) // 2} -vcd sim{mode}.vcd top + """ + ) + + with open(f"sim{mode}.vcd") as fin, open(f"sim{mode}.out", "w") as fout: + vcdextract(["top.A", "top.Y"], ["top.counter"], fin, fout, len(patterns)) + +for mode in ["", "_xprop"]: + if f"sat{mode}" not in steps: + continue + if not Path(f"wrapped{mode}.il").is_file(): + remove(f"sat{mode}.out") + continue + chunk_size = len(patterns) if args.seq else 32 + last_progress = 0 + with open(f"sat{mode}.ys", "w") as ys: + for chunk_start in range(0, len(patterns), chunk_size): + chunk = patterns[chunk_start : chunk_start + chunk_size] + progress = (10 * chunk_start) // len(patterns) + if progress > last_progress: + print(f"log sat {progress}0%", file=ys) + last_progress = progress + + append = "-a" if chunk_start else "-o" + print( + end=f"tee -q {append} sat{mode}.log sat -set-init-undef -seq {len(chunk)}" + " -show A -show Y -dump_vcd sat.vcd -enable_undef", + file=ys, + ) + for i, pattern in enumerate(chunk, 1): + ad = pattern.replace("x", "0") + ax = pattern.replace("1", "0").replace("x", "1") + print(end=f" -set-at {i} A {input_width}'b{pattern}", file=ys) + print(file=ys) + print(f"log sat 100%", file=ys) + + try: + yosys( + f""" + read_rtlil wrapped{mode}.il + clk2fflogic + script sat{mode}.ys + """ + ) + except subprocess.CalledProcessError: + remove(f"sat{mode}.out") + else: + sig_re = re.compile( + r" *[0-9]+ +\\([AY]) +(?:--|[0-9]+) +(?:--|[0-9a-f]+) +([01x]+)" + ) + current_input = None + with open(f"sat{mode}.log") as logfile, open(f"sat{mode}.out", "w") as outfile: + for line in logfile: + match = sig_re.match(line) + if match: + if match[1] == "A": + current_input = match[2] + else: + print(current_input, match[2], file=outfile) + +for mode in ["", "_xprop"]: + if f"opt_expr{mode}" not in steps: + continue + if not Path(f"wrapped{mode}.il").is_file(): + remove(f"opt_expr{mode}.out") + continue + yosys( + f""" + read_verilog const_tb.v + read_rtlil wrapped{mode}.il + hierarchy -top top; proc -noopt + flatten + opt_expr -keepdc; clean + dump -o opt_expr{mode}.list */\Y_* + """ + ) + + values = [] + + connect_re = re.compile(r" *connect +\\Y_([0-9]+) +[0-9]+'([01x]+)$") + with open(f"opt_expr{mode}.list") as connections: + for line in connections: + match = connect_re.match(line) + if match: + seq = int(match[1]) + data = match[2] + if len(data) < output_width: + data = data * output_width + values.append((seq, data)) + + values.sort() + + with open(f"opt_expr{mode}.out", "w") as outfile: + for seq, data in values: + print(patterns[seq], data, file=outfile) + + +if "compare" in steps: + groups = {} + missing = [] + + for output in all_outputs: + try: + with open(f"{output}.out") as f: + groups.setdefault(f.read(), []).append(output) + except FileNotFoundError: + missing.append(output) + + verified = Path(f"verified").is_file() + + with open("status", "w") as status: + name = Path('.').absolute().name + + status_list = [] + + if len(groups) > 1: + status_list.append("mismatch") + if not verified: + status_list.append("failed-verify") + if missing: + status_list.append("missing") + if not status_list: + status_list.append("ok") + print(f"{name}: {', '.join(status_list)}", file=status) + + if len(groups) > 1: + print("output differences:", file=status) + for group in groups.values(): + print(" -", *group, file=status) + if missing: + print("missing:", file=status) + print(" -", *missing, file=status) + + with open("status") as status: + print(end=status.read()) |