pattern ice40_dsp state clock state clock_pol cd_signed o_lo state sigA sigB sigCD sigH sigO state add mux state addAB muxAB state ffAholdpol ffBholdpol ffCDholdpol ffOholdpol state ffArstpol ffBrstpol ffCDrstpol ffOrstpol state ffA ffAholdmux ffArstmux ffB ffBholdmux ffBrstmux ffCD ffCDholdmux state ffFJKG ffH ffO ffOholdmux ffOrstmux // subpattern state argQ argD state ffholdpol ffrstpol state ffoffset udata dffD dffQ udata dffclock udata dff dffholdmux dffrstmux udata 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 AB {\A, \B} select nusers(port(add, AB)) == 2 index 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 AB {\A, \B} select nusers(port(mux, AB)) == 2 index 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 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 port(ffrstmux, \Y) === argD choice BA {\B, \A} // DSP48E1 only supports reset to zero select port(ffrstmux, BA).is_fully_zero() define 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, , 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 port(ffholdmux, \Y) === argD choice BA {\B, \A} index port(ffholdmux, BA) === argQ define 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 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 AB (BA == \B ? \A : \B) index 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 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 BA {\B, \A} // DSP48E1 only supports reset to zero select port(ffrstmux, BA).is_fully_zero() slice offset GetSize(port(ffrstmux, \Y)) define AB (BA == \B ? \A : \B) index 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 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 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