mirror of
https://github.com/YosysHQ/yosys
synced 2025-04-23 09:05:32 +00:00
Merge remote-tracking branch 'origin/master' into xaig_dff
This commit is contained in:
commit
1123c09588
45 changed files with 6255 additions and 294 deletions
2
passes/pmgen/.gitignore
vendored
2
passes/pmgen/.gitignore
vendored
|
@ -1 +1 @@
|
|||
/*_pm.h
|
||||
/*_pm.h
|
||||
|
|
|
@ -21,6 +21,14 @@ $(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 passes/pmgen/xilinx_dsp_CREG_pm.h passes/pmgen/xilinx_dsp_cascade_pm.h
|
||||
$(eval $(call add_extra_objs,passes/pmgen/xilinx_dsp_pm.h))
|
||||
$(eval $(call add_extra_objs,passes/pmgen/xilinx_dsp_CREG_pm.h))
|
||||
$(eval $(call add_extra_objs,passes/pmgen/xilinx_dsp_cascade_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))
|
||||
|
|
|
@ -29,19 +29,19 @@ void create_ice40_dsp(ice40_dsp_pm &pm)
|
|||
{
|
||||
auto &st = pm.st_ice40_dsp;
|
||||
|
||||
#if 0
|
||||
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, "--"));
|
||||
#endif
|
||||
|
||||
log("Checking %s.%s for iCE40 DSP inference.\n", log_id(pm.module), log_id(st.mul));
|
||||
|
||||
log_debug("ffA: %s %s %s\n", log_id(st.ffA, "--"), log_id(st.ffAholdmux, "--"), log_id(st.ffArstmux, "--"));
|
||||
log_debug("ffB: %s %s %s\n", log_id(st.ffB, "--"), log_id(st.ffBholdmux, "--"), log_id(st.ffBrstmux, "--"));
|
||||
log_debug("ffCD: %s %s\n", log_id(st.ffCD, "--"), log_id(st.ffCDholdmux, "--"));
|
||||
log_debug("mul: %s\n", log_id(st.mul, "--"));
|
||||
log_debug("ffFJKG: %s\n", log_id(st.ffFJKG, "--"));
|
||||
log_debug("ffH: %s\n", log_id(st.ffH, "--"));
|
||||
log_debug("add: %s\n", log_id(st.add, "--"));
|
||||
log_debug("mux: %s\n", log_id(st.mux, "--"));
|
||||
log_debug("ffO: %s %s %s\n", log_id(st.ffO, "--"), log_id(st.ffOholdmux, "--"), log_id(st.ffOrstmux, "--"));
|
||||
log_debug("\n");
|
||||
|
||||
if (GetSize(st.sigA) > 16) {
|
||||
log(" input A (%s) is too large (%d > 16).\n", log_signal(st.sigA), GetSize(st.sigA));
|
||||
return;
|
||||
|
@ -52,59 +52,85 @@ 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 == ID($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, ID(SB_MAC16));
|
||||
pm.module->swap_names(cell, st.mul);
|
||||
}
|
||||
else log_assert(cell->type == ID(SB_MAC16));
|
||||
|
||||
// SB_MAC16 Input Interface
|
||||
|
||||
SigSpec A = st.sigA;
|
||||
A.extend_u0(16, mul_signed);
|
||||
A.extend_u0(16, st.mul->getParam(ID(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(ID(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(ID::A, A);
|
||||
cell->setPort(ID::B, B);
|
||||
cell->setPort(ID(C), CD.extract(16, 16));
|
||||
cell->setPort(ID(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);
|
||||
cell->setParam(ID(A_REG), st.ffA ? State::S1 : State::S0);
|
||||
cell->setParam(ID(B_REG), st.ffB ? State::S1 : State::S0);
|
||||
cell->setParam(ID(C_REG), st.ffCD ? State::S1 : State::S0);
|
||||
cell->setParam(ID(D_REG), st.ffCD ? State::S1 : State::S0);
|
||||
|
||||
cell->setPort("\\AHOLD", State::S0);
|
||||
cell->setPort("\\BHOLD", State::S0);
|
||||
cell->setPort("\\CHOLD", State::S0);
|
||||
cell->setPort("\\DHOLD", State::S0);
|
||||
SigSpec AHOLD, BHOLD, CDHOLD;
|
||||
if (st.ffAholdmux)
|
||||
AHOLD = st.ffAholdpol ? st.ffAholdmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffAholdmux->getPort(ID(S)));
|
||||
else
|
||||
AHOLD = State::S0;
|
||||
if (st.ffBholdmux)
|
||||
BHOLD = st.ffBholdpol ? st.ffBholdmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffBholdmux->getPort(ID(S)));
|
||||
else
|
||||
BHOLD = State::S0;
|
||||
if (st.ffCDholdmux)
|
||||
CDHOLD = st.ffCDholdpol ? st.ffCDholdmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffCDholdmux->getPort(ID(S)));
|
||||
else
|
||||
CDHOLD = State::S0;
|
||||
cell->setPort(ID(AHOLD), AHOLD);
|
||||
cell->setPort(ID(BHOLD), BHOLD);
|
||||
cell->setPort(ID(CHOLD), CDHOLD);
|
||||
cell->setPort(ID(DHOLD), CDHOLD);
|
||||
|
||||
cell->setPort("\\IRSTTOP", State::S0);
|
||||
cell->setPort("\\IRSTBOT", State::S0);
|
||||
SigSpec IRSTTOP, IRSTBOT;
|
||||
if (st.ffArstmux)
|
||||
IRSTTOP = st.ffArstpol ? st.ffArstmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffArstmux->getPort(ID(S)));
|
||||
else
|
||||
IRSTTOP = State::S0;
|
||||
if (st.ffBrstmux)
|
||||
IRSTBOT = st.ffBrstpol ? st.ffBrstmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffBrstmux->getPort(ID(S)));
|
||||
else
|
||||
IRSTBOT = State::S0;
|
||||
cell->setPort(ID(IRSTTOP), IRSTTOP);
|
||||
cell->setPort(ID(IRSTBOT), IRSTBOT);
|
||||
|
||||
if (st.clock_vld)
|
||||
if (st.clock != SigBit())
|
||||
{
|
||||
cell->setPort("\\CLK", st.clock);
|
||||
cell->setPort("\\CE", State::S1);
|
||||
cell->setParam("\\NEG_TRIGGER", st.clock_pol ? State::S0 : State::S1);
|
||||
cell->setPort(ID(CLK), st.clock);
|
||||
cell->setPort(ID(CE), State::S1);
|
||||
cell->setParam(ID(NEG_TRIGGER), st.clock_pol ? State::S0 : State::S1);
|
||||
|
||||
log(" clock: %s (%s)", log_signal(st.clock), st.clock_pol ? "posedge" : "negedge");
|
||||
|
||||
|
@ -114,91 +140,137 @@ 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.ffCD)
|
||||
log(" ffCD:%s", log_id(st.ffCD));
|
||||
|
||||
if (st.ffS)
|
||||
log(" ffS:%s", log_id(st.ffS));
|
||||
if (st.ffFJKG)
|
||||
log(" ffFJKG:%s", log_id(st.ffFJKG));
|
||||
|
||||
if (st.ffH)
|
||||
log(" ffH:%s", log_id(st.ffH));
|
||||
|
||||
if (st.ffO)
|
||||
log(" ffO:%s", log_id(st.ffO));
|
||||
|
||||
log("\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
cell->setPort("\\CLK", State::S0);
|
||||
cell->setPort("\\CE", State::S0);
|
||||
cell->setParam("\\NEG_TRIGGER", State::S0);
|
||||
cell->setPort(ID(CLK), State::S0);
|
||||
cell->setPort(ID(CE), State::S0);
|
||||
cell->setParam(ID(NEG_TRIGGER), State::S0);
|
||||
}
|
||||
|
||||
// SB_MAC16 Cascade Interface
|
||||
|
||||
cell->setPort("\\SIGNEXTIN", State::Sx);
|
||||
cell->setPort("\\SIGNEXTOUT", pm.module->addWire(NEW_ID));
|
||||
cell->setPort(ID(SIGNEXTIN), State::Sx);
|
||||
cell->setPort(ID(SIGNEXTOUT), pm.module->addWire(NEW_ID));
|
||||
|
||||
cell->setPort("\\CI", State::Sx);
|
||||
cell->setPort("\\CO", pm.module->addWire(NEW_ID));
|
||||
cell->setPort(ID(CI), State::Sx);
|
||||
|
||||
cell->setPort("\\ACCUMCI", State::Sx);
|
||||
cell->setPort("\\ACCUMCO", pm.module->addWire(NEW_ID));
|
||||
cell->setPort(ID(ACCUMCI), State::Sx);
|
||||
cell->setPort(ID(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.add);
|
||||
// If we have a signed multiply-add, then perform sign extension
|
||||
if (st.add->getParam(ID(A_SIGNED)).as_bool() && st.add->getParam(ID(B_SIGNED)).as_bool())
|
||||
pm.module->connect(O[32], O[31]);
|
||||
else
|
||||
cell->setPort(ID(CO), O[32]);
|
||||
O.remove(O_width-1);
|
||||
}
|
||||
else
|
||||
cell->setPort(ID(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);
|
||||
cell->setPort(ID(O), O);
|
||||
|
||||
if (st.addAB) {
|
||||
log(" accumulator %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);
|
||||
bool accum = false;
|
||||
if (st.add) {
|
||||
accum = (st.ffO && st.add->getPort(st.addAB == ID::A ? ID::B : ID::A) == st.sigO);
|
||||
if (accum)
|
||||
log(" accumulator %s (%s)\n", log_id(st.add), log_id(st.add->type));
|
||||
else
|
||||
log(" adder %s (%s)\n", log_id(st.add), log_id(st.add->type));
|
||||
cell->setPort(ID(ADDSUBTOP), st.add->type == ID($add) ? State::S0 : State::S1);
|
||||
cell->setPort(ID(ADDSUBBOT), st.add->type == ID($add) ? State::S0 : State::S1);
|
||||
} else {
|
||||
cell->setPort("\\ADDSUBTOP", State::S0);
|
||||
cell->setPort("\\ADDSUBBOT", State::S0);
|
||||
cell->setPort(ID(ADDSUBTOP), State::S0);
|
||||
cell->setPort(ID(ADDSUBBOT), State::S0);
|
||||
}
|
||||
|
||||
cell->setPort("\\ORSTTOP", State::S0);
|
||||
cell->setPort("\\ORSTBOT", State::S0);
|
||||
SigSpec OHOLD;
|
||||
if (st.ffOholdmux)
|
||||
OHOLD = st.ffOholdpol ? st.ffOholdmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffOholdmux->getPort(ID(S)));
|
||||
else
|
||||
OHOLD = State::S0;
|
||||
cell->setPort(ID(OHOLDTOP), OHOLD);
|
||||
cell->setPort(ID(OHOLDBOT), OHOLD);
|
||||
|
||||
cell->setPort("\\OHOLDTOP", State::S0);
|
||||
cell->setPort("\\OHOLDBOT", State::S0);
|
||||
SigSpec ORST;
|
||||
if (st.ffOrstmux)
|
||||
ORST = st.ffOrstpol ? st.ffOrstmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffOrstmux->getPort(ID(S)));
|
||||
else
|
||||
ORST = State::S0;
|
||||
cell->setPort(ID(ORSTTOP), ORST);
|
||||
cell->setPort(ID(ORSTBOT), ORST);
|
||||
|
||||
SigSpec acc_reset = State::S0;
|
||||
if (st.muxA)
|
||||
acc_reset = st.muxA->getPort("\\S");
|
||||
if (st.muxB)
|
||||
acc_reset = pm.module->Not(NEW_ID, st.muxB->getPort("\\S"));
|
||||
|
||||
cell->setPort("\\OLOADTOP", acc_reset);
|
||||
cell->setPort("\\OLOADBOT", acc_reset);
|
||||
if (st.mux) {
|
||||
if (st.muxAB == ID::A)
|
||||
acc_reset = st.mux->getPort(ID(S));
|
||||
else
|
||||
acc_reset = pm.module->Not(NEW_ID, st.mux->getPort(ID(S)));
|
||||
}
|
||||
cell->setPort(ID(OLOADTOP), acc_reset);
|
||||
cell->setPort(ID(OLOADBOT), acc_reset);
|
||||
|
||||
// SB_MAC16 Remaining Parameters
|
||||
|
||||
cell->setParam("\\C_REG", State::S0);
|
||||
cell->setParam("\\D_REG", State::S0);
|
||||
cell->setParam(ID(TOP_8x8_MULT_REG), st.ffFJKG ? State::S1 : State::S0);
|
||||
cell->setParam(ID(BOT_8x8_MULT_REG), st.ffFJKG ? State::S1 : State::S0);
|
||||
cell->setParam(ID(PIPELINE_16x16_MULT_REG1), st.ffFJKG ? State::S1 : State::S0);
|
||||
cell->setParam(ID(PIPELINE_16x16_MULT_REG2), st.ffH ? State::S1 : 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("\\PIPELINE_16x16_MULT_REG2", State::S0);
|
||||
cell->setParam(ID(TOPADDSUB_LOWERINPUT), Const(2, 2));
|
||||
cell->setParam(ID(TOPADDSUB_UPPERINPUT), accum ? State::S0 : State::S1);
|
||||
cell->setParam(ID(TOPADDSUB_CARRYSELECT), Const(3, 2));
|
||||
|
||||
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_CARRYSELECT", Const(3, 2));
|
||||
cell->setParam(ID(BOTADDSUB_LOWERINPUT), Const(2, 2));
|
||||
cell->setParam(ID(BOTADDSUB_UPPERINPUT), accum ? State::S0 : State::S1);
|
||||
cell->setParam(ID(BOTADDSUB_CARRYSELECT), Const(0, 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_CARRYSELECT", Const(0, 2));
|
||||
cell->setParam(ID(MODE_8x8), State::S0);
|
||||
cell->setParam(ID(A_SIGNED), st.mul->getParam(ID(A_SIGNED)).as_bool());
|
||||
cell->setParam(ID(B_SIGNED), st.mul->getParam(ID(B_SIGNED)).as_bool());
|
||||
|
||||
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);
|
||||
if (st.ffO) {
|
||||
if (st.o_lo)
|
||||
cell->setParam(ID(TOPOUTPUT_SELECT), Const(st.add ? 0 : 3, 2));
|
||||
else
|
||||
cell->setParam(ID(TOPOUTPUT_SELECT), Const(1, 2));
|
||||
|
||||
pm.autoremove(st.mul);
|
||||
pm.autoremove(st.ffY);
|
||||
pm.autoremove(st.ffS);
|
||||
st.ffO->connections_.at(ID(Q)).replace(O, pm.module->addWire(NEW_ID, GetSize(O)));
|
||||
cell->setParam(ID(BOTOUTPUT_SELECT), Const(1, 2));
|
||||
}
|
||||
else {
|
||||
cell->setParam(ID(TOPOUTPUT_SELECT), Const(st.add ? 0 : 3, 2));
|
||||
cell->setParam(ID(BOTOUTPUT_SELECT), Const(st.add ? 0 : 3, 2));
|
||||
}
|
||||
|
||||
if (cell != st.mul)
|
||||
pm.autoremove(st.mul);
|
||||
else
|
||||
pm.blacklist(st.mul);
|
||||
pm.autoremove(st.ffFJKG);
|
||||
pm.autoremove(st.add);
|
||||
}
|
||||
|
||||
struct Ice40DspPass : public Pass {
|
||||
|
@ -209,7 +281,17 @@ struct Ice40DspPass : public Pass {
|
|||
log("\n");
|
||||
log(" ice40_dsp [options] [selection]\n");
|
||||
log("\n");
|
||||
log("Map multipliers and multiply-accumulate blocks to iCE40 DSP resources.\n");
|
||||
log("Map multipliers ($mul/SB_MAC16) and multiply-accumulate ($mul/SB_MAC16 + $add)\n");
|
||||
log("cells into iCE40 DSP resources.\n");
|
||||
log("Currently, only the 16x16 multiply mode is supported and not the 2 x 8x8 mode.\n");
|
||||
log("\n");
|
||||
log("Pack input registers (A, B, {C,D}; with optional hold), pipeline registers\n");
|
||||
log("({F,J,K,G}, H), output registers (O -- full 32-bits or lower 16-bits only; with\n");
|
||||
log("optional hold), and post-adder into into the SB_MAC16 resource.\n");
|
||||
log("\n");
|
||||
log("Multiply-accumulate operations using the post-adder with feedback on the {C,D}\n");
|
||||
log("input will be folded into the DSP. In this scenario only, resetting the\n");
|
||||
log("the accumulator to an arbitrary value can be inferred to use the {C,D} input.\n");
|
||||
log("\n");
|
||||
}
|
||||
void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
|
||||
|
|
|
@ -1,163 +1,574 @@
|
|||
pattern ice40_dsp
|
||||
|
||||
state <SigBit> clock
|
||||
state <bool> clock_pol clock_vld
|
||||
state <SigSpec> sigA sigB sigY sigS
|
||||
state <Cell*> addAB muxAB
|
||||
state <bool> clock_pol cd_signed o_lo
|
||||
state <SigSpec> sigA sigB sigCD sigH sigO
|
||||
state <Cell*> add mux
|
||||
state <IdString> addAB muxAB
|
||||
|
||||
state <bool> ffAholdpol ffBholdpol ffCDholdpol ffOholdpol
|
||||
state <bool> ffArstpol ffBrstpol ffCDrstpol ffOrstpol
|
||||
|
||||
state <Cell*> ffA ffAholdmux ffArstmux ffB ffBholdmux ffBrstmux ffCD ffCDholdmux
|
||||
state <Cell*> ffFJKG ffH ffO ffOholdmux ffOrstmux
|
||||
|
||||
// subpattern
|
||||
state <SigSpec> argQ argD
|
||||
state <bool> ffholdpol ffrstpol
|
||||
state <int> ffoffset
|
||||
udata <SigSpec> dffD dffQ
|
||||
udata <SigBit> dffclock
|
||||
udata <Cell*> dff dffholdmux dffrstmux
|
||||
udata <bool> dffholdpol dffrstpol dffclock_pol
|
||||
|
||||
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
|
||||
|
||||
match ffA
|
||||
select ffA->type.in($dff)
|
||||
// select nusers(port(ffA, \Q)) == 2
|
||||
index <SigSpec> port(ffA, \Q) === port(mul, \A)
|
||||
optional
|
||||
endmatch
|
||||
code sigA sigB sigH
|
||||
auto 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(mul, \A));
|
||||
sigB = unextend(port(mul, \B));
|
||||
|
||||
code sigA clock clock_pol clock_vld
|
||||
sigA = port(mul, \A);
|
||||
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;
|
||||
|
||||
if (ffA) {
|
||||
sigA = port(ffA, \D);
|
||||
// Only care about those bits that are used
|
||||
int i;
|
||||
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
|
||||
|
||||
clock = port(ffA, \CLK).as_bit();
|
||||
clock_pol = param(ffA, \CLK_POLARITY).as_bool();
|
||||
clock_vld = true;
|
||||
code argQ ffA ffAholdmux ffArstmux ffAholdpol ffArstpol sigA clock clock_pol
|
||||
if (mul->type != \SB_MAC16 || !param(mul, \A_REG).as_bool()) {
|
||||
argQ = sigA;
|
||||
subpattern(in_dffe);
|
||||
if (dff) {
|
||||
ffA = dff;
|
||||
clock = dffclock;
|
||||
clock_pol = dffclock_pol;
|
||||
if (dffrstmux) {
|
||||
ffArstmux = dffrstmux;
|
||||
ffArstpol = dffrstpol;
|
||||
}
|
||||
if (dffholdmux) {
|
||||
ffAholdmux = dffholdmux;
|
||||
ffAholdpol = dffholdpol;
|
||||
}
|
||||
sigA = dffD;
|
||||
}
|
||||
}
|
||||
endcode
|
||||
|
||||
match ffB
|
||||
select ffB->type.in($dff)
|
||||
// select nusers(port(ffB, \Q)) == 2
|
||||
index <SigSpec> port(ffB, \Q) === port(mul, \B)
|
||||
optional
|
||||
endmatch
|
||||
|
||||
code sigB clock clock_pol clock_vld
|
||||
sigB = port(mul, \B);
|
||||
|
||||
if (ffB) {
|
||||
sigB = port(ffB, \D);
|
||||
SigBit c = port(ffB, \CLK).as_bit();
|
||||
bool cp = param(ffB, \CLK_POLARITY).as_bool();
|
||||
|
||||
if (clock_vld && (c != clock || cp != clock_pol))
|
||||
reject;
|
||||
|
||||
clock = c;
|
||||
clock_pol = cp;
|
||||
clock_vld = true;
|
||||
code argQ ffB ffBholdmux ffBrstmux ffBholdpol ffBrstpol sigB clock clock_pol
|
||||
if (mul->type != \SB_MAC16 || !param(mul, \B_REG).as_bool()) {
|
||||
argQ = sigB;
|
||||
subpattern(in_dffe);
|
||||
if (dff) {
|
||||
ffB = dff;
|
||||
clock = dffclock;
|
||||
clock_pol = dffclock_pol;
|
||||
if (dffrstmux) {
|
||||
ffBrstmux = dffrstmux;
|
||||
ffBrstpol = dffrstpol;
|
||||
}
|
||||
if (dffholdmux) {
|
||||
ffBholdmux = dffholdmux;
|
||||
ffBholdpol = dffholdpol;
|
||||
}
|
||||
sigB = dffD;
|
||||
}
|
||||
}
|
||||
endcode
|
||||
|
||||
match ffY
|
||||
select ffY->type.in($dff)
|
||||
select nusers(port(ffY, \D)) == 2
|
||||
index <SigSpec> port(ffY, \D) === port(mul, \Y)
|
||||
optional
|
||||
endmatch
|
||||
code argD ffFJKG sigH clock clock_pol
|
||||
if (nusers(sigH) == 2 &&
|
||||
(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_REG1).as_bool()))) {
|
||||
argD = sigH;
|
||||
subpattern(out_dffe);
|
||||
if (dff) {
|
||||
// F/J/K/G do not have a CE-like (hold) input
|
||||
if (dffholdmux)
|
||||
goto reject_ffFJKG;
|
||||
|
||||
code sigY clock clock_pol clock_vld
|
||||
sigY = port(mul, \Y);
|
||||
// Reset signal of F/J (IRSTTOP) and K/G (IRSTBOT)
|
||||
// shared with A and B
|
||||
if ((ffArstmux != NULL) != (dffrstmux != NULL))
|
||||
goto reject_ffFJKG;
|
||||
if ((ffBrstmux != NULL) != (dffrstmux != NULL))
|
||||
goto reject_ffFJKG;
|
||||
if (ffArstmux) {
|
||||
if (port(ffArstmux, \S) != port(dffrstmux, \S))
|
||||
goto reject_ffFJKG;
|
||||
if (ffArstpol != dffrstpol)
|
||||
goto reject_ffFJKG;
|
||||
}
|
||||
if (ffBrstmux) {
|
||||
if (port(ffBrstmux, \S) != port(dffrstmux, \S))
|
||||
goto reject_ffFJKG;
|
||||
if (ffBrstpol != dffrstpol)
|
||||
goto reject_ffFJKG;
|
||||
}
|
||||
|
||||
if (ffY) {
|
||||
sigY = port(ffY, \Q);
|
||||
SigBit c = port(ffY, \CLK).as_bit();
|
||||
bool cp = param(ffY, \CLK_POLARITY).as_bool();
|
||||
ffFJKG = dff;
|
||||
clock = dffclock;
|
||||
clock_pol = dffclock_pol;
|
||||
sigH = dffQ;
|
||||
|
||||
if (clock_vld && (c != clock || cp != clock_pol))
|
||||
reject;
|
||||
|
||||
clock = c;
|
||||
clock_pol = cp;
|
||||
clock_vld = true;
|
||||
reject_ffFJKG: ;
|
||||
}
|
||||
}
|
||||
endcode
|
||||
|
||||
match addA
|
||||
select addA->type.in($add)
|
||||
select nusers(port(addA, \A)) == 2
|
||||
index <SigSpec> port(addA, \A) === sigY
|
||||
code argD ffH sigH sigO clock clock_pol
|
||||
if (ffFJKG && nusers(sigH) == 2 &&
|
||||
(mul->type != \SB_MAC16 || !param(mul, \PIPELINE_16x16_MULT_REG2).as_bool())) {
|
||||
argD = sigH;
|
||||
subpattern(out_dffe);
|
||||
if (dff) {
|
||||
// H does not have a CE-like (hold) input
|
||||
if (dffholdmux)
|
||||
goto reject_ffH;
|
||||
|
||||
// Reset signal of H (IRSTBOT) shared with B
|
||||
if ((ffBrstmux != NULL) != (dffrstmux != NULL))
|
||||
goto reject_ffH;
|
||||
if (ffBrstmux) {
|
||||
if (port(ffBrstmux, \S) != port(dffrstmux, \S))
|
||||
goto reject_ffH;
|
||||
if (ffBrstpol != dffrstpol)
|
||||
goto reject_ffH;
|
||||
}
|
||||
|
||||
ffH = dff;
|
||||
clock = dffclock;
|
||||
clock_pol = dffclock_pol;
|
||||
sigH = dffQ;
|
||||
|
||||
reject_ffH: ;
|
||||
}
|
||||
}
|
||||
|
||||
sigO = sigH;
|
||||
endcode
|
||||
|
||||
match add
|
||||
if mul->type != \SB_MAC16 || (param(mul, \TOPOUTPUT_SELECT).as_int() == 3 && param(mul, \BOTOUTPUT_SELECT).as_int() == 3)
|
||||
|
||||
select add->type.in($add)
|
||||
choice <IdString> AB {\A, \B}
|
||||
select nusers(port(add, AB)) == 2
|
||||
|
||||
index <SigBit> port(add, AB)[0] === sigH[0]
|
||||
filter GetSize(port(add, AB)) <= GetSize(sigH)
|
||||
filter port(add, AB) == sigH.extract(0, GetSize(port(add, AB)))
|
||||
filter nusers(sigH.extract_end(GetSize(port(add, AB)))) <= 1
|
||||
set addAB AB
|
||||
optional
|
||||
endmatch
|
||||
|
||||
match addB
|
||||
if !addA
|
||||
select addB->type.in($add, $sub)
|
||||
select nusers(port(addB, \B)) == 2
|
||||
index <SigSpec> port(addB, \B) === sigY
|
||||
optional
|
||||
endmatch
|
||||
code sigCD sigO cd_signed
|
||||
if (add) {
|
||||
sigCD = port(add, addAB == \A ? \B : \A);
|
||||
cd_signed = param(add, addAB == \A ? \B_SIGNED : \A_SIGNED).as_bool();
|
||||
|
||||
code addAB sigS
|
||||
if (addA) {
|
||||
addAB = addA;
|
||||
sigS = port(addA, \B);
|
||||
}
|
||||
if (addB) {
|
||||
addAB = addB;
|
||||
sigS = port(addB, \A);
|
||||
}
|
||||
if (addAB) {
|
||||
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(add, \A_SIGNED).as_bool()))
|
||||
reject;
|
||||
|
||||
sigO = port(add, \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)
|
||||
match mux
|
||||
select mux->type == $mux
|
||||
choice <IdString> AB {\A, \B}
|
||||
select nusers(port(mux, AB)) == 2
|
||||
index <SigSpec> port(mux, AB) === sigO
|
||||
set muxAB AB
|
||||
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)
|
||||
optional
|
||||
endmatch
|
||||
|
||||
code muxAB
|
||||
muxAB = addAB;
|
||||
if (muxA)
|
||||
muxAB = muxA;
|
||||
if (muxB)
|
||||
muxAB = muxB;
|
||||
code sigO
|
||||
if (mux)
|
||||
sigO = port(mux, \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
|
||||
endmatch
|
||||
code argD ffO ffOholdmux ffOrstmux ffOholdpol ffOrstpol sigO sigCD clock clock_pol cd_signed o_lo
|
||||
if (mul->type != \SB_MAC16 ||
|
||||
// Ensure that register is not already used
|
||||
((param(mul, \TOPOUTPUT_SELECT, 0).as_int() != 1 && param(mul, \BOTOUTPUT_SELECT, 0).as_int() != 1) &&
|
||||
// Ensure that OLOADTOP/OLOADBOT is unused or zero
|
||||
(port(mul, \OLOADTOP, State::S0).is_fully_zero() && port(mul, \OLOADBOT, State::S0).is_fully_zero()))) {
|
||||
|
||||
code clock clock_pol clock_vld
|
||||
if (ffS) {
|
||||
SigBit c = port(ffS, \CLK).as_bit();
|
||||
bool cp = param(ffS, \CLK_POLARITY).as_bool();
|
||||
dff = nullptr;
|
||||
|
||||
if (clock_vld && (c != clock || cp != clock_pol))
|
||||
reject;
|
||||
// First try entire sigO
|
||||
if (nusers(sigO) == 2) {
|
||||
argD = sigO;
|
||||
subpattern(out_dffe);
|
||||
}
|
||||
|
||||
clock = c;
|
||||
clock_pol = cp;
|
||||
clock_vld = true;
|
||||
// Otherwise try just its least significant 16 bits
|
||||
if (!dff && GetSize(sigO) > 16) {
|
||||
argD = sigO.extract(0, 16);
|
||||
if (nusers(argD) == 2) {
|
||||
subpattern(out_dffe);
|
||||
o_lo = dff;
|
||||
}
|
||||
}
|
||||
|
||||
if (dff) {
|
||||
ffO = dff;
|
||||
clock = dffclock;
|
||||
clock_pol = dffclock_pol;
|
||||
if (dffrstmux) {
|
||||
ffOrstmux = dffrstmux;
|
||||
ffOrstpol = dffrstpol;
|
||||
}
|
||||
if (dffholdmux) {
|
||||
ffOholdmux = dffholdmux;
|
||||
ffOholdpol = dffholdpol;
|
||||
}
|
||||
|
||||
sigO.replace(sigO.extract(0, GetSize(dffQ)), dffQ);
|
||||
}
|
||||
|
||||
// Loading value into output register is not
|
||||
// supported unless using accumulator
|
||||
if (mux) {
|
||||
if (sigCD != sigO)
|
||||
reject;
|
||||
sigCD = port(mux, muxAB == \B ? \A : \B);
|
||||
|
||||
cd_signed = add && param(add, \A_SIGNED).as_bool() && param(add, \B_SIGNED).as_bool();
|
||||
}
|
||||
}
|
||||
endcode
|
||||
|
||||
code argQ ffCD ffCDholdmux ffCDholdpol ffCDrstpol sigCD clock clock_pol
|
||||
if (!sigCD.empty() && sigCD != sigO &&
|
||||
(mul->type != \SB_MAC16 || (!param(mul, \C_REG).as_bool() && !param(mul, \D_REG).as_bool()))) {
|
||||
argQ = sigCD;
|
||||
subpattern(in_dffe);
|
||||
if (dff) {
|
||||
if (dffholdmux) {
|
||||
ffCDholdmux = dffholdmux;
|
||||
ffCDholdpol = dffholdpol;
|
||||
}
|
||||
|
||||
// Reset signal of C (IRSTTOP) and D (IRSTBOT)
|
||||
// shared with A and B
|
||||
if ((ffArstmux != NULL) != (dffrstmux != NULL))
|
||||
goto reject_ffCD;
|
||||
if ((ffBrstmux != NULL) != (dffrstmux != NULL))
|
||||
goto reject_ffCD;
|
||||
if (ffArstmux) {
|
||||
if (port(ffArstmux, \S) != port(dffrstmux, \S))
|
||||
goto reject_ffCD;
|
||||
if (ffArstpol != dffrstpol)
|
||||
goto reject_ffCD;
|
||||
}
|
||||
if (ffBrstmux) {
|
||||
if (port(ffBrstmux, \S) != port(dffrstmux, \S))
|
||||
goto reject_ffCD;
|
||||
if (ffBrstpol != dffrstpol)
|
||||
goto reject_ffCD;
|
||||
}
|
||||
|
||||
ffCD = dff;
|
||||
clock = dffclock;
|
||||
clock_pol = dffclock_pol;
|
||||
sigCD = dffD;
|
||||
|
||||
reject_ffCD: ;
|
||||
}
|
||||
}
|
||||
endcode
|
||||
|
||||
code sigCD
|
||||
sigCD.extend_u0(32, cd_signed);
|
||||
endcode
|
||||
|
||||
code
|
||||
accept;
|
||||
endcode
|
||||
|
||||
// #######################
|
||||
|
||||
subpattern in_dffe
|
||||
arg argD argQ clock clock_pol
|
||||
|
||||
code
|
||||
dff = nullptr;
|
||||
for (auto c : argQ.chunks()) {
|
||||
if (!c.wire)
|
||||
reject;
|
||||
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;
|
||||
}
|
||||
endcode
|
||||
|
||||
match ff
|
||||
select ff->type.in($dff)
|
||||
// DSP48E1 does not support clock inversion
|
||||
select param(ff, \CLK_POLARITY).as_bool()
|
||||
|
||||
slice offset GetSize(port(ff, \D))
|
||||
index <SigBit> port(ff, \Q)[offset] === argQ[0]
|
||||
|
||||
// Check that the rest of argQ is present
|
||||
filter GetSize(port(ff, \Q)) >= offset + GetSize(argQ)
|
||||
filter port(ff, \Q).extract(offset, GetSize(argQ)) == argQ
|
||||
|
||||
set ffoffset offset
|
||||
endmatch
|
||||
|
||||
code argQ argD
|
||||
{
|
||||
if (clock != SigBit()) {
|
||||
if (port(ff, \CLK) != clock)
|
||||
reject;
|
||||
if (param(ff, \CLK_POLARITY).as_bool() != clock_pol)
|
||||
reject;
|
||||
}
|
||||
|
||||
SigSpec Q = port(ff, \Q);
|
||||
dff = ff;
|
||||
dffclock = port(ff, \CLK);
|
||||
dffclock_pol = param(ff, \CLK_POLARITY).as_bool();
|
||||
dffD = argQ;
|
||||
argD = port(ff, \D);
|
||||
argQ = Q;
|
||||
dffD.replace(argQ, argD);
|
||||
// Only search for ffrstmux if dffD only
|
||||
// has two (ff, ffrstmux) users
|
||||
if (nusers(dffD) > 2)
|
||||
argD = SigSpec();
|
||||
}
|
||||
endcode
|
||||
|
||||
match ffrstmux
|
||||
if false /* TODO: ice40 resets are actually async */
|
||||
|
||||
if !argD.empty()
|
||||
select ffrstmux->type.in($mux)
|
||||
index <SigSpec> port(ffrstmux, \Y) === argD
|
||||
|
||||
choice <IdString> BA {\B, \A}
|
||||
// DSP48E1 only supports reset to zero
|
||||
select port(ffrstmux, BA).is_fully_zero()
|
||||
|
||||
define <bool> pol (BA == \B)
|
||||
set ffrstpol pol
|
||||
semioptional
|
||||
endmatch
|
||||
|
||||
code argD
|
||||
if (ffrstmux) {
|
||||
dffrstmux = ffrstmux;
|
||||
dffrstpol = ffrstpol;
|
||||
argD = port(ffrstmux, ffrstpol ? \A : \B);
|
||||
dffD.replace(port(ffrstmux, \Y), argD);
|
||||
|
||||
// Only search for ffholdmux if argQ has at
|
||||
// least 3 users (ff, <upstream>, ffrstmux) and
|
||||
// dffD only has two (ff, ffrstmux)
|
||||
if (!(nusers(argQ) >= 3 && nusers(dffD) == 2))
|
||||
argD = SigSpec();
|
||||
}
|
||||
else
|
||||
dffrstmux = nullptr;
|
||||
endcode
|
||||
|
||||
match ffholdmux
|
||||
if !argD.empty()
|
||||
select ffholdmux->type.in($mux)
|
||||
index <SigSpec> port(ffholdmux, \Y) === argD
|
||||
choice <IdString> BA {\B, \A}
|
||||
index <SigSpec> port(ffholdmux, BA) === argQ
|
||||
define <bool> pol (BA == \B)
|
||||
set ffholdpol pol
|
||||
semioptional
|
||||
endmatch
|
||||
|
||||
code argD
|
||||
if (ffholdmux) {
|
||||
dffholdmux = ffholdmux;
|
||||
dffholdpol = ffholdpol;
|
||||
argD = port(ffholdmux, ffholdpol ? \A : \B);
|
||||
dffD.replace(port(ffholdmux, \Y), argD);
|
||||
}
|
||||
else
|
||||
dffholdmux = nullptr;
|
||||
endcode
|
||||
|
||||
// #######################
|
||||
|
||||
subpattern out_dffe
|
||||
arg argD argQ clock clock_pol
|
||||
|
||||
code
|
||||
dff = nullptr;
|
||||
for (auto c : argD.chunks())
|
||||
if (c.wire->get_bool_attribute(\keep))
|
||||
reject;
|
||||
endcode
|
||||
|
||||
match ffholdmux
|
||||
select ffholdmux->type.in($mux)
|
||||
// ffholdmux output must have two users: ffholdmux and ff.D
|
||||
select nusers(port(ffholdmux, \Y)) == 2
|
||||
|
||||
choice <IdString> BA {\B, \A}
|
||||
// keep-last-value net must have at least three users: ffholdmux, ff, downstream sink(s)
|
||||
select nusers(port(ffholdmux, BA)) >= 3
|
||||
|
||||
slice offset GetSize(port(ffholdmux, \Y))
|
||||
define <IdString> AB (BA == \B ? \A : \B)
|
||||
index <SigBit> port(ffholdmux, AB)[offset] === argD[0]
|
||||
|
||||
// Check that the rest of argD is present
|
||||
filter GetSize(port(ffholdmux, AB)) >= offset + GetSize(argD)
|
||||
filter port(ffholdmux, AB).extract(offset, GetSize(argD)) == argD
|
||||
|
||||
set ffoffset offset
|
||||
define <bool> pol (BA == \B)
|
||||
set ffholdpol pol
|
||||
|
||||
semioptional
|
||||
endmatch
|
||||
|
||||
code argD argQ
|
||||
dffholdmux = ffholdmux;
|
||||
if (ffholdmux) {
|
||||
SigSpec AB = port(ffholdmux, ffholdpol ? \A : \B);
|
||||
SigSpec Y = port(ffholdmux, \Y);
|
||||
argQ = argD;
|
||||
argD.replace(AB, Y);
|
||||
argQ.replace(AB, port(ffholdmux, ffholdpol ? \B : \A));
|
||||
|
||||
dffholdmux = ffholdmux;
|
||||
dffholdpol = ffholdpol;
|
||||
}
|
||||
endcode
|
||||
|
||||
match ffrstmux
|
||||
if false /* TODO: ice40 resets are actually async */
|
||||
|
||||
select ffrstmux->type.in($mux)
|
||||
// ffrstmux output must have two users: ffrstmux and ff.D
|
||||
select nusers(port(ffrstmux, \Y)) == 2
|
||||
|
||||
choice <IdString> BA {\B, \A}
|
||||
// DSP48E1 only supports reset to zero
|
||||
select port(ffrstmux, BA).is_fully_zero()
|
||||
|
||||
slice offset GetSize(port(ffrstmux, \Y))
|
||||
define <IdString> AB (BA == \B ? \A : \B)
|
||||
index <SigBit> port(ffrstmux, AB)[offset] === argD[0]
|
||||
|
||||
// Check that offset is consistent
|
||||
filter !ffholdmux || ffoffset == offset
|
||||
// Check that the rest of argD is present
|
||||
filter GetSize(port(ffrstmux, AB)) >= offset + GetSize(argD)
|
||||
filter port(ffrstmux, AB).extract(offset, GetSize(argD)) == argD
|
||||
|
||||
set ffoffset offset
|
||||
define <bool> pol (AB == \A)
|
||||
set ffrstpol pol
|
||||
|
||||
semioptional
|
||||
endmatch
|
||||
|
||||
code argD argQ
|
||||
dffrstmux = ffrstmux;
|
||||
if (ffrstmux) {
|
||||
SigSpec AB = port(ffrstmux, ffrstpol ? \A : \B);
|
||||
SigSpec Y = port(ffrstmux, \Y);
|
||||
argD.replace(AB, Y);
|
||||
|
||||
dffrstmux = ffrstmux;
|
||||
dffrstpol = ffrstpol;
|
||||
}
|
||||
endcode
|
||||
|
||||
match ff
|
||||
select ff->type.in($dff)
|
||||
// DSP48E1 does not support clock inversion
|
||||
select param(ff, \CLK_POLARITY).as_bool()
|
||||
|
||||
slice offset GetSize(port(ff, \D))
|
||||
index <SigBit> port(ff, \D)[offset] === argD[0]
|
||||
|
||||
// Check that offset is consistent
|
||||
filter (!ffholdmux && !ffrstmux) || ffoffset == offset
|
||||
// Check that the rest of argD is present
|
||||
filter GetSize(port(ff, \D)) >= offset + GetSize(argD)
|
||||
filter port(ff, \D).extract(offset, GetSize(argD)) == argD
|
||||
// Check that FF.Q is connected to CE-mux
|
||||
filter !ffholdmux || port(ff, \Q).extract(offset, GetSize(argQ)) == argQ
|
||||
|
||||
set ffoffset offset
|
||||
endmatch
|
||||
|
||||
code argQ
|
||||
if (ff) {
|
||||
if (clock != SigBit()) {
|
||||
if (port(ff, \CLK) != clock)
|
||||
reject;
|
||||
if (param(ff, \CLK_POLARITY).as_bool() != clock_pol)
|
||||
reject;
|
||||
}
|
||||
SigSpec D = port(ff, \D);
|
||||
SigSpec Q = port(ff, \Q);
|
||||
if (!ffholdmux) {
|
||||
argQ = argD;
|
||||
argQ.replace(D, Q);
|
||||
}
|
||||
|
||||
for (auto c : argQ.chunks()) {
|
||||
Const init = c.wire->attributes.at(\init, State::Sx);
|
||||
if (!init.is_fully_undef() && !init.is_fully_zero())
|
||||
reject;
|
||||
}
|
||||
|
||||
dff = ff;
|
||||
dffQ = argQ;
|
||||
dffclock = port(ff, \CLK);
|
||||
dffclock_pol = param(ff, \CLK_POLARITY).as_bool();
|
||||
}
|
||||
// No enable/reset mux possible without flop
|
||||
else if (dffholdmux || dffrstmux)
|
||||
reject;
|
||||
endcode
|
||||
|
|
|
@ -286,7 +286,7 @@ def process_pmgfile(f, filename):
|
|||
block["gencode"].append(rewrite_cpp(l.rstrip()))
|
||||
break
|
||||
|
||||
assert False
|
||||
raise RuntimeError("'%s' statement not recognised on line %d" % (a[0], linenr))
|
||||
|
||||
if block["optional"]:
|
||||
assert not block["semioptional"]
|
||||
|
@ -305,7 +305,8 @@ def process_pmgfile(f, filename):
|
|||
block["states"] = set()
|
||||
|
||||
for s in line.split()[1:]:
|
||||
assert s in state_types[current_pattern]
|
||||
if s not in state_types[current_pattern]:
|
||||
raise RuntimeError("'%s' not in state_types" % s)
|
||||
block["states"].add(s)
|
||||
|
||||
codetype = "code"
|
||||
|
@ -327,7 +328,7 @@ def process_pmgfile(f, filename):
|
|||
blocks.append(block)
|
||||
continue
|
||||
|
||||
assert False
|
||||
raise RuntimeError("'%s' command not recognised" % cmd)
|
||||
|
||||
for fn in pmgfiles:
|
||||
with open(fn, "r") as f:
|
||||
|
@ -452,11 +453,19 @@ with open(outfile, "w") as f:
|
|||
print(" return sigmap(cell->getPort(portname));", file=f)
|
||||
print(" }", file=f)
|
||||
print("", file=f)
|
||||
print(" SigSpec port(Cell *cell, IdString portname, const SigSpec& defval) {", file=f)
|
||||
print(" return sigmap(cell->connections_.at(portname, defval));", file=f)
|
||||
print(" }", file=f)
|
||||
print("", file=f)
|
||||
|
||||
print(" Const param(Cell *cell, IdString paramname) {", file=f)
|
||||
print(" return cell->getParam(paramname);", file=f)
|
||||
print(" }", file=f)
|
||||
print("", file=f)
|
||||
print(" Const param(Cell *cell, IdString paramname, const Const& defval) {", file=f)
|
||||
print(" return cell->parameters.at(paramname, defval);", file=f)
|
||||
print(" }", file=f)
|
||||
print("", file=f)
|
||||
|
||||
print(" int nusers(const SigSpec &sig) {", file=f)
|
||||
print(" pool<Cell*> users;", file=f)
|
||||
|
|
637
passes/pmgen/xilinx_dsp.cc
Normal file
637
passes/pmgen/xilinx_dsp.cc
Normal file
|
@ -0,0 +1,637 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
|
||||
* 2019 Eddie Hung <eddie@fpgeh.com>
|
||||
*
|
||||
* 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"
|
||||
#include "passes/pmgen/xilinx_dsp_CREG_pm.h"
|
||||
#include "passes/pmgen/xilinx_dsp_cascade_pm.h"
|
||||
|
||||
static Cell* addDsp(Module *module) {
|
||||
Cell *cell = module->addCell(NEW_ID, ID(DSP48E1));
|
||||
cell->setParam(ID(ACASCREG), 0);
|
||||
cell->setParam(ID(ADREG), 0);
|
||||
cell->setParam(ID(A_INPUT), Const("DIRECT"));
|
||||
cell->setParam(ID(ALUMODEREG), 0);
|
||||
cell->setParam(ID(AREG), 0);
|
||||
cell->setParam(ID(BCASCREG), 0);
|
||||
cell->setParam(ID(B_INPUT), Const("DIRECT"));
|
||||
cell->setParam(ID(BREG), 0);
|
||||
cell->setParam(ID(CARRYINREG), 0);
|
||||
cell->setParam(ID(CARRYINSELREG), 0);
|
||||
cell->setParam(ID(CREG), 0);
|
||||
cell->setParam(ID(DREG), 0);
|
||||
cell->setParam(ID(INMODEREG), 0);
|
||||
cell->setParam(ID(MREG), 0);
|
||||
cell->setParam(ID(OPMODEREG), 0);
|
||||
cell->setParam(ID(PREG), 0);
|
||||
cell->setParam(ID(USE_MULT), Const("NONE"));
|
||||
cell->setParam(ID(USE_SIMD), Const("ONE48"));
|
||||
cell->setParam(ID(USE_DPORT), Const("FALSE"));
|
||||
|
||||
cell->setPort(ID(D), Const(0, 25));
|
||||
cell->setPort(ID(INMODE), Const(0, 5));
|
||||
cell->setPort(ID(ALUMODE), Const(0, 4));
|
||||
cell->setPort(ID(OPMODE), Const(0, 7));
|
||||
cell->setPort(ID(CARRYINSEL), Const(0, 3));
|
||||
cell->setPort(ID(ACIN), Const(0, 30));
|
||||
cell->setPort(ID(BCIN), Const(0, 18));
|
||||
cell->setPort(ID(PCIN), Const(0, 48));
|
||||
cell->setPort(ID(CARRYIN), Const(0, 1));
|
||||
return cell;
|
||||
}
|
||||
|
||||
void xilinx_simd_pack(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(ID($add), ID($sub)))
|
||||
continue;
|
||||
SigSpec Y = cell->getPort(ID(Y));
|
||||
if (!Y.is_chunk())
|
||||
continue;
|
||||
if (!Y.as_chunk().wire->get_strpool_attribute(ID(use_dsp)).count("simd"))
|
||||
continue;
|
||||
if (GetSize(Y) > 25)
|
||||
continue;
|
||||
SigSpec A = cell->getPort(ID(A));
|
||||
SigSpec B = cell->getPort(ID(B));
|
||||
if (GetSize(Y) <= 13) {
|
||||
if (GetSize(A) > 12)
|
||||
continue;
|
||||
if (GetSize(B) > 12)
|
||||
continue;
|
||||
if (cell->type == ID($add))
|
||||
simd12_add.push_back(cell);
|
||||
else if (cell->type == ID($sub))
|
||||
simd12_sub.push_back(cell);
|
||||
}
|
||||
else if (GetSize(Y) <= 25) {
|
||||
if (GetSize(A) > 24)
|
||||
continue;
|
||||
if (GetSize(B) > 24)
|
||||
continue;
|
||||
if (cell->type == ID($add))
|
||||
simd24_add.push_back(cell);
|
||||
else if (cell->type == ID($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(ID(A));
|
||||
SigSpec B = lane->getPort(ID(B));
|
||||
SigSpec Y = lane->getPort(ID(Y));
|
||||
A.extend_u0(12, lane->getParam(ID(A_SIGNED)).as_bool());
|
||||
B.extend_u0(12, lane->getParam(ID(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(ID(USE_SIMD), Const("FOUR12"));
|
||||
// X = A:B
|
||||
// Y = 0
|
||||
// Z = C
|
||||
cell->setPort(ID(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(ID(A), AB.extract(18, 30));
|
||||
cell->setPort(ID(B), AB.extract(0, 18));
|
||||
cell->setPort(ID(C), C);
|
||||
cell->setPort(ID(P), P);
|
||||
cell->setPort(ID(CARRYOUT), CARRYOUT);
|
||||
if (lane1->type == ID($sub))
|
||||
cell->setPort(ID(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(ID(A));
|
||||
SigSpec B = lane->getPort(ID(B));
|
||||
SigSpec Y = lane->getPort(ID(Y));
|
||||
A.extend_u0(24, lane->getParam(ID(A_SIGNED)).as_bool());
|
||||
B.extend_u0(24, lane->getParam(ID(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(ID(USE_SIMD), Const("TWO24"));
|
||||
// X = A:B
|
||||
// Y = 0
|
||||
// Z = C
|
||||
cell->setPort(ID(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(ID(A), AB.extract(18, 30));
|
||||
cell->setPort(ID(B), AB.extract(0, 18));
|
||||
cell->setPort(ID(C), C);
|
||||
cell->setPort(ID(P), P);
|
||||
cell->setPort(ID(CARRYOUT), CARRYOUT);
|
||||
if (lane1->type == ID($sub))
|
||||
cell->setPort(ID(ALUMODE), Const::from_string("0011"));
|
||||
|
||||
module->remove(lane1);
|
||||
module->remove(lane2);
|
||||
|
||||
module->design->select(module, cell);
|
||||
}
|
||||
};
|
||||
g24(simd24_add);
|
||||
g24(simd24_sub);
|
||||
}
|
||||
|
||||
void xilinx_dsp_pack(xilinx_dsp_pm &pm)
|
||||
{
|
||||
auto &st = pm.st_xilinx_dsp_pack;
|
||||
|
||||
log("Analysing %s.%s for Xilinx DSP packing.\n", log_id(pm.module), log_id(st.dsp));
|
||||
|
||||
log_debug("preAdd: %s\n", log_id(st.preAdd, "--"));
|
||||
log_debug("ffAD: %s %s %s\n", log_id(st.ffAD, "--"), log_id(st.ffADcemux, "--"), log_id(st.ffADrstmux, "--"));
|
||||
log_debug("ffA2: %s %s %s\n", log_id(st.ffA2, "--"), log_id(st.ffA2cemux, "--"), log_id(st.ffA2rstmux, "--"));
|
||||
log_debug("ffA1: %s %s %s\n", log_id(st.ffA1, "--"), log_id(st.ffA1cemux, "--"), log_id(st.ffA1rstmux, "--"));
|
||||
log_debug("ffB2: %s %s %s\n", log_id(st.ffB2, "--"), log_id(st.ffB2cemux, "--"), log_id(st.ffB2rstmux, "--"));
|
||||
log_debug("ffB1: %s %s %s\n", log_id(st.ffB1, "--"), log_id(st.ffB1cemux, "--"), log_id(st.ffB1rstmux, "--"));
|
||||
log_debug("ffD: %s %s %s\n", log_id(st.ffD, "--"), log_id(st.ffDcemux, "--"), log_id(st.ffDrstmux, "--"));
|
||||
log_debug("dsp: %s\n", log_id(st.dsp, "--"));
|
||||
log_debug("ffM: %s %s %s\n", log_id(st.ffM, "--"), log_id(st.ffMcemux, "--"), log_id(st.ffMrstmux, "--"));
|
||||
log_debug("postAdd: %s\n", log_id(st.postAdd, "--"));
|
||||
log_debug("postAddMux: %s\n", log_id(st.postAddMux, "--"));
|
||||
log_debug("ffP: %s %s %s\n", log_id(st.ffP, "--"), log_id(st.ffPcemux, "--"), log_id(st.ffPrstmux, "--"));
|
||||
log_debug("overflow: %s\n", log_id(st.overflow, "--"));
|
||||
|
||||
Cell *cell = st.dsp;
|
||||
|
||||
if (st.preAdd) {
|
||||
log(" preadder %s (%s)\n", log_id(st.preAdd), log_id(st.preAdd->type));
|
||||
bool A_SIGNED = st.preAdd->getParam(ID(A_SIGNED)).as_bool();
|
||||
bool D_SIGNED = st.preAdd->getParam(ID(B_SIGNED)).as_bool();
|
||||
if (st.sigA == st.preAdd->getPort(ID(B)))
|
||||
std::swap(A_SIGNED, D_SIGNED);
|
||||
st.sigA.extend_u0(30, A_SIGNED);
|
||||
st.sigD.extend_u0(25, D_SIGNED);
|
||||
cell->setPort(ID(A), st.sigA);
|
||||
cell->setPort(ID(D), st.sigD);
|
||||
cell->setPort(ID(INMODE), Const::from_string("00100"));
|
||||
|
||||
if (st.ffAD) {
|
||||
if (st.ffADcemux) {
|
||||
SigSpec S = st.ffADcemux->getPort(ID(S));
|
||||
cell->setPort(ID(CEAD), st.ffADcepol ? S : pm.module->Not(NEW_ID, S));
|
||||
}
|
||||
else
|
||||
cell->setPort(ID(CEAD), State::S1);
|
||||
cell->setParam(ID(ADREG), 1);
|
||||
}
|
||||
|
||||
cell->setParam(ID(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(ID(OPMODE));
|
||||
if (st.postAddMux) {
|
||||
log_assert(st.ffP);
|
||||
opmode[4] = st.postAddMux->getPort(ID(S));
|
||||
pm.autoremove(st.postAddMux);
|
||||
}
|
||||
else if (st.ffP && st.sigC == st.sigP)
|
||||
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 == ID(A))
|
||||
st.sigC.extend_u0(48, st.postAdd->getParam(ID(B_SIGNED)).as_bool());
|
||||
else
|
||||
st.sigC.extend_u0(48, st.postAdd->getParam(ID(A_SIGNED)).as_bool());
|
||||
cell->setPort(ID(C), st.sigC);
|
||||
}
|
||||
|
||||
pm.autoremove(st.postAdd);
|
||||
}
|
||||
if (st.overflow) {
|
||||
log(" overflow %s (%s)\n", log_id(st.overflow), log_id(st.overflow->type));
|
||||
cell->setParam(ID(USE_PATTERN_DETECT), Const("PATDET"));
|
||||
cell->setParam(ID(SEL_PATTERN), Const("PATTERN"));
|
||||
cell->setParam(ID(SEL_MASK), Const("MASK"));
|
||||
|
||||
if (st.overflow->type == ID($ge)) {
|
||||
Const B = st.overflow->getPort(ID(B)).as_const();
|
||||
log_assert(std::count(B.bits.begin(), B.bits.end(), State::S1) == 1);
|
||||
// Since B is an exact power of 2, subtract 1
|
||||
// by inverting all bits up until hitting
|
||||
// that one hi bit
|
||||
for (auto &b : B.bits)
|
||||
if (b == State::S0) b = State::S1;
|
||||
else if (b == State::S1) {
|
||||
b = State::S0;
|
||||
break;
|
||||
}
|
||||
B.extu(48);
|
||||
|
||||
cell->setParam(ID(MASK), B);
|
||||
cell->setParam(ID(PATTERN), Const(0, 48));
|
||||
cell->setPort(ID(OVERFLOW), st.overflow->getPort(ID(Y)));
|
||||
}
|
||||
else log_abort();
|
||||
|
||||
pm.autoremove(st.overflow);
|
||||
}
|
||||
|
||||
if (st.clock != SigBit())
|
||||
{
|
||||
cell->setPort(ID(CLK), st.clock);
|
||||
|
||||
auto f = [&pm,cell](SigSpec &A, Cell* ff, Cell* cemux, bool cepol, IdString ceport, Cell* rstmux, bool rstpol, IdString rstport) {
|
||||
SigSpec D = ff->getPort(ID(D));
|
||||
SigSpec Q = pm.sigmap(ff->getPort(ID(Q)));
|
||||
if (!A.empty())
|
||||
A.replace(Q, D);
|
||||
if (rstmux) {
|
||||
SigSpec Y = rstmux->getPort(ID(Y));
|
||||
SigSpec AB = rstmux->getPort(rstpol ? ID(A) : ID(B));
|
||||
if (!A.empty())
|
||||
A.replace(Y, AB);
|
||||
if (rstport != IdString()) {
|
||||
SigSpec S = rstmux->getPort(ID(S));
|
||||
cell->setPort(rstport, rstpol ? S : pm.module->Not(NEW_ID, S));
|
||||
}
|
||||
}
|
||||
else if (rstport != IdString())
|
||||
cell->setPort(rstport, State::S0);
|
||||
if (cemux) {
|
||||
SigSpec Y = cemux->getPort(ID(Y));
|
||||
SigSpec BA = cemux->getPort(cepol ? ID(B) : ID(A));
|
||||
SigSpec S = cemux->getPort(ID(S));
|
||||
if (!A.empty())
|
||||
A.replace(Y, BA);
|
||||
cell->setPort(ceport, cepol ? S : pm.module->Not(NEW_ID, S));
|
||||
}
|
||||
else
|
||||
cell->setPort(ceport, State::S1);
|
||||
|
||||
for (auto c : Q.chunks()) {
|
||||
auto it = c.wire->attributes.find(ID(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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (st.ffA2) {
|
||||
SigSpec A = cell->getPort(ID(A));
|
||||
f(A, st.ffA2, st.ffA2cemux, st.ffA2cepol, ID(CEA2), st.ffA2rstmux, st.ffArstpol, ID(RSTA));
|
||||
if (st.ffA1) {
|
||||
f(A, st.ffA1, st.ffA1cemux, st.ffA1cepol, ID(CEA1), st.ffA1rstmux, st.ffArstpol, IdString());
|
||||
cell->setParam(ID(AREG), 2);
|
||||
cell->setParam(ID(ACASCREG), 2);
|
||||
}
|
||||
else {
|
||||
cell->setParam(ID(AREG), 1);
|
||||
cell->setParam(ID(ACASCREG), 1);
|
||||
}
|
||||
pm.add_siguser(A, cell);
|
||||
cell->setPort(ID(A), A);
|
||||
}
|
||||
if (st.ffB2) {
|
||||
SigSpec B = cell->getPort(ID(B));
|
||||
f(B, st.ffB2, st.ffB2cemux, st.ffB2cepol, ID(CEB2), st.ffB2rstmux, st.ffBrstpol, ID(RSTB));
|
||||
if (st.ffB1) {
|
||||
f(B, st.ffB1, st.ffB1cemux, st.ffB1cepol, ID(CEB1), st.ffB1rstmux, st.ffBrstpol, IdString());
|
||||
cell->setParam(ID(BREG), 2);
|
||||
cell->setParam(ID(BCASCREG), 2);
|
||||
}
|
||||
else {
|
||||
cell->setParam(ID(BREG), 1);
|
||||
cell->setParam(ID(BCASCREG), 1);
|
||||
}
|
||||
pm.add_siguser(B, cell);
|
||||
cell->setPort(ID(B), B);
|
||||
}
|
||||
if (st.ffD) {
|
||||
SigSpec D = cell->getPort(ID(D));
|
||||
f(D, st.ffD, st.ffDcemux, st.ffDcepol, ID(CED), st.ffDrstmux, st.ffDrstpol, ID(RSTD));
|
||||
pm.add_siguser(D, cell);
|
||||
cell->setPort(ID(D), D);
|
||||
cell->setParam(ID(DREG), 1);
|
||||
}
|
||||
if (st.ffM) {
|
||||
SigSpec M; // unused
|
||||
f(M, st.ffM, st.ffMcemux, st.ffMcepol, ID(CEM), st.ffMrstmux, st.ffMrstpol, ID(RSTM));
|
||||
st.ffM->connections_.at(ID(Q)).replace(st.sigM, pm.module->addWire(NEW_ID, GetSize(st.sigM)));
|
||||
cell->setParam(ID(MREG), State::S1);
|
||||
}
|
||||
if (st.ffP) {
|
||||
SigSpec P; // unused
|
||||
f(P, st.ffP, st.ffPcemux, st.ffPcepol, ID(CEP), st.ffPrstmux, st.ffPrstpol, ID(RSTP));
|
||||
st.ffP->connections_.at(ID(Q)).replace(st.sigP, pm.module->addWire(NEW_ID, GetSize(st.sigP)));
|
||||
cell->setParam(ID(PREG), State::S1);
|
||||
}
|
||||
|
||||
log(" clock: %s (%s)", log_signal(st.clock), "posedge");
|
||||
|
||||
if (st.ffA2) {
|
||||
log(" ffA2:%s", log_id(st.ffA2));
|
||||
if (st.ffA1)
|
||||
log(" ffA1:%s", log_id(st.ffA1));
|
||||
}
|
||||
|
||||
if (st.ffAD)
|
||||
log(" ffAD:%s", log_id(st.ffAD));
|
||||
|
||||
if (st.ffB2) {
|
||||
log(" ffB2:%s", log_id(st.ffB2));
|
||||
if (st.ffB1)
|
||||
log(" ffB1:%s", log_id(st.ffB1));
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
SigSpec P = st.sigP;
|
||||
if (GetSize(P) < 48)
|
||||
P.append(pm.module->addWire(NEW_ID, 48-GetSize(P)));
|
||||
cell->setPort(ID(P), P);
|
||||
|
||||
pm.blacklist(cell);
|
||||
}
|
||||
|
||||
void xilinx_dsp_packC(xilinx_dsp_CREG_pm &pm)
|
||||
{
|
||||
auto &st = pm.st_xilinx_dsp_packC;
|
||||
|
||||
log_debug("Analysing %s.%s for Xilinx DSP packing (CREG).\n", log_id(pm.module), log_id(st.dsp));
|
||||
log_debug("ffC: %s %s %s\n", log_id(st.ffC, "--"), log_id(st.ffCcemux, "--"), log_id(st.ffCrstmux, "--"));
|
||||
|
||||
Cell *cell = st.dsp;
|
||||
|
||||
if (st.clock != SigBit())
|
||||
{
|
||||
cell->setPort(ID(CLK), st.clock);
|
||||
|
||||
auto f = [&pm,cell](SigSpec &A, Cell* ff, Cell* cemux, bool cepol, IdString ceport, Cell* rstmux, bool rstpol, IdString rstport) {
|
||||
SigSpec D = ff->getPort(ID(D));
|
||||
SigSpec Q = pm.sigmap(ff->getPort(ID(Q)));
|
||||
if (!A.empty())
|
||||
A.replace(Q, D);
|
||||
if (rstmux) {
|
||||
SigSpec Y = rstmux->getPort(ID(Y));
|
||||
SigSpec AB = rstmux->getPort(rstpol ? ID(A) : ID(B));
|
||||
if (!A.empty())
|
||||
A.replace(Y, AB);
|
||||
if (rstport != IdString()) {
|
||||
SigSpec S = rstmux->getPort(ID(S));
|
||||
cell->setPort(rstport, rstpol ? S : pm.module->Not(NEW_ID, S));
|
||||
}
|
||||
}
|
||||
else if (rstport != IdString())
|
||||
cell->setPort(rstport, State::S0);
|
||||
if (cemux) {
|
||||
SigSpec Y = cemux->getPort(ID(Y));
|
||||
SigSpec BA = cemux->getPort(cepol ? ID(B) : ID(A));
|
||||
SigSpec S = cemux->getPort(ID(S));
|
||||
if (!A.empty())
|
||||
A.replace(Y, BA);
|
||||
cell->setPort(ceport, cepol ? S : pm.module->Not(NEW_ID, S));
|
||||
}
|
||||
else
|
||||
cell->setPort(ceport, State::S1);
|
||||
|
||||
for (auto c : Q.chunks()) {
|
||||
auto it = c.wire->attributes.find(ID(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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (st.ffC) {
|
||||
SigSpec C = cell->getPort(ID(C));
|
||||
f(C, st.ffC, st.ffCcemux, st.ffCcepol, ID(CEC), st.ffCrstmux, st.ffCrstpol, ID(RSTC));
|
||||
pm.add_siguser(C, cell);
|
||||
cell->setPort(ID(C), C);
|
||||
cell->setParam(ID(CREG), 1);
|
||||
}
|
||||
|
||||
log(" clock: %s (%s)", log_signal(st.clock), "posedge");
|
||||
|
||||
if (st.ffC)
|
||||
log(" ffC:%s", log_id(st.ffC));
|
||||
log("\n");
|
||||
}
|
||||
|
||||
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 (A2, A1, B2, B1, C, D, AD; with optional enable/reset),\n");
|
||||
log("pipeline registers (M; with optional enable/reset), output registers (P; with\n");
|
||||
log("optional enable/reset), 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 current accumulation result with a new value, which will\n");
|
||||
log("be added to the multiplier result to form the next accumulation result.\n");
|
||||
log("\n");
|
||||
log("Use of the dedicated 'PCOUT' -> 'PCIN' cascade path is detected for 'P' -> 'C'\n");
|
||||
log("connections (optionally, where 'P' is right-shifted by 17-bits and used as an\n");
|
||||
log("input to the post-adder -- a pattern common for summing partial products to\n");
|
||||
log("implement wide multipliers). Limited support also exists for similar cascading\n");
|
||||
log("for A and B using '[AB]COUT' -> '[AB]CIN'. Currently, cascade chains are limited\n");
|
||||
log("to a maximum length of 20 cells, corresponding to the smallest Xilinx 7 Series\n");
|
||||
log("device.\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");
|
||||
log("Experimental feature: the presence of a `$ge' cell attached to the registered\n");
|
||||
log("P output implementing the operation \"(P >= <power-of-2>)\" will be transformed\n");
|
||||
log("into using the DSP48E1's pattern detector feature for overflow detection.\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()) {
|
||||
xilinx_simd_pack(module, module->selected_cells());
|
||||
|
||||
{
|
||||
xilinx_dsp_pm pm(module, module->selected_cells());
|
||||
pm.run_xilinx_dsp_pack(xilinx_dsp_pack);
|
||||
}
|
||||
// Separating out CREG packing is necessary since there
|
||||
// is no guarantee that the cell ordering corresponds
|
||||
// to the "expected" case (i.e. the order in which
|
||||
// they appear in the source) thus the possiblity
|
||||
// existed that a register got packed as CREG into a
|
||||
// downstream DSP that should have otherwise been a
|
||||
// PREG of an upstream DSP that had not been pattern
|
||||
// matched yet
|
||||
{
|
||||
xilinx_dsp_CREG_pm pm(module, module->selected_cells());
|
||||
pm.run_xilinx_dsp_packC(xilinx_dsp_packC);
|
||||
}
|
||||
{
|
||||
xilinx_dsp_cascade_pm pm(module, module->selected_cells());
|
||||
pm.run_xilinx_dsp_cascade();
|
||||
}
|
||||
}
|
||||
}
|
||||
} XilinxDspPass;
|
||||
|
||||
PRIVATE_NAMESPACE_END
|
587
passes/pmgen/xilinx_dsp.pmg
Normal file
587
passes/pmgen/xilinx_dsp.pmg
Normal file
|
@ -0,0 +1,587 @@
|
|||
pattern xilinx_dsp_pack
|
||||
|
||||
state <SigBit> clock
|
||||
state <SigSpec> sigA sigB sigC sigD sigM sigP
|
||||
state <IdString> postAddAB postAddMuxAB
|
||||
state <bool> ffA1cepol ffA2cepol ffADcepol ffB1cepol ffB2cepol ffDcepol ffMcepol ffPcepol
|
||||
state <bool> ffArstpol ffADrstpol ffBrstpol ffDrstpol ffMrstpol ffPrstpol
|
||||
|
||||
state <Cell*> ffAD ffADcemux ffADrstmux ffA1 ffA1cemux ffA1rstmux ffA2 ffA2cemux ffA2rstmux
|
||||
state <Cell*> ffB1 ffB1cemux ffB1rstmux ffB2 ffB2cemux ffB2rstmux
|
||||
state <Cell*> ffD ffDcemux ffDrstmux ffM ffMcemux ffMrstmux ffP ffPcemux ffPrstmux
|
||||
|
||||
// subpattern
|
||||
state <SigSpec> argQ argD
|
||||
state <bool> ffcepol ffrstpol
|
||||
state <int> ffoffset
|
||||
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 sigA sigB sigC sigD sigM clock
|
||||
auto 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 = port(dsp, \C, SigSpec());
|
||||
sigD = port(dsp, \D, SigSpec());
|
||||
|
||||
SigSpec P = port(dsp, \P);
|
||||
if (param(dsp, \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;
|
||||
// This sigM could have no users if downstream $add
|
||||
// is narrower than $mul result, for example
|
||||
if (sigM.empty())
|
||||
reject;
|
||||
|
||||
clock = port(dsp, \CLK, SigBit());
|
||||
endcode
|
||||
|
||||
code argQ ffAD ffADcemux ffADrstmux ffADcepol ffADrstpol sigA clock
|
||||
if (param(dsp, \ADREG).as_int() == 0) {
|
||||
argQ = sigA;
|
||||
subpattern(in_dffe);
|
||||
if (dff) {
|
||||
ffAD = dff;
|
||||
clock = dffclock;
|
||||
if (dffrstmux) {
|
||||
ffADrstmux = dffrstmux;
|
||||
ffADrstpol = dffrstpol;
|
||||
}
|
||||
if (dffcemux) {
|
||||
ffADcemux = dffcemux;
|
||||
ffADcepol = dffcepol;
|
||||
}
|
||||
sigA = dffD;
|
||||
}
|
||||
}
|
||||
endcode
|
||||
|
||||
match preAdd
|
||||
if sigD.empty() || sigD.is_fully_zero()
|
||||
// Ensure that preAdder not already used
|
||||
if param(dsp, \USE_DPORT, Const("FALSE")).decode_string() == "FALSE"
|
||||
if port(dsp, \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 ffAD ffADcemux ffADrstmux ffADcepol ffADrstpol sigA clock ffA2 ffA2cemux ffA2rstmux ffA2cepol ffArstpol ffA1 ffA1cemux ffA1rstmux ffA1cepol
|
||||
// Only search for ffA2 if there was a pre-adder
|
||||
// (otherwise ffA2 would have been matched as ffAD)
|
||||
if (preAdd) {
|
||||
if (param(dsp, \AREG).as_int() == 0) {
|
||||
argQ = sigA;
|
||||
subpattern(in_dffe);
|
||||
if (dff) {
|
||||
ffA2 = dff;
|
||||
clock = dffclock;
|
||||
if (dffrstmux) {
|
||||
ffA2rstmux = dffrstmux;
|
||||
ffArstpol = dffrstpol;
|
||||
}
|
||||
if (dffcemux) {
|
||||
ffA2cepol = dffcepol;
|
||||
ffA2cemux = dffcemux;
|
||||
}
|
||||
sigA = dffD;
|
||||
}
|
||||
}
|
||||
}
|
||||
// And if there wasn't a pre-adder,
|
||||
// move AD register to A
|
||||
else if (ffAD) {
|
||||
log_assert(!ffA2 && !ffA2cemux && !ffA2rstmux);
|
||||
std::swap(ffA2, ffAD);
|
||||
std::swap(ffA2cemux, ffADcemux);
|
||||
std::swap(ffA2rstmux, ffADrstmux);
|
||||
ffA2cepol = ffADcepol;
|
||||
ffArstpol = ffADrstpol;
|
||||
}
|
||||
|
||||
// Now attempt to match A1
|
||||
if (ffA2) {
|
||||
argQ = sigA;
|
||||
subpattern(in_dffe);
|
||||
if (dff) {
|
||||
if ((ffA2rstmux != nullptr) ^ (dffrstmux != nullptr))
|
||||
goto ffA1_end;
|
||||
if (dffrstmux) {
|
||||
if (ffArstpol != dffrstpol)
|
||||
goto ffA1_end;
|
||||
if (port(ffA2rstmux, \S) != port(dffrstmux, \S))
|
||||
goto ffA1_end;
|
||||
ffA1rstmux = dffrstmux;
|
||||
}
|
||||
|
||||
ffA1 = dff;
|
||||
clock = dffclock;
|
||||
|
||||
if (dffcemux) {
|
||||
ffA1cemux = dffcemux;
|
||||
ffA1cepol = dffcepol;
|
||||
}
|
||||
sigA = dffD;
|
||||
|
||||
ffA1_end: ;
|
||||
}
|
||||
}
|
||||
endcode
|
||||
|
||||
code argQ ffB2 ffB2cemux ffB2rstmux ffB2cepol ffBrstpol sigB clock ffB1 ffB1cemux ffB1rstmux ffB1cepol
|
||||
if (param(dsp, \BREG).as_int() == 0) {
|
||||
argQ = sigB;
|
||||
subpattern(in_dffe);
|
||||
if (dff) {
|
||||
ffB2 = dff;
|
||||
clock = dffclock;
|
||||
if (dffrstmux) {
|
||||
ffB2rstmux = dffrstmux;
|
||||
ffBrstpol = dffrstpol;
|
||||
}
|
||||
if (dffcemux) {
|
||||
ffB2cemux = dffcemux;
|
||||
ffB2cepol = dffcepol;
|
||||
}
|
||||
sigB = dffD;
|
||||
|
||||
// Now attempt to match B1
|
||||
if (ffB2) {
|
||||
argQ = sigB;
|
||||
subpattern(in_dffe);
|
||||
if (dff) {
|
||||
if ((ffB2rstmux != nullptr) ^ (dffrstmux != nullptr))
|
||||
goto ffB1_end;
|
||||
if (dffrstmux) {
|
||||
if (ffBrstpol != dffrstpol)
|
||||
goto ffB1_end;
|
||||
if (port(ffB2rstmux, \S) != port(dffrstmux, \S))
|
||||
goto ffB1_end;
|
||||
ffB1rstmux = dffrstmux;
|
||||
}
|
||||
|
||||
ffB1 = dff;
|
||||
clock = dffclock;
|
||||
|
||||
if (dffcemux) {
|
||||
ffB1cemux = dffcemux;
|
||||
ffB1cepol = dffcepol;
|
||||
}
|
||||
sigB = dffD;
|
||||
|
||||
ffB1_end: ;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
endcode
|
||||
|
||||
code argQ ffD ffDcemux ffDrstmux ffDcepol ffDrstpol sigD clock
|
||||
if (param(dsp, \DREG).as_int() == 0) {
|
||||
argQ = sigD;
|
||||
subpattern(in_dffe);
|
||||
if (dff) {
|
||||
ffD = dff;
|
||||
clock = dffclock;
|
||||
if (dffrstmux) {
|
||||
ffDrstmux = dffrstmux;
|
||||
ffDrstpol = dffrstpol;
|
||||
}
|
||||
if (dffcemux) {
|
||||
ffDcemux = dffcemux;
|
||||
ffDcepol = dffcepol;
|
||||
}
|
||||
sigD = dffD;
|
||||
}
|
||||
}
|
||||
endcode
|
||||
|
||||
code argD ffM ffMcemux ffMrstmux ffMcepol ffMrstpol 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 (dffrstmux) {
|
||||
ffMrstmux = dffrstmux;
|
||||
ffMrstpol = dffrstpol;
|
||||
}
|
||||
if (dffcemux) {
|
||||
ffMcemux = dffcemux;
|
||||
ffMcepol = dffcepol;
|
||||
}
|
||||
sigM = dffQ;
|
||||
}
|
||||
}
|
||||
sigP = sigM;
|
||||
endcode
|
||||
|
||||
match postAdd
|
||||
// Ensure that Z mux is not already used
|
||||
if port(dsp, \OPMODE, SigSpec()).extract(4,3).is_fully_zero()
|
||||
|
||||
select postAdd->type.in($add)
|
||||
select GetSize(port(postAdd, \Y)) <= 48
|
||||
choice <IdString> AB {\A, \B}
|
||||
select nusers(port(postAdd, AB)) <= 3
|
||||
filter ffMcemux || nusers(port(postAdd, AB)) == 2
|
||||
filter !ffMcemux || nusers(port(postAdd, AB)) == 3
|
||||
|
||||
index <SigBit> port(postAdd, AB)[0] === sigP[0]
|
||||
filter GetSize(port(postAdd, AB)) >= GetSize(sigP)
|
||||
filter port(postAdd, AB).extract(0, GetSize(sigP)) == sigP
|
||||
filter port(postAdd, AB).extract_end(GetSize(sigP)) == SigSpec(sigP[GetSize(sigP)-1], GetSize(port(postAdd, AB))-GetSize(sigP))
|
||||
set postAddAB AB
|
||||
optional
|
||||
endmatch
|
||||
|
||||
code sigC sigP
|
||||
if (postAdd) {
|
||||
sigC = port(postAdd, postAddAB == \A ? \B : \A);
|
||||
sigP = port(postAdd, \Y);
|
||||
}
|
||||
endcode
|
||||
|
||||
code argD ffP ffPcemux ffPrstmux ffPcepol ffPrstpol sigP clock
|
||||
if (param(dsp, \PREG).as_int() == 0) {
|
||||
int users = 2;
|
||||
// If ffMcemux and no postAdd new-value net must have three users: ffMcemux, ffM and ffPcemux
|
||||
if (ffMcemux && !postAdd) users++;
|
||||
if (nusers(sigP) == users) {
|
||||
argD = sigP;
|
||||
subpattern(out_dffe);
|
||||
if (dff) {
|
||||
ffP = dff;
|
||||
clock = dffclock;
|
||||
if (dffrstmux) {
|
||||
ffPrstmux = dffrstmux;
|
||||
ffPrstpol = dffrstpol;
|
||||
}
|
||||
if (dffcemux) {
|
||||
ffPcemux = dffcemux;
|
||||
ffPcepol = dffcepol;
|
||||
}
|
||||
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
|
||||
|
||||
match overflow
|
||||
if ffP
|
||||
if param(dsp, \USE_PATTERN_DETECT, Const("NO_PATDET")).decode_string() == "NO_PATDET"
|
||||
select overflow->type.in($ge)
|
||||
select GetSize(port(overflow, \Y)) <= 48
|
||||
select port(overflow, \B).is_fully_const()
|
||||
define <Const> B port(overflow, \B).as_const()
|
||||
select std::count(B.bits.begin(), B.bits.end(), State::S1) == 1
|
||||
index <SigSpec> port(overflow, \A) === sigP
|
||||
optional
|
||||
endmatch
|
||||
|
||||
code
|
||||
accept;
|
||||
endcode
|
||||
|
||||
// #######################
|
||||
|
||||
subpattern in_dffe
|
||||
arg argD argQ clock
|
||||
|
||||
code
|
||||
dff = nullptr;
|
||||
for (auto c : argQ.chunks()) {
|
||||
if (!c.wire)
|
||||
reject;
|
||||
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;
|
||||
}
|
||||
endcode
|
||||
|
||||
match ff
|
||||
select ff->type.in($dff)
|
||||
// DSP48E1 does not support clock inversion
|
||||
select param(ff, \CLK_POLARITY).as_bool()
|
||||
|
||||
slice offset GetSize(port(ff, \D))
|
||||
index <SigBit> port(ff, \Q)[offset] === argQ[0]
|
||||
|
||||
// Check that the rest of argQ is present
|
||||
filter GetSize(port(ff, \Q)) >= offset + GetSize(argQ)
|
||||
filter port(ff, \Q).extract(offset, GetSize(argQ)) == argQ
|
||||
|
||||
set ffoffset offset
|
||||
endmatch
|
||||
|
||||
code argQ argD
|
||||
{
|
||||
if (clock != SigBit() && port(ff, \CLK) != clock)
|
||||
reject;
|
||||
|
||||
SigSpec Q = port(ff, \Q);
|
||||
dff = ff;
|
||||
dffclock = port(ff, \CLK);
|
||||
dffD = argQ;
|
||||
argD = port(ff, \D);
|
||||
argQ = Q;
|
||||
dffD.replace(argQ, argD);
|
||||
// Only search for ffrstmux if dffD only
|
||||
// has two (ff, ffrstmux) users
|
||||
if (nusers(dffD) > 2)
|
||||
argD = SigSpec();
|
||||
}
|
||||
endcode
|
||||
|
||||
match ffrstmux
|
||||
if !argD.empty()
|
||||
select ffrstmux->type.in($mux)
|
||||
index <SigSpec> port(ffrstmux, \Y) === argD
|
||||
|
||||
choice <IdString> BA {\B, \A}
|
||||
// DSP48E1 only supports reset to zero
|
||||
select port(ffrstmux, BA).is_fully_zero()
|
||||
|
||||
define <bool> pol (BA == \B)
|
||||
set ffrstpol pol
|
||||
semioptional
|
||||
endmatch
|
||||
|
||||
code argD
|
||||
if (ffrstmux) {
|
||||
dffrstmux = ffrstmux;
|
||||
dffrstpol = ffrstpol;
|
||||
argD = port(ffrstmux, ffrstpol ? \A : \B);
|
||||
dffD.replace(port(ffrstmux, \Y), argD);
|
||||
|
||||
// Only search for ffcemux if argQ has at
|
||||
// least 3 users (ff, <upstream>, ffrstmux) and
|
||||
// dffD only has two (ff, ffrstmux)
|
||||
if (!(nusers(argQ) >= 3 && nusers(dffD) == 2))
|
||||
argD = SigSpec();
|
||||
}
|
||||
else
|
||||
dffrstmux = nullptr;
|
||||
endcode
|
||||
|
||||
match ffcemux
|
||||
if !argD.empty()
|
||||
select ffcemux->type.in($mux)
|
||||
index <SigSpec> port(ffcemux, \Y) === argD
|
||||
choice <IdString> AB {\A, \B}
|
||||
index <SigSpec> port(ffcemux, AB) === argQ
|
||||
define <bool> pol (AB == \A)
|
||||
set ffcepol pol
|
||||
semioptional
|
||||
endmatch
|
||||
|
||||
code argD
|
||||
if (ffcemux) {
|
||||
dffcemux = ffcemux;
|
||||
dffcepol = ffcepol;
|
||||
argD = port(ffcemux, ffcepol ? \B : \A);
|
||||
dffD.replace(port(ffcemux, \Y), argD);
|
||||
}
|
||||
else
|
||||
dffcemux = nullptr;
|
||||
endcode
|
||||
|
||||
// #######################
|
||||
|
||||
subpattern out_dffe
|
||||
arg argD argQ clock
|
||||
|
||||
code
|
||||
dff = nullptr;
|
||||
for (auto c : argD.chunks())
|
||||
if (c.wire->get_bool_attribute(\keep))
|
||||
reject;
|
||||
endcode
|
||||
|
||||
match ffcemux
|
||||
select ffcemux->type.in($mux)
|
||||
// ffcemux output must have two users: ffcemux and ff.D
|
||||
select nusers(port(ffcemux, \Y)) == 2
|
||||
|
||||
choice <IdString> AB {\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))
|
||||
define <IdString> BA (AB == \A ? \B : \A)
|
||||
index <SigBit> port(ffcemux, BA)[offset] === argD[0]
|
||||
|
||||
// Check that the rest of argD is present
|
||||
filter GetSize(port(ffcemux, BA)) >= offset + GetSize(argD)
|
||||
filter port(ffcemux, BA).extract(offset, GetSize(argD)) == argD
|
||||
|
||||
set ffoffset offset
|
||||
define <bool> pol (AB == \A)
|
||||
set ffcepol pol
|
||||
|
||||
semioptional
|
||||
endmatch
|
||||
|
||||
code argD argQ
|
||||
dffcemux = ffcemux;
|
||||
if (ffcemux) {
|
||||
SigSpec BA = port(ffcemux, ffcepol ? \B : \A);
|
||||
SigSpec Y = port(ffcemux, \Y);
|
||||
argQ = argD;
|
||||
argD.replace(BA, Y);
|
||||
argQ.replace(BA, port(ffcemux, ffcepol ? \A : \B));
|
||||
|
||||
dffcemux = ffcemux;
|
||||
dffcepol = ffcepol;
|
||||
}
|
||||
endcode
|
||||
|
||||
match ffrstmux
|
||||
select ffrstmux->type.in($mux)
|
||||
// ffrstmux output must have two users: ffrstmux and ff.D
|
||||
select nusers(port(ffrstmux, \Y)) == 2
|
||||
|
||||
choice <IdString> BA {\B, \A}
|
||||
// DSP48E1 only supports reset to zero
|
||||
select port(ffrstmux, BA).is_fully_zero()
|
||||
|
||||
slice offset GetSize(port(ffrstmux, \Y))
|
||||
define <IdString> AB (BA == \B ? \A : \B)
|
||||
index <SigBit> port(ffrstmux, AB)[offset] === argD[0]
|
||||
|
||||
// Check that offset is consistent
|
||||
filter !ffcemux || ffoffset == offset
|
||||
// Check that the rest of argD is present
|
||||
filter GetSize(port(ffrstmux, AB)) >= offset + GetSize(argD)
|
||||
filter port(ffrstmux, AB).extract(offset, GetSize(argD)) == argD
|
||||
|
||||
set ffoffset offset
|
||||
define <bool> pol (AB == \A)
|
||||
set ffrstpol pol
|
||||
|
||||
semioptional
|
||||
endmatch
|
||||
|
||||
code argD argQ
|
||||
dffrstmux = ffrstmux;
|
||||
if (ffrstmux) {
|
||||
SigSpec AB = port(ffrstmux, ffrstpol ? \A : \B);
|
||||
SigSpec Y = port(ffrstmux, \Y);
|
||||
argD.replace(AB, Y);
|
||||
|
||||
dffrstmux = ffrstmux;
|
||||
dffrstpol = ffrstpol;
|
||||
}
|
||||
endcode
|
||||
|
||||
match ff
|
||||
select ff->type.in($dff)
|
||||
// DSP48E1 does not support clock inversion
|
||||
select param(ff, \CLK_POLARITY).as_bool()
|
||||
|
||||
slice offset GetSize(port(ff, \D))
|
||||
index <SigBit> port(ff, \D)[offset] === argD[0]
|
||||
|
||||
// Check that offset is consistent
|
||||
filter (!ffcemux && !ffrstmux) || ffoffset == offset
|
||||
// Check that the rest of argD is present
|
||||
filter GetSize(port(ff, \D)) >= offset + GetSize(argD)
|
||||
filter port(ff, \D).extract(offset, GetSize(argD)) == argD
|
||||
// Check that FF.Q is connected to CE-mux
|
||||
filter !ffcemux || port(ff, \Q).extract(offset, GetSize(argQ)) == argQ
|
||||
|
||||
set ffoffset offset
|
||||
endmatch
|
||||
|
||||
code argQ
|
||||
if (ff) {
|
||||
if (clock != SigBit() && port(ff, \CLK) != clock)
|
||||
reject;
|
||||
|
||||
SigSpec D = port(ff, \D);
|
||||
SigSpec Q = port(ff, \Q);
|
||||
if (!ffcemux) {
|
||||
argQ = argD;
|
||||
argQ.replace(D, Q);
|
||||
}
|
||||
|
||||
for (auto c : argQ.chunks()) {
|
||||
Const init = c.wire->attributes.at(\init, State::Sx);
|
||||
if (!init.is_fully_undef() && !init.is_fully_zero())
|
||||
reject;
|
||||
}
|
||||
|
||||
dff = ff;
|
||||
dffQ = argQ;
|
||||
dffclock = port(ff, \CLK);
|
||||
}
|
||||
// No enable/reset mux possible without flop
|
||||
else if (dffcemux || dffrstmux)
|
||||
reject;
|
||||
endcode
|
181
passes/pmgen/xilinx_dsp_CREG.pmg
Normal file
181
passes/pmgen/xilinx_dsp_CREG.pmg
Normal file
|
@ -0,0 +1,181 @@
|
|||
pattern xilinx_dsp_packC
|
||||
|
||||
udata <std::function<SigSpec(const SigSpec&)>> unextend
|
||||
state <SigBit> clock
|
||||
state <SigSpec> sigC sigP
|
||||
state <bool> ffCcepol ffCrstpol
|
||||
state <Cell*> ffC ffCcemux ffCrstmux
|
||||
|
||||
// subpattern
|
||||
state <SigSpec> argQ argD
|
||||
state <bool> ffcepol ffrstpol
|
||||
state <int> ffoffset
|
||||
udata <SigSpec> dffD dffQ
|
||||
udata <SigBit> dffclock
|
||||
udata <Cell*> dff dffcemux dffrstmux
|
||||
udata <bool> dffcepol dffrstpol
|
||||
|
||||
match dsp
|
||||
select dsp->type.in(\DSP48E1)
|
||||
select param(dsp, \CREG, 1).as_int() == 0
|
||||
select nusers(port(dsp, \C, SigSpec())) > 1
|
||||
endmatch
|
||||
|
||||
code argQ ffC ffCcemux ffCrstmux ffCcepol ffCrstpol sigC sigP clock
|
||||
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);
|
||||
};
|
||||
sigC = unextend(port(dsp, \C, SigSpec()));
|
||||
|
||||
SigSpec P = port(dsp, \P);
|
||||
if (param(dsp, \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;
|
||||
sigP.append(P[i]);
|
||||
}
|
||||
log_assert(nusers(P.extract_end(i)) <= 1);
|
||||
}
|
||||
else
|
||||
sigP = P;
|
||||
|
||||
if (sigC == sigP)
|
||||
reject;
|
||||
|
||||
clock = port(dsp, \CLK, SigBit());
|
||||
|
||||
argQ = sigC;
|
||||
subpattern(in_dffe);
|
||||
if (dff) {
|
||||
ffC = dff;
|
||||
clock = dffclock;
|
||||
if (dffrstmux) {
|
||||
ffCrstmux = dffrstmux;
|
||||
ffCrstpol = dffrstpol;
|
||||
}
|
||||
if (dffcemux) {
|
||||
ffCcemux = dffcemux;
|
||||
ffCcepol = dffcepol;
|
||||
}
|
||||
sigC = dffD;
|
||||
}
|
||||
endcode
|
||||
|
||||
code
|
||||
if (ffC)
|
||||
accept;
|
||||
endcode
|
||||
|
||||
// #######################
|
||||
|
||||
subpattern in_dffe
|
||||
arg argD argQ clock
|
||||
|
||||
code
|
||||
dff = nullptr;
|
||||
for (auto c : argQ.chunks()) {
|
||||
if (!c.wire)
|
||||
reject;
|
||||
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;
|
||||
}
|
||||
endcode
|
||||
|
||||
match ff
|
||||
select ff->type.in($dff)
|
||||
// DSP48E1 does not support clock inversion
|
||||
select param(ff, \CLK_POLARITY).as_bool()
|
||||
|
||||
slice offset GetSize(port(ff, \D))
|
||||
index <SigBit> port(ff, \Q)[offset] === argQ[0]
|
||||
|
||||
// Check that the rest of argQ is present
|
||||
filter GetSize(port(ff, \Q)) >= offset + GetSize(argQ)
|
||||
filter port(ff, \Q).extract(offset, GetSize(argQ)) == argQ
|
||||
|
||||
set ffoffset offset
|
||||
endmatch
|
||||
|
||||
code argQ argD
|
||||
{
|
||||
if (clock != SigBit() && port(ff, \CLK) != clock)
|
||||
reject;
|
||||
|
||||
SigSpec Q = port(ff, \Q);
|
||||
dff = ff;
|
||||
dffclock = port(ff, \CLK);
|
||||
dffD = argQ;
|
||||
argD = port(ff, \D);
|
||||
argQ = Q;
|
||||
dffD.replace(argQ, argD);
|
||||
// Only search for ffrstmux if dffD only
|
||||
// has two (ff, ffrstmux) users
|
||||
if (nusers(dffD) > 2)
|
||||
argD = SigSpec();
|
||||
}
|
||||
endcode
|
||||
|
||||
match ffrstmux
|
||||
if !argD.empty()
|
||||
select ffrstmux->type.in($mux)
|
||||
index <SigSpec> port(ffrstmux, \Y) === argD
|
||||
|
||||
choice <IdString> BA {\B, \A}
|
||||
// DSP48E1 only supports reset to zero
|
||||
select port(ffrstmux, BA).is_fully_zero()
|
||||
|
||||
define <bool> pol (BA == \B)
|
||||
set ffrstpol pol
|
||||
semioptional
|
||||
endmatch
|
||||
|
||||
code argD
|
||||
if (ffrstmux) {
|
||||
dffrstmux = ffrstmux;
|
||||
dffrstpol = ffrstpol;
|
||||
argD = port(ffrstmux, ffrstpol ? \A : \B);
|
||||
dffD.replace(port(ffrstmux, \Y), argD);
|
||||
|
||||
// Only search for ffcemux if argQ has at
|
||||
// least 3 users (ff, <upstream>, ffrstmux) and
|
||||
// dffD only has two (ff, ffrstmux)
|
||||
if (!(nusers(argQ) >= 3 && nusers(dffD) == 2))
|
||||
argD = SigSpec();
|
||||
}
|
||||
else
|
||||
dffrstmux = nullptr;
|
||||
endcode
|
||||
|
||||
match ffcemux
|
||||
if !argD.empty()
|
||||
select ffcemux->type.in($mux)
|
||||
index <SigSpec> port(ffcemux, \Y) === argD
|
||||
choice <IdString> AB {\A, \B}
|
||||
index <SigSpec> port(ffcemux, AB) === argQ
|
||||
define <bool> pol (AB == \A)
|
||||
set ffcepol pol
|
||||
semioptional
|
||||
endmatch
|
||||
|
||||
code argD
|
||||
if (ffcemux) {
|
||||
dffcemux = ffcemux;
|
||||
dffcepol = ffcepol;
|
||||
argD = port(ffcemux, ffcepol ? \B : \A);
|
||||
dffD.replace(port(ffcemux, \Y), argD);
|
||||
}
|
||||
else
|
||||
dffcemux = nullptr;
|
||||
endcode
|
336
passes/pmgen/xilinx_dsp_cascade.pmg
Normal file
336
passes/pmgen/xilinx_dsp_cascade.pmg
Normal file
|
@ -0,0 +1,336 @@
|
|||
pattern xilinx_dsp_cascade
|
||||
|
||||
udata <std::function<SigSpec(const SigSpec&)>> unextend
|
||||
udata <vector<std::tuple<Cell*,int,int,int>>> chain longest_chain
|
||||
state <Cell*> next
|
||||
state <SigSpec> clock
|
||||
state <int> AREG BREG
|
||||
|
||||
// subpattern
|
||||
state <SigSpec> argQ argD
|
||||
state <bool> ffcepol ffrstpol
|
||||
state <int> ffoffset
|
||||
udata <SigSpec> dffD dffQ
|
||||
udata <SigBit> dffclock
|
||||
udata <Cell*> dff dffcemux dffrstmux
|
||||
udata <bool> dffcepol dffrstpol
|
||||
|
||||
code
|
||||
#define MAX_DSP_CASCADE 20
|
||||
endcode
|
||||
|
||||
match first
|
||||
select first->type.in(\DSP48E1)
|
||||
select port(first, \OPMODE, Const(0, 7)).extract(4,3) == Const::from_string("000")
|
||||
select nusers(port(first, \PCOUT, SigSpec())) <= 1
|
||||
endmatch
|
||||
|
||||
code
|
||||
longest_chain.clear();
|
||||
chain.emplace_back(first, -1, -1, -1);
|
||||
subpattern(tail);
|
||||
finally
|
||||
chain.pop_back();
|
||||
log_assert(chain.empty());
|
||||
if (GetSize(longest_chain) > 1) {
|
||||
Cell *dsp = std::get<0>(longest_chain.front());
|
||||
|
||||
Cell *dsp_pcin;
|
||||
int P, AREG, BREG;
|
||||
for (int i = 1; i < GetSize(longest_chain); i++) {
|
||||
std::tie(dsp_pcin,P,AREG,BREG) = longest_chain[i];
|
||||
|
||||
if (i % MAX_DSP_CASCADE > 0) {
|
||||
if (P >= 0) {
|
||||
Wire *cascade = module->addWire(NEW_ID, 48);
|
||||
dsp_pcin->setPort(ID(C), Const(0, 48));
|
||||
dsp_pcin->setPort(ID(PCIN), cascade);
|
||||
dsp->setPort(ID(PCOUT), cascade);
|
||||
add_siguser(cascade, dsp_pcin);
|
||||
add_siguser(cascade, dsp);
|
||||
|
||||
SigSpec opmode = port(dsp_pcin, \OPMODE, Const(0, 7));
|
||||
if (P == 17)
|
||||
opmode[6] = State::S1;
|
||||
else if (P == 0)
|
||||
opmode[6] = State::S0;
|
||||
else log_abort();
|
||||
|
||||
opmode[5] = State::S0;
|
||||
opmode[4] = State::S1;
|
||||
dsp_pcin->setPort(\OPMODE, opmode);
|
||||
|
||||
log_debug("PCOUT -> PCIN cascade for %s -> %s\n", log_id(dsp), log_id(dsp_pcin));
|
||||
}
|
||||
if (AREG >= 0) {
|
||||
Wire *cascade = module->addWire(NEW_ID, 30);
|
||||
dsp_pcin->setPort(ID(A), Const(0, 30));
|
||||
dsp_pcin->setPort(ID(ACIN), cascade);
|
||||
dsp->setPort(ID(ACOUT), cascade);
|
||||
add_siguser(cascade, dsp_pcin);
|
||||
add_siguser(cascade, dsp);
|
||||
|
||||
dsp->setParam(ID(ACASCREG), AREG);
|
||||
dsp_pcin->setParam(ID(A_INPUT), Const("CASCADE"));
|
||||
|
||||
log_debug("ACOUT -> ACIN cascade for %s -> %s\n", log_id(dsp), log_id(dsp_pcin));
|
||||
}
|
||||
if (BREG >= 0) {
|
||||
Wire *cascade = module->addWire(NEW_ID, 18);
|
||||
dsp_pcin->setPort(ID(B), Const(0, 18));
|
||||
dsp_pcin->setPort(ID(BCIN), cascade);
|
||||
dsp->setPort(ID(BCOUT), cascade);
|
||||
add_siguser(cascade, dsp_pcin);
|
||||
add_siguser(cascade, dsp);
|
||||
|
||||
dsp->setParam(ID(BCASCREG), BREG);
|
||||
dsp_pcin->setParam(ID(B_INPUT), Const("CASCADE"));
|
||||
|
||||
log_debug("BCOUT -> BCIN cascade for %s -> %s\n", log_id(dsp), log_id(dsp_pcin));
|
||||
}
|
||||
}
|
||||
else {
|
||||
log_debug(" Blocking %s -> %s cascade (exceeds max: %d)\n", log_id(dsp), log_id(dsp_pcin), MAX_DSP_CASCADE);
|
||||
}
|
||||
|
||||
dsp = dsp_pcin;
|
||||
}
|
||||
|
||||
accept;
|
||||
}
|
||||
endcode
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
subpattern tail
|
||||
arg first
|
||||
arg next
|
||||
|
||||
match nextP
|
||||
select nextP->type.in(\DSP48E1)
|
||||
select !param(nextP, \CREG, State::S1).as_bool()
|
||||
select port(nextP, \OPMODE, Const(0, 7)).extract(4,3) == Const::from_string("011")
|
||||
select nusers(port(nextP, \C, SigSpec())) > 1
|
||||
select nusers(port(nextP, \PCIN, SigSpec())) == 0
|
||||
index <SigBit> port(nextP, \C)[0] === port(std::get<0>(chain.back()), \P)[0]
|
||||
semioptional
|
||||
endmatch
|
||||
|
||||
match nextP_shift17
|
||||
if !nextP
|
||||
select nextP_shift17->type.in(\DSP48E1)
|
||||
select !param(nextP_shift17, \CREG, State::S1).as_bool()
|
||||
select port(nextP_shift17, \OPMODE, Const(0, 7)).extract(4,3) == Const::from_string("011")
|
||||
select nusers(port(nextP_shift17, \C, SigSpec())) > 1
|
||||
select nusers(port(nextP_shift17, \PCIN, SigSpec())) == 0
|
||||
index <SigBit> port(nextP_shift17, \C)[0] === port(std::get<0>(chain.back()), \P)[17]
|
||||
semioptional
|
||||
endmatch
|
||||
|
||||
code next
|
||||
next = nextP;
|
||||
if (!nextP)
|
||||
next = nextP_shift17;
|
||||
if (next) {
|
||||
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);
|
||||
};
|
||||
}
|
||||
endcode
|
||||
|
||||
code argQ clock AREG
|
||||
AREG = -1;
|
||||
if (next) {
|
||||
Cell *prev = std::get<0>(chain.back());
|
||||
if (param(prev, \AREG, 2).as_int() > 0 &&
|
||||
param(next, \AREG, 2).as_int() > 0 &&
|
||||
param(next, \A_INPUT, Const("DIRECT")).decode_string() == "DIRECT" &&
|
||||
port(next, \ACIN, SigSpec()).is_fully_zero() &&
|
||||
nusers(port(prev, \ACOUT, SigSpec())) <= 1) {
|
||||
argQ = unextend(port(next, \A));
|
||||
clock = port(prev, \CLK);
|
||||
subpattern(in_dffe);
|
||||
if (dff) {
|
||||
if (!dffrstmux && port(prev, \RSTA, State::S0) != State::S0)
|
||||
goto reject_AREG;
|
||||
if (dffrstmux && port(dffrstmux, \S) != port(prev, \RSTA, State::S0))
|
||||
goto reject_AREG;
|
||||
if (!dffcemux && port(prev, \CEA2, State::S0) != State::S0)
|
||||
goto reject_AREG;
|
||||
if (dffcemux && port(dffcemux, \S) != port(prev, \CEA2, State::S0))
|
||||
goto reject_AREG;
|
||||
if (dffD == unextend(port(prev, \A)))
|
||||
AREG = 1;
|
||||
reject_AREG: ;
|
||||
}
|
||||
}
|
||||
}
|
||||
endcode
|
||||
|
||||
code argQ clock BREG
|
||||
BREG = -1;
|
||||
if (next) {
|
||||
Cell *prev = std::get<0>(chain.back());
|
||||
if (param(prev, \BREG, 2).as_int() > 0 &&
|
||||
param(next, \BREG, 2).as_int() > 0 &&
|
||||
param(next, \B_INPUT, Const("DIRECT")).decode_string() == "DIRECT" &&
|
||||
port(next, \BCIN, SigSpec()).is_fully_zero() &&
|
||||
nusers(port(prev, \BCOUT, SigSpec())) <= 1) {
|
||||
argQ = unextend(port(next, \B));
|
||||
clock = port(prev, \CLK);
|
||||
subpattern(in_dffe);
|
||||
if (dff) {
|
||||
if (!dffrstmux && port(prev, \RSTB, State::S0) != State::S0)
|
||||
goto reject_BREG;
|
||||
if (dffrstmux && port(dffrstmux, \S) != port(prev, \RSTB, State::S0))
|
||||
goto reject_BREG;
|
||||
if (!dffcemux && port(prev, \CEB2, State::S0) != State::S0)
|
||||
goto reject_BREG;
|
||||
if (dffcemux && port(dffcemux, \S) != port(prev, \CEB2, State::S0))
|
||||
goto reject_BREG;
|
||||
if (dffD == unextend(port(prev, \B)))
|
||||
BREG = 1;
|
||||
reject_BREG: ;
|
||||
}
|
||||
}
|
||||
}
|
||||
endcode
|
||||
|
||||
code
|
||||
if (next) {
|
||||
chain.emplace_back(next, nextP_shift17 ? 17 : nextP ? 0 : -1, AREG, BREG);
|
||||
|
||||
SigSpec sigC = unextend(port(next, \C));
|
||||
|
||||
// TODO: Cannot use 'reject' since semioptional
|
||||
if (nextP_shift17) {
|
||||
if (GetSize(sigC)+17 <= GetSize(port(std::get<0>(chain.back()), \P)) &&
|
||||
port(std::get<0>(chain.back()), \P).extract(17, GetSize(sigC)) != sigC)
|
||||
subpattern(tail);
|
||||
}
|
||||
else {
|
||||
if (GetSize(sigC) <= GetSize(port(std::get<0>(chain.back()), \P)) &&
|
||||
port(std::get<0>(chain.back()), \P).extract(0, GetSize(sigC)) != sigC)
|
||||
subpattern(tail);
|
||||
|
||||
}
|
||||
} else {
|
||||
if (GetSize(chain) > GetSize(longest_chain))
|
||||
longest_chain = chain;
|
||||
}
|
||||
finally
|
||||
if (next)
|
||||
chain.pop_back();
|
||||
endcode
|
||||
|
||||
// #######################
|
||||
|
||||
subpattern in_dffe
|
||||
arg argD argQ clock
|
||||
|
||||
code
|
||||
dff = nullptr;
|
||||
for (auto c : argQ.chunks()) {
|
||||
if (!c.wire)
|
||||
reject;
|
||||
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;
|
||||
}
|
||||
endcode
|
||||
|
||||
match ff
|
||||
select ff->type.in($dff)
|
||||
// DSP48E1 does not support clock inversion
|
||||
select param(ff, \CLK_POLARITY).as_bool()
|
||||
|
||||
slice offset GetSize(port(ff, \D))
|
||||
index <SigBit> port(ff, \Q)[offset] === argQ[0]
|
||||
|
||||
// Check that the rest of argQ is present
|
||||
filter GetSize(port(ff, \Q)) >= offset + GetSize(argQ)
|
||||
filter port(ff, \Q).extract(offset, GetSize(argQ)) == argQ
|
||||
|
||||
set ffoffset offset
|
||||
endmatch
|
||||
|
||||
code argQ argD
|
||||
{
|
||||
if (clock != SigBit() && port(ff, \CLK) != clock)
|
||||
reject;
|
||||
|
||||
SigSpec Q = port(ff, \Q);
|
||||
dff = ff;
|
||||
dffclock = port(ff, \CLK);
|
||||
dffD = argQ;
|
||||
argD = port(ff, \D);
|
||||
argQ = Q;
|
||||
dffD.replace(argQ, argD);
|
||||
// Only search for ffrstmux if dffD only
|
||||
// has two (ff, ffrstmux) users
|
||||
if (nusers(dffD) > 2)
|
||||
argD = SigSpec();
|
||||
}
|
||||
endcode
|
||||
|
||||
match ffrstmux
|
||||
if !argD.empty()
|
||||
select ffrstmux->type.in($mux)
|
||||
index <SigSpec> port(ffrstmux, \Y) === argD
|
||||
|
||||
choice <IdString> BA {\B, \A}
|
||||
// DSP48E1 only supports reset to zero
|
||||
select port(ffrstmux, BA).is_fully_zero()
|
||||
|
||||
define <bool> pol (BA == \B)
|
||||
set ffrstpol pol
|
||||
semioptional
|
||||
endmatch
|
||||
|
||||
code argD
|
||||
if (ffrstmux) {
|
||||
dffrstmux = ffrstmux;
|
||||
dffrstpol = ffrstpol;
|
||||
argD = port(ffrstmux, ffrstpol ? \A : \B);
|
||||
dffD.replace(port(ffrstmux, \Y), argD);
|
||||
|
||||
// Only search for ffcemux if argQ has at
|
||||
// least 3 users (ff, <upstream>, ffrstmux) and
|
||||
// dffD only has two (ff, ffrstmux)
|
||||
if (!(nusers(argQ) >= 3 && nusers(dffD) == 2))
|
||||
argD = SigSpec();
|
||||
}
|
||||
else
|
||||
dffrstmux = nullptr;
|
||||
endcode
|
||||
|
||||
match ffcemux
|
||||
if !argD.empty()
|
||||
select ffcemux->type.in($mux)
|
||||
index <SigSpec> port(ffcemux, \Y) === argD
|
||||
choice <IdString> AB {\A, \B}
|
||||
index <SigSpec> port(ffcemux, AB) === argQ
|
||||
define <bool> pol (AB == \A)
|
||||
set ffcepol pol
|
||||
semioptional
|
||||
endmatch
|
||||
|
||||
code argD
|
||||
if (ffcemux) {
|
||||
dffcemux = ffcemux;
|
||||
dffcepol = ffcepol;
|
||||
argD = port(ffcemux, ffcepol ? \B : \A);
|
||||
dffD.replace(port(ffcemux, \Y), argD);
|
||||
}
|
||||
else
|
||||
dffcemux = nullptr;
|
||||
endcode
|
|
@ -13,9 +13,9 @@ 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()
|
||||
select !first->type.in(\FDRE) || !param(first, \IS_R_INVERTED, State::S0).as_bool()
|
||||
select !first->type.in(\FDRE) || !param(first, \IS_D_INVERTED, State::S0).as_bool()
|
||||
select !first->type.in(\FDRE, \FDRE_1) || port(first, \R, State::S0).is_fully_zero()
|
||||
filter !non_first_cells.count(first)
|
||||
generate
|
||||
SigSpec C = module->addWire(NEW_ID);
|
||||
|
@ -84,9 +84,9 @@ 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()
|
||||
select !first->type.in(\FDRE) || !param(first, \IS_R_INVERTED, State::S0).as_bool()
|
||||
select !first->type.in(\FDRE) || !param(first, \IS_D_INVERTED, State::S0).as_bool()
|
||||
select !first->type.in(\FDRE, \FDRE_1) || port(first, \R, State::S0).is_fully_zero()
|
||||
endmatch
|
||||
|
||||
code clk_port en_port
|
||||
|
@ -111,10 +111,10 @@ match next
|
|||
index <SigBit> 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()
|
||||
filter !first->type.in(\FDRE) || param(next, \IS_C_INVERTED, State::S0).as_bool() == param(first, \IS_C_INVERTED, State::S0).as_bool()
|
||||
filter !first->type.in(\FDRE) || param(next, \IS_D_INVERTED, State::S0).as_bool() == param(first, \IS_D_INVERTED, State::S0).as_bool()
|
||||
filter !first->type.in(\FDRE) || param(next, \IS_R_INVERTED, State::S0).as_bool() == param(first, \IS_R_INVERTED, State::S0).as_bool()
|
||||
filter !first->type.in(\FDRE, \FDRE_1) || port(next, \R, State::S0).is_fully_zero()
|
||||
endmatch
|
||||
|
||||
code
|
||||
|
@ -138,10 +138,10 @@ match next
|
|||
index <SigBit> 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()
|
||||
filter !first->type.in(\FDRE) || param(next, \IS_C_INVERTED, State::S0).as_bool() == param(first, \IS_C_INVERTED, State::S0).as_bool()
|
||||
filter !first->type.in(\FDRE) || param(next, \IS_D_INVERTED, State::S0).as_bool() == param(first, \IS_D_INVERTED, State::S0).as_bool()
|
||||
filter !first->type.in(\FDRE) || param(next, \IS_R_INVERTED, State::S0).as_bool() == param(first, \IS_R_INVERTED, State::S0).as_bool()
|
||||
filter !first->type.in(\FDRE, \FDRE_1) || port(next, \R, State::S0).is_fully_zero()
|
||||
generate
|
||||
Cell *cell = module->addCell(NEW_ID, chain.back()->type);
|
||||
cell->setPort(\C, chain.back()->getPort(\C));
|
||||
|
@ -149,7 +149,7 @@ generate
|
|||
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(\R, port(chain.back(), \R, State::S0));
|
||||
cell->setPort(\CE, chain.back()->getPort(\CE));
|
||||
}
|
||||
else if (cell->type.begins_with("$_DFFE_"))
|
||||
|
|
|
@ -553,7 +553,6 @@ void abc9_module(RTLIL::Design *design, RTLIL::Module *current_module, std::stri
|
|||
existing_cell = module->cell(mapped_cell->name);
|
||||
log_assert(existing_cell);
|
||||
cell = module->addCell(remap_name(mapped_cell->name), mapped_cell->type);
|
||||
module->swap_names(cell, existing_cell);
|
||||
}
|
||||
|
||||
if (markgroups) cell->attributes[ID(abcgroup)] = map_autoidx;
|
||||
|
@ -594,8 +593,22 @@ void abc9_module(RTLIL::Design *design, RTLIL::Module *current_module, std::stri
|
|||
}
|
||||
}
|
||||
|
||||
for (auto cell : boxes)
|
||||
module->remove(cell);
|
||||
for (auto existing_cell : boxes) {
|
||||
Cell *cell = module->cell(remap_name(existing_cell->name));
|
||||
if (cell) {
|
||||
for (auto &conn : existing_cell->connections()) {
|
||||
if (!conn.second.is_wire())
|
||||
continue;
|
||||
Wire *wire = conn.second.as_wire();
|
||||
if (!wire->get_bool_attribute(ID(abc_padding)))
|
||||
continue;
|
||||
cell->unsetPort(conn.first);
|
||||
log_debug("Dropping padded port connection for %s (%s) .%s (%s )\n", log_id(cell), cell->type.c_str(), log_id(conn.first), log_signal(conn.second));
|
||||
}
|
||||
module->swap_names(cell, existing_cell);
|
||||
}
|
||||
module->remove(existing_cell);
|
||||
}
|
||||
|
||||
// Copy connections (and rename) from mapped_mod to module
|
||||
for (auto conn : mapped_mod->connections()) {
|
||||
|
|
|
@ -351,6 +351,11 @@ struct TestAutotbBackend : public Backend {
|
|||
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
|
||||
{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue