xilinx_dsp_cascade to also cascade AREG and BREG
authorEddie Hung <eddie@fpgeh.com>
Thu, 26 Sep 2019 20:29:18 +0000 (13:29 -0700)
committerEddie Hung <eddie@fpgeh.com>
Thu, 26 Sep 2019 20:29:18 +0000 (13:29 -0700)
passes/pmgen/xilinx_dsp.cc
passes/pmgen/xilinx_dsp_cascade.pmg

index 4c297a50ae2ac6dad19efcd929d3f3c2984c600a..b0251de50776820ebd8788bf57522f869ce69a50 100644 (file)
@@ -609,30 +609,26 @@ struct XilinxDspPass : public Pass {
                for (auto module : design->selected_modules()) {
                        xilinx_simd_pack(module, module->selected_cells());
 
-            {
-                xilinx_dsp_pm pm(module, module->selected_cells());
-                pm.run_xilinx_dsp_pack(xilinx_dsp_pack);
-            }
-            // 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 CREG into a
-            //   downstream DSP that should have otherwise been a
-            //   PREG of an upstream DSP that had not been pattern
-            //   matched yet
-            {
-                xilinx_dsp_CREG_pm pm(module, module->selected_cells());
-                pm.run_xilinx_dsp_packC(xilinx_dsp_packC);
-            }
-
-                       do {
-                               did_something = false;
+                       {
+                               xilinx_dsp_pm pm(module, module->selected_cells());
+                               pm.run_xilinx_dsp_pack(xilinx_dsp_pack);
+                       }
+                       // 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 CREG into a
+                       //   downstream DSP that should have otherwise been a
+                       //   PREG of an upstream DSP that had not been pattern
+                       //   matched yet
+                       {
+                               xilinx_dsp_CREG_pm pm(module, module->selected_cells());
+                               pm.run_xilinx_dsp_packC(xilinx_dsp_packC);
+                       }
+                       {
                                xilinx_dsp_cascade_pm pm(module, module->selected_cells());
-                               pm.run_xilinx_dsp_cascadeP();
-                               //pm.run_xilinx_dsp_cascadeAB();
-                               break;
-                       } while (did_something);
+                               pm.run_xilinx_dsp_cascade();
+                       }
                }
        }
 } XilinxDspPass;
index 59cd1267d632fcf0d885750736ca5fb845ddf6c5..2fc943a66b2e047e9a32e5149255af653a9906c8 100644 (file)
@@ -1,6 +1,19 @@
-pattern xilinx_dsp_cascadeP
+pattern xilinx_dsp_cascade
 
-udata <vector<std::pair<Cell*,bool>>> chain longest_chain
+udata <std::function<SigSpec(const SigSpec&)>> unextend
+udata <vector<std::tuple<Cell*,int,int,int>>> chain longest_chain
+state <Cell*> next
+state <SigSpec> clock
+state <int> AREG BREG
+
+// subpattern
+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
 
 code
 #define MAX_DSP_CASCADE 20
@@ -14,41 +27,71 @@ endmatch
 
 code
        longest_chain.clear();
-       chain.emplace_back(first, false);
+       chain.emplace_back(first, -1, -1, -1);
        subpattern(tail);
 finally
        chain.pop_back();
        log_assert(chain.empty());
        if (GetSize(longest_chain) > 1) {
-               Cell *dsp = longest_chain.front().first;
+               Cell *dsp = std::get<0>(longest_chain.front());
 
+               Cell *dsp_pcin;
+               int P, AREG, BREG;
                for (int i = 1; i < GetSize(longest_chain); i++) {
-                       Cell *dsp_pcin = longest_chain[i].first;
-                       bool shift17 = longest_chain[i].second;
+                       std::tie(dsp_pcin,P,AREG,BREG) = longest_chain[i];
 
                        dsp_pcin->setPort(ID(C), Const(0, 48));
 
                        if (i % MAX_DSP_CASCADE > 0) {
-                               Wire *cascade = module->addWire(NEW_ID, 48);
-                               dsp_pcin->setPort(ID(PCIN), cascade);
-                               dsp->setPort(ID(PCOUT), cascade);
-                               add_siguser(cascade, dsp_pcin);
-                               add_siguser(cascade, dsp);
-
-                               SigSpec opmode = port(dsp_pcin, \OPMODE, Const(0, 7));
-                               if (shift17)
-                                       opmode[6] = State::S1;
-                               else
-                                       opmode[6] = State::S0;
-
-                               opmode[5] = State::S0;
-                               opmode[4] = State::S1;
-                               dsp_pcin->setPort(\OPMODE, opmode);
-
-                               log_debug("PCOUT -> PCIN cascade for %s -> %s\n", log_id(dsp), log_id(dsp_pcin));
+                               if (P >= 0) {
+                                       Wire *cascade = module->addWire(NEW_ID, 48);
+                                       dsp_pcin->setPort(ID(PCIN), cascade);
+                                       dsp->setPort(ID(PCOUT), cascade);
+                                       add_siguser(cascade, dsp_pcin);
+                                       add_siguser(cascade, dsp);
+
+                                       SigSpec opmode = port(dsp_pcin, \OPMODE, Const(0, 7));
+                                       if (P == 17)
+                                               opmode[6] = State::S1;
+                                       else if (P == 0)
+                                               opmode[6] = State::S0;
+                                       else log_abort();
+
+                                       opmode[5] = State::S0;
+                                       opmode[4] = State::S1;
+                                       dsp_pcin->setPort(\OPMODE, opmode);
+
+                                       log_debug("PCOUT -> PCIN cascade for %s -> %s\n", log_id(dsp), log_id(dsp_pcin));
+                               }
+                               if (AREG >= 0) {
+                                       Wire *cascade = module->addWire(NEW_ID, 30);
+                                       dsp_pcin->setPort(ID(ACIN), cascade);
+                                       dsp->setPort(ID(ACOUT), cascade);
+                                       dsp_pcin->unsetPort(ID(A));
+                                       add_siguser(cascade, dsp_pcin);
+                                       add_siguser(cascade, dsp);
+
+                                       dsp->setParam(ID(ACASCREG), AREG);
+                                       dsp_pcin->setParam(ID(A_INPUT), Const("CASCADE"));
+
+                                       log_debug("ACOUT -> ACIN cascade for %s -> %s\n", log_id(dsp), log_id(dsp_pcin));
+                               }
+                               if (BREG >= 0) {
+                                       Wire *cascade = module->addWire(NEW_ID, 18);
+                                       dsp_pcin->setPort(ID(BCIN), cascade);
+                                       dsp->setPort(ID(BCOUT), cascade);
+                                       dsp_pcin->unsetPort(ID(B));
+                                       add_siguser(cascade, dsp_pcin);
+                                       add_siguser(cascade, dsp);
+
+                                       dsp->setParam(ID(BCASCREG), BREG);
+                                       dsp_pcin->setParam(ID(B_INPUT), Const("CASCADE"));
+
+                                       log_debug("BCOUT -> BCIN cascade for %s -> %s\n", log_id(dsp), log_id(dsp_pcin));
+                               }
                        }
                        else {
-                               log_debug("Blocking PCOUT -> PCIN cascade for %s -> %s (exceeds max: %d)\n", log_id(dsp), log_id(dsp_pcin), MAX_DSP_CASCADE);
+                               log_debug("  Blocking %s -> %s cascade (exceeds max: %d)\n", log_id(dsp), log_id(dsp_pcin), MAX_DSP_CASCADE);
                        }
 
                        dsp = dsp_pcin;
@@ -63,35 +106,35 @@ endcode
 
 subpattern tail
 arg first
-
-match next
-       select next->type.in(\DSP48E1)
-       select !param(next, \CREG, State::S1).as_bool()
-       select port(next, \OPMODE, Const(0, 7)).extract(4,3) == Const::from_string("011")
-       select nusers(port(next, \C, SigSpec())) > 1
-       select nusers(port(next, \PCIN, SigSpec())) == 0
-       index <SigBit> port(next, \C)[0] === port(chain.back().first, \P)[0]
+arg next
+
+match nextP
+       select nextP->type.in(\DSP48E1)
+       select !param(nextP, \CREG, State::S1).as_bool()
+       select port(nextP, \OPMODE, Const(0, 7)).extract(4,3) == Const::from_string("011")
+       select nusers(port(nextP, \C, SigSpec())) > 1
+       select nusers(port(nextP, \PCIN, SigSpec())) == 0
+       index <SigBit> port(nextP, \C)[0] === port(std::get<0>(chain.back()), \P)[0]
        semioptional
 endmatch
 
-match next_shift17
-       if !next_shift17
-       select next_shift17->type.in(\DSP48E1)
-       select !param(next_shift17, \CREG, State::S1).as_bool()
-       select port(next_shift17, \OPMODE, Const(0, 7)).extract(4,3) == Const::from_string("011")
-       select nusers(port(next_shift17, \C, SigSpec())) > 1
-       select nusers(port(next_shift17, \PCIN, SigSpec())) == 0
-       index <SigBit> port(next_shift17, \C)[0] === port(chain.back().first, \P)[17]
+match nextP_shift17
+       if !nextP
+       select nextP_shift17->type.in(\DSP48E1)
+       select !param(nextP_shift17, \CREG, State::S1).as_bool()
+       select port(nextP_shift17, \OPMODE, Const(0, 7)).extract(4,3) == Const::from_string("011")
+       select nusers(port(nextP_shift17, \C, SigSpec())) > 1
+       select nusers(port(nextP_shift17, \PCIN, SigSpec())) == 0
+       index <SigBit> port(nextP_shift17, \C)[0] === port(std::get<0>(chain.back()), \P)[17]
        semioptional
 endmatch
 
 code next
-       if (!next)
-               next = next_shift17;
+       next = nextP;
+       if (!nextP)
+               next = nextP_shift17;
        if (next) {
-               chain.emplace_back(next, next_shift17);
-
-               auto unextend = [](const SigSpec &sig) {
+               unextend = [](const SigSpec &sig) {
                        int i;
                        for (i = GetSize(sig)-1; i > 0; i--)
                                if (sig[i] != sig[i-1])
@@ -101,418 +144,94 @@ code next
                                ++i;
                        return sig.extract(0, i);
                };
-               SigSpec sigC = unextend(port(next, \C));
-
-               // TODO: Cannot use 'reject' since semioptional
-               if (next_shift17) {
-                       if (GetSize(sigC)+17 <= GetSize(port(chain.back().first, \P)) &&
-                                       port(chain.back().first, \P).extract(17, GetSize(sigC)) != sigC)
-                               subpattern(tail);
-               }
-               else {
-                       if (GetSize(sigC) <= GetSize(port(chain.back().first, \P)) &&
-                                       port(chain.back().first, \P).extract(0, GetSize(sigC)) != sigC)
-                               subpattern(tail);
-
-               }
-       } else {
-               if (GetSize(chain) > GetSize(longest_chain))
-                       longest_chain = chain;
        }
-finally
-       if (next)
-               chain.pop_back();
-endcode
-
-// ##########
-
-pattern xilinx_dsp_cascadeAB
-
-state <SigBit> clock
-state <SigSpec> sigA sigB
-
-state <bool> ffA1cepol ffA2cepol ffB1cepol ffB2cepol
-state <bool> ffArstpol ffBrstpol
-
-state <Cell*> ffA1 ffA1cemux ffA1rstmux ffA2 ffA2cemux ffA2rstmux
-state <Cell*> ffB1 ffB1cemux ffB1rstmux ffB2 ffB2cemux ffB2rstmux
-
-// subpattern
-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
-
-match dspD
-       select dspD->type.in(\DSP48E1)
-       select (param(dspD, \A_INPUT, Const("DIRECT")).decode_string() == "DIRECT" && nusers(port(dspD, \A, SigSpec())) > 1 && nusers(port(dspD, \ACIN, SigSpec())) == 0) || (param(dspD, \B_INPUT, Const("DIRECT")).decode_string() == "DIRECT" && nusers(port(dspD, \B, SigSpec())) > 1 && nusers(port(dspD, \BCIN, SigSpec())) == 0)
-endmatch
-
-code sigA sigB
-       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);
-       };
-       if (param(dspD, \A_INPUT, Const("DIRECT")).decode_string() == "DIRECT")
-               sigA = unextend(port(dspD, \A));
-       if (param(dspD, \B_INPUT, Const("DIRECT")).decode_string() == "DIRECT")
-               sigB = unextend(port(dspD, \B));
 endcode
 
-code argQ ffA2 ffA2cemux ffA2rstmux ffA2cepol ffArstpol ffA1 ffA1cemux ffA1rstmux ffA1cepol sigA clock
-       if (!sigA.empty()) {
-               argQ = sigA;
-               subpattern(in_dffe);
-               if (dff) {
-                       ffA2 = dff;
-                       clock = dffclock;
-                       if (dffrstmux) {
-                               ffA2rstmux = dffrstmux;
-                               ffArstpol = dffrstpol;
-                       }
-                       if (dffcemux) {
-                               ffA2cemux = dffcemux;
-                               ffA2cepol = dffcepol;
-                       }
-                       sigA = dffD;
-
-                       // Now attempt to match A1
-                       argQ = sigA;
+code argQ clock AREG
+       AREG = 0;
+       if (next) {
+               Cell *prev = std::get<0>(chain.back());
+               if (param(prev, \AREG, 2).as_int() > 0 &&
+                               param(next, \AREG, 2).as_int() > 0 &&
+                               param(next, \A_INPUT, Const("DIRECT")).decode_string() == "DIRECT" &&
+                               port(next, \ACIN, SigSpec()).is_fully_zero() &&
+                               nusers(port(prev, \ACOUT, SigSpec())) <= 1) {
+                       argQ = unextend(port(next, \A));
+                       clock = port(prev, \CLK);
                        subpattern(in_dffe);
                        if (dff) {
-                               if ((ffA2rstmux != nullptr) ^ (dffrstmux != nullptr))
-                                       goto reject_ffA1;
-                               if (dffrstmux) {
-                                       if (ffArstpol != dffrstpol)
-                                               goto reject_ffA1;
-                                       if (port(ffA2rstmux, \S) != port(dffrstmux, \S))
-                                               goto reject_ffA1;
-                                       ffA1rstmux = dffrstmux;
-                               }
-
-                               ffA1 = dff;
-                               clock = dffclock;
-
-                               if (dffcemux) {
-                                       ffA1cemux = dffcemux;
-                                       ffA1cepol = dffcepol;
-                               }
-                               sigA = dffD;
-
-reject_ffA1:                   ;
+                               if (!dffrstmux && port(prev, \RSTA, State::S0) != State::S0)
+                                       goto reject_AREG;
+                               if (dffrstmux && port(dffrstmux, \S) != port(prev, \RSTA, State::S0))
+                                       goto reject_AREG;
+                               if (!dffcemux && port(prev, \CEA2, State::S0) != State::S0)
+                                       goto reject_AREG;
+                               if (dffcemux && port(dffcemux, \S) != port(prev, \CEA2, State::S0))
+                                       goto reject_AREG;
+                               if (dffD == unextend(port(prev, \A)))
+                                       AREG = 1;
+reject_AREG:                   ;
                        }
                }
        }
 endcode
 
-match dspQA2
-       if ffA1
-       select dspQA2->type.in(\DSP48E1)
-       select param(dspQA2, \A_REG, 2).as_int() == 2
-       select nusers(port(dspQA2, \A, SigSpec())) > 1
-       select nusers(port(dspQA2, \ACOUT, SigSpec())) == 0
-       slice offset GetSize(port(dspQA2, \A))
-       index <SigBit> port(dspQA2, \A)[offset] === sigA[0]
-       index <SigBit> port(dspQA2, \CLK, State::S0) === port(dspD, \CLK, State::S0)
-
-       // Check that the rest of sigA is present
-       filter GetSize(port(dspQA2, \A)) >= offset + GetSize(sigA)
-       filter port(dspQA2, \A).extract(offset, GetSize(sigA)) == sigA
-
-       optional
-endmatch
-
-code
-       if (dspQA2) {
-               // Check CE and RST are compatible
-               if ((ffA1cemux != nullptr) == port(dspQA2, \CEA1, State::S1).is_fully_const())
-                       reject;
-               if ((ffA2cemux != nullptr) == port(dspQA2, \CEA2, State::S1).is_fully_const())
-                       reject;
-               if ((ffA1rstmux != nullptr) == port(dspQA2, \RSTA, State::S0).is_fully_const())
-                       reject;
-               if ((ffA2rstmux != nullptr) == port(dspQA2, \RSTA, State::S0).is_fully_const())
-                       reject;
-
-               if (ffA1cemux) {
-                       if (port(dspQA2, \CEA1) != port(ffA1cemux, \S))
-                               reject;
-                       // TODO: Support inversions
-                       if (!ffA1cepol)
-                               reject;
-               }
-               if (ffA2cemux) {
-                       if (port(dspQA2, \CEA2) != port(ffA2cemux, \S))
-                               reject;
-                       // TODO: Support inversions
-                       if (!ffA2cepol)
-                               reject;
-               }
-               if (ffA1rstmux) {
-                       if (port(dspQA2, \RSTA) != port(ffA1rstmux, \S))
-                               reject;
-                       // TODO: Support inversions
-                       if (!ffArstpol)
-                               reject;
-               }
-               if (ffA2rstmux) {
-                       if (port(dspQA2, \RSTA) != port(ffA2rstmux, \S))
-                               reject;
-                       // TODO: Support inversions
-                       if (!ffArstpol)
-                               reject;
-               }
-       }
-endcode
-
-match dspQA1
-       if !dspQA1 && !ffA1
-       if ffA2
-       select dspQA1->type.in(\DSP48E1)
-       select param(dspQA1, \A_REG, 2).as_int() == 1
-       select nusers(port(dspQA1, \A, SigSpec())) > 1
-       select nusers(port(dspQA1, \ACOUT, SigSpec())) == 0
-       slice offset GetSize(port(dspQA1, \A))
-       index <SigBit> port(dspQA1, \A)[offset] === sigA[0]
-       index <SigBit> port(dspQA1, \CLK, State::S0) === port(dspD, \CLK, State::S0)
-
-       // Check that the rest of sigA is present
-       filter GetSize(port(dspQA1, \A)) >= offset + GetSize(sigA)
-       filter port(dspQA1, \A).extract(offset, GetSize(sigA)) == sigA
-
-       optional
-endmatch
-
-code
-       if (dspQA1) {
-               // Check CE and RST are compatible
-               if ((ffA2cemux != NULL) == port(dspQA1, \CEA2, State::S1).is_fully_const())
-                       reject;
-               if ((ffA2rstmux != NULL) == port(dspQA1, \RSTA, State::S0).is_fully_const())
-                       reject;
-
-               if (!ffA2cepol || !ffArstpol)
-                       reject;
-
-               if (ffA2cemux) {
-                       if (port(dspQA1, \CEA2) != port(ffA2cemux, \S))
-                               reject;
-                       // TODO: Support inversions
-                       if (!ffA2cepol)
-                               reject;
-               }
-               if (ffA2rstmux) {
-                       if (port(dspQA1, \RSTA) != port(ffA2rstmux, \S))
-                               reject;
-                       // TODO: Support inversions
-                       if (!ffArstpol)
-                               reject;
-               }
-       }
-endcode
-
-code argQ ffB2 ffB2cemux ffB2rstmux ffB2cepol ffBrstpol ffB1 ffB1cemux ffB1rstmux ffB1cepol sigB clock
-       if (!sigB.empty()) {
-               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
-                       argQ = sigB;
+code argQ clock BREG
+       BREG = 0;
+       if (next) {
+               Cell *prev = std::get<0>(chain.back());
+               if (param(prev, \BREG, 2).as_int() > 0 &&
+                               param(next, \BREG, 2).as_int() > 0 &&
+                               param(next, \B_INPUT, Const("DIRECT")).decode_string() == "DIRECT" &&
+                               port(next, \BCIN, SigSpec()).is_fully_zero() &&
+                               nusers(port(prev, \BCOUT, SigSpec())) <= 1) {
+                       argQ = unextend(port(next, \B));
+                       clock = port(prev, \CLK);
                        subpattern(in_dffe);
                        if (dff) {
-                               if ((ffB2rstmux != nullptr) ^ (dffrstmux != nullptr))
-                                       goto reject_ffB1;
-                               if (dffrstmux) {
-                                       if (ffBrstpol != dffrstpol)
-                                               goto reject_ffB1;
-                                       if (port(ffB2rstmux, \S) != port(dffrstmux, \S))
-                                               goto reject_ffB1;
-                                       ffB1rstmux = dffrstmux;
-                               }
-
-                               ffB1 = dff;
-                               clock = dffclock;
-
-                               if (dffcemux) {
-                                       ffB1cemux = dffcemux;
-                                       ffB1cepol = dffcepol;
-                               }
-                               sigB = dffD;
-
-reject_ffB1:                   ;
+                               if (!dffrstmux && port(prev, \RSTB, State::S0) != State::S0)
+                                       goto reject_BREG;
+                               if (dffrstmux && port(dffrstmux, \S) != port(prev, \RSTB, State::S0))
+                                       goto reject_BREG;
+                               if (!dffcemux && port(prev, \CEB2, State::S0) != State::S0)
+                                       goto reject_BREG;
+                               if (dffcemux && port(dffcemux, \S) != port(prev, \CEB2, State::S0))
+                                       goto reject_BREG;
+                               if (dffD == unextend(port(prev, \B)))
+                                       BREG = 1;
+reject_BREG:                   ;
                        }
                }
        }
 endcode
 
-match dspQB2
-       if ffB1
-       select dspQB2->type.in(\DSP48E1)
-       select param(dspQB2, \B_REG, 2).as_int() == 2
-       select nusers(port(dspQB2, \B, SigSpec())) > 1
-       select nusers(port(dspQB2, \BCOUT, SigSpec())) == 0
-       slice offset GetSize(port(dspQB2, \B))
-       index <SigBit> port(dspQB2, \B)[offset] === sigB[0]
-       index <SigBit> port(dspQB2, \CLK, State::S0) === port(dspD, \CLK, State::S0)
-
-       // Check that the rest of sigB is present
-       filter GetSize(port(dspQB2, \B)) >= offset + GetSize(sigB)
-       filter port(dspQB2, \B).extract(offset, GetSize(sigB)) == sigB
-
-       optional
-endmatch
-
 code
-       if (dspQB2) {
-               // Check CE and RST are compatible
-               if ((ffB1cemux != nullptr) == port(dspQB2, \CEB1, State::S1).is_fully_const())
-                       reject;
-               if ((ffB2cemux != NULL) == port(dspQB2, \CEB2, State::S1).is_fully_const())
-                       reject;
-               if ((ffB1rstmux != NULL) == port(dspQB2, \RSTB, State::S0).is_fully_const())
-                       reject;
-               if ((ffB2rstmux != NULL) == port(dspQB2, \RSTB, State::S0).is_fully_const())
-                       reject;
-
-               if (ffB1cemux) {
-                       if (port(dspQB2, \CEB1) != port(ffB1cemux, \S))
-                               reject;
-                       // TODO: Support inversions
-                       if (!ffB1cepol)
-                               reject;
-               }
-               if (ffB2cemux) {
-                       if (port(dspQB2, \CEB2) != port(ffB2cemux, \S))
-                               reject;
-                       // TODO: Support inversions
-                       if (!ffB2cepol)
-                               reject;
-               }
-               if (ffB2rstmux) {
-                       if (port(dspQB2, \RSTB) != port(ffB2rstmux, \S))
-                               reject;
-                       // TODO: Support inversions
-                       if (!ffBrstpol)
-                               reject;
-               }
-       }
-endcode
-
-match dspQB1
-       if !dspQB1 && !ffB1
-       if ffB2
-       select dspQB1->type.in(\DSP48E1)
-       select param(dspQB1, \B_REG, 2).as_int() >= 1
-       select nusers(port(dspQB1, \B, SigSpec())) > 1
-       select nusers(port(dspQB1, \BCOUT, SigSpec())) == 0
-       slice offset GetSize(port(dspQB1, \B))
-       index <SigBit> port(dspQB1, \B)[offset] === sigB[0]
-       index <SigBit> port(dspQB1, \CLK, State::S0) === port(dspD, \CLK, State::S0)
-
-       // Check that the rest of sigB is present
-       filter GetSize(port(dspQB1, \B)) >= offset + GetSize(sigB)
-       filter port(dspQB1, \B).extract(offset, GetSize(sigB)) == sigB
-
-       optional
-endmatch
-
-code
-       if (dspQB1) {
-               // Check CE and RST are compatible
-               if ((ffB2cemux != NULL) != port(dspQB1, \CEB2, State::S1).is_fully_const())
-                       reject;
-               if ((ffB2rstmux != NULL) != port(dspQB1, \RSTB, State::S0).is_fully_const())
-                       reject;
-
-               if (!ffA2cepol || !ffArstpol)
-                       reject;
+       if (next) {
+               chain.emplace_back(next, nextP_shift17 ? 17 : nextP ? 0 : -1, AREG, BREG);
 
-               if (ffA2cemux) {
-                       if (port(dspQB1, \CEB2) != port(ffB2cemux, \S))
-                               reject;
-                       // TODO: Support inversions
-                       if (!ffA2cepol)
-                               reject;
-               }
-               if (ffA2rstmux) {
-                       if (port(dspQB1, \RSTB) != port(ffB2rstmux, \S))
-                               reject;
-                       // TODO: Support inversions
-                       if (!ffArstpol)
-                               reject;
-               }
-       }
-endcode
+               SigSpec sigC = unextend(port(next, \C));
 
-code
-       if (dspQA1 || dspQA2) {
-               dspD->setParam(\A_INPUT, Const("CASCADE"));
-               dspD->setPort(\A, Const(0, 30));
-
-               Wire *cascade = module->addWire(NEW_ID, 30);
-               if (dspQA1) {
-                       dspQA1->setParam(\ACASCREG, 1);
-                       dspQA1->setPort(\ACOUT, cascade);
-                       log_debug("ACOUT -> ACIN cascade for %s -> %s\n", log_id(dspQA1), log_id(dspD));
-               }
-               else if (dspQA2) {
-                       dspQA2->setParam(\ACASCREG, 2);
-                       dspQA2->setPort(\ACOUT, cascade);
-                       log_debug("ACOUT -> ACIN cascade for %s -> %s\n", log_id(dspQA2), log_id(dspD));
+               // TODO: Cannot use 'reject' since semioptional
+               if (nextP_shift17) {
+                       if (GetSize(sigC)+17 <= GetSize(port(std::get<0>(chain.back()), \P)) &&
+                                       port(std::get<0>(chain.back()), \P).extract(17, GetSize(sigC)) != sigC)
+                               subpattern(tail);
                }
-               else
-                       log_abort();
+               else {
+                       if (GetSize(sigC) <= GetSize(port(std::get<0>(chain.back()), \P)) &&
+                                       port(std::get<0>(chain.back()), \P).extract(0, GetSize(sigC)) != sigC)
+                               subpattern(tail);
 
-               dspD->setPort(\ACIN, cascade);
-               did_something = true;
-       }
-       if (dspQB1 || dspQB2) {
-               dspD->setParam(\B_INPUT, Const("CASCADE"));
-               dspD->setPort(\B, Const(0, 18));
-
-               Wire *cascade = module->addWire(NEW_ID, 18);
-               if (dspQB1) {
-                       dspQB1->setParam(\BCASCREG, 1);
-                       dspQB1->setPort(\BCOUT, cascade);
-                       log_debug("BCOUT -> BCIN cascade for %s -> %s\n", log_id(dspQB1), log_id(dspD));
                }
-               else if (dspQB2) {
-                       dspQB2->setParam(\BCASCREG, 2);
-                       dspQB2->setPort(\BCOUT, cascade);
-                       log_debug("BCOUT -> BCIN cascade for %s -> %s\n", log_id(dspQB2), log_id(dspD));
-               }
-               else
-                       log_abort();
-
-               dspD->setPort(\BCIN, cascade);
-               did_something = true;
+       } else {
+               if (GetSize(chain) > GetSize(longest_chain))
+                       longest_chain = chain;
        }
-
-       accept;
+finally
+       if (next)
+               chain.pop_back();
 endcode
 
-
 // #######################
 
 subpattern in_dffe
@@ -525,6 +244,9 @@ code
                        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