diff options
Diffstat (limited to 'passes')
-rw-r--r-- | passes/pmgen/.gitignore | 2 | ||||
-rw-r--r-- | passes/pmgen/Makefile.inc | 6 | ||||
-rw-r--r-- | passes/pmgen/README.md | 2 | ||||
-rw-r--r-- | passes/pmgen/ice40_dsp.cc | 135 | ||||
-rw-r--r-- | passes/pmgen/ice40_dsp.pmg | 231 | ||||
-rw-r--r-- | passes/pmgen/xilinx_dsp.cc | 593 | ||||
-rw-r--r-- | passes/pmgen/xilinx_dsp.pmg | 467 | ||||
-rw-r--r-- | passes/techmap/abc9.cc | 58 | ||||
-rw-r--r-- | passes/techmap/techmap.cc | 115 | ||||
-rw-r--r-- | passes/tests/test_autotb.cc | 8 |
10 files changed, 1455 insertions, 162 deletions
diff --git a/passes/pmgen/.gitignore b/passes/pmgen/.gitignore index 6b319b8c3..e52f3282f 100644 --- a/passes/pmgen/.gitignore +++ b/passes/pmgen/.gitignore @@ -1 +1 @@ -/*_pm.h
\ No newline at end of file +/*_pm.h diff --git a/passes/pmgen/Makefile.inc b/passes/pmgen/Makefile.inc index 98691d0fe..21f29a49a 100644 --- a/passes/pmgen/Makefile.inc +++ b/passes/pmgen/Makefile.inc @@ -21,6 +21,12 @@ $(eval $(call add_extra_objs,passes/pmgen/ice40_wrapcarry_pm.h)) # -------------------------------------- +OBJS += passes/pmgen/xilinx_dsp.o +passes/pmgen/xilinx_dsp.o: passes/pmgen/xilinx_dsp_pm.h +$(eval $(call add_extra_objs,passes/pmgen/xilinx_dsp_pm.h)) + +# -------------------------------------- + OBJS += passes/pmgen/peepopt.o passes/pmgen/peepopt.o: passes/pmgen/peepopt_pm.h $(eval $(call add_extra_objs,passes/pmgen/peepopt_pm.h)) diff --git a/passes/pmgen/README.md b/passes/pmgen/README.md index 0856c9ba3..2f5b8d0b2 100644 --- a/passes/pmgen/README.md +++ b/passes/pmgen/README.md @@ -352,7 +352,7 @@ state variables used to pass arguments. subpattern tail ... -Subpatterns cann be called recursively. +Subpatterns can be called recursively. If a `subpattern` statement is preceded by a `fallthrough` statement, this is equivalent to calling the subpattern at the end of the preceding block. diff --git a/passes/pmgen/ice40_dsp.cc b/passes/pmgen/ice40_dsp.cc index 16bfe537f..68fc29f31 100644 --- a/passes/pmgen/ice40_dsp.cc +++ b/passes/pmgen/ice40_dsp.cc @@ -29,15 +29,15 @@ void create_ice40_dsp(ice40_dsp_pm &pm) { auto &st = pm.st_ice40_dsp; -#if 0 +#if 1 log("\n"); - log("ffA: %s\n", log_id(st.ffA, "--")); - log("ffB: %s\n", log_id(st.ffB, "--")); - log("mul: %s\n", log_id(st.mul, "--")); - log("ffY: %s\n", log_id(st.ffY, "--")); - log("addAB: %s\n", log_id(st.addAB, "--")); - log("muxAB: %s\n", log_id(st.muxAB, "--")); - log("ffS: %s\n", log_id(st.ffS, "--")); + log("ffA: %s\n", log_id(st.ffA, "--")); + log("ffB: %s\n", log_id(st.ffB, "--")); + log("mul: %s\n", log_id(st.mul, "--")); + log("ffFJKG: %s\n", log_id(st.ffFJKG, "--")); + log("addAB: %s\n", log_id(st.addAB, "--")); + log("muxAB: %s\n", log_id(st.muxAB, "--")); + log("ffO: %s\n", log_id(st.ffO, "--")); #endif log("Checking %s.%s for iCE40 DSP inference.\n", log_id(pm.module), log_id(st.mul)); @@ -52,42 +52,44 @@ void create_ice40_dsp(ice40_dsp_pm &pm) return; } - if (GetSize(st.sigS) > 32) { - log(" accumulator (%s) is too large (%d > 32).\n", log_signal(st.sigS), GetSize(st.sigS)); + if (GetSize(st.sigO) > 33) { + log(" adder/accumulator (%s) is too large (%d > 33).\n", log_signal(st.sigO), GetSize(st.sigO)); return; } - if (GetSize(st.sigY) > 32) { - log(" output (%s) is too large (%d > 32).\n", log_signal(st.sigY), GetSize(st.sigY)); + if (GetSize(st.sigH) > 32) { + log(" output (%s) is too large (%d > 32).\n", log_signal(st.sigH), GetSize(st.sigH)); return; } - bool mul_signed = st.mul->getParam("\\A_SIGNED").as_bool(); + Cell *cell = st.mul; + if (cell->type == "$mul") { + log(" replacing %s with SB_MAC16 cell.\n", log_id(st.mul->type)); - log(" replacing $mul with SB_MAC16 cell.\n"); - - Cell *cell = pm.module->addCell(NEW_ID, "\\SB_MAC16"); - pm.module->swap_names(cell, st.mul); + cell = pm.module->addCell(NEW_ID, "\\SB_MAC16"); + pm.module->swap_names(cell, st.mul); + } + else log_assert(cell->type == "\\SB_MAC16"); // SB_MAC16 Input Interface - SigSpec A = st.sigA; - A.extend_u0(16, mul_signed); + A.extend_u0(16, st.mul->getParam("\\A_SIGNED").as_bool()); + log_assert(GetSize(A) == 16); SigSpec B = st.sigB; - B.extend_u0(16, mul_signed); + B.extend_u0(16, st.mul->getParam("\\B_SIGNED").as_bool()); + log_assert(GetSize(B) == 16); - SigSpec CD; - if (st.muxA) - CD = st.muxA->getPort("\\B"); - if (st.muxB) - CD = st.muxB->getPort("\\A"); - CD.extend_u0(32, mul_signed); + SigSpec CD = st.sigCD; + if (CD.empty()) + CD = RTLIL::Const(0, 32); + else + log_assert(GetSize(CD) == 32); cell->setPort("\\A", A); cell->setPort("\\B", B); - cell->setPort("\\C", CD.extract(0, 16)); - cell->setPort("\\D", CD.extract(16, 16)); + cell->setPort("\\C", CD.extract(16, 16)); + cell->setPort("\\D", CD.extract(0, 16)); cell->setParam("\\A_REG", st.ffA ? State::S1 : State::S0); cell->setParam("\\B_REG", st.ffB ? State::S1 : State::S0); @@ -100,7 +102,7 @@ void create_ice40_dsp(ice40_dsp_pm &pm) cell->setPort("\\IRSTTOP", State::S0); cell->setPort("\\IRSTBOT", State::S0); - if (st.clock_vld) + if (st.clock != SigBit()) { cell->setPort("\\CLK", st.clock); cell->setPort("\\CE", State::S1); @@ -114,11 +116,11 @@ void create_ice40_dsp(ice40_dsp_pm &pm) if (st.ffB) log(" ffB:%s", log_id(st.ffB)); - if (st.ffY) - log(" ffY:%s", log_id(st.ffY)); + if (st.ffFJKG) + log(" ffFJKG:%s", log_id(st.ffFJKG)); - if (st.ffS) - log(" ffS:%s", log_id(st.ffS)); + if (st.ffO) + log(" ffO:%s", log_id(st.ffO)); log("\n"); } @@ -135,21 +137,43 @@ void create_ice40_dsp(ice40_dsp_pm &pm) cell->setPort("\\SIGNEXTOUT", pm.module->addWire(NEW_ID)); cell->setPort("\\CI", State::Sx); - cell->setPort("\\CO", pm.module->addWire(NEW_ID)); cell->setPort("\\ACCUMCI", State::Sx); cell->setPort("\\ACCUMCO", pm.module->addWire(NEW_ID)); // SB_MAC16 Output Interface - SigSpec O = st.ffS ? st.sigS : st.sigY; + SigSpec O = st.sigO; + int O_width = GetSize(O); + if (O_width == 33) { + log_assert(st.addAB); + // If we have a signed multiply-add, then perform sign extension + // TODO: Need to check CD[31:16] is sign extension of CD[15:0]? + if (st.addAB->getParam("\\A_SIGNED").as_bool() && st.addAB->getParam("\\B_SIGNED").as_bool()) + pm.module->connect(O[32], O[31]); + else + cell->setPort("\\CO", O[32]); + O.remove(O_width-1); + } + else + cell->setPort("\\CO", pm.module->addWire(NEW_ID)); + log_assert(GetSize(O) <= 32); if (GetSize(O) < 32) O.append(pm.module->addWire(NEW_ID, 32-GetSize(O))); cell->setPort("\\O", O); + bool accum = false; if (st.addAB) { - log(" accumulator %s (%s)\n", log_id(st.addAB), log_id(st.addAB->type)); + if (st.addA) + accum = (st.ffO && st.addAB->getPort("\\B") == st.sigO); + else if (st.addB) + accum = (st.ffO && st.addAB->getPort("\\A") == st.sigO); + else log_abort(); + if (accum) + log(" accumulator %s (%s)\n", log_id(st.addAB), log_id(st.addAB->type)); + else + log(" adder %s (%s)\n", log_id(st.addAB), log_id(st.addAB->type)); cell->setPort("\\ADDSUBTOP", st.addAB->type == "$add" ? State::S0 : State::S1); cell->setPort("\\ADDSUBBOT", st.addAB->type == "$add" ? State::S0 : State::S1); } else { @@ -177,28 +201,43 @@ void create_ice40_dsp(ice40_dsp_pm &pm) cell->setParam("\\C_REG", State::S0); cell->setParam("\\D_REG", State::S0); - cell->setParam("\\TOP_8x8_MULT_REG", st.ffY ? State::S1 : State::S0); - cell->setParam("\\BOT_8x8_MULT_REG", st.ffY ? State::S1 : State::S0); - cell->setParam("\\PIPELINE_16x16_MULT_REG1", st.ffY ? State::S1 : State::S0); + cell->setParam("\\TOP_8x8_MULT_REG", st.ffFJKG ? State::S1 : State::S0); + cell->setParam("\\BOT_8x8_MULT_REG", st.ffFJKG ? State::S1 : State::S0); + cell->setParam("\\PIPELINE_16x16_MULT_REG1", st.ffFJKG ? State::S1 : State::S0); cell->setParam("\\PIPELINE_16x16_MULT_REG2", State::S0); - cell->setParam("\\TOPOUTPUT_SELECT", Const(st.ffS ? 1 : 3, 2)); cell->setParam("\\TOPADDSUB_LOWERINPUT", Const(2, 2)); - cell->setParam("\\TOPADDSUB_UPPERINPUT", State::S0); + cell->setParam("\\TOPADDSUB_UPPERINPUT", accum ? State::S0 : State::S1); cell->setParam("\\TOPADDSUB_CARRYSELECT", Const(3, 2)); - cell->setParam("\\BOTOUTPUT_SELECT", Const(st.ffS ? 1 : 3, 2)); cell->setParam("\\BOTADDSUB_LOWERINPUT", Const(2, 2)); - cell->setParam("\\BOTADDSUB_UPPERINPUT", State::S0); + cell->setParam("\\BOTADDSUB_UPPERINPUT", accum ? State::S0 : State::S1); cell->setParam("\\BOTADDSUB_CARRYSELECT", Const(0, 2)); cell->setParam("\\MODE_8x8", State::S0); - cell->setParam("\\A_SIGNED", mul_signed ? State::S1 : State::S0); - cell->setParam("\\B_SIGNED", mul_signed ? State::S1 : State::S0); + cell->setParam("\\A_SIGNED", st.mul->getParam("\\A_SIGNED").as_bool()); + cell->setParam("\\B_SIGNED", st.mul->getParam("\\B_SIGNED").as_bool()); + + if (st.ffO) { + if (st.ffO_lo) + cell->setParam("\\TOPOUTPUT_SELECT", Const(st.addAB ? 0 : 3, 2)); + else + cell->setParam("\\TOPOUTPUT_SELECT", Const(1, 2)); + + st.ffO->connections_.at("\\Q").replace(O, pm.module->addWire(NEW_ID, GetSize(O))); + cell->setParam("\\BOTOUTPUT_SELECT", Const(1, 2)); + } + else { + cell->setParam("\\TOPOUTPUT_SELECT", Const(st.addAB ? 0 : 3, 2)); + cell->setParam("\\BOTOUTPUT_SELECT", Const(st.addAB ? 0 : 3, 2)); + } - pm.autoremove(st.mul); - pm.autoremove(st.ffY); - pm.autoremove(st.ffS); + if (cell != st.mul) + pm.autoremove(st.mul); + else + pm.blacklist(st.mul); + pm.autoremove(st.ffFJKG); + pm.autoremove(st.addAB); } struct Ice40DspPass : public Pass { diff --git a/passes/pmgen/ice40_dsp.pmg b/passes/pmgen/ice40_dsp.pmg index 7003092bb..fbf498109 100644 --- a/passes/pmgen/ice40_dsp.pmg +++ b/passes/pmgen/ice40_dsp.pmg @@ -1,87 +1,137 @@ pattern ice40_dsp state <SigBit> clock -state <bool> clock_pol clock_vld -state <SigSpec> sigA sigB sigY sigS +state <bool> clock_pol cd_signed +state <SigSpec> sigA sigB sigCD sigH sigO state <Cell*> addAB muxAB match mul - select mul->type.in($mul) + select mul->type.in($mul, \SB_MAC16) select GetSize(mul->getPort(\A)) + GetSize(mul->getPort(\B)) > 10 - select GetSize(mul->getPort(\Y)) > 10 endmatch +code sigA sigB sigH + SigSpec O; + if (mul->type == $mul) + O = mul->getPort(\Y); + else if (mul->type == \SB_MAC16) + O = mul->getPort(\O); + else log_abort(); + if (GetSize(O) <= 10) + reject; + + sigA = port(mul, \A); + int i; + for (i = GetSize(sigA)-1; i > 0; i--) + if (sigA[i] != sigA[i-1]) + break; + // Do not remove non-const sign bit + if (sigA[i].wire) + ++i; + sigA.remove(i, GetSize(sigA)-i); + sigB = port(mul, \B); + for (i = GetSize(sigB)-1; i > 0; i--) + if (sigB[i] != sigB[i-1]) + break; + // Do not remove non-const sign bit + if (sigB[i].wire) + ++i; + sigB.remove(i, GetSize(sigB)-i); + + // Only care about those bits that are used + for (i = 0; i < GetSize(O); i++) { + if (nusers(O[i]) <= 1) + break; + sigH.append(O[i]); + } + log_assert(nusers(O.extract_end(i)) <= 1); +endcode + match ffA + if mul->type != \SB_MAC16 || !param(mul, \A_REG).as_bool() select ffA->type.in($dff) - // select nusers(port(ffA, \Q)) == 2 - index <SigSpec> port(ffA, \Q) === port(mul, \A) + filter GetSize(port(ffA, \Q)) >= GetSize(sigA) + slice offset GetSize(port(ffA, \Q)) + filter offset+GetSize(sigA) <= GetSize(port(ffA, \Q)) && port(ffA, \Q).extract(offset, GetSize(sigA)) == sigA optional endmatch -code sigA clock clock_pol clock_vld - sigA = port(mul, \A); - +code sigA clock clock_pol if (ffA) { - sigA = port(ffA, \D); + for (auto b : port(ffA, \Q)) + if (b.wire->get_bool_attribute(\keep)) + reject; clock = port(ffA, \CLK).as_bit(); clock_pol = param(ffA, \CLK_POLARITY).as_bool(); - clock_vld = true; + + sigA.replace(port(ffA, \Q), port(ffA, \D)); } endcode match ffB + if mul->type != \SB_MAC16 || !param(mul, \B_REG).as_bool() select ffB->type.in($dff) - // select nusers(port(ffB, \Q)) == 2 - index <SigSpec> port(ffB, \Q) === port(mul, \B) + filter GetSize(port(ffB, \Q)) >= GetSize(sigB) + slice offset GetSize(port(ffB, \Q)) + filter offset+GetSize(sigB) <= GetSize(port(ffB, \Q)) && port(ffB, \Q).extract(offset, GetSize(sigB)) == sigB optional endmatch -code sigB clock clock_pol clock_vld - sigB = port(mul, \B); - +code sigB clock clock_pol if (ffB) { - sigB = port(ffB, \D); + for (auto b : port(ffB, \Q)) + if (b.wire->get_bool_attribute(\keep)) + reject; + SigBit c = port(ffB, \CLK).as_bit(); bool cp = param(ffB, \CLK_POLARITY).as_bool(); - if (clock_vld && (c != clock || cp != clock_pol)) + if (clock != SigBit() && (c != clock || cp != clock_pol)) reject; clock = c; clock_pol = cp; - clock_vld = true; + + sigB.replace(port(ffB, \Q), port(ffB, \D)); } endcode -match ffY - select ffY->type.in($dff) - select nusers(port(ffY, \D)) == 2 - index <SigSpec> port(ffY, \D) === port(mul, \Y) +match ffFJKG + // Ensure pipeline register is not already used + if mul->type != \SB_MAC16 || (!param(mul, \TOP_8x8_MULT_REG).as_bool() && !param(mul, \BOT_8x8_MULT_REG).as_bool() && !param(mul, \PIPELINE_16x16_MULT_REG1).as_bool() && !param(mul, \PIPELINE_16x16_MULT_REG2).as_bool()) + select ffFJKG->type.in($dff) + select nusers(port(ffFJKG, \D)) == 2 + index <SigSpec> port(ffFJKG, \D) === sigH optional endmatch -code sigY clock clock_pol clock_vld - sigY = port(mul, \Y); +code sigH sigO clock clock_pol + if (ffFJKG) { + sigH = port(ffFJKG, \Q); + for (auto b : sigH) + if (b.wire->get_bool_attribute(\keep)) + reject; - if (ffY) { - sigY = port(ffY, \Q); - SigBit c = port(ffY, \CLK).as_bit(); - bool cp = param(ffY, \CLK_POLARITY).as_bool(); + SigBit c = port(ffFJKG, \CLK).as_bit(); + bool cp = param(ffFJKG, \CLK_POLARITY).as_bool(); - if (clock_vld && (c != clock || cp != clock_pol)) + if (clock != SigBit() && (c != clock || cp != clock_pol)) reject; clock = c; clock_pol = cp; - clock_vld = true; } + + sigO = sigH; endcode match addA select addA->type.in($add) select nusers(port(addA, \A)) == 2 - index <SigSpec> port(addA, \A) === sigY + filter param(addA, \A_WIDTH).as_int() <= GetSize(sigH) + //index <SigSpec> port(addA, \A) === sigH.extract(0, param(addA, \A_WIDTH).as_int()) + filter port(addA, \A) == sigH.extract(0, param(addA, \A_WIDTH).as_int()) optional endmatch @@ -89,75 +139,134 @@ match addB if !addA select addB->type.in($add, $sub) select nusers(port(addB, \B)) == 2 - index <SigSpec> port(addB, \B) === sigY + filter param(addB, \B_WIDTH).as_int() <= GetSize(sigH) + //index <SigSpec> port(addB, \B) === sigH.extract(0, param(addB, \B_WIDTH).as_int()) + filter port(addB, \B) == sigH.extract(0, param(addB, \B_WIDTH).as_int()) optional endmatch -code addAB sigS +code addAB sigCD sigO cd_signed if (addA) { addAB = addA; - sigS = port(addA, \B); + sigCD = port(addAB, \B); + cd_signed = param(addAB, \B_SIGNED).as_bool(); } - if (addB) { + else if (addB) { addAB = addB; - sigS = port(addB, \A); + sigCD = port(addAB, \A); + cd_signed = param(addAB, \A_SIGNED).as_bool(); } if (addAB) { + if (mul->type == \SB_MAC16) { + // Ensure that adder is not used + if (param(mul, \TOPOUTPUT_SELECT).as_int() != 3 || + param(mul, \BOTOUTPUT_SELECT).as_int() != 3) + reject; + } + int natural_mul_width = GetSize(sigA) + GetSize(sigB); - int actual_mul_width = GetSize(sigY); - int actual_acc_width = GetSize(sigS); + int actual_mul_width = GetSize(sigH); + int actual_acc_width = GetSize(sigCD); if ((actual_acc_width > actual_mul_width) && (natural_mul_width > actual_mul_width)) reject; - if ((actual_acc_width != actual_mul_width) && (param(mul, \A_SIGNED).as_bool() != param(addAB, \A_SIGNED).as_bool())) + // If accumulator, check adder width and signedness + if (sigCD == sigH && (actual_acc_width != actual_mul_width) && (param(mul, \A_SIGNED).as_bool() != param(addAB, \A_SIGNED).as_bool())) reject; + + sigO = port(addAB, \Y); } endcode match muxA - if addAB select muxA->type.in($mux) - select nusers(port(muxA, \A)) == 2 - index <SigSpec> port(muxA, \A) === port(addAB, \Y) + index <int> nusers(port(muxA, \A)) === 2 + index <SigSpec> port(muxA, \A) === sigO optional endmatch match muxB - if addAB if !muxA select muxB->type.in($mux) - select nusers(port(muxB, \B)) == 2 - index <SigSpec> port(muxB, \B) === port(addAB, \Y) + index <int> nusers(port(muxB, \B)) === 2 + index <SigSpec> port(muxB, \B) === sigO optional endmatch -code muxAB - muxAB = addAB; +code muxAB sigO if (muxA) muxAB = muxA; - if (muxB) + else if (muxB) muxAB = muxB; + if (muxAB) + sigO = port(muxAB, \Y); endcode -match ffS - if muxAB - select ffS->type.in($dff) - select nusers(port(ffS, \D)) == 2 - index <SigSpec> port(ffS, \D) === port(muxAB, \Y) - index <SigSpec> port(ffS, \Q) === sigS +match ffO + // Ensure that register is not already used + if mul->type != \SB_MAC16 || (mul->parameters.at(\TOPOUTPUT_SELECT, 0).as_int() != 1 && mul->parameters.at(\BOTOUTPUT_SELECT, 0).as_int() != 1) + // Ensure that OLOADTOP/OLOADBOT is unused or zero + if mul->type != \SB_MAC16 || (mul->connections_.at(\OLOADTOP, State::S0).is_fully_zero() && mul->connections_.at(\OLOADBOT, State::S0).is_fully_zero()) + if nusers(sigO) == 2 + select ffO->type.in($dff) + filter GetSize(port(ffO, \D)) >= GetSize(sigO) + slice offset GetSize(port(ffO, \D)) + filter offset+GetSize(sigO) <= GetSize(port(ffO, \D)) && port(ffO, \D).extract(offset, GetSize(sigO)) == sigO + optional +endmatch + +match ffO_lo + if !ffO && GetSize(sigO) > 16 + // Ensure that register is not already used + if mul->type != \SB_MAC16 || (mul->parameters.at(\TOPOUTPUT_SELECT, 0).as_int() != 1 && mul->parameters.at(\BOTOUTPUT_SELECT, 0).as_int() != 1) + // Ensure that OLOADTOP/OLOADBOT is unused or zero + if mul->type != \SB_MAC16 || (mul->connections_.at(\OLOADTOP, State::S0).is_fully_zero() && mul->connections_.at(\OLOADBOT, State::S0).is_fully_zero()) + if nusers(sigO.extract(0, 16)) == 2 + select ffO_lo->type.in($dff) + filter GetSize(port(ffO_lo, \D)) >= 16 + slice offset GetSize(port(ffO_lo, \D)) + filter offset+GetSize(sigO) <= GetSize(port(ffO_lo, \D)) && port(ffO_lo, \D).extract(offset, 16) == sigO.extract(0, 16) + optional endmatch -code clock clock_pol clock_vld - if (ffS) { - SigBit c = port(ffS, \CLK).as_bit(); - bool cp = param(ffS, \CLK_POLARITY).as_bool(); +code ffO clock clock_pol sigO sigCD cd_signed + if (ffO_lo) { + log_assert(!ffO); + ffO = ffO_lo; + } + if (ffO) { + for (auto b : port(ffO, \Q)) + if (b.wire->get_bool_attribute(\keep)) + reject; + + SigBit c = port(ffO, \CLK).as_bit(); + bool cp = param(ffO, \CLK_POLARITY).as_bool(); - if (clock_vld && (c != clock || cp != clock_pol)) + if (clock != SigBit() && (c != clock || cp != clock_pol)) reject; clock = c; clock_pol = cp; - clock_vld = true; + + sigO.replace(port(ffO, \D), port(ffO, \Q)); + + // Loading value into output register is not + // supported unless using accumulator + if (muxAB) { + if (sigCD != sigO) + reject; + if (muxA) + sigCD = port(muxAB, \B); + else if (muxB) + sigCD = port(muxAB, \A); + else log_abort(); + + cd_signed = addAB && param(addAB, \A_SIGNED).as_bool() && param(addAB, \B_SIGNED).as_bool(); + } } + sigCD.extend_u0(32, cd_signed); +endcode + +code accept; endcode diff --git a/passes/pmgen/xilinx_dsp.cc b/passes/pmgen/xilinx_dsp.cc new file mode 100644 index 000000000..5d50c7795 --- /dev/null +++ b/passes/pmgen/xilinx_dsp.cc @@ -0,0 +1,593 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at> + * + * 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 + +#include "passes/pmgen/xilinx_dsp_pm.h" + +static Cell* addDsp(Module *module) { + Cell *cell = module->addCell(NEW_ID, "\\DSP48E1"); + cell->setParam("\\ACASCREG", 0); + cell->setParam("\\ADREG", 0); + cell->setParam("\\A_INPUT", Const("DIRECT")); + cell->setParam("\\ALUMODEREG", 0); + cell->setParam("\\AREG", 0); + cell->setParam("\\BCASCREG", 0); + cell->setParam("\\B_INPUT", Const("DIRECT")); + cell->setParam("\\BREG", 0); + cell->setParam("\\CARRYINREG", 0); + cell->setParam("\\CARRYINSELREG", 0); + cell->setParam("\\CREG", 0); + cell->setParam("\\DREG", 0); + cell->setParam("\\INMODEREG", 0); + cell->setParam("\\MREG", 0); + cell->setParam("\\OPMODEREG", 0); + cell->setParam("\\PREG", 0); + cell->setParam("\\USE_MULT", Const("NONE")); + + cell->setPort("\\D", Const(0, 24)); + cell->setPort("\\INMODE", Const(0, 5)); + cell->setPort("\\ALUMODE", Const(0, 4)); + cell->setPort("\\OPMODE", Const(0, 7)); + cell->setPort("\\CARRYINSEL", Const(0, 3)); + cell->setPort("\\ACIN", Const(0, 30)); + cell->setPort("\\BCIN", Const(0, 18)); + cell->setPort("\\PCIN", Const(0, 48)); + cell->setPort("\\CARRYIN", Const(0, 1)); + return cell; +} + +void pack_xilinx_simd(Module *module, const std::vector<Cell*> &selected_cells) +{ + std::deque<Cell*> simd12_add, simd12_sub; + std::deque<Cell*> simd24_add, simd24_sub; + + for (auto cell : selected_cells) { + if (!cell->type.in("$add", "$sub")) + continue; + SigSpec Y = cell->getPort("\\Y"); + if (!Y.is_chunk()) + continue; + if (!Y.as_chunk().wire->get_strpool_attribute("\\use_dsp").count("simd")) + continue; + if (GetSize(Y) > 25) + continue; + SigSpec A = cell->getPort("\\A"); + SigSpec B = cell->getPort("\\B"); + if (GetSize(Y) <= 13) { + if (GetSize(A) > 12) + continue; + if (GetSize(B) > 12) + continue; + if (cell->type == "$add") + simd12_add.push_back(cell); + else if (cell->type == "$sub") + simd12_sub.push_back(cell); + } + else if (GetSize(Y) <= 25) { + if (GetSize(A) > 24) + continue; + if (GetSize(B) > 24) + continue; + if (cell->type == "$add") + simd24_add.push_back(cell); + else if (cell->type == "$sub") + simd24_sub.push_back(cell); + } + else + log_abort(); + } + + auto f12 = [module](SigSpec &AB, SigSpec &C, SigSpec &P, SigSpec &CARRYOUT, Cell *lane) { + SigSpec A = lane->getPort("\\A"); + SigSpec B = lane->getPort("\\B"); + SigSpec Y = lane->getPort("\\Y"); + A.extend_u0(12, lane->getParam("\\A_SIGNED").as_bool()); + B.extend_u0(12, lane->getParam("\\B_SIGNED").as_bool()); + AB.append(A); + C.append(B); + if (GetSize(Y) < 13) + Y.append(module->addWire(NEW_ID, 13-GetSize(Y))); + else + log_assert(GetSize(Y) == 13); + P.append(Y.extract(0, 12)); + CARRYOUT.append(Y[12]); + }; + auto g12 = [&f12,module](std::deque<Cell*> &simd12) { + while (simd12.size() > 1) { + SigSpec AB, C, P, CARRYOUT; + + Cell *lane1 = simd12.front(); + simd12.pop_front(); + Cell *lane2 = simd12.front(); + simd12.pop_front(); + Cell *lane3 = nullptr; + Cell *lane4 = nullptr; + + if (!simd12.empty()) { + lane3 = simd12.front(); + simd12.pop_front(); + if (!simd12.empty()) { + lane4 = simd12.front(); + simd12.pop_front(); + } + } + + log("Analysing %s.%s for Xilinx DSP SIMD12 packing.\n", log_id(module), log_id(lane1)); + + Cell *cell = addDsp(module); + cell->setParam("\\USE_SIMD", Const("FOUR12")); + // X = A:B + // Y = 0 + // Z = C + cell->setPort("\\OPMODE", Const::from_string("0110011")); + + log_assert(lane1); + log_assert(lane2); + f12(AB, C, P, CARRYOUT, lane1); + f12(AB, C, P, CARRYOUT, lane2); + if (lane3) { + f12(AB, C, P, CARRYOUT, lane3); + if (lane4) + f12(AB, C, P, CARRYOUT, lane4); + else { + AB.append(Const(0, 12)); + C.append(Const(0, 12)); + P.append(module->addWire(NEW_ID, 12)); + CARRYOUT.append(module->addWire(NEW_ID, 1)); + } + } + else { + AB.append(Const(0, 24)); + C.append(Const(0, 24)); + P.append(module->addWire(NEW_ID, 24)); + CARRYOUT.append(module->addWire(NEW_ID, 2)); + } + log_assert(GetSize(AB) == 48); + log_assert(GetSize(C) == 48); + log_assert(GetSize(P) == 48); + log_assert(GetSize(CARRYOUT) == 4); + cell->setPort("\\A", AB.extract(18, 30)); + cell->setPort("\\B", AB.extract(0, 18)); + cell->setPort("\\C", C); + cell->setPort("\\P", P); + cell->setPort("\\CARRYOUT", CARRYOUT); + if (lane1->type == "$sub") + cell->setPort("\\ALUMODE", Const::from_string("0011")); + + module->remove(lane1); + module->remove(lane2); + if (lane3) module->remove(lane3); + if (lane4) module->remove(lane4); + + module->design->select(module, cell); + } + }; + g12(simd12_add); + g12(simd12_sub); + + auto f24 = [module](SigSpec &AB, SigSpec &C, SigSpec &P, SigSpec &CARRYOUT, Cell *lane) { + SigSpec A = lane->getPort("\\A"); + SigSpec B = lane->getPort("\\B"); + SigSpec Y = lane->getPort("\\Y"); + A.extend_u0(24, lane->getParam("\\A_SIGNED").as_bool()); + B.extend_u0(24, lane->getParam("\\B_SIGNED").as_bool()); + C.append(A); + AB.append(B); + if (GetSize(Y) < 25) + Y.append(module->addWire(NEW_ID, 25-GetSize(Y))); + else + log_assert(GetSize(Y) == 25); + P.append(Y.extract(0, 24)); + CARRYOUT.append(module->addWire(NEW_ID)); // TWO24 uses every other bit + CARRYOUT.append(Y[24]); + }; + auto g24 = [&f24,module](std::deque<Cell*> &simd24) { + while (simd24.size() > 1) { + SigSpec AB; + SigSpec C; + SigSpec P; + SigSpec CARRYOUT; + + Cell *lane1 = simd24.front(); + simd24.pop_front(); + Cell *lane2 = simd24.front(); + simd24.pop_front(); + + log("Analysing %s.%s for Xilinx DSP SIMD24 packing.\n", log_id(module), log_id(lane1)); + + Cell *cell = addDsp(module); + cell->setParam("\\USE_SIMD", Const("TWO24")); + // X = A:B + // Y = 0 + // Z = C + cell->setPort("\\OPMODE", Const::from_string("0110011")); + + log_assert(lane1); + log_assert(lane2); + f24(AB, C, P, CARRYOUT, lane1); + f24(AB, C, P, CARRYOUT, lane2); + log_assert(GetSize(AB) == 48); + log_assert(GetSize(C) == 48); + log_assert(GetSize(P) == 48); + log_assert(GetSize(CARRYOUT) == 4); + cell->setPort("\\A", AB.extract(18, 30)); + cell->setPort("\\B", AB.extract(0, 18)); + cell->setPort("\\C", C); + cell->setPort("\\P", P); + cell->setPort("\\CARRYOUT", CARRYOUT); + if (lane1->type == "$sub") + cell->setPort("\\ALUMODE", Const::from_string("0011")); + + module->remove(lane1); + module->remove(lane2); + + module->design->select(module, cell); + } + }; + g24(simd24_add); + g24(simd24_sub); +} + + +void pack_xilinx_dsp(dict<SigBit, Cell*> &bit_to_driver, xilinx_dsp_pm &pm) +{ + auto &st = pm.st_xilinx_dsp; + +#if 1 + log("\n"); + log("preAdd: %s\n", log_id(st.preAdd, "--")); + log("ffAD: %s\n", log_id(st.ffAD, "--")); + log("ffADmux: %s\n", log_id(st.ffADmux, "--")); + log("ffA: %s\n", log_id(st.ffA, "--")); + log("ffAmux: %s\n", log_id(st.ffAmux, "--")); + log("ffB: %s\n", log_id(st.ffB, "--")); + log("ffBmux: %s\n", log_id(st.ffBmux, "--")); + log("ffC: %s\n", log_id(st.ffC, "--")); + log("ffCmux: %s\n", log_id(st.ffCmux, "--")); + log("ffD: %s\n", log_id(st.ffD, "--")); + log("ffDmux: %s\n", log_id(st.ffDmux, "--")); + log("dsp: %s\n", log_id(st.dsp, "--")); + log("ffM: %s\n", log_id(st.ffM, "--")); + log("ffMmux: %s\n", log_id(st.ffMmux, "--")); + log("postAdd: %s\n", log_id(st.postAdd, "--")); + log("postAddMux: %s\n", log_id(st.postAddMux, "--")); + log("ffP: %s\n", log_id(st.ffP, "--")); + log("ffPcemux: %s\n", log_id(st.ffPcemux, "--")); + log("ffPrstmux: %s\n", log_id(st.ffPrstmux, "--")); +#endif + + log("Analysing %s.%s for Xilinx DSP packing.\n", log_id(pm.module), log_id(st.dsp)); + + Cell *cell = st.dsp; + bit_to_driver.insert(std::make_pair(cell->getPort("\\P")[17], cell)); + SigSpec P = st.sigP; + + if (st.preAdd) { + log(" preadder %s (%s)\n", log_id(st.preAdd), log_id(st.preAdd->type)); + bool A_SIGNED = st.preAdd->getParam("\\A_SIGNED").as_bool(); + bool D_SIGNED = st.preAdd->getParam("\\B_SIGNED").as_bool(); + if (st.sigA == st.preAdd->getPort("\\B")) + std::swap(A_SIGNED, D_SIGNED); + st.sigA.extend_u0(30, A_SIGNED); + st.sigD.extend_u0(25, D_SIGNED); + cell->setPort("\\A", st.sigA); + cell->setPort("\\D", st.sigD); + cell->connections_.at("\\INMODE") = Const::from_string("00100"); + + if (st.ffAD) { + if (st.ffADmux) { + SigSpec S = st.ffADmux->getPort("\\S"); + cell->setPort("\\CEAD", st.ffADcepol ? S : pm.module->Not(NEW_ID, S)); + } + else + cell->setPort("\\CEAD", State::S1); + cell->setParam("\\ADREG", 1); + } + + cell->setParam("\\USE_DPORT", Const("TRUE")); + + pm.autoremove(st.preAdd); + } + if (st.postAdd) { + log(" postadder %s (%s)\n", log_id(st.postAdd), log_id(st.postAdd->type)); + + SigSpec &opmode = cell->connections_.at("\\OPMODE"); + if (st.postAddMux) { + log_assert(st.ffP); + opmode[4] = st.postAddMux->getPort("\\S"); + pm.autoremove(st.postAddMux); + } + else if (st.ffP && st.sigC == P) + opmode[4] = State::S0; + else + opmode[4] = State::S1; + opmode[6] = State::S0; + opmode[5] = State::S1; + + if (opmode[4] != State::S0) { + if (st.postAddMuxAB == "\\A") + st.sigC.extend_u0(48, st.postAdd->getParam("\\B_SIGNED").as_bool()); + else + st.sigC.extend_u0(48, st.postAdd->getParam("\\A_SIGNED").as_bool()); + cell->setPort("\\C", st.sigC); + } + + pm.autoremove(st.postAdd); + } + + if (st.clock != SigBit()) + { + cell->setPort("\\CLK", st.clock); + + if (st.ffA) { + SigSpec A = cell->getPort("\\A"); + SigSpec D = st.ffA->getPort("\\D"); + SigSpec Q = pm.sigmap(st.ffA->getPort("\\Q")); + A.replace(Q, D); + if (st.ffAmux) { + SigSpec Y = st.ffAmux->getPort("\\Y"); + SigSpec AB = st.ffAmux->getPort(st.ffAcepol ? "\\B" : "\\A"); + SigSpec S = st.ffAmux->getPort("\\S"); + A.replace(Y, AB); + cell->setPort("\\CEA2", st.ffAcepol ? S : pm.module->Not(NEW_ID, S)); + } + else + cell->setPort("\\CEA2", State::S1); + cell->setPort("\\A", A); + + cell->setParam("\\AREG", 1); + } + if (st.ffB) { + SigSpec B = cell->getPort("\\B"); + SigSpec D = st.ffB->getPort("\\D"); + SigSpec Q = st.ffB->getPort("\\Q"); + B.replace(Q, D); + if (st.ffBmux) { + SigSpec Y = st.ffBmux->getPort("\\Y"); + SigSpec AB = st.ffBmux->getPort(st.ffBcepol ? "\\B" : "\\A"); + SigSpec S = st.ffBmux->getPort("\\S"); + B.replace(Y, AB); + cell->setPort("\\CEB2", st.ffBcepol ? S : pm.module->Not(NEW_ID, S)); + } + else + cell->setPort("\\CEB2", State::S1); + cell->setPort("\\B", B); + + cell->setParam("\\BREG", 1); + } + if (st.ffC) { + SigSpec C = cell->getPort("\\C"); + SigSpec D = st.ffC->getPort("\\D"); + SigSpec Q = st.ffC->getPort("\\Q"); + C.replace(Q, D); + + if (st.ffCmux) { + SigSpec Y = st.ffCmux->getPort("\\Y"); + SigSpec AB = st.ffCmux->getPort(st.ffCcepol ? "\\B" : "\\A"); + SigSpec S = st.ffCmux->getPort("\\S"); + C.replace(Y, AB); + + cell->setPort("\\CEC", st.ffCcepol ? S : pm.module->Not(NEW_ID, S)); + } + else + cell->setPort("\\CEC", State::S1); + cell->setPort("\\C", C); + + cell->setParam("\\CREG", 1); + } + if (st.ffD) { + SigSpec D_ = cell->getPort("\\D"); + SigSpec D = st.ffD->getPort("\\D"); + SigSpec Q = st.ffD->getPort("\\Q"); + D_.replace(Q, D); + + if (st.ffDmux) { + SigSpec Y = st.ffDmux->getPort("\\Y"); + SigSpec AB = st.ffDmux->getPort(st.ffDcepol ? "\\B" : "\\A"); + SigSpec S = st.ffDmux->getPort("\\S"); + D_.replace(Y, AB); + + cell->setPort("\\CED", st.ffDcepol ? S : pm.module->Not(NEW_ID, S)); + } + else + cell->setPort("\\CED", State::S1); + cell->setPort("\\D", D_); + + cell->setParam("\\DREG", 1); + } + if (st.ffM) { + if (st.ffMmux) { + SigSpec S = st.ffMmux->getPort("\\S"); + cell->setPort("\\CEM", st.ffMcepol ? S : pm.module->Not(NEW_ID, S)); + pm.autoremove(st.ffMmux); + } + else + cell->setPort("\\CEM", State::S1); + SigSpec D = st.ffM->getPort("\\D"); + SigSpec Q = st.ffM->getPort("\\Q"); + P.replace(pm.sigmap(D), Q); + + cell->setParam("\\MREG", State::S1); + pm.autoremove(st.ffM); + } + if (st.ffP) { + if (st.ffPrstmux) { + SigSpec S = st.ffPrstmux->getPort("\\S"); + cell->setPort("\\RSTP", st.ffPrstpol ? S : pm.module->Not(NEW_ID, S)); + st.ffPrstmux->connections_.at("\\Y").replace(P, pm.module->addWire(NEW_ID, GetSize(P))); + } + else + cell->setPort("\\RSTP", State::S0); + if (st.ffPcemux) { + SigSpec S = st.ffPcemux->getPort("\\S"); + cell->setPort("\\CEP", st.ffPcepol ? S : pm.module->Not(NEW_ID, S)); + st.ffPcemux->connections_.at("\\Y").replace(P, pm.module->addWire(NEW_ID, GetSize(P))); + } + else + cell->setPort("\\CEP", State::S1); + SigSpec D = st.ffP->getPort("\\D"); + SigSpec Q = st.ffP->getPort("\\Q"); + P.replace(pm.sigmap(D), Q); + st.ffP->connections_.at("\\Q").replace(P, pm.module->addWire(NEW_ID, GetSize(P))); + + for (auto c : Q.chunks()) { + auto it = c.wire->attributes.find("\\init"); + if (it == c.wire->attributes.end()) + continue; + for (int i = c.offset; i < c.offset+c.width; i++) { + log_assert(it->second[i] == State::S0 || it->second[i] == State::Sx); + it->second[i] = State::Sx; + } + } + + cell->setParam("\\PREG", State::S1); + } + + log(" clock: %s (%s)", log_signal(st.clock), "posedge"); + + if (st.ffA) + log(" ffA:%s", log_id(st.ffA)); + + if (st.ffAD) + log(" ffAD:%s", log_id(st.ffAD)); + + if (st.ffB) + log(" ffB:%s", log_id(st.ffB)); + + if (st.ffC) + log(" ffC:%s", log_id(st.ffC)); + + if (st.ffD) + log(" ffD:%s", log_id(st.ffD)); + + if (st.ffM) + log(" ffM:%s", log_id(st.ffM)); + + if (st.ffP) + log(" ffP:%s", log_id(st.ffP)); + + log("\n"); + } + + if (GetSize(P) < 48) + P.append(pm.module->addWire(NEW_ID, 48-GetSize(P))); + cell->setPort("\\P", P); + + pm.blacklist(cell); +} + +struct XilinxDspPass : public Pass { + XilinxDspPass() : Pass("xilinx_dsp", "Xilinx: pack resources into DSPs") { } + void help() YS_OVERRIDE + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" xilinx_dsp [options] [selection]\n"); + log("\n"); + log("Pack input registers (A, B, C, D, AD; with optional enable), pipeline registers\n"); + log("(M; with optional enable), output registers (P; with optional enable),\n"); + log("pre-adder and/or post-adder into Xilinx DSP resources.\n"); + log("\n"); + log("Multiply-accumulate operations using the post-adder with feedback on the 'C'\n"); + log("input will be folded into the DSP. In this scenario only, the 'C' input can be\n"); + log("used to override the existing accumulation result with a new value.\n"); + log("\n"); + log("Use of the dedicated 'PCOUT' -> 'PCIN' path is detected for 'P' -> 'C' connections\n"); + log("where 'P' is right-shifted by 18-bits and used as an input to the post-adder (a\n"); + log("pattern common for summing partial products to implement wide multiplies).\n"); + log("\n"); + log("Not currently supported: reset (RST*) inputs on any register.\n"); + log("\n"); + log("\n"); + log("Experimental feature: addition/subtractions less than 12 or 24 bits with the\n"); + log("'(* use_dsp=\"simd\" *)' attribute attached to the output wire or attached to\n"); + log("the add/subtract operator will cause those operations to be implemented using\n"); + log("the 'SIMD' feature of DSPs.\n"); + log("\n"); + } + void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE + { + log_header(design, "Executing XILINX_DSP pass (pack resources into DSPs).\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) + { + // if (args[argidx] == "-singleton") { + // singleton_mode = true; + // continue; + // } + break; + } + extra_args(args, argidx, design); + + for (auto module : design->selected_modules()) { + pack_xilinx_simd(module, module->selected_cells()); + + xilinx_dsp_pm pm(module, module->selected_cells()); + dict<SigBit, Cell*> bit_to_driver; + auto f = [&bit_to_driver](xilinx_dsp_pm &pm){ pack_xilinx_dsp(bit_to_driver, pm); }; + pm.run_xilinx_dsp(f); + + // Look for ability to convert C input from another DSP into PCIN + // NB: Needs to be done after pattern matcher has folded all + // $add cells into the DSP + for (auto cell : module->cells()) { + if (cell->type != "\\DSP48E1") + continue; + if (cell->parameters.at("\\CREG", State::S1).as_bool()) + continue; + SigSpec &opmode = cell->connections_.at("\\OPMODE"); + if (opmode.extract(4,3) != Const::from_string("011")) + continue; + SigSpec C = pm.sigmap(cell->getPort("\\C")); + if (C.has_const()) + continue; + auto it = bit_to_driver.find(C[0]); + if (it == bit_to_driver.end()) + continue; + auto driver = it->second; + + // Unextend C + int i; + for (i = GetSize(C)-1; i > 0; i--) + if (C[i] != C[i-1]) + break; + if (i > 48-17) + continue; + if (driver->getPort("\\P").extract(17, i) == C.extract(0, i)) { + cell->setPort("\\C", Const(0, 48)); + Wire *cascade = module->addWire(NEW_ID, 48); + driver->setPort("\\PCOUT", cascade); + cell->setPort("\\PCIN", cascade); + opmode[6] = State::S1; + opmode[5] = State::S0; + opmode[4] = State::S1; + bit_to_driver.erase(it); + } + } + } + } +} XilinxDspPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/pmgen/xilinx_dsp.pmg b/passes/pmgen/xilinx_dsp.pmg new file mode 100644 index 000000000..7db8e95a6 --- /dev/null +++ b/passes/pmgen/xilinx_dsp.pmg @@ -0,0 +1,467 @@ +pattern xilinx_dsp + +state <std::function<SigSpec(const SigSpec&)>> unextend +state <SigBit> clock +state <SigSpec> sigA sigffAmuxY sigB sigffBmuxY sigC sigffCmuxY sigD sigffDmuxY sigM sigP +state <IdString> postAddAB postAddMuxAB +state <bool> ffAcepol ffADcepol ffBcepol ffCcepol ffDcepol ffMcepol ffPcepol ffPrstpol +state <int> ffPoffset + +state <Cell*> ffAD ffADmux ffA ffAmux ffB ffBmux ffC ffCmux ffD ffDmux ffM ffMmux ffP ffPcemux ffPrstmux + +// subpattern +state <SigSpec> argQ argD +state <bool> ffcepol ffrstpol +udata <SigSpec> dffD dffQ +udata <SigBit> dffclock +udata <Cell*> dff dffcemux dffrstmux +udata <bool> dffcepol dffrstpol + +match dsp + select dsp->type.in(\DSP48E1) +endmatch + +code unextend sigA sigB sigC sigD sigM + unextend = [](const SigSpec &sig) { + int i; + for (i = GetSize(sig)-1; i > 0; i--) + if (sig[i] != sig[i-1]) + break; + // Do not remove non-const sign bit + if (sig[i].wire) + ++i; + return sig.extract(0, i); + }; + sigA = unextend(port(dsp, \A)); + sigB = unextend(port(dsp, \B)); + + sigC = dsp->connections_.at(\C, SigSpec()); + sigD = dsp->connections_.at(\D, SigSpec()); + + SigSpec P = port(dsp, \P); + if (dsp->parameters.at(\USE_MULT, Const("MULTIPLY")).decode_string() == "MULTIPLY") { + // Only care about those bits that are used + int i; + for (i = 0; i < GetSize(P); i++) { + if (nusers(P[i]) <= 1) + break; + sigM.append(P[i]); + } + log_assert(nusers(P.extract_end(i)) <= 1); + } + else + sigM = P; +endcode + +code argQ ffAD ffADmux ffADcepol sigA clock + if (param(dsp, \ADREG).as_int() == 0) { + argQ = sigA; + subpattern(in_dffe); + if (dff) { + ffAD = dff; + clock = dffclock; + if (dffcemux) { + ffADmux = dffcemux; + ffADcepol = dffcepol; + } + sigA = dffD; + } + } +endcode + +match preAdd + if sigD.empty() || sigD.is_fully_zero() + // Ensure that preAdder not already used + if dsp->parameters.at(\USE_DPORT, Const("FALSE")).decode_string() == "FALSE" + if dsp->connections_.at(\INMODE, Const(0, 5)).is_fully_zero() + + select preAdd->type.in($add) + // Output has to be 25 bits or less + select GetSize(port(preAdd, \Y)) <= 25 + select nusers(port(preAdd, \Y)) == 2 + choice <IdString> AB {\A, \B} + // A port has to be 30 bits or less + select GetSize(port(preAdd, AB)) <= 30 + define <IdString> BA (AB == \A ? \B : \A) + // D port has to be 25 bits or less + select GetSize(port(preAdd, BA)) <= 25 + index <SigSpec> port(preAdd, \Y) === sigA + + optional +endmatch + +code sigA sigD + if (preAdd) { + sigA = port(preAdd, \A); + sigD = port(preAdd, \B); + if (GetSize(sigA) < GetSize(sigD)) + std::swap(sigA, sigD); + } +endcode + +code argQ ffA ffAmux ffAcepol sigA clock ffAD ffADmux ffADcepol + // Only search for ffA if there was a pre-adder + // (otherwise ffA would have been matched as ffAD) + if (preAdd) { + if (param(dsp, \AREG).as_int() == 0) { + argQ = sigA; + subpattern(in_dffe); + if (dff) { + ffA = dff; + clock = dffclock; + if (dffcemux) { + ffAmux = dffcemux; + ffAcepol = dffcepol; + } + sigA = dffD; + } + } + } + // And if there wasn't a pre-adder, + // move AD register to A + else if (ffAD) { + log_assert(!ffA && !ffAmux); + std::swap(ffA, ffAD); + std::swap(ffAmux, ffADmux); + ffAcepol = ffADcepol; + } +endcode + +code argQ ffB ffBmux ffBcepol sigB clock + if (param(dsp, \BREG).as_int() == 0) { + argQ = sigB; + subpattern(in_dffe); + if (dff) { + ffB = dff; + clock = dffclock; + if (dffcemux) { + ffBmux = dffcemux; + ffBcepol = dffcepol; + } + sigB = dffD; + } + } +endcode + +code argQ ffD ffDmux ffDcepol sigD clock + if (param(dsp, \DREG).as_int() == 0) { + argQ = sigD; + subpattern(in_dffe); + if (dff) { + ffD = dff; + clock = dffclock; + if (dffcemux) { + ffDmux = dffcemux; + ffDcepol = dffcepol; + } + sigD = dffD; + } + } +endcode + +code argD ffM ffMmux ffMcepol sigM sigP clock + if (param(dsp, \MREG).as_int() == 0 && nusers(sigM) == 2) { + argD = sigM; + subpattern(out_dffe); + if (dff) { + ffM = dff; + clock = dffclock; + if (dffcemux) { + ffMmux = dffcemux; + ffMcepol = dffcepol; + } + sigM = dffQ; + } + } + sigP = sigM; +endcode + +match postAdd + // Ensure that Z mux is not already used + if port(dsp, \OPMODE).extract(4,3).is_fully_zero() + + select postAdd->type.in($add) + select GetSize(port(postAdd, \Y)) <= 48 + select nusers(port(postAdd, \Y)) == 2 + choice <IdString> AB {\A, \B} + select nusers(port(postAdd, AB)) <= 3 + filter ffMmux || nusers(port(postAdd, AB)) == 2 + filter !ffMmux || nusers(port(postAdd, AB)) == 3 + filter GetSize(unextend(port(postAdd, AB))) <= GetSize(sigP) + filter unextend(port(postAdd, AB)) == sigP.extract(0, GetSize(unextend(port(postAdd, AB)))) + filter nusers(sigP.extract_end(GetSize(unextend(port(postAdd, AB))))) <= 1 + set postAddAB AB + optional +endmatch + +code sigC sigP + if (postAdd) { + sigC = port(postAdd, postAddAB == \A ? \B : \A); + + // TODO for DSP48E1, which will have sign extended inputs/outputs + //int natural_mul_width = GetSize(port(dsp, \A)) + GetSize(port(dsp, \B)); + //int actual_mul_width = GetSize(sigP); + //int actual_acc_width = GetSize(sigC); + + //if ((actual_acc_width > actual_mul_width) && (natural_mul_width > actual_mul_width)) + // reject; + //if ((actual_acc_width != actual_mul_width) && (param(dsp, \A_SIGNED).as_bool() != param(postAdd, \A_SIGNED).as_bool())) + // reject; + + sigP = port(postAdd, \Y); + } +endcode + +code argD ffP ffPcemux ffPrstmux ffPcepol ffPrstpol sigP clock + if (param(dsp, \PREG).as_int() == 0) { + // If ffMmux and no postAdd new-value net must have exactly three users: ffMmux, ffM and ffPcemux + if ((ffMmux && !postAdd && nusers(sigP) == 3) || + // Otherwise new-value net must have exactly two users: dsp and ffPcemux + ((!ffMmux || postAdd) && nusers(sigP) == 2)) { + argD = sigP; + subpattern(out_dffe); + if (dff) { + ffP = dff; + clock = dffclock; + if (dffcemux) { + ffPcemux = dffcemux; + ffPcepol = dffcepol; + ffPrstmux = dffrstmux; + ffPrstpol = dffrstpol; + } + sigP = dffQ; + } + } + } +endcode + +match postAddMux + if postAdd + if ffP + select postAddMux->type.in($mux) + select nusers(port(postAddMux, \Y)) == 2 + choice <IdString> AB {\A, \B} + index <SigSpec> port(postAddMux, AB) === sigP + index <SigSpec> port(postAddMux, \Y) === sigC + set postAddMuxAB AB + optional +endmatch + +code sigC + if (postAddMux) + sigC = port(postAddMux, postAddMuxAB == \A ? \B : \A); +endcode + +code argQ ffC ffCmux ffCcepol sigC clock + if (param(dsp, \CREG).as_int() == 0) { + argQ = sigC; + subpattern(in_dffe); + if (dff) { + ffC = dff; + clock = dffclock; + if (dffcemux) { + ffCmux = dffcemux; + ffCcepol = dffcepol; + } + sigC = dffD; + } + } +endcode + +code + accept; +endcode + +// ####################### + +subpattern in_dffe +arg argQ clock ffcepol + +match ff + select ff->type.in($dff) + // DSP48E1 does not support clock inversion + select param(ff, \CLK_POLARITY).as_bool() + filter GetSize(port(ff, \Q)) >= GetSize(argQ) + slice offset GetSize(port(ff, \Q)) + filter offset+GetSize(argQ) <= GetSize(port(ff, \Q)) + filter port(ff, \Q).extract(offset, GetSize(argQ)) == argQ + semioptional +endmatch + +code argQ + if (ff) { + for (auto c : argQ.chunks()) + if (c.wire->get_bool_attribute(\keep)) + reject; + + if (clock != SigBit()) { + if (port(ff, \CLK) != clock) + reject; + } + dffclock = port(ff, \CLK); + + dff = ff; + dffD = argQ; + dffD.replace(port(ff, \Q), port(ff, \D)); + // Only search for ffcemux if argQ has at + // least 3 users (ff, <upstream>, ffcemux) and + // its ff.D only has two (ff, ffcemux) + if (!(nusers(argQ) >= 3 && nusers(dffD) == 2)) + argQ = SigSpec(); + } + else { + dff = nullptr; + argQ = SigSpec(); + } +endcode + +match ffcemux + if !argQ.empty() + select ffcemux->type.in($mux) + index <SigSpec> port(ffcemux, \Y) === port(ff, \D) + filter GetSize(port(ffcemux, \Y)) >= GetSize(dffD) + slice offset GetSize(port(ffcemux, \Y)) + filter offset+GetSize(dffD) <= GetSize(port(ffcemux, \Y)) + filter port(ffcemux, \Y).extract(offset, GetSize(dffD)) == dffD + choice <IdString> AB {\A, \B} + filter offset+GetSize(argQ) <= GetSize(port(ffcemux, \Y)) + filter port(ffcemux, AB).extract(offset, GetSize(argQ)) == argQ + define <bool> pol (AB == \A) + set ffcepol pol + semioptional +endmatch + +code + if (ffcemux) { + dffcemux = ffcemux; + dffcepol = ffcepol; + dffD = port(ffcemux, dffcepol ? \B : \A); + } + else + dffcemux = nullptr; +endcode + +// ####################### + +subpattern out_dffe +arg argD argQ clock +arg unextend + +match ffcemux + select ffcemux->type.in($mux) + // ffcemux output must have two users: ffcemux and ff.D + select nusers(port(ffcemux, \Y)) == 2 + filter GetSize(port(ffcemux, \Y)) >= GetSize(argD) + + choice <IdString> BA {\B, \A} + // new-value net must have exactly two users: (upstream) and ffcemux + select nusers(port(ffcemux, BA)) == 2 + + define <IdString> AB (BA == \B ? \A : \B) + // keep-last-value net must have at least three users: ffcemux, ff, downstream sink(s) + select nusers(port(ffcemux, AB)) >= 3 + + slice offset GetSize(port(ffcemux, \Y)) + filter GetSize(unextend(port(ffcemux, BA))) <= GetSize(argD) + filter unextend(port(ffcemux, BA)) == argD.extract(0, GetSize(unextend(port(ffcemux, BA)))) + // Remaining bits on argD must not have any other users + filter nusers(argD.extract_end(GetSize(unextend(port(ffcemux, BA))))) <= 1 + + define <bool> pol (AB == \A) + set ffcepol pol + semioptional +endmatch + +code argD argQ + if (ffcemux) { + dffcemux = ffcemux; + dffcepol = ffcepol; + argD = port(ffcemux, \Y); + argQ = port(ffcemux, ffcepol ? \A : \B); + } + else + dffcemux = nullptr; +endcode + +match ffrstmux + if !argQ.empty() + select ffrstmux->type.in($mux) + // ffrstmux output must have two users: ffrstmux and ff.D + select nusers(port(ffrstmux, \Y)) == 2 + filter GetSize(port(ffrstmux, \Y)) >= GetSize(argD) + + choice <IdString> BA {\B, \A} + // DSP48E1 only supports reset to zero + select port(ffrstmux, BA).is_fully_zero() + + define <IdString> AB (BA == \B ? \A : \B) + // keep-last-value net must have exactly 2 users: ffrstmux, ffcemux/<upstream> + select nusers(port(ffrstmux, AB)) == 2 + + slice offset GetSize(port(ffrstmux, \Y)) + filter GetSize(port(ffrstmux, AB)) <= GetSize(argD) + filter port(ffrstmux, AB) == argD.extract(0, GetSize(port(ffrstmux, AB))) + // Remaining bits on argD must not have any other users + filter nusers(argD.extract_end(GetSize(port(ffrstmux, AB)))) <= 1 + + define <bool> pol (AB == \A) + set ffrstpol pol + semioptional +endmatch + +code argD argQ + if (ffrstmux) { + dffrstmux = ffrstmux; + dffrstpol = ffrstpol; + argD = port(ffrstmux, \Y); + } + else { + dffrstmux = nullptr; + argQ = SigSpec(); + } +endcode + +match ff_enable + if !argQ.empty() + select ff_enable->type.in($dff) + // DSP48E1 does not support clock inversion + select param(ff_enable, \CLK_POLARITY).as_bool() + index <SigSpec> port(ff_enable, \D) === argD + index <SigSpec> port(ff_enable, \Q) === argQ +endmatch + +match ff + if !ff_enable + select ff->type.in($dff) + // DSP48E1 does not support clock inversion + select param(ff, \CLK_POLARITY).as_bool() + index <SigSpec> port(ff, \D) === argD + semioptional +endmatch + +code + if (ff_enable) + dff = ff_enable; + else + dff = ff; + if (dff) { + dffQ = port(dff, \Q); + + for (auto c : dffQ.chunks()) { + if (c.wire->get_bool_attribute(\keep)) + reject; + Const init = c.wire->attributes.at(\init, State::Sx); + if (!init.is_fully_undef() && !init.is_fully_zero()) + reject; + } + + if (clock != SigBit()) { + if (port(dff, \CLK) != clock) + reject; + } + dffclock = port(dff, \CLK); + } + // No enable/reset mux possible without flop + else if (ffcemux || ffrstmux) + reject; +endcode diff --git a/passes/techmap/abc9.cc b/passes/techmap/abc9.cc index 6fdf987f0..7eac08d17 100644 --- a/passes/techmap/abc9.cc +++ b/passes/techmap/abc9.cc @@ -76,8 +76,7 @@ inline std::string remap_name(RTLIL::IdString abc_name) return stringf("$abc$%d$%s", map_autoidx, abc_name.c_str()+1); } -void handle_loops(RTLIL::Design *design, - const dict<IdString,pool<IdString>> &scc_break_inputs) +void handle_loops(RTLIL::Design *design) { Pass::call(design, "scc -set_attr abc_scc_id {}"); @@ -114,30 +113,6 @@ void handle_loops(RTLIL::Design *design, } cell->attributes.erase(it); } - - auto jt = scc_break_inputs.find(cell->type); - if (jt != scc_break_inputs.end()) - for (auto port_name : jt->second) { - RTLIL::SigSpec sig; - auto &rhs = cell->connections_.at(port_name); - for (auto b : rhs) { - Wire *w = b.wire; - if (!w) continue; - w->port_output = true; - w->set_bool_attribute(ID(abc_scc_break)); - w = module->wire(stringf("%s.abci", w->name.c_str())); - if (!w) { - w = module->addWire(stringf("%s.abci", b.wire->name.c_str()), GetSize(b.wire)); - w->port_input = true; - } - else { - log_assert(b.offset < GetSize(w)); - log_assert(w->port_input); - } - sig.append(RTLIL::SigBit(w, b.offset)); - } - rhs = sig; - } } module->fixup_ports(); @@ -272,8 +247,7 @@ void abc9_module(RTLIL::Design *design, RTLIL::Module *current_module, std::stri bool cleanup, vector<int> lut_costs, bool dff_mode, std::string clk_str, bool /*keepff*/, std::string delay_target, std::string /*lutin_shared*/, bool fast_mode, bool show_tempdir, std::string box_file, std::string lut_file, - std::string wire_delay, const dict<int,IdString> &box_lookup, - const dict<IdString,pool<IdString>> &scc_break_inputs + std::string wire_delay, const dict<int,IdString> &box_lookup ) { module = current_module; @@ -413,7 +387,7 @@ void abc9_module(RTLIL::Design *design, RTLIL::Module *current_module, std::stri RTLIL::Selection& sel = design->selection_stack.back(); sel.select(module); - handle_loops(design, scc_break_inputs); + handle_loops(design); Pass::call(design, "aigmap"); @@ -1050,9 +1024,6 @@ struct Abc9Pass : public Pass { } if (arg == "-box" && argidx+1 < args.size()) { box_file = args[++argidx]; - rewrite_filename(box_file); - if (!box_file.empty() && !is_absolute_path(box_file)) - box_file = std::string(pwd) + "/" + box_file; continue; } if (arg == "-W" && argidx+1 < args.size()) { @@ -1063,8 +1034,15 @@ struct Abc9Pass : public Pass { } extra_args(args, argidx, design); + // ABC expects a box file for XAIG + if (box_file.empty()) + box_file = "+/dummy.box"; + + rewrite_filename(box_file); + if (!box_file.empty() && !is_absolute_path(box_file)) + box_file = std::string(pwd) + "/" + box_file; + dict<int,IdString> box_lookup; - dict<IdString,pool<IdString>> scc_break_inputs; for (auto m : design->modules()) { auto it = m->attributes.find(ID(abc_box_id)); if (it == m->attributes.end()) @@ -1082,17 +1060,13 @@ struct Abc9Pass : public Pass { for (auto p : m->ports) { auto w = m->wire(p); log_assert(w); - if (w->port_input) { - if (w->attributes.count(ID(abc_scc_break))) - scc_break_inputs[m->name].insert(p); - if (w->attributes.count(ID(abc_carry))) { + if (w->attributes.count(ID(abc_carry))) { + if (w->port_input) { if (carry_in) log_error("Module '%s' contains more than one 'abc_carry' input port.\n", log_id(m)); carry_in = w; } - } - if (w->port_output) { - if (w->attributes.count(ID(abc_carry))) { + else if (w->port_output) { if (carry_out) log_error("Module '%s' contains more than one 'abc_carry' input port.\n", log_id(m)); carry_out = w; @@ -1144,7 +1118,7 @@ struct Abc9Pass : public Pass { if (!dff_mode || !clk_str.empty()) { abc9_module(design, mod, script_file, exe_file, cleanup, lut_costs, dff_mode, clk_str, keepff, delay_target, lutin_shared, fast_mode, show_tempdir, - box_file, lut_file, wire_delay, box_lookup, scc_break_inputs); + box_file, lut_file, wire_delay, box_lookup); continue; } @@ -1290,7 +1264,7 @@ struct Abc9Pass : public Pass { en_sig = assign_map(std::get<3>(it.first)); abc9_module(design, mod, script_file, exe_file, cleanup, lut_costs, !clk_sig.empty(), "$", keepff, delay_target, lutin_shared, fast_mode, show_tempdir, - box_file, lut_file, wire_delay, box_lookup, scc_break_inputs); + box_file, lut_file, wire_delay, box_lookup); assign_map.set(mod); } } diff --git a/passes/techmap/techmap.cc b/passes/techmap/techmap.cc index c4496f76f..51a65aea6 100644 --- a/passes/techmap/techmap.cc +++ b/passes/techmap/techmap.cc @@ -205,20 +205,38 @@ struct TechmapWorker } std::map<RTLIL::IdString, RTLIL::IdString> positional_ports; + dict<Wire*, IdString> temp_renamed_wires; for (auto &it : tpl->wires_) { if (it.second->port_id > 0) positional_ports[stringf("$%d", it.second->port_id)] = it.first; IdString w_name = it.second->name; apply_prefix(cell->name, w_name); - RTLIL::Wire *w = module->addWire(w_name, it.second); - w->port_input = false; - w->port_output = false; - w->port_id = 0; - if (it.second->get_bool_attribute(ID(_techmap_special_))) - w->attributes.clear(); - if (w->attributes.count(ID(src))) - w->add_strpool_attribute(ID(src), extra_src_attrs); + RTLIL::Wire *w = module->wire(w_name); + if (w != nullptr) { + if (!flatten_mode || !w->get_bool_attribute(ID(hierconn))) { + temp_renamed_wires[w] = w->name; + module->rename(w, NEW_ID); + w = nullptr; + } else { + w->attributes.erase(ID(hierconn)); + if (GetSize(w) < GetSize(it.second)) { + log_warning("Widening signal %s.%s to match size of %s.%s (via %s.%s).\n", log_id(module), log_id(w), + log_id(tpl), log_id(it.second), log_id(module), log_id(cell)); + w->width = GetSize(it.second); + } + } + } + if (w == nullptr) { + w = module->addWire(w_name, it.second); + w->port_input = false; + w->port_output = false; + w->port_id = 0; + if (it.second->get_bool_attribute(ID(_techmap_special_))) + w->attributes.clear(); + if (w->attributes.count(ID(src))) + w->add_strpool_attribute(ID(src), extra_src_attrs); + } design->select(module, w); } @@ -380,6 +398,16 @@ struct TechmapWorker } module->remove(cell); + + for (auto &it : temp_renamed_wires) + { + Wire *w = it.first; + IdString name = it.second; + IdString altname = module->uniquify(name); + Wire *other_w = module->wire(name); + module->rename(other_w, altname); + module->rename(w, name); + } } bool techmap_module(RTLIL::Design *design, RTLIL::Module *module, RTLIL::Design *map, std::set<RTLIL::Cell*> &handled_cells, @@ -396,6 +424,18 @@ struct TechmapWorker SigMap sigmap(module); + dict<SigBit, State> init_bits; + pool<SigBit> remove_init_bits; + + for (auto wire : module->wires()) { + if (wire->attributes.count("\\init")) { + Const value = wire->attributes.at("\\init"); + for (int i = 0; i < min(GetSize(value), GetSize(wire)); i++) + if (value[i] != State::Sx) + init_bits[sigmap(SigBit(wire, i))] = value[i]; + } + } + TopoSort<RTLIL::Cell*, RTLIL::IdString::compare_ptr_by_name<RTLIL::Cell>> cells; std::map<RTLIL::Cell*, std::set<RTLIL::SigBit>> cell_to_inbit; std::map<RTLIL::SigBit, std::set<RTLIL::Cell*>> outbit_to_cell; @@ -633,6 +673,17 @@ struct TechmapWorker bit = RTLIL::SigBit(RTLIL::State::Sx); parameters[stringf("\\_TECHMAP_CONSTVAL_%s_", RTLIL::id2cstr(conn.first))] = RTLIL::SigSpec(v).as_const(); } + if (tpl->avail_parameters.count(stringf("\\_TECHMAP_WIREINIT_%s_", RTLIL::id2cstr(conn.first))) != 0) { + auto sig = sigmap(conn.second); + RTLIL::Const value(State::Sx, sig.size()); + for (int i = 0; i < sig.size(); i++) { + auto it = init_bits.find(sig[i]); + if (it != init_bits.end()) { + value[i] = it->second; + } + } + parameters[stringf("\\_TECHMAP_WIREINIT_%s_", RTLIL::id2cstr(conn.first))] = value; + } } int unique_bit_id_counter = 0; @@ -833,12 +884,25 @@ struct TechmapWorker TechmapWires twd = techmap_find_special_wires(tpl); for (auto &it : twd) { - if (it.first != "_TECHMAP_FAIL_" && it.first.substr(0, 12) != "_TECHMAP_DO_" && it.first.substr(0, 14) != "_TECHMAP_DONE_") + if (it.first != "_TECHMAP_FAIL_" && (it.first.substr(0, 20) != "_TECHMAP_REMOVEINIT_" || it.first[it.first.size()-1] != '_') && it.first.substr(0, 12) != "_TECHMAP_DO_" && it.first.substr(0, 14) != "_TECHMAP_DONE_") log_error("Techmap yielded unknown config wire %s.\n", it.first.c_str()); if (techmap_do_cache[tpl]) for (auto &it2 : it.second) if (!it2.value.is_fully_const()) log_error("Techmap yielded config wire %s with non-const value %s.\n", RTLIL::id2cstr(it2.wire->name), log_signal(it2.value)); + if (it.first.substr(0, 20) == "_TECHMAP_REMOVEINIT_" && techmap_do_cache[tpl]) { + for (auto &it2 : it.second) { + auto val = it2.value.as_const(); + auto wirename = RTLIL::escape_id(it.first.substr(20, it.first.size() - 20 - 1)); + auto it = cell->connections().find(wirename); + if (it != cell->connections().end()) { + auto sig = sigmap(it->second); + for (int i = 0; i < sig.size(); i++) + if (val[i] == State::S1) + remove_init_bits.insert(sig[i]); + } + } + } techmap_wire_names.erase(it.first); } @@ -907,6 +971,25 @@ struct TechmapWorker handled_cells.insert(cell); } + if (!remove_init_bits.empty()) { + for (auto wire : module->wires()) + if (wire->attributes.count("\\init")) { + Const &value = wire->attributes.at("\\init"); + bool do_cleanup = true; + for (int i = 0; i < min(GetSize(value), GetSize(wire)); i++) { + SigBit bit = sigmap(SigBit(wire, i)); + if (remove_init_bits.count(bit)) + value[i] = State::Sx; + else if (value[i] != State::Sx) + do_cleanup = false; + } + if (do_cleanup) { + log("Removing init attribute from wire %s.%s.\n", log_id(module), log_id(wire)); + wire->attributes.erase("\\init"); + } + } + } + if (log_continue) { log_header(design, "Continuing TECHMAP pass.\n"); log_continue = false; @@ -1019,6 +1102,13 @@ struct TechmapPass : public Pass { log("\n"); log(" It is possible to combine both prefixes to 'RECURSION; CONSTMAP; '.\n"); log("\n"); + log(" _TECHMAP_REMOVEINIT_<port-name>_\n"); + log(" When this wire is set to a constant value, the init attribute of the wire(s)\n"); + log(" connected to this port will be consumed. This wire must have the same\n"); + log(" width as the given port, and for every bit that is set to 1 in the value,\n"); + log(" the corresponding init attribute bit will be changed to 1'bx. If all\n"); + log(" bits of an init attribute are left as x, it will be removed.\n"); + log("\n"); log("In addition to this special wires, techmap also supports special parameters in\n"); log("modules in the map file:\n"); log("\n"); @@ -1032,6 +1122,13 @@ struct TechmapPass : public Pass { log(" former has a 1-bit for each constant input bit and the latter has the\n"); log(" value for this bit. The unused bits of the latter are set to undef (x).\n"); log("\n"); + log(" _TECHMAP_WIREINIT_<port-name>_\n"); + log(" When a parameter with this name exists, it will be set to the initial\n"); + log(" value of the wire(s) connected to the given port, as specified by the init\n"); + log(" attribute. If the attribute doesn't exist, x will be filled for the\n"); + log(" missing bits. To remove the init attribute bits used, use the\n"); + log(" _TECHMAP_REMOVEINIT_*_ wires.\n"); + log("\n"); log(" _TECHMAP_BITS_CONNMAP_\n"); log(" _TECHMAP_CONNMAP_<port-name>_\n"); log(" For an N-bit port, the _TECHMAP_CONNMAP_<port-name>_ parameter, if it\n"); diff --git a/passes/tests/test_autotb.cc b/passes/tests/test_autotb.cc index bfb1d6642..2b6a86c25 100644 --- a/passes/tests/test_autotb.cc +++ b/passes/tests/test_autotb.cc @@ -345,9 +345,17 @@ struct TestAutotbBackend : public Backend { log("value after initialization. This can e.g. be used to force a reset signal\n"); log("low in order to explore more inner states in a state machine.\n"); log("\n"); + log("The attribute 'gentb_skip' can be attached to modules to suppress testbench\n"); + log("generation.\n"); + log("\n"); log(" -n <int>\n"); log(" number of iterations the test bench should run (default = 1000)\n"); log("\n"); + log(" -seed <int>\n"); + log(" seed used for pseudo-random number generation (default = 0).\n"); + log(" a value of 0 will cause an arbitrary seed to be chosen, based on\n"); + log(" the current system time.\n"); + log("\n"); } void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE { |