| 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 |