| pattern ql_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 <Cell*> ffA ffB ffCD |
| state <Cell*> ffFJKG ffH ffO |
| // |
| // subpattern |
| state <bool> argSdff |
| state <SigSpec> argQ argD |
| udata <SigSpec> dffD dffQ |
| udata <SigBit> dffclock |
| udata <Cell*> dff |
| udata <bool> dffclock_pol |
| |
| match mul |
| select mul->type.in($mul, \QL_DSP) |
| 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 == \QL_DSP) |
| 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 sigA clock clock_pol |
| if (mul->type != \QL_DSP || !param(mul, \A_REG).as_bool()) { |
| argQ = sigA; |
| //subpattern(in_dffe); |
| if (dff) { |
| ffA = dff; |
| clock = dffclock; |
| clock_pol = dffclock_pol; |
| sigA = dffD; |
| } |
| } |
| endcode |
| |
| code argQ ffB sigB clock clock_pol |
| if (mul->type != \QL_DSP || !param(mul, \B_REG).as_bool()) { |
| argQ = sigB; |
| //subpattern(in_dffe); |
| if (dff) { |
| ffB = dff; |
| clock = dffclock; |
| clock_pol = dffclock_pol; |
| sigB = dffD; |
| } |
| } |
| endcode |
| |
| code argD argSdff ffFJKG sigH clock clock_pol |
| if (nusers(sigH) == 2 && |
| (mul->type != \QL_DSP)) { |
| argD = sigH; |
| argSdff = false; |
| //subpattern(out_dffe); |
| if (dff) { |
| // F/J/K/G do not have a CE-like (hold) input |
| if (dff->hasPort(\EN)) |
| goto reject_ffFJKG; |
| |
| // Reset signal of F/J (IRSTTOP) and K/G (IRSTBOT) |
| // shared with A and B |
| if (ffA) { |
| if (ffA->hasPort(\ARST) != dff->hasPort(\ARST)) |
| goto reject_ffFJKG; |
| if (ffA->hasPort(\ARST)) { |
| if (port(ffA, \ARST) != port(dff, \ARST)) |
| goto reject_ffFJKG; |
| if (param(ffA, \ARST_POLARITY) != param(dff, \ARST_POLARITY)) |
| goto reject_ffFJKG; |
| } |
| } |
| if (ffB) { |
| if (ffB->hasPort(\ARST) != dff->hasPort(\ARST)) |
| goto reject_ffFJKG; |
| if (ffB->hasPort(\ARST)) { |
| if (port(ffB, \ARST) != port(dff, \ARST)) |
| goto reject_ffFJKG; |
| if (param(ffB, \ARST_POLARITY) != param(dff, \ARST_POLARITY)) |
| goto reject_ffFJKG; |
| } |
| } |
| |
| ffFJKG = dff; |
| clock = dffclock; |
| clock_pol = dffclock_pol; |
| sigH = dffQ; |
| |
| reject_ffFJKG: ; |
| } |
| } |
| endcode |
| |
| code argD argSdff ffH sigH sigO clock clock_pol |
| if (ffFJKG && nusers(sigH) == 2 && |
| (mul->type != \QL_DSP)) { |
| argD = sigH; |
| argSdff = false; |
| //subpattern(out_dffe); |
| if (dff) { |
| // H does not have a CE-like (hold) input |
| if (dff->hasPort(\EN)) |
| goto reject_ffH; |
| |
| // Reset signal of H (IRSTBOT) shared with B |
| if (ffB->hasPort(\ARST) != dff->hasPort(\ARST)) |
| goto reject_ffH; |
| if (ffB->hasPort(\ARST)) { |
| if (port(ffB, \ARST) != port(dff, \ARST)) |
| goto reject_ffH; |
| if (param(ffB, \ARST_POLARITY) != param(dff, \ARST_POLARITY)) |
| goto reject_ffH; |
| } |
| |
| ffH = dff; |
| clock = dffclock; |
| clock_pol = dffclock_pol; |
| sigH = dffQ; |
| |
| reject_ffH: ; |
| } |
| } |
| |
| sigO = sigH; |
| endcode |
| |
| match add |
| if mul->type != \QL_DSP || (param(mul, \ENABLE_DSP).as_int() == 1) |
| |
| 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 argSdff ffO sigO sigCD clock clock_pol cd_signed o_lo |
| if (mul->type != \QL_DSP || |
| // Ensure that register is not already used |
| ((param(mul, \ENABLE_DSP).as_int() == 1))) { |
| |
| dff = nullptr; |
| |
| // First try entire sigO |
| if (nusers(sigO) == 2) { |
| argD = sigO; |
| argSdff = !mux; |
| //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) { |
| argSdff = !mux; |
| //subpattern(out_dffe); |
| o_lo = dff; |
| } |
| } |
| |
| if (dff) { |
| ffO = dff; |
| clock = dffclock; |
| clock_pol = dffclock_pol; |
| |
| 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(); |
| } else if (dff && dff->hasPort(\SRST)) { |
| if (sigCD != sigO) |
| reject; |
| sigCD = param(dff, \SRST_VALUE); |
| |
| cd_signed = add && param(add, \A_SIGNED).as_bool() && param(add, \B_SIGNED).as_bool(); |
| } |
| } |
| endcode |
| |
| code argQ ffCD sigCD clock clock_pol |
| if (!sigCD.empty() && sigCD != sigO && |
| (mul->type != \QL_DSP || (!param(mul, \C_REG).as_bool() && !param(mul, \D_REG).as_bool()))) { |
| argQ = sigCD; |
| //subpattern(in_dffe); |
| if (dff) { |
| // Reset signal of C (IRSTTOP) and D (IRSTBOT) |
| // shared with A and B |
| if (ffA) { |
| if (ffA->hasPort(\ARST) != dff->hasPort(\ARST)) |
| goto reject_ffCD; |
| if (ffA->hasPort(\ARST)) { |
| if (port(ffA, \ARST) != port(dff, \ARST)) |
| goto reject_ffCD; |
| if (param(ffA, \ARST_POLARITY) != param(dff, \ARST_POLARITY)) |
| goto reject_ffCD; |
| } |
| } |
| if (ffB) { |
| if (ffB->hasPort(\ARST) != dff->hasPort(\ARST)) |
| goto reject_ffCD; |
| if (ffB->hasPort(\ARST)) { |
| if (port(ffB, \ARST) != port(dff, \ARST)) |
| goto reject_ffCD; |
| if (param(ffB, \ARST_POLARITY) != param(dff, \ARST_POLARITY)) |
| 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 |
| |
| // ####################### |
| // Currently, QL_DSP performs only combinatorial operations |
| // but we aim to convert it into a mac unit in the futur |
| // so we're leaving the subpattern in the code, it is |
| // however not being used for now. |
| |
| 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, $dffe) |
| // 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 |
| 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); |
| } |
| endcode |
| |
| // ####################### |
| // Currently, QL_DSP performs only combinatorial operations |
| // but we aim to convert it into a mac unit in the futur |
| // so we're leaving the subpattern in the code, it is |
| // however not being used for now. |
| |
| subpattern out_dffe |
| arg argD argSdff argQ clock clock_pol |
| |
| code |
| dff = nullptr; |
| for (auto c : argD.chunks()) |
| if (c.wire->get_bool_attribute(\keep)) |
| reject; |
| endcode |
| |
| match ff |
| select ff->type.in($dff, $dffe, $sdff, $sdffce) |
| // QL_DSP 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] |
| |
| // Only allow sync reset if requested. |
| filter argSdff || ff->type.in($dff, $dffe) |
| // 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 |
| 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); |
| 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(); |
| } |
| endcode |