diff --git a/CHANGELOG b/CHANGELOG index f7774cb9e..c29429295 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -36,6 +36,8 @@ Yosys 0.9 .. Yosys 0.9-dev - Added "ice40_wrapcarry" to encapsulate SB_LUT+SB_CARRY pairs for techmapping - Removed "ice40_unlut" - Improvements in pmgen: slices, choices, define, generate + - Added "xilinx_srl" for Xilinx shift register extraction + - Removed "shregmap -tech xilinx" (superseded by "xilinx_srl") Yosys 0.8 .. Yosys 0.9 ---------------------- diff --git a/passes/pmgen/Makefile.inc b/passes/pmgen/Makefile.inc index 8e0cbdca8..e73a7b1c9 100644 --- a/passes/pmgen/Makefile.inc +++ b/passes/pmgen/Makefile.inc @@ -30,3 +30,9 @@ PEEPOPT_PATTERN += passes/pmgen/peepopt_muldiv.pmg passes/pmgen/peepopt_pm.h: passes/pmgen/pmgen.py $(PEEPOPT_PATTERN) $(P) mkdir -p passes/pmgen && python3 $< -o $@ -p peepopt $(filter-out $<,$^) + +# -------------------------------------- + +OBJS += passes/pmgen/xilinx_srl.o +passes/pmgen/xilinx_srl.o: passes/pmgen/xilinx_srl_pm.h +$(eval $(call add_extra_objs,passes/pmgen/xilinx_srl_pm.h)) diff --git a/passes/pmgen/test_pmgen.cc b/passes/pmgen/test_pmgen.cc index 0ad769dfd..4f3eec935 100644 --- a/passes/pmgen/test_pmgen.cc +++ b/passes/pmgen/test_pmgen.cc @@ -28,6 +28,7 @@ bool did_something; #include "passes/pmgen/test_pmgen_pm.h" #include "passes/pmgen/ice40_dsp_pm.h" +#include "passes/pmgen/xilinx_srl_pm.h" #include "passes/pmgen/peepopt_pm.h" void reduce_chain(test_pmgen_pm &pm) @@ -180,7 +181,7 @@ void generate_pattern(std::function)> run, const while (modcnt < maxmodcnt && submodcnt < maxsubcnt && itercnt++ < 1000) { if (timeout++ > 10000) - log_error("pmgen generator is stuck: 10000 iterations an no matching module generated.\n"); + log_error("pmgen generator is stuck: 10000 iterations with no matching module generated.\n"); pm matcher(mod, mod->cells()); @@ -216,7 +217,7 @@ void generate_pattern(std::function)> run, const run(matcher, [](){}); } - if (submodcnt) + if (submodcnt && maxsubcnt < (1 << 16)) maxsubcnt *= 2; design->remove(mod); @@ -349,13 +350,18 @@ struct TestPmgenPass : public Pass { if (pattern == "ice40_dsp") return GENERATE_PATTERN(ice40_dsp_pm, ice40_dsp); + if (pattern == "xilinx_srl.fixed") + return GENERATE_PATTERN(xilinx_srl_pm, fixed); + if (pattern == "xilinx_srl.variable") + return GENERATE_PATTERN(xilinx_srl_pm, variable); + if (pattern == "peepopt-muldiv") return GENERATE_PATTERN(peepopt_pm, muldiv); if (pattern == "peepopt-shiftmul") return GENERATE_PATTERN(peepopt_pm, shiftmul); - log_cmd_error("Unkown pattern: %s\n", pattern.c_str()); + log_cmd_error("Unknown pattern: %s\n", pattern.c_str()); } void execute(std::vector args, RTLIL::Design *design) YS_OVERRIDE diff --git a/passes/pmgen/xilinx_srl.cc b/passes/pmgen/xilinx_srl.cc new file mode 100644 index 000000000..87fcaa15a --- /dev/null +++ b/passes/pmgen/xilinx_srl.cc @@ -0,0 +1,262 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Clifford Wolf + * (C) 2019 Eddie Hung + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/yosys.h" +#include "kernel/sigtools.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +// for peepopt_pm +bool did_something; + +#include "passes/pmgen/xilinx_srl_pm.h" +#include "passes/pmgen/peepopt_pm.h" + +void run_fixed(xilinx_srl_pm &pm) +{ + auto &st = pm.st_fixed; + auto &ud = pm.ud_fixed; + log("Found fixed chain of length %d (%s):\n", GetSize(ud.longest_chain), log_id(st.first->type)); + + SigSpec initval; + for (auto cell : ud.longest_chain) { + log_debug(" %s\n", log_id(cell)); + if (cell->type.in(ID($_DFF_N_), ID($_DFF_P_), ID($_DFFE_NN_), ID($_DFFE_NP_), ID($_DFFE_PN_), ID($_DFFE_PP_))) { + SigBit Q = cell->getPort(ID(Q)); + log_assert(Q.wire); + auto it = Q.wire->attributes.find(ID(init)); + if (it != Q.wire->attributes.end()) { + auto &i = it->second[Q.offset]; + initval.append(i); + i = State::Sx; + } + else + initval.append(State::Sx); + } + else if (cell->type.in(ID(FDRE), ID(FDRE_1))) { + if (cell->parameters.at(ID(INIT), State::S0).as_bool()) + initval.append(State::S1); + else + initval.append(State::S0); + } + else + log_abort(); + pm.autoremove(cell); + } + + auto first_cell = ud.longest_chain.back(); + auto last_cell = ud.longest_chain.front(); + Cell *c = pm.module->addCell(NEW_ID, ID($__XILINX_SHREG_)); + pm.module->swap_names(c, first_cell); + + if (first_cell->type.in(ID($_DFF_N_), ID($_DFF_P_), ID($_DFFE_NN_), ID($_DFFE_NP_), ID($_DFFE_PN_), ID($_DFFE_PP_), ID(FDRE), ID(FDRE_1))) { + c->setParam(ID(DEPTH), GetSize(ud.longest_chain)); + c->setParam(ID(INIT), initval.as_const()); + if (first_cell->type.in(ID($_DFF_P_), ID($_DFFE_PN_), ID($_DFFE_PP_))) + c->setParam(ID(CLKPOL), 1); + else if (first_cell->type.in(ID($_DFF_N_), ID($DFFE_NN_), ID($_DFFE_NP_), ID(FDRE_1))) + c->setParam(ID(CLKPOL), 0); + else if (first_cell->type.in(ID(FDRE))) { + if (!first_cell->parameters.at(ID(IS_C_INVERTED), State::S0).as_bool()) + c->setParam(ID(CLKPOL), 1); + else + c->setParam(ID(CLKPOL), 0); + } + else + log_abort(); + if (first_cell->type.in(ID($_DFFE_NP_), ID($_DFFE_PP_))) + c->setParam(ID(ENPOL), 1); + else if (first_cell->type.in(ID($_DFFE_NN_), ID($_DFFE_PN_))) + c->setParam(ID(ENPOL), 0); + else + c->setParam(ID(ENPOL), 2); + + c->setPort(ID(C), first_cell->getPort(ID(C))); + c->setPort(ID(D), first_cell->getPort(ID(D))); + c->setPort(ID(Q), last_cell->getPort(ID(Q))); + c->setPort(ID(L), GetSize(ud.longest_chain)-1); + if (first_cell->type.in(ID($_DFF_N_), ID($_DFF_P_))) + c->setPort(ID(E), State::S1); + else if (first_cell->type.in(ID($_DFFE_NN_), ID($_DFFE_NP_), ID($_DFFE_PN_), ID($_DFFE_PP_))) + c->setPort(ID(E), first_cell->getPort(ID(E))); + else if (first_cell->type.in(ID(FDRE), ID(FDRE_1))) + c->setPort(ID(E), first_cell->getPort(ID(CE))); + else + log_abort(); + } + else + log_abort(); + + log(" -> %s (%s)\n", log_id(c), log_id(c->type)); +} + +void run_variable(xilinx_srl_pm &pm) +{ + auto &st = pm.st_variable; + auto &ud = pm.ud_variable; + + log("Found variable chain of length %d (%s):\n", GetSize(ud.chain), log_id(st.first->type)); + + SigSpec initval; + for (const auto &i : ud.chain) { + auto cell = i.first; + auto slice = i.second; + log_debug(" %s\n", log_id(cell)); + if (cell->type.in(ID($_DFF_N_), ID($_DFF_P_), ID($_DFFE_NN_), ID($_DFFE_NP_), ID($_DFFE_PN_), ID($_DFFE_PP_), ID($dff), ID($dffe))) { + SigBit Q = cell->getPort(ID(Q))[slice]; + log_assert(Q.wire); + auto it = Q.wire->attributes.find(ID(init)); + if (it != Q.wire->attributes.end()) { + auto &i = it->second[Q.offset]; + initval.append(i); + i = State::Sx; + } + else + initval.append(State::Sx); + } + else + log_abort(); + } + pm.autoremove(st.shiftx); + + auto first_cell = ud.chain.back().first; + auto first_slice = ud.chain.back().second; + + Cell *c = pm.module->addCell(NEW_ID, ID($__XILINX_SHREG_)); + pm.module->swap_names(c, first_cell); + + if (first_cell->type.in(ID($_DFF_N_), ID($_DFF_P_), ID($_DFFE_NN_), ID($_DFFE_NP_), ID($_DFFE_PN_), ID($_DFFE_PP_), ID($dff), ID($dffe))) { + c->setParam(ID(DEPTH), GetSize(ud.chain)); + c->setParam(ID(INIT), initval.as_const()); + Const clkpol, enpol; + if (first_cell->type.in(ID($_DFF_P_), ID($_DFFE_PN_), ID($_DFFE_PP_))) + clkpol = 1; + else if (first_cell->type.in(ID($_DFF_N_), ID($DFFE_NN_), ID($_DFFE_NP_))) + clkpol = 0; + else if (first_cell->type.in(ID($dff), ID($dffe))) + clkpol = first_cell->getParam(ID(CLK_POLARITY)); + else + log_abort(); + if (first_cell->type.in(ID($_DFFE_NP_), ID($_DFFE_PP_))) + enpol = 1; + else if (first_cell->type.in(ID($_DFFE_NN_), ID($_DFFE_PN_))) + enpol = 0; + else if (first_cell->type.in(ID($dffe))) + enpol = first_cell->getParam(ID(EN_POLARITY)); + else + enpol = 2; + c->setParam(ID(CLKPOL), clkpol); + c->setParam(ID(ENPOL), enpol); + + if (first_cell->type.in(ID($_DFF_N_), ID($_DFF_P_), ID($_DFFE_NN_), ID($_DFFE_NP_), ID($_DFFE_PN_), ID($_DFFE_PP_))) + c->setPort(ID(C), first_cell->getPort(ID(C))); + else if (first_cell->type.in(ID($dff), ID($dffe))) + c->setPort(ID(C), first_cell->getPort(ID(CLK))); + else + log_abort(); + c->setPort(ID(D), first_cell->getPort(ID(D))[first_slice]); + c->setPort(ID(Q), st.shiftx->getPort(ID(Y))); + c->setPort(ID(L), st.shiftx->getPort(ID(B))); + if (first_cell->type.in(ID($_DFF_N_), ID($_DFF_P_), ID($dff))) + c->setPort(ID(E), State::S1); + else if (first_cell->type.in(ID($_DFFE_NN_), ID($_DFFE_NP_), ID($_DFFE_PN_), ID($_DFFE_PP_))) + c->setPort(ID(E), first_cell->getPort(ID(E))); + else if (first_cell->type.in(ID($dffe))) + c->setPort(ID(E), first_cell->getPort(ID(EN))); + else + log_abort(); + } + else + log_abort(); + + log(" -> %s (%s)\n", log_id(c), log_id(c->type)); +} + +struct XilinxSrlPass : public Pass { + XilinxSrlPass() : Pass("xilinx_srl", "Xilinx shift register extraction") { } + void help() YS_OVERRIDE + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" xilinx_srl [options] [selection]\n"); + log("\n"); + log("This pass converts chains of built-in flops (bit-level: $_DFF_[NP]_, $_DFFE_*\n"); + log("and word-level: $dff, $dffe) as well as Xilinx flops (FDRE, FDRE_1) into a\n"); + log("$__XILINX_SHREG cell. Chains must be of the same cell type, clock, clock polarity,\n"); + log("enable, and enable polarity (where relevant).\n"); + log("Flops with resets cannot be mapped to Xilinx devices and will not be inferred."); + log("\n"); + log(" -minlen N\n"); + log(" min length of shift register (default = 3)\n"); + log("\n"); + log(" -fixed\n"); + log(" infer fixed-length shift registers.\n"); + log("\n"); + log(" -variable\n"); + log(" infer variable-length shift registers (i.e. fixed-length shifts where\n"); + log(" each element also fans-out to a $shiftx cell).\n"); + log("\n"); + } + + void execute(std::vector args, RTLIL::Design *design) YS_OVERRIDE + { + log_header(design, "Executing XILINX_SRL pass (Xilinx shift register extraction).\n"); + + bool fixed = false; + bool variable = false; + int minlen = 3; + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) + { + if (args[argidx] == "-minlen" && argidx+1 < args.size()) { + minlen = atoi(args[++argidx].c_str()); + continue; + } + if (args[argidx] == "-fixed") { + fixed = true; + continue; + } + if (args[argidx] == "-variable") { + variable = true; + continue; + } + break; + } + extra_args(args, argidx, design); + + if (!fixed && !variable) + log_cmd_error("'-fixed' and/or '-variable' must be specified.\n"); + + for (auto module : design->selected_modules()) { + auto pm = xilinx_srl_pm(module, module->selected_cells()); + pm.ud_fixed.minlen = minlen; + pm.ud_variable.minlen = minlen; + + if (fixed) + pm.run_fixed(run_fixed); + if (variable) + pm.run_variable(run_variable); + } + } +} XilinxSrlPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/pmgen/xilinx_srl.pmg b/passes/pmgen/xilinx_srl.pmg new file mode 100644 index 000000000..45d44247a --- /dev/null +++ b/passes/pmgen/xilinx_srl.pmg @@ -0,0 +1,326 @@ +pattern fixed + +state clk_port en_port +udata > chain longest_chain +udata > non_first_cells +udata minlen + +code + non_first_cells.clear(); + subpattern(setup); +endcode + +match first + select first->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_, \FDRE, \FDRE_1) + select !first->has_keep_attr() + select !first->type.in(\FDRE) || !first->parameters.at(\IS_R_INVERTED, State::S0).as_bool() + select !first->type.in(\FDRE) || !first->parameters.at(\IS_D_INVERTED, State::S0).as_bool() + select !first->type.in(\FDRE, \FDRE_1) || first->connections_.at(\R, State::S0).is_fully_zero() + filter !non_first_cells.count(first) +generate + SigSpec C = module->addWire(NEW_ID); + SigSpec D = module->addWire(NEW_ID); + SigSpec Q = module->addWire(NEW_ID); + auto r = rng(8); + Cell* cell; + switch (r) + { + case 0: + case 1: + cell = module->addCell(NEW_ID, \FDRE); + cell->setPort(\C, C); + cell->setPort(\D, D); + cell->setPort(\Q, Q); + cell->setPort(\CE, module->addWire(NEW_ID)); + if (r & 1) + cell->setPort(\R, module->addWire(NEW_ID)); + else { + if (rng(2) == 0) + cell->setPort(\R, State::S0); + } + break; + case 2: + case 3: + cell = module->addDffGate(NEW_ID, C, D, Q, r & 1); + break; + case 4: + case 5: + case 6: + case 7: + cell = module->addDffeGate(NEW_ID, C, module->addWire(NEW_ID), D, Q, r & 1, r & 2); + break; + default: log_abort(); + } +endmatch + +code clk_port en_port + if (first->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_, \FDRE, \FDRE_1)) + clk_port = \C; + else log_abort(); + if (first->type.in($_DFF_N_, $_DFF_P_)) + en_port = IdString(); + else if (first->type.in($_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_)) + en_port = \E; + else if (first->type.in(\FDRE, \FDRE_1)) + en_port = \CE; + else log_abort(); + + longest_chain.clear(); + chain.push_back(first); + subpattern(tail); +finally + chain.pop_back(); + log_assert(chain.empty()); + if (GetSize(longest_chain) >= minlen) + accept; +endcode + +// ------------------------------------------------------------------ + +subpattern setup +arg clk_port +arg en_port + +match first + select first->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_, \FDRE, \FDRE_1) + select !first->has_keep_attr() + select !first->type.in(\FDRE) || !first->parameters.at(\IS_R_INVERTED, State::S0).as_bool() + select !first->type.in(\FDRE) || !first->parameters.at(\IS_D_INVERTED, State::S0).as_bool() + select !first->type.in(\FDRE, \FDRE_1) || first->connections_.at(\R, State::S0).is_fully_zero() +endmatch + +code clk_port en_port + if (first->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_, \FDRE, \FDRE_1)) + clk_port = \C; + else log_abort(); + if (first->type.in($_DFF_N_, $_DFF_P_)) + en_port = IdString(); + else if (first->type.in($_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_)) + en_port = \E; + else if (first->type.in(\FDRE, \FDRE_1)) + en_port = \CE; + else log_abort(); +endcode + +match next + select next->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_, \FDRE, \FDRE_1) + select !next->has_keep_attr() + select !port(next, \D)[0].wire->get_bool_attribute(\keep) + select nusers(port(next, \Q)) == 2 + index next->type === first->type + index port(next, \Q) === port(first, \D) + filter port(next, clk_port) == port(first, clk_port) + filter en_port == IdString() || port(next, en_port) == port(first, en_port) + filter !first->type.in(\FDRE) || next->parameters.at(\IS_C_INVERTED, State::S0).as_bool() == first->parameters.at(\IS_C_INVERTED, State::S0).as_bool() + filter !first->type.in(\FDRE) || next->parameters.at(\IS_D_INVERTED, State::S0).as_bool() == first->parameters.at(\IS_D_INVERTED, State::S0).as_bool() + filter !first->type.in(\FDRE) || next->parameters.at(\IS_R_INVERTED, State::S0).as_bool() == first->parameters.at(\IS_R_INVERTED, State::S0).as_bool() + filter !first->type.in(\FDRE, \FDRE_1) || next->connections_.at(\R, State::S0).is_fully_zero() +endmatch + +code + non_first_cells.insert(next); +endcode + +// ------------------------------------------------------------------ + +subpattern tail +arg first +arg clk_port +arg en_port + +match next + semioptional + select next->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_, \FDRE, \FDRE_1) + select !next->has_keep_attr() + select !port(next, \D)[0].wire->get_bool_attribute(\keep) + select nusers(port(next, \Q)) == 2 + index next->type === chain.back()->type + index port(next, \Q) === port(chain.back(), \D) + filter port(next, clk_port) == port(first, clk_port) + filter en_port == IdString() || port(next, en_port) == port(first, en_port) + filter !first->type.in(\FDRE) || next->parameters.at(\IS_C_INVERTED, State::S0).as_bool() == first->parameters.at(\IS_C_INVERTED, State::S0).as_bool() + filter !first->type.in(\FDRE) || next->parameters.at(\IS_D_INVERTED, State::S0).as_bool() == first->parameters.at(\IS_D_INVERTED, State::S0).as_bool() + filter !first->type.in(\FDRE) || next->parameters.at(\IS_R_INVERTED, State::S0).as_bool() == first->parameters.at(\IS_R_INVERTED, State::S0).as_bool() + filter !first->type.in(\FDRE, \FDRE_1) || next->connections_.at(\R, State::S0).is_fully_zero() +generate + Cell *cell = module->addCell(NEW_ID, chain.back()->type); + cell->setPort(\C, chain.back()->getPort(\C)); + cell->setPort(\D, module->addWire(NEW_ID)); + cell->setPort(\Q, chain.back()->getPort(\D)); + if (cell->type == \FDRE) { + if (rng(2) == 0) + cell->setPort(\R, chain.back()->connections_.at(\R, State::S0)); + cell->setPort(\CE, chain.back()->getPort(\CE)); + } + else if (cell->type.begins_with("$_DFFE_")) + cell->setPort(\E, chain.back()->getPort(\E)); +endmatch + +code + if (next) { + chain.push_back(next); + subpattern(tail); + } else { + if (GetSize(chain) > GetSize(longest_chain)) + longest_chain = chain; + } +finally + if (next) + chain.pop_back(); +endcode + +// ----------- + +pattern variable + +state clk_port en_port +state shiftx_width +state slice +udata minlen +udata >> chain +udata > chain_bits + +code + chain_bits.clear(); +endcode + +match shiftx + select shiftx->type.in($shiftx) + select !shiftx->has_keep_attr() + select param(shiftx, \Y_WIDTH).as_int() == 1 + filter param(shiftx, \A_WIDTH).as_int() >= minlen +generate + minlen = 3; + module->addShiftx(NEW_ID, module->addWire(NEW_ID, rng(6)+minlen), module->addWire(NEW_ID, 3), module->addWire(NEW_ID)); +endmatch + +code shiftx_width + shiftx_width = param(shiftx, \A_WIDTH).as_int(); +endcode + +match first + select first->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_, $dff, $dffe) + select !first->has_keep_attr() + select !port(first, \Q)[0].wire->get_bool_attribute(\keep) + slice idx GetSize(port(first, \Q)) + select nusers(port(first, \Q)[idx]) <= 2 + index port(first, \Q)[idx] === port(shiftx, \A)[shiftx_width-1] + set slice idx +generate + SigSpec C = module->addWire(NEW_ID); + auto WIDTH = rng(3)+1; + SigSpec D = module->addWire(NEW_ID, WIDTH); + SigSpec Q = module->addWire(NEW_ID, WIDTH); + auto r = rng(8); + Cell *cell = nullptr; + switch (r) + { + case 0: + case 1: + cell = module->addDff(NEW_ID, C, D, Q, r & 1); + break; + case 2: + case 3: + case 4: + case 5: + //cell = module->addDffe(NEW_ID, C, module->addWire(NEW_ID), D, Q, r & 1, r & 4); + //break; + case 6: + case 7: + WIDTH = 1; + cell = module->addDffGate(NEW_ID, C, D[0], Q[0], r & 1); + break; + default: log_abort(); + } + shiftx->connections_.at(\A)[shiftx_width-1] = port(cell, \Q)[rng(WIDTH)]; +endmatch + +code clk_port en_port + if (first->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_)) + clk_port = \C; + else if (first->type.in($dff, $dffe)) + clk_port = \CLK; + else log_abort(); + if (first->type.in($_DFF_N_, $_DFF_P_, $dff)) + en_port = IdString(); + else if (first->type.in($_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_)) + en_port = \E; + else if (first->type.in($dffe)) + en_port = \EN; + else log_abort(); + + chain_bits.insert(port(first, \Q)[slice]); + chain.emplace_back(first, slice); + subpattern(tail); +finally + if (GetSize(chain) == shiftx_width) + accept; + chain.clear(); +endcode + +// ------------------------------------------------------------------ + +subpattern tail +arg first +arg shiftx +arg shiftx_width +arg slice +arg clk_port +arg en_port + +match next + semioptional + select next->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_, $dff, $dffe) + select !next->has_keep_attr() + select !port(next, \D)[0].wire->get_bool_attribute(\keep) + slice idx GetSize(port(next, \Q)) + select nusers(port(next, \Q)[idx]) <= 3 + index next->type === chain.back().first->type + index port(next, \Q)[idx] === port(chain.back().first, \D)[chain.back().second] + index port(next, \Q)[idx] === port(shiftx, \A)[shiftx_width-1-GetSize(chain)] + filter port(next, clk_port) == port(first, clk_port) + filter en_port == IdString() || port(next, en_port) == port(first, en_port) + filter !next->type.in($dff, $dffe) || param(next, \CLK_POLARITY).as_bool() == param(first, \CLK_POLARITY).as_bool() + filter !next->type.in($dffe) || param(next, \EN_POLARITY).as_bool() == param(first, \EN_POLARITY).as_bool() + filter !chain_bits.count(port(next, \D)[idx]) + set slice idx +generate + if (GetSize(chain) < shiftx_width) { + auto back = chain.back().first; + auto slice = chain.back().second; + if (back->type.in($dff, $dffe)) { + auto WIDTH = GetSize(port(back, \D)); + if (rng(2) == 0 && slice < WIDTH-1) { + auto new_slice = slice + rng(WIDTH-1-slice); + back->connections_.at(\D)[slice] = port(back, \Q)[new_slice]; + } + else { + auto D = module->addWire(NEW_ID, WIDTH); + if (back->type == $dff) + module->addDff(NEW_ID, port(back, \CLK), D, port(back, \D), param(back, \CLK_POLARITY).as_bool()); + else if (back->type == $dffe) + module->addDffe(NEW_ID, port(back, \CLK), port(back, \EN), D, port(back, \D), param(back, \CLK_POLARITY).as_bool(), param(back, \EN_POLARITY).as_bool()); + else + log_abort(); + } + } + else if (back->type.begins_with("$_DFF_")) { + Cell *cell = module->addCell(NEW_ID, back->type); + cell->setPort(\C, back->getPort(\C)); + cell->setPort(\D, module->addWire(NEW_ID)); + cell->setPort(\Q, back->getPort(\D)); + } + else + log_abort(); + shiftx->connections_.at(\A)[shiftx_width-1-GetSize(chain)] = port(back, \D)[slice]; + } +endmatch + +code + if (next) { + chain_bits.insert(port(next, \Q)[slice]); + chain.emplace_back(next, slice); + if (GetSize(chain) < shiftx_width) + subpattern(tail); + } +endcode diff --git a/passes/techmap/shregmap.cc b/passes/techmap/shregmap.cc index 5e298d8dd..9da69e8ba 100644 --- a/passes/techmap/shregmap.cc +++ b/passes/techmap/shregmap.cc @@ -26,9 +26,7 @@ PRIVATE_NAMESPACE_BEGIN struct ShregmapTech { virtual ~ShregmapTech() { } - virtual void init(const Module * /*module*/, const SigMap &/*sigmap*/) {} - virtual void non_chain_user(const SigBit &/*bit*/, const Cell* /*cell*/, IdString /*port*/) {} - virtual bool analyze(vector &taps, const vector &qbits) = 0; + virtual bool analyze(vector &taps) = 0; virtual bool fixup(Cell *cell, dict &taps) = 0; }; @@ -56,7 +54,7 @@ struct ShregmapOptions struct ShregmapTechGreenpak4 : ShregmapTech { - bool analyze(vector &taps, const vector &/*qbits*/) + bool analyze(vector &taps) { if (GetSize(taps) > 2 && taps[0] == 0 && taps[2] < 17) { taps.clear(); @@ -93,155 +91,6 @@ struct ShregmapTechGreenpak4 : ShregmapTech } }; -struct ShregmapTechXilinx7 : ShregmapTech -{ - dict> sigbit_to_shiftx_offset; - const ShregmapOptions &opts; - - ShregmapTechXilinx7(const ShregmapOptions &opts) : opts(opts) {} - - virtual void init(const Module* module, const SigMap &sigmap) override - { - for (const auto &i : module->cells_) { - auto cell = i.second; - if (cell->type == ID($shiftx)) { - if (cell->getParam(ID(Y_WIDTH)) != 1) continue; - int j = 0; - for (auto bit : sigmap(cell->getPort(ID::A))) - sigbit_to_shiftx_offset[bit] = std::make_tuple(cell, j++, 0); - log_assert(j == cell->getParam(ID(A_WIDTH)).as_int()); - } - else if (cell->type == ID($mux)) { - int j = 0; - for (auto bit : sigmap(cell->getPort(ID::A))) - sigbit_to_shiftx_offset[bit] = std::make_tuple(cell, 0, j++); - j = 0; - for (auto bit : sigmap(cell->getPort(ID::B))) - sigbit_to_shiftx_offset[bit] = std::make_tuple(cell, 1, j++); - } - } - } - - virtual void non_chain_user(const SigBit &bit, const Cell *cell, IdString port) override - { - auto it = sigbit_to_shiftx_offset.find(bit); - if (it == sigbit_to_shiftx_offset.end()) - return; - if (cell) { - if (cell->type == ID($shiftx) && port == ID::A) - return; - if (cell->type == ID($mux) && port.in(ID::A, ID::B)) - return; - } - sigbit_to_shiftx_offset.erase(it); - } - - virtual bool analyze(vector &taps, const vector &qbits) override - { - if (GetSize(taps) == 1) - return taps[0] >= opts.minlen-1 && sigbit_to_shiftx_offset.count(qbits[0]); - - if (taps.back() < opts.minlen-1) - return false; - - Cell *shiftx = nullptr; - int group = 0; - for (int i = 0; i < GetSize(taps); ++i) { - auto it = sigbit_to_shiftx_offset.find(qbits[i]); - if (it == sigbit_to_shiftx_offset.end()) - return false; - - // Check taps are sequential - if (i != taps[i]) - return false; - // Check taps are not connected to a shift register, - // or sequential to the same shift register - if (i == 0) { - int offset; - std::tie(shiftx,offset,group) = it->second; - if (offset != i) - return false; - } - else { - Cell *shiftx_ = std::get<0>(it->second); - if (shiftx_ != shiftx) - return false; - int offset = std::get<1>(it->second); - if (offset != i) - return false; - int group_ = std::get<2>(it->second); - if (group_ != group) - return false; - } - } - log_assert(shiftx); - - // Only map if $shiftx exclusively covers the shift register - if (shiftx->type == ID($shiftx)) { - if (GetSize(taps) > shiftx->getParam(ID(A_WIDTH)).as_int()) - return false; - // Due to padding the most significant bits of A may be 1'bx, - // and if so, discount them - if (GetSize(taps) < shiftx->getParam(ID(A_WIDTH)).as_int()) { - const SigSpec A = shiftx->getPort(ID::A); - const int A_width = shiftx->getParam(ID(A_WIDTH)).as_int(); - for (int i = GetSize(taps); i < A_width; ++i) - if (A[i] != RTLIL::Sx) return false; - } - else if (GetSize(taps) != shiftx->getParam(ID(A_WIDTH)).as_int()) - return false; - } - else if (shiftx->type == ID($mux)) { - if (GetSize(taps) != 2) - return false; - } - else log_abort(); - - return true; - } - - virtual bool fixup(Cell *cell, dict &taps) override - { - const auto &tap = *taps.begin(); - auto bit = tap.second; - - auto it = sigbit_to_shiftx_offset.find(bit); - log_assert(it != sigbit_to_shiftx_offset.end()); - - auto newcell = cell->module->addCell(NEW_ID, ID($__XILINX_SHREG_)); - newcell->set_src_attribute(cell->get_src_attribute()); - newcell->setParam(ID(DEPTH), cell->getParam(ID(DEPTH))); - newcell->setParam(ID(INIT), cell->getParam(ID(INIT))); - newcell->setParam(ID(CLKPOL), cell->getParam(ID(CLKPOL))); - newcell->setParam(ID(ENPOL), cell->getParam(ID(ENPOL))); - - newcell->setPort(ID(C), cell->getPort(ID(C))); - newcell->setPort(ID(D), cell->getPort(ID(D))); - if (cell->hasPort(ID(E))) - newcell->setPort(ID(E), cell->getPort(ID(E))); - - Cell* shiftx = std::get<0>(it->second); - RTLIL::SigSpec l_wire, q_wire; - if (shiftx->type == ID($shiftx)) { - l_wire = shiftx->getPort(ID::B); - q_wire = shiftx->getPort(ID::Y); - shiftx->setPort(ID::Y, cell->module->addWire(NEW_ID)); - } - else if (shiftx->type == ID($mux)) { - l_wire = shiftx->getPort(ID(S)); - q_wire = shiftx->getPort(ID::Y); - shiftx->setPort(ID::Y, cell->module->addWire(NEW_ID)); - } - else log_abort(); - - newcell->setPort(ID(Q), q_wire); - newcell->setPort(ID(L), l_wire); - - return false; - } -}; - - struct ShregmapWorker { Module *module; @@ -264,10 +113,8 @@ struct ShregmapWorker for (auto wire : module->wires()) { if (wire->port_output || wire->get_bool_attribute(ID::keep)) { - for (auto bit : sigmap(wire)) { + for (auto bit : sigmap(wire)) sigbit_with_non_chain_users.insert(bit); - if (opts.tech) opts.tech->non_chain_user(bit, nullptr, {}); - } } if (wire->attributes.count(ID(init))) { @@ -317,10 +164,8 @@ struct ShregmapWorker for (auto conn : cell->connections()) if (cell->input(conn.first)) - for (auto bit : sigmap(conn.second)) { + for (auto bit : sigmap(conn.second)) sigbit_with_non_chain_users.insert(bit); - if (opts.tech) opts.tech->non_chain_user(bit, cell, conn.first); - } } } @@ -425,7 +270,7 @@ struct ShregmapWorker if (taps.empty() || taps.back() < depth-1) taps.push_back(depth-1); - if (opts.tech->analyze(taps, qbits)) + if (opts.tech->analyze(taps)) break; taps.pop_back(); @@ -544,9 +389,6 @@ struct ShregmapWorker ShregmapWorker(Module *module, const ShregmapOptions &opts) : module(module), sigmap(module), opts(opts), dff_count(0), shreg_count(0) { - if (opts.tech) - opts.tech->init(module, sigmap); - make_sigbit_chain_next_prev(); find_chain_start_cells(); @@ -617,11 +459,6 @@ struct ShregmapPass : public Pass { log("\n"); log(" -tech greenpak4\n"); log(" map to greenpak4 shift registers.\n"); - log(" this option also implies -clkpol pos -zinit\n"); - log("\n"); - log(" -tech xilinx\n"); - log(" map to xilinx dynamic-length shift registers.\n"); - log(" this option also implies -params -init\n"); log("\n"); } void execute(std::vector args, RTLIL::Design *design) YS_OVERRIDE @@ -676,12 +513,6 @@ struct ShregmapPass : public Pass { clkpol = "pos"; opts.zinit = true; opts.tech = new ShregmapTechGreenpak4; - } - else if (tech == "xilinx") { - opts.init = true; - opts.params = true; - enpol = "any_or_none"; - opts.tech = new ShregmapTechXilinx7(opts); } else { argidx--; break; diff --git a/techlibs/xilinx/synth_xilinx.cc b/techlibs/xilinx/synth_xilinx.cc index db8c9fa31..bfc0ac2bf 100644 --- a/techlibs/xilinx/synth_xilinx.cc +++ b/techlibs/xilinx/synth_xilinx.cc @@ -303,9 +303,8 @@ struct SynthXilinxPass : public ScriptPass if (widemux > 0 || help_mode) run("muxpack", " ('-widemux' only)"); - // shregmap -tech xilinx can cope with $shiftx and $mux - // cells for identifying variable-length shift registers, - // so attempt to convert $pmux-es to the former + // xilinx_srl looks for $shiftx cells for identifying variable-length + // shift registers, so attempt to convert $pmux-es to this // Also: wide multiplexer inference benefits from this too if (!(nosrl && widemux == 0) || help_mode) { run("pmux2shiftx", "(skip if '-nosrl' and '-widemux=0')"); @@ -387,13 +386,8 @@ struct SynthXilinxPass : public ScriptPass } run("opt -full"); - if (!nosrl || help_mode) { - // shregmap operates on bit-level flops, not word-level, - // so break those down here - run("simplemap t:$dff t:$dffe", " (skip if '-nosrl')"); - // shregmap with '-tech xilinx' infers variable length shift regs - run("shregmap -tech xilinx -minlen 3", "(skip if '-nosrl')"); - } + if (!nosrl || help_mode) + run("xilinx_srl -variable -minlen 3", "(skip if '-nosrl')"); std::string techmap_args = " -map +/techmap.v"; if (help_mode) @@ -421,6 +415,14 @@ struct SynthXilinxPass : public ScriptPass run("clean"); } + if (check_label("map_ffs")) { + if (abc9 || help_mode) { + run("techmap -map +/xilinx/ff_map.v", "('-abc9' only)"); + run("dffinit -ff FDRE Q INIT -ff FDCE Q INIT -ff FDPE Q INIT -ff FDSE Q INIT " + "-ff FDRE_1 Q INIT -ff FDCE_1 Q INIT -ff FDPE_1 Q INIT -ff FDSE_1 Q INIT", "('-abc9' only)"); + } + } + if (check_label("map_luts")) { run("opt_expr -mux_undef"); if (flatten_before_abc) @@ -448,13 +450,18 @@ struct SynthXilinxPass : public ScriptPass // This shregmap call infers fixed length shift registers after abc // has performed any necessary retiming if (!nosrl || help_mode) - run("shregmap -minlen 3 -init -params -enpol any_or_none", "(skip if '-nosrl')"); - std::string techmap_args = "-map +/xilinx/lut_map.v -map +/xilinx/ff_map.v"; - if (abc9) + run("xilinx_srl -fixed -minlen 3", "(skip if '-nosrl')"); + std::string techmap_args = "-map +/xilinx/lut_map.v -map +/xilinx/cells_map.v"; + if (help_mode) + techmap_args += " [-map +/xilinx/ff_map.v]"; + else if (abc9) techmap_args += " -map +/xilinx/abc_unmap.v"; + else + techmap_args += " -map +/xilinx/ff_map.v"; run("techmap " + techmap_args); - run("dffinit -ff FDRE Q INIT -ff FDCE Q INIT -ff FDPE Q INIT -ff FDSE Q INIT " - "-ff FDRE_1 Q INIT -ff FDCE_1 Q INIT -ff FDPE_1 Q INIT -ff FDSE_1 Q INIT"); + if (!abc9 || help_mode) + run("dffinit -ff FDRE Q INIT -ff FDCE Q INIT -ff FDPE Q INIT -ff FDSE Q INIT " + "-ff FDRE_1 Q INIT -ff FDCE_1 Q INIT -ff FDPE_1 Q INIT -ff FDSE_1 Q INIT", "(without '-abc9' only)"); run("clean"); } diff --git a/tests/various/shregmap.ys b/tests/various/shregmap.ys index 0e5fe882b..16e5f40e1 100644 --- a/tests/various/shregmap.ys +++ b/tests/various/shregmap.ys @@ -31,36 +31,3 @@ sat -verify -prove-asserts -show-ports -seq 5 miter #design -load gate #stat - -########## - -design -load read -design -copy-to model $__XILINX_SHREG_ -hierarchy -top shregmap_variable_test -prep -design -save gold - -simplemap t:$dff t:$dffe -shregmap -tech xilinx - -#stat -# show -width -# write_verilog -noexpr -norename -select -assert-count 1 t:$_DFF_P_ -select -assert-count 2 t:$__XILINX_SHREG_ - -design -stash gate - -design -import gold -as gold -design -import gate -as gate -design -copy-from model -as $__XILINX_SHREG_ \$__XILINX_SHREG_ -prep - -miter -equiv -flatten -make_assert -make_outputs gold gate miter -sat -verify -prove-asserts -show-ports -seq 5 miter - -# design -load gold -# stat - -# design -load gate -# stat diff --git a/tests/xilinx/.gitignore b/tests/xilinx/.gitignore new file mode 100644 index 000000000..b48f808a1 --- /dev/null +++ b/tests/xilinx/.gitignore @@ -0,0 +1,3 @@ +/*.log +/*.out +/run-test.mk diff --git a/tests/xilinx/pmgen_xilinx_srl.ys b/tests/xilinx/pmgen_xilinx_srl.ys new file mode 100644 index 000000000..ea2f20487 --- /dev/null +++ b/tests/xilinx/pmgen_xilinx_srl.ys @@ -0,0 +1,57 @@ +read_verilog -icells < run-test.mk +exec ${MAKE:-make} -f run-test.mk diff --git a/tests/xilinx/xilinx_srl.v b/tests/xilinx/xilinx_srl.v new file mode 100644 index 000000000..bc2a15ab2 --- /dev/null +++ b/tests/xilinx/xilinx_srl.v @@ -0,0 +1,40 @@ +module xilinx_srl_static_test(input i, clk, output [1:0] q); +reg head = 1'b0; +reg [3:0] shift1 = 4'b0000; +reg [3:0] shift2 = 4'b0000; + +always @(posedge clk) begin + head <= i; + shift1 <= {shift1[2:0], head}; + shift2 <= {shift2[2:0], head}; +end + +assign q = {shift2[3], shift1[3]}; +endmodule + +module xilinx_srl_variable_test(input i, clk, input [1:0] l1, l2, output [1:0] q); +reg head = 1'b0; +reg [3:0] shift1 = 4'b0000; +reg [3:0] shift2 = 4'b0000; + +always @(posedge clk) begin + head <= i; + shift1 <= {shift1[2:0], head}; + shift2 <= {shift2[2:0], head}; +end + +assign q = {shift2[l2], shift1[l1]}; +endmodule + +module $__XILINX_SHREG_(input C, D, E, input [1:0] L, output Q); +parameter CLKPOL = 1; +parameter ENPOL = 1; +parameter DEPTH = 1; +parameter [DEPTH-1:0] INIT = {DEPTH{1'b0}}; +reg [DEPTH-1:0] r = INIT; +wire clk = C ^ CLKPOL; +always @(posedge C) + if (E) + r <= { r[DEPTH-2:0], D }; +assign Q = r[L]; +endmodule diff --git a/tests/xilinx/xilinx_srl.ys b/tests/xilinx/xilinx_srl.ys new file mode 100644 index 000000000..b8df0e55a --- /dev/null +++ b/tests/xilinx/xilinx_srl.ys @@ -0,0 +1,67 @@ +read_verilog xilinx_srl.v +design -save read + +design -copy-to model $__XILINX_SHREG_ +hierarchy -top xilinx_srl_static_test +prep +design -save gold + +techmap +xilinx_srl -fixed +opt + +# stat +# show -width +select -assert-count 1 t:$_DFF_P_ +select -assert-count 2 t:$__XILINX_SHREG_ + +design -stash gate + +design -import gold -as gold +design -import gate -as gate +design -copy-from model -as $__XILINX_SHREG_ \$__XILINX_SHREG_ +prep + +miter -equiv -flatten -make_assert -make_outputs gold gate miter +dump gate +sat -verify -prove-asserts -show-ports -seq 5 miter + +#design -load gold +#stat + +#design -load gate +#stat + +########## + +design -load read +design -copy-to model $__XILINX_SHREG_ +hierarchy -top xilinx_srl_variable_test +prep +design -save gold + +xilinx_srl -variable +opt + +#stat +# show -width +# write_verilog -noexpr -norename +select -assert-count 1 t:$dff +select -assert-count 1 t:$dff r:WIDTH=1 %i +select -assert-count 2 t:$__XILINX_SHREG_ + +design -stash gate + +design -import gold -as gold +design -import gate -as gate +design -copy-from model -as $__XILINX_SHREG_ \$__XILINX_SHREG_ +prep + +miter -equiv -flatten -make_assert -make_outputs gold gate miter +sat -verify -prove-asserts -show-ports -seq 5 miter + +# design -load gold +# stat + +# design -load gate +# stat