| 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]); |
| } |
| 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).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).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).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; |
| |
| // 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).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 |
| |
| 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).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).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 |