// 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 DSP48E1 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: // - Separating out CREG packing 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 // subpattern state argQ argD state ffcepol ffrstpol state ffoffset udata dffD dffQ udata dffclock udata dff dffcemux dffrstmux udata dffcepol dffrstpol // (1) Starting from a DSP48E1 cell that (a) doesn't have a CREG already, // and (b) uses the 'C' port match dsp select dsp->type.in(\DSP48E1) select param(dsp, \CREG, 1).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 (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; sigP.append(P[i]); } log_assert(nusers(P.extract_end(i)) <= 1); } 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 a subpattern discussed below) code argQ ffC ffCcemux ffCrstmux ffCcepol ffCrstpol sigC clock // TODO: Any downside to allowing this? // If this DSP implements an accumulator, do not attempt to match if (sigC == sigP) reject; 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 in_dffe arg argD argQ clock code dff = nullptr; 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() && port(ff, \CLK) != clock) reject; 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 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 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