diff options
Diffstat (limited to 'passes/pmgen/ice40_dsp.pmg')
-rw-r--r-- | passes/pmgen/ice40_dsp.pmg | 581 |
1 files changed, 581 insertions, 0 deletions
diff --git a/passes/pmgen/ice40_dsp.pmg b/passes/pmgen/ice40_dsp.pmg new file mode 100644 index 000000000..9d649cb98 --- /dev/null +++ b/passes/pmgen/ice40_dsp.pmg @@ -0,0 +1,581 @@ +pattern ice40_dsp + +state <SigBit> clock +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, \SB_MAC16) + select GetSize(mul->getPort(\A)) + GetSize(mul->getPort(\B)) > 10 +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)); + + 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; + + // 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]); + } + // This sigM could have no users if downstream sinks (e.g. $add) is + // narrower than $mul result, for example + if (i == 0) + reject; + + log_assert(nusers(O.extract_end(i)) <= 1); +endcode + +code argQ ffA ffAholdmux ffArstmux ffAholdpol ffArstpol sigA clock clock_pol + if (mul->type != \SB_MAC16 || !param(mul, \A_REG, State::S0).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 + +code argQ ffB ffBholdmux ffBrstmux ffBholdpol ffBrstpol sigB clock clock_pol + if (mul->type != \SB_MAC16 || !param(mul, \B_REG, State::S0).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 + +code argD ffFJKG sigH clock clock_pol + if (nusers(sigH) == 2 && + (mul->type != \SB_MAC16 || + (!param(mul, \TOP_8x8_MULT_REG, State::S0).as_bool() && !param(mul, \BOT_8x8_MULT_REG, State::S0).as_bool() && !param(mul, \PIPELINE_16x16_MULT_REG1, State::S0).as_bool() && !param(mul, \PIPELINE_16x16_MULT_REG1, State::S0).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; + + // 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; + } + + ffFJKG = dff; + clock = dffclock; + clock_pol = dffclock_pol; + sigH = dffQ; + +reject_ffFJKG: ; + } + } +endcode + +code argD ffH sigH sigO clock clock_pol + if (ffFJKG && nusers(sigH) == 2 && + (mul->type != \SB_MAC16 || !param(mul, \PIPELINE_16x16_MULT_REG2, State::S0).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, State::S0).as_int() == 3 && param(mul, \BOTOUTPUT_SELECT, State::S0).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 + +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(); + + int natural_mul_width = GetSize(sigA) + GetSize(sigB); + 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 accumulator, check adder width and signedness + if (sigCD == sigH && (actual_acc_width != actual_mul_width) && (param(mul, \A_SIGNED, State::S0).as_bool() != param(add, \A_SIGNED).as_bool())) + reject; + + sigO = port(add, \Y); + } +endcode + +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 + +code sigO + if (mux) + sigO = port(mux, \Y); +endcode + +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()))) { + + dff = nullptr; + + // First try entire sigO + if (nusers(sigO) == 2) { + argD = sigO; + subpattern(out_dffe); + } + + // 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, State::S0).as_bool() && !param(mul, \D_REG, State::S0).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; + if (argQ.empty()) + reject; + 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 |