// This file describes the second of three pattern matcher setups that // forms the `xilinx_dsp` pass described in xilinx_dsp.cc // At a high level, it works as follows: // (1) Starting from a DSP48* cell that (a) doesn't have a CREG already, // and (b) uses the 'C' port // (2) Match the driver of the 'C' input to a possible $dff cell (CREG) // (attached to at most two $mux cells that implement clock-enable or // reset functionality, using a subpattern discussed below) // Notes: // - Running CREG packing after xilinx_dsp_pack is necessary since there is no // guarantee that the cell ordering corresponds to the "expected" case (i.e. // the order in which they appear in the source) thus the possiblity existed // that a register got packed as a CREG into a downstream DSP that should // have otherwise been a PREG of an upstream DSP that had not been visited // yet // - The reason this is separated out from the xilinx_dsp.pmg file is // for efficiency --- each *.pmg file creates a class of the same basename, // which when constructed, creates a custom database tailored to the // pattern(s) contained within. Since the pattern in this file must be // executed after the pattern contained in xilinx_dsp.pmg, it is necessary // to reconstruct this database. Separating the two patterns into // independent files causes two smaller, more specific, databases. pattern xilinx_dsp_packC udata > unextend state clock state sigC sigP state ffCcepol ffCrstpol state ffC ffCcemux ffCrstmux // Variables used for subpatterns state argQ argD state ffcepol ffrstpol state ffoffset udata dffD dffQ udata dffclock udata dff dffcemux dffrstmux udata dffcepol dffrstpol // (1) Starting from a DSP48* cell that (a) doesn't have a CREG already, // and (b) uses the 'C' port match dsp select dsp->type.in(\DSP48A, \DSP48A1, \DSP48E1) select param(dsp, \CREG).as_int() == 0 select nusers(port(dsp, \C, SigSpec())) > 1 endmatch code sigC sigP clock 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); }; sigC = unextend(port(dsp, \C, SigSpec())); SigSpec P = port(dsp, \P); if (!dsp->type.in(\DSP48E1) || param(dsp, \USE_MULT).decode_string() == "MULTIPLY") { // Only care about those bits that are used int i; for (i = GetSize(P)-1; i >= 0; i--) if (nusers(P[i]) > 1) break; i++; log_assert(nusers(P.extract_end(i)) <= 1); sigP = P.extract(0, i); } else sigP = P; clock = port(dsp, \CLK, SigBit()); endcode // (2) Match the driver of the 'C' input to a possible $dff cell (CREG) // (attached to at most two $mux cells that implement clock-enable or // reset functionality, using the in_dffe subpattern) code argQ ffC ffCcemux ffCrstmux ffCcepol ffCrstpol sigC clock argQ = sigC; subpattern(in_dffe); if (dff) { ffC = dff; clock = dffclock; if (dffrstmux) { ffCrstmux = dffrstmux; ffCrstpol = dffrstpol; } if (dffcemux) { ffCcemux = dffcemux; ffCcepol = dffcepol; } sigC = dffD; } endcode code if (ffC) 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()); 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 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 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 ffcemux 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 // (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 port(ffcemux, \Y) === argD choice AB {\A, \B} index port(ffcemux, AB) === argQ define 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