diff options
Diffstat (limited to 'passes/pmgen/xilinx_dsp.pmg')
-rw-r--r-- | passes/pmgen/xilinx_dsp.pmg | 467 |
1 files changed, 467 insertions, 0 deletions
diff --git a/passes/pmgen/xilinx_dsp.pmg b/passes/pmgen/xilinx_dsp.pmg new file mode 100644 index 000000000..7db8e95a6 --- /dev/null +++ b/passes/pmgen/xilinx_dsp.pmg @@ -0,0 +1,467 @@ +pattern xilinx_dsp + +state <std::function<SigSpec(const SigSpec&)>> unextend +state <SigBit> clock +state <SigSpec> sigA sigffAmuxY sigB sigffBmuxY sigC sigffCmuxY sigD sigffDmuxY sigM sigP +state <IdString> postAddAB postAddMuxAB +state <bool> ffAcepol ffADcepol ffBcepol ffCcepol ffDcepol ffMcepol ffPcepol ffPrstpol +state <int> ffPoffset + +state <Cell*> ffAD ffADmux ffA ffAmux ffB ffBmux ffC ffCmux ffD ffDmux ffM ffMmux ffP ffPcemux ffPrstmux + +// subpattern +state <SigSpec> argQ argD +state <bool> ffcepol ffrstpol +udata <SigSpec> dffD dffQ +udata <SigBit> dffclock +udata <Cell*> dff dffcemux dffrstmux +udata <bool> dffcepol dffrstpol + +match dsp + select dsp->type.in(\DSP48E1) +endmatch + +code unextend sigA sigB sigC sigD sigM + 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 = dsp->connections_.at(\C, SigSpec()); + sigD = dsp->connections_.at(\D, SigSpec()); + + SigSpec P = port(dsp, \P); + if (dsp->parameters.at(\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); + } + else + sigM = P; +endcode + +code argQ ffAD ffADmux ffADcepol sigA clock + if (param(dsp, \ADREG).as_int() == 0) { + argQ = sigA; + subpattern(in_dffe); + if (dff) { + ffAD = dff; + clock = dffclock; + if (dffcemux) { + ffADmux = dffcemux; + ffADcepol = dffcepol; + } + sigA = dffD; + } + } +endcode + +match preAdd + if sigD.empty() || sigD.is_fully_zero() + // Ensure that preAdder not already used + if dsp->parameters.at(\USE_DPORT, Const("FALSE")).decode_string() == "FALSE" + if dsp->connections_.at(\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); + if (GetSize(sigA) < GetSize(sigD)) + std::swap(sigA, sigD); + } +endcode + +code argQ ffA ffAmux ffAcepol sigA clock ffAD ffADmux ffADcepol + // Only search for ffA if there was a pre-adder + // (otherwise ffA would have been matched as ffAD) + if (preAdd) { + if (param(dsp, \AREG).as_int() == 0) { + argQ = sigA; + subpattern(in_dffe); + if (dff) { + ffA = dff; + clock = dffclock; + if (dffcemux) { + ffAmux = dffcemux; + ffAcepol = dffcepol; + } + sigA = dffD; + } + } + } + // And if there wasn't a pre-adder, + // move AD register to A + else if (ffAD) { + log_assert(!ffA && !ffAmux); + std::swap(ffA, ffAD); + std::swap(ffAmux, ffADmux); + ffAcepol = ffADcepol; + } +endcode + +code argQ ffB ffBmux ffBcepol sigB clock + if (param(dsp, \BREG).as_int() == 0) { + argQ = sigB; + subpattern(in_dffe); + if (dff) { + ffB = dff; + clock = dffclock; + if (dffcemux) { + ffBmux = dffcemux; + ffBcepol = dffcepol; + } + sigB = dffD; + } + } +endcode + +code argQ ffD ffDmux ffDcepol sigD clock + if (param(dsp, \DREG).as_int() == 0) { + argQ = sigD; + subpattern(in_dffe); + if (dff) { + ffD = dff; + clock = dffclock; + if (dffcemux) { + ffDmux = dffcemux; + ffDcepol = dffcepol; + } + sigD = dffD; + } + } +endcode + +code argD ffM ffMmux ffMcepol 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 (dffcemux) { + ffMmux = dffcemux; + ffMcepol = dffcepol; + } + sigM = dffQ; + } + } + sigP = sigM; +endcode + +match postAdd + // Ensure that Z mux is not already used + if port(dsp, \OPMODE).extract(4,3).is_fully_zero() + + select postAdd->type.in($add) + select GetSize(port(postAdd, \Y)) <= 48 + select nusers(port(postAdd, \Y)) == 2 + choice <IdString> AB {\A, \B} + select nusers(port(postAdd, AB)) <= 3 + filter ffMmux || nusers(port(postAdd, AB)) == 2 + filter !ffMmux || nusers(port(postAdd, AB)) == 3 + filter GetSize(unextend(port(postAdd, AB))) <= GetSize(sigP) + filter unextend(port(postAdd, AB)) == sigP.extract(0, GetSize(unextend(port(postAdd, AB)))) + filter nusers(sigP.extract_end(GetSize(unextend(port(postAdd, AB))))) <= 1 + set postAddAB AB + optional +endmatch + +code sigC sigP + if (postAdd) { + sigC = port(postAdd, postAddAB == \A ? \B : \A); + + // TODO for DSP48E1, which will have sign extended inputs/outputs + //int natural_mul_width = GetSize(port(dsp, \A)) + GetSize(port(dsp, \B)); + //int actual_mul_width = GetSize(sigP); + //int actual_acc_width = GetSize(sigC); + + //if ((actual_acc_width > actual_mul_width) && (natural_mul_width > actual_mul_width)) + // reject; + //if ((actual_acc_width != actual_mul_width) && (param(dsp, \A_SIGNED).as_bool() != param(postAdd, \A_SIGNED).as_bool())) + // reject; + + sigP = port(postAdd, \Y); + } +endcode + +code argD ffP ffPcemux ffPrstmux ffPcepol ffPrstpol sigP clock + if (param(dsp, \PREG).as_int() == 0) { + // If ffMmux and no postAdd new-value net must have exactly three users: ffMmux, ffM and ffPcemux + if ((ffMmux && !postAdd && nusers(sigP) == 3) || + // Otherwise new-value net must have exactly two users: dsp and ffPcemux + ((!ffMmux || postAdd) && nusers(sigP) == 2)) { + argD = sigP; + subpattern(out_dffe); + if (dff) { + ffP = dff; + clock = dffclock; + if (dffcemux) { + ffPcemux = dffcemux; + ffPcepol = dffcepol; + ffPrstmux = dffrstmux; + ffPrstpol = dffrstpol; + } + sigP = dffQ; + } + } + } +endcode + +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 + +code argQ ffC ffCmux ffCcepol sigC clock + if (param(dsp, \CREG).as_int() == 0) { + argQ = sigC; + subpattern(in_dffe); + if (dff) { + ffC = dff; + clock = dffclock; + if (dffcemux) { + ffCmux = dffcemux; + ffCcepol = dffcepol; + } + sigC = dffD; + } + } +endcode + +code + accept; +endcode + +// ####################### + +subpattern in_dffe +arg argQ clock ffcepol + +match ff + select ff->type.in($dff) + // DSP48E1 does not support clock inversion + select param(ff, \CLK_POLARITY).as_bool() + filter GetSize(port(ff, \Q)) >= GetSize(argQ) + slice offset GetSize(port(ff, \Q)) + filter offset+GetSize(argQ) <= GetSize(port(ff, \Q)) + filter port(ff, \Q).extract(offset, GetSize(argQ)) == argQ + semioptional +endmatch + +code argQ + if (ff) { + for (auto c : argQ.chunks()) + if (c.wire->get_bool_attribute(\keep)) + reject; + + if (clock != SigBit()) { + if (port(ff, \CLK) != clock) + reject; + } + dffclock = port(ff, \CLK); + + dff = ff; + dffD = argQ; + dffD.replace(port(ff, \Q), port(ff, \D)); + // Only search for ffcemux if argQ has at + // least 3 users (ff, <upstream>, ffcemux) and + // its ff.D only has two (ff, ffcemux) + if (!(nusers(argQ) >= 3 && nusers(dffD) == 2)) + argQ = SigSpec(); + } + else { + dff = nullptr; + argQ = SigSpec(); + } +endcode + +match ffcemux + if !argQ.empty() + select ffcemux->type.in($mux) + index <SigSpec> port(ffcemux, \Y) === port(ff, \D) + filter GetSize(port(ffcemux, \Y)) >= GetSize(dffD) + slice offset GetSize(port(ffcemux, \Y)) + filter offset+GetSize(dffD) <= GetSize(port(ffcemux, \Y)) + filter port(ffcemux, \Y).extract(offset, GetSize(dffD)) == dffD + choice <IdString> AB {\A, \B} + filter offset+GetSize(argQ) <= GetSize(port(ffcemux, \Y)) + filter port(ffcemux, AB).extract(offset, GetSize(argQ)) == argQ + define <bool> pol (AB == \A) + set ffcepol pol + semioptional +endmatch + +code + if (ffcemux) { + dffcemux = ffcemux; + dffcepol = ffcepol; + dffD = port(ffcemux, dffcepol ? \B : \A); + } + else + dffcemux = nullptr; +endcode + +// ####################### + +subpattern out_dffe +arg argD argQ clock +arg unextend + +match ffcemux + select ffcemux->type.in($mux) + // ffcemux output must have two users: ffcemux and ff.D + select nusers(port(ffcemux, \Y)) == 2 + filter GetSize(port(ffcemux, \Y)) >= GetSize(argD) + + choice <IdString> BA {\B, \A} + // new-value net must have exactly two users: (upstream) and ffcemux + select nusers(port(ffcemux, BA)) == 2 + + define <IdString> AB (BA == \B ? \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)) + filter GetSize(unextend(port(ffcemux, BA))) <= GetSize(argD) + filter unextend(port(ffcemux, BA)) == argD.extract(0, GetSize(unextend(port(ffcemux, BA)))) + // Remaining bits on argD must not have any other users + filter nusers(argD.extract_end(GetSize(unextend(port(ffcemux, BA))))) <= 1 + + define <bool> pol (AB == \A) + set ffcepol pol + semioptional +endmatch + +code argD argQ + if (ffcemux) { + dffcemux = ffcemux; + dffcepol = ffcepol; + argD = port(ffcemux, \Y); + argQ = port(ffcemux, ffcepol ? \A : \B); + } + else + dffcemux = nullptr; +endcode + +match ffrstmux + if !argQ.empty() + select ffrstmux->type.in($mux) + // ffrstmux output must have two users: ffrstmux and ff.D + select nusers(port(ffrstmux, \Y)) == 2 + filter GetSize(port(ffrstmux, \Y)) >= GetSize(argD) + + choice <IdString> BA {\B, \A} + // DSP48E1 only supports reset to zero + select port(ffrstmux, BA).is_fully_zero() + + define <IdString> AB (BA == \B ? \A : \B) + // keep-last-value net must have exactly 2 users: ffrstmux, ffcemux/<upstream> + select nusers(port(ffrstmux, AB)) == 2 + + slice offset GetSize(port(ffrstmux, \Y)) + filter GetSize(port(ffrstmux, AB)) <= GetSize(argD) + filter port(ffrstmux, AB) == argD.extract(0, GetSize(port(ffrstmux, AB))) + // Remaining bits on argD must not have any other users + filter nusers(argD.extract_end(GetSize(port(ffrstmux, AB)))) <= 1 + + define <bool> pol (AB == \A) + set ffrstpol pol + semioptional +endmatch + +code argD argQ + if (ffrstmux) { + dffrstmux = ffrstmux; + dffrstpol = ffrstpol; + argD = port(ffrstmux, \Y); + } + else { + dffrstmux = nullptr; + argQ = SigSpec(); + } +endcode + +match ff_enable + if !argQ.empty() + select ff_enable->type.in($dff) + // DSP48E1 does not support clock inversion + select param(ff_enable, \CLK_POLARITY).as_bool() + index <SigSpec> port(ff_enable, \D) === argD + index <SigSpec> port(ff_enable, \Q) === argQ +endmatch + +match ff + if !ff_enable + select ff->type.in($dff) + // DSP48E1 does not support clock inversion + select param(ff, \CLK_POLARITY).as_bool() + index <SigSpec> port(ff, \D) === argD + semioptional +endmatch + +code + if (ff_enable) + dff = ff_enable; + else + dff = ff; + if (dff) { + dffQ = port(dff, \Q); + + for (auto c : dffQ.chunks()) { + 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; + } + + if (clock != SigBit()) { + if (port(dff, \CLK) != clock) + reject; + } + dffclock = port(dff, \CLK); + } + // No enable/reset mux possible without flop + else if (ffcemux || ffrstmux) + reject; +endcode |