| // This file describes the main pattern matcher setup (of three total) that | |
| // forms the `xilinx_dsp` pass described in xilinx_dsp.cc | |
| // At a high level, it works as follows: | |
| // ( 1) Starting from a DSP48E1 cell | |
| // ( 2) Match the driver of the 'A' input to a possible $dff cell (ADREG) | |
| // (attached to at most two $mux cells that implement clock-enable or | |
| // reset functionality, using a subpattern discussed below) | |
| // If ADREG matched, treat 'A' input as input of ADREG | |
| // ( 3) Match the driver of the 'A' and 'D' inputs for a possible $add cell | |
| // (pre-adder) | |
| // ( 4) If pre-adder was present, find match 'A' input for A2REG | |
| // If pre-adder was not present, move ADREG to A2REG | |
| // If A2REG, then match 'A' input for A1REG | |
| // ( 5) Match 'B' input for B2REG | |
| // If B2REG, then match 'B' input for B1REG | |
| // ( 6) Match 'D' input for DREG | |
| // ( 7) Match 'P' output that exclusively drives an MREG | |
| // ( 8) Match 'P' output that exclusively drives one of two inputs to an $add | |
| // cell (post-adder). | |
| // The other input to the adder is assumed to come in from the 'C' input | |
| // (note: 'P' -> 'C' connections that exist for accumulators are | |
| // recognised in xilinx_dsp.cc). | |
| // ( 9) Match 'P' output that exclusively drives a PREG | |
| // (10) If post-adder and PREG both present, match for a $mux cell driving | |
| // the 'C' input, where one of the $mux's inputs is the PREG output. | |
| // This indicates an accumulator situation, and one where a $mux exists | |
| // to override the accumulated value: | |
| // +--------------------------------+ | |
| // | ____ | | |
| // +--| \ | | |
| // |$mux|-+ | | |
| // 'C' ---|____/ | | | |
| // | /-------\ +----+ | | |
| // +----+ +-| post- |___|PREG|---+ 'P' | |
| // |MREG|------ | adder | +----+ | |
| // +----+ \-------/ | |
| // (11) If PREG present, match for a greater-than-or-equal $ge cell attached | |
| // to the 'P' output where it is compared to a constant that is a | |
| // power-of-2: e.g. `assign overflow = (PREG >= 2**40);` | |
| // In this scenario, the pattern detector functionality of a DSP48E1 can | |
| // to implement this function | |
| // Notes: | |
| // - The intention of this pattern matcher is for it to be compatible with | |
| // DSP48E1 cells inferred from multiply operations by Yosys, as well as for | |
| // user instantiations that may already contain the cells being packed... | |
| // (though the latter is currently untested) | |
| // - Since the $dff-with-optional-clock-enable-or-reset-mux pattern is used | |
| // for each *REG match, it has been factored out into two subpatterns: | |
| // in_dffe and out_dffe located at the bottom of this file. | |
| // - Matching for pattern detector features is currently incomplete. For | |
| // example, matching for underflow as well as overflow detection is | |
| // possible, as would auto-reset, enabling saturated arithmetic, detecting | |
| // custom patterns, etc. | |
| pattern xilinx_dsp_pack | |
| state <SigBit> clock | |
| state <SigSpec> sigA sigB sigC sigD sigM sigP | |
| state <IdString> postAddAB postAddMuxAB | |
| state <bool> ffA1cepol ffA2cepol ffADcepol ffB1cepol ffB2cepol ffDcepol ffMcepol ffPcepol | |
| state <bool> ffArstpol ffADrstpol ffBrstpol ffDrstpol ffMrstpol ffPrstpol | |
| state <Cell*> ffAD ffADcemux ffADrstmux ffA1 ffA1cemux ffA1rstmux ffA2 ffA2cemux ffA2rstmux | |
| state <Cell*> ffB1 ffB1cemux ffB1rstmux ffB2 ffB2cemux ffB2rstmux | |
| state <Cell*> ffD ffDcemux ffDrstmux ffM ffMcemux ffMrstmux ffP ffPcemux ffPrstmux | |
| // Variables used for subpatterns | |
| state <SigSpec> argQ argD | |
| state <bool> ffcepol ffrstpol | |
| state <int> ffoffset | |
| udata <SigSpec> dffD dffQ | |
| udata <SigBit> dffclock | |
| udata <Cell*> dff dffcemux dffrstmux | |
| udata <bool> dffcepol dffrstpol | |
| // (1) Starting from a DSP48E1 cell | |
| match dsp | |
| select dsp->type.in(\DSP48E1) | |
| endmatch | |
| code sigA sigB sigC sigD sigM clock | |
| 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(dsp, \A)); | |
| sigB = unextend(port(dsp, \B)); | |
| sigC = port(dsp, \C, SigSpec()); | |
| sigD = port(dsp, \D, SigSpec()); | |
| SigSpec P = port(dsp, \P); | |
| if (param(dsp, \USE_MULT, Const("MULTIPLY")).decode_string() == "MULTIPLY") { | |
| // Only care about those bits that are used | |
| int i; | |
| for (i = 0; i < GetSize(P); i++) { | |
| if (nusers(P[i]) <= 1) | |
| break; | |
| sigM.append(P[i]); | |
| } | |
| log_assert(nusers(P.extract_end(i)) <= 1); | |
| // This sigM could have no users if downstream sinks (e.g. $add) is | |
| // narrower than $mul result, for example | |
| if (sigM.empty()) | |
| reject; | |
| } | |
| else | |
| sigM = P; | |
| clock = port(dsp, \CLK, SigBit()); | |
| endcode | |
| // (2) Match the driver of the 'A' input to a possible $dff cell (ADREG) | |
| // (attached to at most two $mux cells that implement clock-enable or | |
| // reset functionality, using a subpattern discussed above) | |
| // If matched, treat 'A' input as input of ADREG | |
| code argQ ffAD ffADcemux ffADrstmux ffADcepol ffADrstpol sigA clock | |
| if (param(dsp, \ADREG).as_int() == 0) { | |
| argQ = sigA; | |
| subpattern(in_dffe); | |
| if (dff) { | |
| ffAD = dff; | |
| clock = dffclock; | |
| if (dffrstmux) { | |
| ffADrstmux = dffrstmux; | |
| ffADrstpol = dffrstpol; | |
| } | |
| if (dffcemux) { | |
| ffADcemux = dffcemux; | |
| ffADcepol = dffcepol; | |
| } | |
| sigA = dffD; | |
| } | |
| } | |
| endcode | |
| // (3) Match the driver of the 'A' and 'D' inputs for a possible $add cell | |
| // (pre-adder) | |
| match preAdd | |
| if sigD.empty() || sigD.is_fully_zero() | |
| // Ensure that preAdder not already used | |
| if param(dsp, \USE_DPORT, Const("FALSE")).decode_string() == "FALSE" | |
| if port(dsp, \INMODE, Const(0, 5)).is_fully_zero() | |
| select preAdd->type.in($add) | |
| // Output has to be 25 bits or less | |
| select GetSize(port(preAdd, \Y)) <= 25 | |
| select nusers(port(preAdd, \Y)) == 2 | |
| choice <IdString> AB {\A, \B} | |
| // A port has to be 30 bits or less | |
| select GetSize(port(preAdd, AB)) <= 30 | |
| define <IdString> BA (AB == \A ? \B : \A) | |
| // D port has to be 25 bits or less | |
| select GetSize(port(preAdd, BA)) <= 25 | |
| index <SigSpec> port(preAdd, \Y) === sigA | |
| optional | |
| endmatch | |
| code sigA sigD | |
| if (preAdd) { | |
| sigA = port(preAdd, \A); | |
| sigD = port(preAdd, \B); | |
| } | |
| endcode | |
| // (4) If pre-adder was present, find match 'A' input for A2REG | |
| // If pre-adder was not present, move ADREG to A2REG | |
| // Then match 'A' input for A1REG | |
| code argQ ffAD ffADcemux ffADrstmux ffADcepol ffADrstpol sigA clock ffA2 ffA2cemux ffA2rstmux ffA2cepol ffArstpol ffA1 ffA1cemux ffA1rstmux ffA1cepol | |
| // Only search for ffA2 if there was a pre-adder | |
| // (otherwise ffA2 would have been matched as ffAD) | |
| if (preAdd) { | |
| if (param(dsp, \AREG).as_int() == 0) { | |
| argQ = sigA; | |
| subpattern(in_dffe); | |
| if (dff) { | |
| ffA2 = dff; | |
| clock = dffclock; | |
| if (dffrstmux) { | |
| ffA2rstmux = dffrstmux; | |
| ffArstpol = dffrstpol; | |
| } | |
| if (dffcemux) { | |
| ffA2cepol = dffcepol; | |
| ffA2cemux = dffcemux; | |
| } | |
| sigA = dffD; | |
| } | |
| } | |
| } | |
| // And if there wasn't a pre-adder, | |
| // move AD register to A | |
| else if (ffAD) { | |
| log_assert(!ffA2 && !ffA2cemux && !ffA2rstmux); | |
| std::swap(ffA2, ffAD); | |
| std::swap(ffA2cemux, ffADcemux); | |
| std::swap(ffA2rstmux, ffADrstmux); | |
| ffA2cepol = ffADcepol; | |
| ffArstpol = ffADrstpol; | |
| } | |
| // Now attempt to match A1 | |
| if (ffA2) { | |
| argQ = sigA; | |
| subpattern(in_dffe); | |
| if (dff) { | |
| if ((ffA2rstmux != nullptr) ^ (dffrstmux != nullptr)) | |
| goto ffA1_end; | |
| if (dffrstmux) { | |
| if (ffArstpol != dffrstpol) | |
| goto ffA1_end; | |
| if (port(ffA2rstmux, \S) != port(dffrstmux, \S)) | |
| goto ffA1_end; | |
| ffA1rstmux = dffrstmux; | |
| } | |
| ffA1 = dff; | |
| clock = dffclock; | |
| if (dffcemux) { | |
| ffA1cemux = dffcemux; | |
| ffA1cepol = dffcepol; | |
| } | |
| sigA = dffD; | |
| ffA1_end: ; | |
| } | |
| } | |
| endcode | |
| // (5) Match 'B' input for B2REG | |
| // If B2REG, then match 'B' input for B1REG | |
| code argQ ffB2 ffB2cemux ffB2rstmux ffB2cepol ffBrstpol sigB clock ffB1 ffB1cemux ffB1rstmux ffB1cepol | |
| if (param(dsp, \BREG).as_int() == 0) { | |
| argQ = sigB; | |
| subpattern(in_dffe); | |
| if (dff) { | |
| ffB2 = dff; | |
| clock = dffclock; | |
| if (dffrstmux) { | |
| ffB2rstmux = dffrstmux; | |
| ffBrstpol = dffrstpol; | |
| } | |
| if (dffcemux) { | |
| ffB2cemux = dffcemux; | |
| ffB2cepol = dffcepol; | |
| } | |
| sigB = dffD; | |
| // Now attempt to match B1 | |
| if (ffB2) { | |
| argQ = sigB; | |
| subpattern(in_dffe); | |
| if (dff) { | |
| if ((ffB2rstmux != nullptr) ^ (dffrstmux != nullptr)) | |
| goto ffB1_end; | |
| if (dffrstmux) { | |
| if (ffBrstpol != dffrstpol) | |
| goto ffB1_end; | |
| if (port(ffB2rstmux, \S) != port(dffrstmux, \S)) | |
| goto ffB1_end; | |
| ffB1rstmux = dffrstmux; | |
| } | |
| ffB1 = dff; | |
| clock = dffclock; | |
| if (dffcemux) { | |
| ffB1cemux = dffcemux; | |
| ffB1cepol = dffcepol; | |
| } | |
| sigB = dffD; | |
| ffB1_end: ; | |
| } | |
| } | |
| } | |
| } | |
| endcode | |
| // (6) Match 'D' input for DREG | |
| code argQ ffD ffDcemux ffDrstmux ffDcepol ffDrstpol sigD clock | |
| if (param(dsp, \DREG).as_int() == 0) { | |
| argQ = sigD; | |
| subpattern(in_dffe); | |
| if (dff) { | |
| ffD = dff; | |
| clock = dffclock; | |
| if (dffrstmux) { | |
| ffDrstmux = dffrstmux; | |
| ffDrstpol = dffrstpol; | |
| } | |
| if (dffcemux) { | |
| ffDcemux = dffcemux; | |
| ffDcepol = dffcepol; | |
| } | |
| sigD = dffD; | |
| } | |
| } | |
| endcode | |
| // (7) Match 'P' output that exclusively drives an MREG | |
| code argD ffM ffMcemux ffMrstmux ffMcepol ffMrstpol sigM sigP clock | |
| if (param(dsp, \MREG).as_int() == 0 && nusers(sigM) == 2) { | |
| argD = sigM; | |
| subpattern(out_dffe); | |
| if (dff) { | |
| ffM = dff; | |
| clock = dffclock; | |
| if (dffrstmux) { | |
| ffMrstmux = dffrstmux; | |
| ffMrstpol = dffrstpol; | |
| } | |
| if (dffcemux) { | |
| ffMcemux = dffcemux; | |
| ffMcepol = dffcepol; | |
| } | |
| sigM = dffQ; | |
| } | |
| } | |
| sigP = sigM; | |
| endcode | |
| // (8) Match 'P' output that exclusively drives one of two inputs to an $add | |
| // cell (post-adder). | |
| // The other input to the adder is assumed to come in from the 'C' input | |
| // (note: 'P' -> 'C' connections that exist for accumulators are | |
| // recognised in xilinx_dsp.cc). | |
| match postAdd | |
| // Ensure that Z mux is not already used | |
| if port(dsp, \OPMODE, SigSpec()).extract(4,3).is_fully_zero() | |
| select postAdd->type.in($add) | |
| select GetSize(port(postAdd, \Y)) <= 48 | |
| choice <IdString> AB {\A, \B} | |
| select nusers(port(postAdd, AB)) <= 3 | |
| filter ffMcemux || nusers(port(postAdd, AB)) == 2 | |
| filter !ffMcemux || nusers(port(postAdd, AB)) == 3 | |
| index <SigBit> port(postAdd, AB)[0] === sigP[0] | |
| filter GetSize(port(postAdd, AB)) >= GetSize(sigP) | |
| filter port(postAdd, AB).extract(0, GetSize(sigP)) == sigP | |
| // Check that remainder of AB is a sign-extension | |
| define <bool> AB_SIGNED (param(postAdd, AB == \A ? \A_SIGNED : \B_SIGNED).as_bool()) | |
| filter port(postAdd, AB).extract_end(GetSize(sigP)) == SigSpec(AB_SIGNED ? sigP[GetSize(sigP)-1] : State::S0, GetSize(port(postAdd, AB))-GetSize(sigP)) | |
| set postAddAB AB | |
| optional | |
| endmatch | |
| code sigC sigP | |
| if (postAdd) { | |
| sigC = port(postAdd, postAddAB == \A ? \B : \A); | |
| sigP = port(postAdd, \Y); | |
| } | |
| endcode | |
| // (9) Match 'P' output that exclusively drives a PREG | |
| code argD ffP ffPcemux ffPrstmux ffPcepol ffPrstpol sigP clock | |
| if (param(dsp, \PREG).as_int() == 0) { | |
| int users = 2; | |
| // If ffMcemux and no postAdd new-value net must have three users: ffMcemux, ffM and ffPcemux | |
| if (ffMcemux && !postAdd) users++; | |
| if (nusers(sigP) == users) { | |
| argD = sigP; | |
| subpattern(out_dffe); | |
| if (dff) { | |
| ffP = dff; | |
| clock = dffclock; | |
| if (dffrstmux) { | |
| ffPrstmux = dffrstmux; | |
| ffPrstpol = dffrstpol; | |
| } | |
| if (dffcemux) { | |
| ffPcemux = dffcemux; | |
| ffPcepol = dffcepol; | |
| } | |
| sigP = dffQ; | |
| } | |
| } | |
| } | |
| endcode | |
| // (10) If post-adder and PREG both present, match for a $mux cell driving | |
| // the 'C' input, where one of the $mux's inputs is the PREG output. | |
| // This indicates an accumulator situation, and one where a $mux exists | |
| // to override the accumulated value: | |
| // +--------------------------------+ | |
| // | ____ | | |
| // +--| \ | | |
| // |$mux|-+ | | |
| // 'C' ---|____/ | | | |
| // | /-------\ +----+ | | |
| // +----+ +-| post- |___|PREG|---+ 'P' | |
| // |MREG|------ | adder | +----+ | |
| // +----+ \-------/ | |
| match postAddMux | |
| if postAdd | |
| if ffP | |
| select postAddMux->type.in($mux) | |
| select nusers(port(postAddMux, \Y)) == 2 | |
| choice <IdString> AB {\A, \B} | |
| index <SigSpec> port(postAddMux, AB) === sigP | |
| index <SigSpec> port(postAddMux, \Y) === sigC | |
| set postAddMuxAB AB | |
| optional | |
| endmatch | |
| code sigC | |
| if (postAddMux) | |
| sigC = port(postAddMux, postAddMuxAB == \A ? \B : \A); | |
| endcode | |
| // (11) If PREG present, match for a greater-than-or-equal $ge cell attached to | |
| // the 'P' output where it is compared to a constant that is a power-of-2: | |
| // e.g. `assign overflow = (PREG >= 2**40);` | |
| // In this scenario, the pattern detector functionality of a DSP48E1 can | |
| // to implement this function | |
| match overflow | |
| if ffP | |
| if param(dsp, \USE_PATTERN_DETECT, Const("NO_PATDET")).decode_string() == "NO_PATDET" | |
| select overflow->type.in($ge) | |
| select GetSize(port(overflow, \Y)) <= 48 | |
| select port(overflow, \B).is_fully_const() | |
| define <Const> B port(overflow, \B).as_const() | |
| select std::count(B.bits.begin(), B.bits.end(), State::S1) == 1 | |
| index <SigSpec> port(overflow, \A) === sigP | |
| optional | |
| endmatch | |
| code | |
| accept; | |
| endcode | |
| // ####################### | |
| // Subpattern for matching against input registers, based on knowledge of the | |
| // 'Q' input. Typically, identifying registers with clock-enable and reset | |
| // capability would be a task would be handled by other Yosys passes such as | |
| // dff2dffe, but since DSP inference happens much before this, these patterns | |
| // have to be manually identified. | |
| // At a high level: | |
| // (1) Starting from a $dff cell that (partially or fully) drives the given | |
| // 'Q' argument | |
| // (2) Match for a $mux cell implementing synchronous reset semantics --- | |
| // one that exclusively drives the 'D' input of the $dff, with one of its | |
| // $mux inputs being fully zero | |
| // (3) Match for a $mux cell implement clock enable semantics --- one that | |
| // exclusively drives the 'D' input of the $dff (or the other input of | |
| // the reset $mux) and where one of this $mux's inputs is connected to | |
| // the 'Q' output of the $dff | |
| subpattern in_dffe | |
| arg argD argQ clock | |
| code | |
| dff = nullptr; | |
| for (const auto &c : argQ.chunks()) { | |
| // Abandon matches when 'Q' is a constant | |
| if (!c.wire) | |
| reject; | |
| // Abandon matches when 'Q' has the keep attribute set | |
| if (c.wire->get_bool_attribute(\keep)) | |
| reject; | |
| // Abandon matches when 'Q' has a non-zero init attribute set | |
| // (not supported by DSP48E1) | |
| Const init = c.wire->attributes.at(\init, Const()); | |
| if (!init.empty()) | |
| for (auto b : init.extract(c.offset, c.width)) | |
| if (b != State::Sx && b != State::S0) | |
| reject; | |
| } | |
| endcode | |
| // (1) Starting from a $dff cell that (partially or fully) drives the given | |
| // 'Q' argument | |
| 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 | |
| filter clock == SigBit() || port(ff, \CLK) == clock | |
| set ffoffset offset | |
| endmatch | |
| code argQ argD | |
| SigSpec Q = port(ff, \Q); | |
| dff = ff; | |
| dffclock = port(ff, \CLK); | |
| 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 | |
| // (2) Match for a $mux cell implementing synchronous reset semantics --- | |
| // exclusively drives the 'D' input of the $dff, with one of the $mux | |
| // inputs being fully zero | |
| match ffrstmux | |
| 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 ffcemux 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 | |
| // (3) Match for a $mux cell implement clock enable semantics --- one that | |
| // exclusively drives the 'D' input of the $dff (or the other input of | |
| // the reset $mux) and where one of this $mux's inputs is connected to | |
| // the 'Q' output of the $dff | |
| match ffcemux | |
| if !argD.empty() | |
| select ffcemux->type.in($mux) | |
| index <SigSpec> port(ffcemux, \Y) === argD | |
| choice <IdString> AB {\A, \B} | |
| index <SigSpec> port(ffcemux, AB) === argQ | |
| define <bool> pol (AB == \A) | |
| set ffcepol pol | |
| semioptional | |
| endmatch | |
| code argD | |
| if (ffcemux) { | |
| dffcemux = ffcemux; | |
| dffcepol = ffcepol; | |
| argD = port(ffcemux, ffcepol ? \B : \A); | |
| dffD.replace(port(ffcemux, \Y), argD); | |
| } | |
| else | |
| dffcemux = nullptr; | |
| endcode | |
| // ####################### | |
| // Subpattern for matching against output registers, based on knowledge of the | |
| // 'D' input. | |
| // At a high level: | |
| // (1) Starting from an optional $mux cell that implements clock enable | |
| // semantics --- one where the given 'D' argument (partially or fully) | |
| // drives one of its two inputs | |
| // (2) Starting from, or continuing onto, another optional $mux cell that | |
| // implements synchronous reset semantics --- one where the given 'D' | |
| // argument (or the clock enable $mux output) drives one of its two inputs | |
| // and where the other input is fully zero | |
| // (3) Match for a $dff cell (whose 'D' input is the 'D' argument, or the | |
| // output of the previous clock enable or reset $mux cells) | |
| subpattern out_dffe | |
| arg argD argQ clock | |
| code | |
| dff = nullptr; | |
| for (auto c : argD.chunks()) | |
| // Abandon matches when 'D' has the keep attribute set | |
| if (c.wire->get_bool_attribute(\keep)) | |
| reject; | |
| endcode | |
| // (1) Starting from an optional $mux cell that implements clock enable | |
| // semantics --- one where the given 'D' argument (partially or fully) | |
| // drives one of its two inputs | |
| match ffcemux | |
| select ffcemux->type.in($mux) | |
| // ffcemux output must have two users: ffcemux and ff.D | |
| select nusers(port(ffcemux, \Y)) == 2 | |
| choice <IdString> AB {\A, \B} | |
| // keep-last-value net must have at least three users: ffcemux, ff, downstream sink(s) | |
| select nusers(port(ffcemux, AB)) >= 3 | |
| slice offset GetSize(port(ffcemux, \Y)) | |
| define <IdString> BA (AB == \A ? \B : \A) | |
| index <SigBit> port(ffcemux, BA)[offset] === argD[0] | |
| // Check that the rest of argD is present | |
| filter GetSize(port(ffcemux, BA)) >= offset + GetSize(argD) | |
| filter port(ffcemux, BA).extract(offset, GetSize(argD)) == argD | |
| set ffoffset offset | |
| define <bool> pol (AB == \A) | |
| set ffcepol pol | |
| semioptional | |
| endmatch | |
| code argD argQ | |
| dffcemux = ffcemux; | |
| if (ffcemux) { | |
| SigSpec BA = port(ffcemux, ffcepol ? \B : \A); | |
| SigSpec Y = port(ffcemux, \Y); | |
| argQ = argD; | |
| argD.replace(BA, Y); | |
| argQ.replace(BA, port(ffcemux, ffcepol ? \A : \B)); | |
| dffcemux = ffcemux; | |
| dffcepol = ffcepol; | |
| } | |
| endcode | |
| // (2) Starting from, or continuing onto, another optional $mux cell that | |
| // implements synchronous reset semantics --- one where the given 'D' | |
| // argument (or the clock enable $mux output) drives one of its two inputs | |
| // and where the other input is fully zero | |
| match ffrstmux | |
| 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 !ffcemux || 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 | |
| // (3) Match for a $dff cell (whose 'D' input is the 'D' argument, or the | |
| // output of the previous clock enable or reset $mux cells) | |
| 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 (!ffcemux && !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 !ffcemux || port(ff, \Q).extract(offset, GetSize(argQ)) == argQ | |
| filter clock == SigBit() || port(ff, \CLK) == clock | |
| set ffoffset offset | |
| endmatch | |
| code argQ | |
| SigSpec D = port(ff, \D); | |
| SigSpec Q = port(ff, \Q); | |
| if (!ffcemux) { | |
| argQ = argD; | |
| argQ.replace(D, Q); | |
| } | |
| // Abandon matches when 'Q' has a non-zero init attribute set | |
| // (not supported by DSP48E1) | |
| for (auto c : argQ.chunks()) { | |
| Const init = c.wire->attributes.at(\init, Const()); | |
| if (!init.empty()) | |
| for (auto b : init.extract(c.offset, c.width)) | |
| if (b != State::Sx && b != State::S0) | |
| reject; | |
| } | |
| dff = ff; | |
| dffQ = argQ; | |
| dffclock = port(ff, \CLK); | |
| endcode |