From 3948e7f94c95709905f6edaab6a6c7235dc4152b Mon Sep 17 00:00:00 2001 From: nella Date: Wed, 3 Jun 2026 12:46:26 +0200 Subject: [PATCH 1/6] Use Nexus DSP pipelined registers. --- techlibs/lattice/dsp_map_nexus.v | 53 +++++-- techlibs/lattice/lattice_dsp_nexus.cc | 5 +- techlibs/lattice/lattice_dsp_nexus.pmg | 207 ++++++++++++++++++++++++- tests/arch/nexus/pipe_mul.sv | 156 +++++++++++++++++++ tests/arch/nexus/pipe_mul.ys | 73 +++++++++ 5 files changed, 479 insertions(+), 15 deletions(-) create mode 100644 tests/arch/nexus/pipe_mul.sv create mode 100644 tests/arch/nexus/pipe_mul.ys diff --git a/techlibs/lattice/dsp_map_nexus.v b/techlibs/lattice/dsp_map_nexus.v index 61d2d96a8..73aec9cef 100644 --- a/techlibs/lattice/dsp_map_nexus.v +++ b/techlibs/lattice/dsp_map_nexus.v @@ -1,57 +1,84 @@ -module \$__NX_MUL36X36 (input [35:0] A, input [35:0] B, output [71:0] Y); +module \$__NX_MUL36X36 (input [35:0] A, input [35:0] B, input CLK, input CEA, RSTA, CEB, RSTB, CEOUT, RSTOUT, output [71:0] Y); parameter A_WIDTH = 36; parameter B_WIDTH = 36; parameter Y_WIDTH = 72; parameter A_SIGNED = 0; parameter B_SIGNED = 0; + parameter REGINPUTA = "BYPASS"; + parameter REGINPUTB = "BYPASS"; + parameter REGOUTPUT = "BYPASS"; + parameter RESETMODE = "SYNC"; MULT36X36 #( - .REGINPUTA("BYPASS"), - .REGINPUTB("BYPASS"), - .REGOUTPUT("BYPASS") + .REGINPUTA(REGINPUTA), + .REGINPUTB(REGINPUTB), + .REGOUTPUT(REGOUTPUT), + .RESETMODE(RESETMODE) ) _TECHMAP_REPLACE_ ( .A(A), .B(B), + .CLK(CLK), + .CEA(CEA), .RSTA(RSTA), + .CEB(CEB), .RSTB(RSTB), + .CEOUT(CEOUT), .RSTOUT(RSTOUT), .SIGNEDA(A_SIGNED ? 1'b1 : 1'b0), .SIGNEDB(B_SIGNED ? 1'b1 : 1'b0), .Z(Y) ); endmodule -module \$__NX_MUL36X18 (input [35:0] A, input [17:0] B, output [53:0] Y); +module \$__NX_MUL36X18 (input [35:0] A, input [17:0] B, input CLK, input CEA, RSTA, CEB, RSTB, CEOUT, RSTOUT, output [53:0] Y); parameter A_WIDTH = 36; parameter B_WIDTH = 18; parameter Y_WIDTH = 54; parameter A_SIGNED = 0; parameter B_SIGNED = 0; + parameter REGINPUTA = "BYPASS"; + parameter REGINPUTB = "BYPASS"; + parameter REGOUTPUT = "BYPASS"; + parameter RESETMODE = "SYNC"; MULT18X36 #( - .REGINPUTA("BYPASS"), - .REGINPUTB("BYPASS"), - .REGOUTPUT("BYPASS") + .REGINPUTA(REGINPUTB), + .REGINPUTB(REGINPUTA), + .REGOUTPUT(REGOUTPUT), + .RESETMODE(RESETMODE) ) _TECHMAP_REPLACE_ ( .A(B), .B(A), + .CLK(CLK), + .CEA(CEB), .RSTA(RSTB), + .CEB(CEA), .RSTB(RSTA), + .CEOUT(CEOUT), .RSTOUT(RSTOUT), .SIGNEDA(B_SIGNED ? 1'b1 : 1'b0), .SIGNEDB(A_SIGNED ? 1'b1 : 1'b0), .Z(Y) ); endmodule -module \$__NX_MUL18X18 (input [17:0] A, input [17:0] B, output [35:0] Y); +module \$__NX_MUL18X18 (input [17:0] A, input [17:0] B, input CLK, input CEA, RSTA, CEB, RSTB, CEOUT, RSTOUT, output [35:0] Y); parameter A_WIDTH = 18; parameter B_WIDTH = 18; parameter Y_WIDTH = 36; parameter A_SIGNED = 0; parameter B_SIGNED = 0; + parameter REGINPUTA = "BYPASS"; + parameter REGINPUTB = "BYPASS"; + parameter REGOUTPUT = "BYPASS"; + parameter RESETMODE = "SYNC"; MULT18X18 #( - .REGINPUTA("BYPASS"), - .REGINPUTB("BYPASS"), - .REGOUTPUT("BYPASS") + .REGINPUTA(REGINPUTA), + .REGINPUTB(REGINPUTB), + .REGOUTPUT(REGOUTPUT), + .RESETMODE(RESETMODE) ) _TECHMAP_REPLACE_ ( .A(A), .B(B), + .CLK(CLK), + .CEA(CEA), .RSTA(RSTA), + .CEB(CEB), .RSTB(RSTB), + .CEOUT(CEOUT), .RSTOUT(RSTOUT), .SIGNEDA(A_SIGNED ? 1'b1 : 1'b0), .SIGNEDB(B_SIGNED ? 1'b1 : 1'b0), .Z(Y) @@ -148,4 +175,4 @@ module \$__NX_MAC9X9WIDE_4LANE (input [8:0] A0, B0, A1, B1, A2, B2, A3, B3, outp .ADDSUB(4'b0000), .Z(Y) ); -endmodule +endmodule \ No newline at end of file diff --git a/techlibs/lattice/lattice_dsp_nexus.cc b/techlibs/lattice/lattice_dsp_nexus.cc index d072c552d..b0aaa5f17 100644 --- a/techlibs/lattice/lattice_dsp_nexus.cc +++ b/techlibs/lattice/lattice_dsp_nexus.cc @@ -15,7 +15,9 @@ struct LatticeDspNexusPass : public Pass { log(" lattice_dsp_nexus [options] [selection]\n"); log("\n"); log("Infer Lattice Nexus sysDSP macrocells (MULTADDSUB18X18, MULTPREADD18X18,\n"); - log("MULTADDSUB9X9WIDE) from MAC and dot-product patterns.\n"); + log("MULTADDSUB9X9WIDE) from MAC and dot-product patterns, and absorb the\n"); + log("pipeline flip-flops around bare MULT18X18 / MULT36X36 multipliers into\n"); + log("the hardened DSP input and output registers.\n"); log("\n"); } void execute(std::vector args, RTLIL::Design *design) override @@ -29,6 +31,7 @@ struct LatticeDspNexusPass : public Pass { pm.run_nexus_mac9_4lane(); pm.run_nexus_mac18(); pm.run_nexus_preadd18(); + pm.run_nexus_mul_reg(); } } } LatticeDspNexusPass; diff --git a/techlibs/lattice/lattice_dsp_nexus.pmg b/techlibs/lattice/lattice_dsp_nexus.pmg index 5fb828bb9..45084ff1e 100644 --- a/techlibs/lattice/lattice_dsp_nexus.pmg +++ b/techlibs/lattice/lattice_dsp_nexus.pmg @@ -205,4 +205,209 @@ code } accept; -endcode \ No newline at end of file +endcode + +pattern nexus_mul_reg + +match mul + select mul->type.in($mul) + select GetSize(port(mul, \A)) <= 36 + select GetSize(port(mul, \B)) <= 36 + select GetSize(port(mul, \Y)) <= 72 +endmatch + +match ffA + select ffA->type.in($dff, $dffe, $sdff, $sdffe) + select param(ffA, \CLK_POLARITY).as_bool() + filter ffA->type.in($dff, $dffe) || param(ffA, \SRST_VALUE).is_fully_zero() + filter GetSize(port(ffA, \Q)) == GetSize(port(mul, \A)) + index port(ffA, \Q) === port(mul, \A) + optional +endmatch + +match ffB + select ffB->type.in($dff, $dffe, $sdff, $sdffe) + select param(ffB, \CLK_POLARITY).as_bool() + filter ffB->type.in($dff, $dffe) || param(ffB, \SRST_VALUE).is_fully_zero() + filter GetSize(port(ffB, \Q)) == GetSize(port(mul, \B)) + index port(ffB, \Q) === port(mul, \B) + optional +endmatch + +match ffY + select ffY->type.in($dff, $dffe, $sdff, $sdffe) + select param(ffY, \CLK_POLARITY).as_bool() + filter ffY->type.in($dff, $dffe) || param(ffY, \SRST_VALUE).is_fully_zero() + filter GetSize(port(ffY, \D)) == GetSize(port(mul, \Y)) + index port(ffY, \D) === port(mul, \Y) + optional +endmatch + +code + Cell *fa = ffA; + Cell *fb = ffB; + Cell *fy = ffY; + + if (!fa && !fb && !fy) + reject; + + { + // Drop any FF whose Q carries a non-zero power-up value, since the DSP reg powers up at 0 + auto bad_init = [&](Cell *ff) -> bool { + SigSpec q = port(ff, \Q); + + for (auto bit : sigmap(q)) { + if (!bit.wire) + continue; + + auto it = bit.wire->attributes.find(\init); + if (it == bit.wire->attributes.end()) + continue; + + Const init = it->second; + int off = bit.offset; + + if (off < GetSize(init)) { + State s = init[off]; + if (s != State::Sx && s != State::S0) + return true; + } + } + + return false; + }; + + if (fa && bad_init(fa)) fa = nullptr; + if (fb && bad_init(fb)) fb = nullptr; + if (fy && bad_init(fy)) fy = nullptr; + + // All absorbed FFs must share one clock domain + SigBit clk; + bool have_clk = false; + + { + Cell *ffs[3] = { fa, fb, fy }; + for (Cell *ff : ffs) { + if (!ff) continue; + + SigBit c = sigmap(port(ff, \CLK)[0]); + + if (!have_clk) { + clk = c; + have_clk = true; + continue; + } + + if (c != clk) { + if (ff == fa) fa = nullptr; + else if (ff == fb) fb = nullptr; + else fy = nullptr; + } + } + } + + // If every candidate was filtered out, don't build a DSP reg cell + if (fa || fb || fy) { + int aw = GetSize(port(mul, \A)); + int bw = GetSize(port(mul, \B)); + IdString prim; + int A_PINS; + int B_PINS; + + if (aw <= 18 && bw <= 18) { + prim = "$__NX_MUL18X18"; + A_PINS = 18; + B_PINS = 18; + } else if (aw <= 36 && bw <= 18) { + prim = "$__NX_MUL36X18"; + A_PINS = 36; + B_PINS = 18; + } else if (aw <= 18 && bw <= 36) { + prim = "$__NX_MUL36X18"; + A_PINS = 36; + B_PINS = 18; + } else { + prim = "$__NX_MUL36X36"; + A_PINS = 36; + B_PINS = 36; + } + + bool a_signed = mul->getParam(\A_SIGNED).as_bool(); + bool b_signed = mul->getParam(\B_SIGNED).as_bool(); + SigSpec sigA = port(mul, \A); + SigSpec sigB = port(mul, \B); + SigSpec sigY = fy ? port(fy, \Q) : port(mul, \Y); + + // 36X18 -> wide operand goes to A + Cell *ffA_use = fa; + Cell *ffB_use = fb; + if (prim == "$__NX_MUL36X18" && aw <= 18 && bw <= 36) { + std::swap(sigA, sigB); + std::swap(a_signed, b_signed); + std::swap(ffA_use, ffB_use); + } + + sigA.extend_u0(A_PINS, a_signed); + sigB.extend_u0(B_PINS, b_signed); + + Cell *cell = module->addCell(NEW_ID, prim); + cell->setPort(\A, sigA); + cell->setPort(\B, sigB); + cell->setPort(\Y, sigY); + cell->setParam(\A_WIDTH, A_PINS); + cell->setParam(\B_WIDTH, B_PINS); + cell->setParam(\Y_WIDTH, GetSize(sigY)); + cell->setParam(\A_SIGNED, a_signed ? State::S1 : State::S0); + cell->setParam(\B_SIGNED, b_signed ? State::S1 : State::S0); + cell->setParam(\RESETMODE, std::string("SYNC")); + + auto ce_of = [&](Cell *ff) -> SigBit { + if (!ff) return State::S0; + + if (ff->type.in($dffe, $sdffe)) { + SigBit e = port(ff, \EN)[0]; + if (!ff->getParam(\EN_POLARITY).as_bool()) + e = module->NotGate(NEW_ID, e); + + return e; + } + + return State::S1; // Always enabled + }; + + auto rst_of = [&](Cell *ff) -> SigBit { + if (!ff) return State::S0; + + if (ff->type.in($sdff, $sdffe)) { + SigBit r = port(ff, \SRST)[0]; + if (!ff->getParam(\SRST_POLARITY).as_bool()) + r = module->NotGate(NEW_ID, r); + + return r; + } + + return State::S0; // No reset + }; + + cell->setPort(\CLK, have_clk ? clk : State::S0); + cell->setPort(\CEA, ce_of(ffA_use)); + cell->setPort(\RSTA, rst_of(ffA_use)); + cell->setPort(\CEB, ce_of(ffB_use)); + cell->setPort(\RSTB, rst_of(ffB_use)); + cell->setPort(\CEOUT, ce_of(fy)); + cell->setPort(\RSTOUT, rst_of(fy)); + cell->setParam(\REGINPUTA, ffA_use ? std::string("REGISTER") : std::string("BYPASS")); + cell->setParam(\REGINPUTB, ffB_use ? std::string("REGISTER") : std::string("BYPASS")); + cell->setParam(\REGOUTPUT, fy ? std::string("REGISTER") : std::string("BYPASS")); + + if (ffA_use) autoremove(ffA_use); + if (ffB_use && ffB_use != ffA_use) autoremove(ffB_use); + if (fy) autoremove(fy); + autoremove(mul); + + accept; + } + } + + reject; +endcode diff --git a/tests/arch/nexus/pipe_mul.sv b/tests/arch/nexus/pipe_mul.sv new file mode 100644 index 000000000..75769a29c --- /dev/null +++ b/tests/arch/nexus/pipe_mul.sv @@ -0,0 +1,156 @@ +// https://github.com/YosysHQ/yosys/issues/5917 + +module mul18_pipe( + input logic clk, + input logic [17:0] a, + input logic [17:0] b, + output logic [35:0] y +); + logic [17:0] a_r; + logic [17:0] b_r; + + always_ff @(posedge clk) begin + a_r <= a; + b_r <= b; + y <= a_r * b_r; + end +endmodule + +module mul18_pipe_signed ( + input logic clk, + input logic signed [17:0] a, + input logic signed [17:0] b, + output logic signed [35:0] y +); + logic signed [17:0] a_r; + logic signed [17:0] b_r; + + always_ff @(posedge clk) begin + a_r <= a; + b_r <= b; + y <= a_r * b_r; + end +endmodule + + +module mul18_pipe_in_only ( + input logic clk, + input logic [17:0] a, + input logic [17:0] b, + output logic [35:0] y +); + logic [17:0] a_r; + logic [17:0] b_r; + + always_ff @(posedge clk) begin + a_r <= a; + b_r <= b; + end + + assign y = a_r * b_r; +endmodule + +module mul18_pipe_out_only ( + input logic clk, + input logic [17:0] a, + input logic [17:0] b, + output logic [35:0] y +); + always_ff @(posedge clk) + y <= a * b; +endmodule + +module mul18_pipe_io_rst ( + input logic clk, + input logic rst, + input logic [17:0] a, + input logic [17:0] b, + output logic [35:0] y +); + logic [17:0] a_r; + logic [17:0] b_r; + + always_ff @(posedge clk) + if (rst) begin a_r <= 0; b_r <= 0; y <= 0; end + else begin a_r <= a; b_r <= b; y <= a_r * b_r; end +endmodule + +module mul24_io ( + input logic clk, + input logic [23:0] a, + input logic [23:0] b, + output logic [47:0] y +); + logic [23:0] a_r; + logic [23:0] b_r; + + always_ff @(posedge clk) begin + a_r <= a; + b_r <= b; + y <= a_r * b_r; + end +endmodule + +module mul32_io ( + input logic clk, + input logic [31:0] a, + input logic [31:0] b, + output logic [63:0] y +); + logic [31:0] a_r; + logic [31:0] b_r; + + always_ff @(posedge clk) begin + a_r <= a; + b_r <= b; + y <= a_r * b_r; + end +endmodule + +module mul18_negedge ( + input logic clk, + input logic [17:0] a, + input logic [17:0] b, + output logic [35:0] y +); + logic [17:0] a_r; + logic [17:0] b_r; + + always_ff @(negedge clk) begin + a_r <= a; + b_r <= b; + y <= a_r * b_r; + end +endmodule + +module mul18_rst_nonzero ( + input logic clk, + input logic rst, + input logic [17:0] a, + input logic [17:0] b, + output logic [35:0] y +); + logic [17:0] a_r; + logic [17:0] b_r; + + always_ff @(posedge clk) + if (rst) begin a_r <= 18'h3; b_r <= 18'h7; end + else begin a_r <= a; b_r <= b; end + always_ff @(posedge clk) + y <= a_r * b_r; +endmodule + +module mul18_two_clock ( + input logic clk0, + input logic clk1, + input logic [17:0] a, + input logic [17:0] b, + output logic [35:0] y +); + logic [17:0] a_r; + logic [17:0] b_r; + + always_ff @(posedge clk0) a_r <= a; + always_ff @(posedge clk1) b_r <= b; + assign y = a_r * b_r; +endmodule \ No newline at end of file diff --git a/tests/arch/nexus/pipe_mul.ys b/tests/arch/nexus/pipe_mul.ys new file mode 100644 index 000000000..d2c5a6282 --- /dev/null +++ b/tests/arch/nexus/pipe_mul.ys @@ -0,0 +1,73 @@ +read_verilog -sv pipe_mul.sv + +design -save pristine + +# 18x18 MULT +design -load pristine +hierarchy -top mul18_pipe +synth_nexus -family lifcl -top mul18_pipe +select -assert-count 1 t:MULT18X18 +select -assert-count 0 t:FD1P3* + +# 18x18 MULT (signed) +design -load pristine +hierarchy -top mul18_pipe_signed +synth_nexus -family lifcl -top mul18_pipe_signed +select -assert-count 1 t:MULT18X18 +select -assert-count 0 t:FD1P3* + +# 18x18 MULT (input only) +design -load pristine +hierarchy -top mul18_pipe_in_only +synth_nexus -family lifcl -top mul18_pipe_in_only +select -assert-count 1 t:MULT18X18 +select -assert-count 0 t:FD1P3* + +# 18x18 MULT (output only) +design -load pristine +hierarchy -top mul18_pipe_out_only +synth_nexus -family lifcl -top mul18_pipe_out_only +select -assert-count 1 t:MULT18X18 +select -assert-count 0 t:FD1P3* + +# 18x18 MULT (reset) +design -load pristine +hierarchy -top mul18_pipe_io_rst +synth_nexus -family lifcl -top mul18_pipe_io_rst +select -assert-count 1 t:MULT18X18 +select -assert-count 0 t:FD1P3* + +# 24x24 MUL -> pipelined 36X36 MULT +design -load pristine +hierarchy -top mul24_io +synth_nexus -family lifcl -top mul24_io +select -assert-count 1 t:MULT36X36 +select -assert-count 0 t:FD1P3* + +# 32x32 MUL -> pipelined 36X36 MULT +design -load pristine +hierarchy -top mul32_io +synth_nexus -family lifcl -top mul32_io +select -assert-count 1 t:MULT36X36 +select -assert-count 0 t:FD1P3* + +# DSP reg is rising-edge +design -load pristine +hierarchy -top mul18_negedge +synth_nexus -family lifcl -top mul18_negedge +select -assert-count 1 t:MULT18X18 +select -assert-min 1 t:FD1P3* + +# DSP reg only resets to 0 +design -load pristine +hierarchy -top mul18_rst_nonzero +synth_nexus -family lifcl -top mul18_rst_nonzero +select -assert-count 1 t:MULT18X18 +select -assert-min 1 t:FD1P3* + +# two clocks feeding input regs -> can't share one CLK pin +design -load pristine +hierarchy -top mul18_two_clock +synth_nexus -family lifcl -top mul18_two_clock +select -assert-count 1 t:MULT18X18 +select -assert-min 1 t:FD1P3* From 5328762fe584df10c9434e121f11e271f0730a38 Mon Sep 17 00:00:00 2001 From: nella Date: Wed, 3 Jun 2026 12:51:57 +0200 Subject: [PATCH 2/6] Add regtype checks to tests. --- tests/arch/nexus/pipe_mul.ys | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tests/arch/nexus/pipe_mul.ys b/tests/arch/nexus/pipe_mul.ys index d2c5a6282..c5e40b96e 100644 --- a/tests/arch/nexus/pipe_mul.ys +++ b/tests/arch/nexus/pipe_mul.ys @@ -6,68 +6,69 @@ design -save pristine design -load pristine hierarchy -top mul18_pipe synth_nexus -family lifcl -top mul18_pipe -select -assert-count 1 t:MULT18X18 +select -assert-count 1 t:MULT18X18 r:REGINPUTA=REGISTER r:REGINPUTB=REGISTER r:REGOUTPUT=REGISTER select -assert-count 0 t:FD1P3* # 18x18 MULT (signed) design -load pristine hierarchy -top mul18_pipe_signed synth_nexus -family lifcl -top mul18_pipe_signed -select -assert-count 1 t:MULT18X18 +select -assert-count 1 t:MULT18X18 r:REGINPUTA=REGISTER r:REGINPUTB=REGISTER r:REGOUTPUT=REGISTER select -assert-count 0 t:FD1P3* # 18x18 MULT (input only) design -load pristine hierarchy -top mul18_pipe_in_only synth_nexus -family lifcl -top mul18_pipe_in_only -select -assert-count 1 t:MULT18X18 +select -assert-count 1 t:MULT18X18 r:REGINPUTA=REGISTER r:REGINPUTB=REGISTER r:REGOUTPUT=BYPASS select -assert-count 0 t:FD1P3* # 18x18 MULT (output only) design -load pristine hierarchy -top mul18_pipe_out_only synth_nexus -family lifcl -top mul18_pipe_out_only -select -assert-count 1 t:MULT18X18 +select -assert-count 1 t:MULT18X18 r:REGINPUTA=BYPASS r:REGINPUTB=BYPASS r:REGOUTPUT=REGISTER select -assert-count 0 t:FD1P3* # 18x18 MULT (reset) design -load pristine hierarchy -top mul18_pipe_io_rst synth_nexus -family lifcl -top mul18_pipe_io_rst -select -assert-count 1 t:MULT18X18 +select -assert-count 1 t:MULT18X18 r:REGINPUTA=REGISTER r:REGINPUTB=REGISTER r:REGOUTPUT=REGISTER select -assert-count 0 t:FD1P3* # 24x24 MUL -> pipelined 36X36 MULT design -load pristine hierarchy -top mul24_io synth_nexus -family lifcl -top mul24_io -select -assert-count 1 t:MULT36X36 +select -assert-count 1 t:MULT36X36 r:REGINPUTA=REGISTER r:REGINPUTB=REGISTER r:REGOUTPUT=REGISTER select -assert-count 0 t:FD1P3* # 32x32 MUL -> pipelined 36X36 MULT design -load pristine hierarchy -top mul32_io synth_nexus -family lifcl -top mul32_io -select -assert-count 1 t:MULT36X36 +select -assert-count 1 t:MULT36X36 r:REGINPUTA=REGISTER r:REGINPUTB=REGISTER r:REGOUTPUT=REGISTER select -assert-count 0 t:FD1P3* +# reject # DSP reg is rising-edge design -load pristine hierarchy -top mul18_negedge synth_nexus -family lifcl -top mul18_negedge -select -assert-count 1 t:MULT18X18 +select -assert-count 1 t:MULT18X18 r:REGINPUTA=BYPASS r:REGINPUTB=BYPASS r:REGOUTPUT=BYPASS select -assert-min 1 t:FD1P3* # DSP reg only resets to 0 design -load pristine hierarchy -top mul18_rst_nonzero synth_nexus -family lifcl -top mul18_rst_nonzero -select -assert-count 1 t:MULT18X18 +select -assert-count 1 t:MULT18X18 r:REGINPUTA=BYPASS r:REGINPUTB=BYPASS r:REGOUTPUT=BYPASS select -assert-min 1 t:FD1P3* # two clocks feeding input regs -> can't share one CLK pin design -load pristine hierarchy -top mul18_two_clock synth_nexus -family lifcl -top mul18_two_clock -select -assert-count 1 t:MULT18X18 -select -assert-min 1 t:FD1P3* +select -assert-count 1 t:MULT18X18 r:REGINPUTA=BYPASS r:REGINPUTB=BYPASS +select -assert-min 1 t:FD1P3* \ No newline at end of file From 1c851aecfb9a0062b35f41856e4a3e374dc2975d Mon Sep 17 00:00:00 2001 From: nella Date: Mon, 22 Jun 2026 14:22:14 +0200 Subject: [PATCH 3/6] Fixup op precedence. --- techlibs/lattice/dsp_map_nexus.v | 13 ++++++++++--- techlibs/lattice/lattice_dsp_nexus.pmg | 24 ++++++++++++++++++------ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/techlibs/lattice/dsp_map_nexus.v b/techlibs/lattice/dsp_map_nexus.v index 73aec9cef..ef9beba44 100644 --- a/techlibs/lattice/dsp_map_nexus.v +++ b/techlibs/lattice/dsp_map_nexus.v @@ -119,13 +119,20 @@ module \$__NX_MAC18X18 (input [17:0] A, input [17:0] B, input [47:0] C, output [ .REGINPUTA("BYPASS"), .REGINPUTB("BYPASS"), .REGINPUTC("BYPASS"), + .REGADDSUB("BYPASS"), + .REGLOADC("BYPASS"), + .REGLOADC2("BYPASS"), + .REGCIN("BYPASS"), + .REGPIPELINE("BYPASS"), .REGOUTPUT("BYPASS") ) _TECHMAP_REPLACE_ ( - .A(A), - .B(B), + .A(A), + .B(B), .C({6'b0, C}), - .SIGNED(A_SIGNED ? 1'b1 : 1'b0), + .SIGNED(A_SIGNED ? 1'b1 : 1'b0), .ADDSUB(SUBTRACT ? 1'b1 : 1'b0), + .LOADC(1'b1), + .CIN(1'b0), .Z(Y) ); endmodule diff --git a/techlibs/lattice/lattice_dsp_nexus.pmg b/techlibs/lattice/lattice_dsp_nexus.pmg index 45084ff1e..13e82e389 100644 --- a/techlibs/lattice/lattice_dsp_nexus.pmg +++ b/techlibs/lattice/lattice_dsp_nexus.pmg @@ -31,14 +31,25 @@ code reject; } + if (add->type == $sub && add_AB == \A) + reject; + Cell *mac = module->addCell(NEW_ID, "$__NX_MAC18X18"); IdString add_C = (add_AB == \A) ? \B : \A; - mac->setPort(\A, port(mul, \A)); - mac->setPort(\B, port(mul, \B)); - mac->setPort(\C, port(add, add_C)); + bool ab_signed = mul->getParam(\A_SIGNED).as_bool(); + SigSpec sigA = port(mul, \A); + SigSpec sigB = port(mul, \B); + SigSpec sigC = port(add, add_C); + sigA.extend_u0(18, ab_signed); + sigB.extend_u0(18, ab_signed); + sigC.extend_u0(48, ab_signed); + + mac->setPort(\A, sigA); + mac->setPort(\B, sigB); + mac->setPort(\C, sigC); mac->setPort(\Y, port(add, \Y)); - mac->setParam(\A_SIGNED, mul->getParam(\A_SIGNED)); + mac->setParam(\A_SIGNED, mul->getParam(\A_SIGNED)); mac->setParam(\SUBTRACT, add->type == $sub ? State::S1 : State::S0); autoremove(mul); @@ -334,8 +345,9 @@ code bool a_signed = mul->getParam(\A_SIGNED).as_bool(); bool b_signed = mul->getParam(\B_SIGNED).as_bool(); - SigSpec sigA = port(mul, \A); - SigSpec sigB = port(mul, \B); + // Drive the DSP from the FF's D + SigSpec sigA = fa ? port(fa, \D) : port(mul, \A); + SigSpec sigB = fb ? port(fb, \D) : port(mul, \B); SigSpec sigY = fy ? port(fy, \Q) : port(mul, \Y); // 36X18 -> wide operand goes to A From 2b6214ad7147991978b6f0f7d62c0d2dd7e30947 Mon Sep 17 00:00:00 2001 From: nella Date: Mon, 22 Jun 2026 14:25:43 +0200 Subject: [PATCH 4/6] Add eq tests. --- tests/arch/nexus/dsp_equiv.sv | 45 ++++++++++ tests/arch/nexus/dsp_equiv.ys | 154 ++++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 tests/arch/nexus/dsp_equiv.sv create mode 100644 tests/arch/nexus/dsp_equiv.ys diff --git a/tests/arch/nexus/dsp_equiv.sv b/tests/arch/nexus/dsp_equiv.sv new file mode 100644 index 000000000..88eb8bb21 --- /dev/null +++ b/tests/arch/nexus/dsp_equiv.sv @@ -0,0 +1,45 @@ +// https://github.com/YosysHQ/yosys/issues/5906 +module mac_add_u (input [8:0] a, b, input [17:0] c, output [17:0] y); + assign y = a*b + c; +endmodule + +module mac_add_s (input signed [8:0] a, b, input signed [17:0] c, output signed [17:0] y); + assign y = a*b + c; +endmodule + +module mac_sub_u (input [8:0] a, b, input [17:0] c, output [17:0] y); + assign y = c - a*b; +endmodule + +module mac_sub_s (input signed [8:0] a, b, input signed [17:0] c, output signed [17:0] y); + assign y = c - a*b; +endmodule + +module mac_subrev_u (input [8:0] a, b, input [17:0] c, output [17:0] y); + assign y = a*b - c; +endmodule + +module mac_wide_s (input signed [8:0] a, b, input signed [17:0] c, output signed [29:0] y); + assign y = c - a*b; +endmodule + +// https://github.com/YosysHQ/yosys/issues/5929 +module mul_pipe_u (input clk, input [8:0] a, b, output reg [17:0] y); + reg [8:0] a_r; + reg [8:0] b_r; + always @(posedge clk) begin + a_r <= a; + b_r <= b; + y <= a_r * b_r; + end +endmodule + +module mul_pipe_s (input clk, input signed [8:0] a, b, output reg signed [17:0] y); + reg signed [8:0] a_r; + reg signed [8:0] b_r; + always @(posedge clk) begin + a_r <= a; + b_r <= b; + y <= a_r * b_r; + end +endmodule diff --git a/tests/arch/nexus/dsp_equiv.ys b/tests/arch/nexus/dsp_equiv.ys new file mode 100644 index 000000000..61b0df443 --- /dev/null +++ b/tests/arch/nexus/dsp_equiv.ys @@ -0,0 +1,154 @@ +read_verilog -sv dsp_equiv.sv +design -save src + +# y = a*b + c (unsigned) +design -load src +hierarchy -top mac_add_u +proc +design -stash gold +design -load src +hierarchy -top mac_add_u +synth_nexus -family lifcl -noiopad +select -assert-count 1 t:MULTADDSUB18X18 +read_verilog +/lattice/cells_sim_nexus.v +hierarchy -top mac_add_u +flatten +proc +design -stash gate +design -copy-from gold -as gold mac_add_u +design -copy-from gate -as gate mac_add_u +miter -equiv -make_assert -flatten gold gate equiv +sat -seq 1 -set-init-zero -prove-asserts -verify equiv + +# y = a*b + c (signed) +design -load src +hierarchy -top mac_add_s +proc +design -stash gold +design -load src +hierarchy -top mac_add_s +synth_nexus -family lifcl -noiopad +select -assert-count 1 t:MULTADDSUB18X18 +read_verilog +/lattice/cells_sim_nexus.v +hierarchy -top mac_add_s +flatten +proc +design -stash gate +design -copy-from gold -as gold mac_add_s +design -copy-from gate -as gate mac_add_s +miter -equiv -make_assert -flatten gold gate equiv +sat -seq 1 -set-init-zero -prove-asserts -verify equiv + +# y = c - a*b (unsigned) +design -load src +hierarchy -top mac_sub_u +proc +design -stash gold +design -load src +hierarchy -top mac_sub_u +synth_nexus -family lifcl -noiopad +select -assert-count 1 t:MULTADDSUB18X18 +read_verilog +/lattice/cells_sim_nexus.v +hierarchy -top mac_sub_u +flatten +proc +design -stash gate +design -copy-from gold -as gold mac_sub_u +design -copy-from gate -as gate mac_sub_u +miter -equiv -make_assert -flatten gold gate equiv +sat -seq 1 -set-init-zero -prove-asserts -verify equiv + +# y = c - a*b (signed) +design -load src +hierarchy -top mac_sub_s +proc +design -stash gold +design -load src +hierarchy -top mac_sub_s +synth_nexus -family lifcl -noiopad +select -assert-count 1 t:MULTADDSUB18X18 +read_verilog +/lattice/cells_sim_nexus.v +hierarchy -top mac_sub_s +flatten +proc +design -stash gate +design -copy-from gold -as gold mac_sub_s +design -copy-from gate -as gate mac_sub_s +miter -equiv -make_assert -flatten gold gate equiv +sat -seq 1 -set-init-zero -prove-asserts -verify equiv + +# y = c - a*b (signed, wide out) +design -load src +hierarchy -top mac_wide_s +proc +design -stash gold +design -load src +hierarchy -top mac_wide_s +synth_nexus -family lifcl -noiopad +select -assert-count 1 t:MULTADDSUB18X18 +read_verilog +/lattice/cells_sim_nexus.v +hierarchy -top mac_wide_s +flatten +proc +design -stash gate +design -copy-from gold -as gold mac_wide_s +design -copy-from gate -as gate mac_wide_s +miter -equiv -make_assert -flatten gold gate equiv +sat -seq 1 -set-init-zero -prove-asserts -verify equiv + +# y = a*b - c (minuend mul) +design -load src +hierarchy -top mac_subrev_u +proc +design -stash gold +design -load src +hierarchy -top mac_subrev_u +synth_nexus -family lifcl -noiopad +select -assert-count 0 t:MULTADDSUB18X18 +read_verilog +/lattice/cells_sim_nexus.v +hierarchy -top mac_subrev_u +flatten +proc +design -stash gate +design -copy-from gold -as gold mac_subrev_u +design -copy-from gate -as gate mac_subrev_u +miter -equiv -make_assert -flatten gold gate equiv +sat -seq 1 -set-init-zero -prove-asserts -verify equiv + +# pipelined mul (unsigned) +design -load src +hierarchy -top mul_pipe_u +proc +design -stash gold +design -load src +hierarchy -top mul_pipe_u +synth_nexus -family lifcl -noiopad +select -assert-count 1 t:MULT18X18 r:REGINPUTA=REGISTER r:REGINPUTB=REGISTER r:REGOUTPUT=REGISTER +read_verilog +/lattice/cells_sim_nexus.v +hierarchy -top mul_pipe_u +flatten +proc +design -stash gate +design -copy-from gold -as gold mul_pipe_u +design -copy-from gate -as gate mul_pipe_u +miter -equiv -make_assert -flatten gold gate equiv +sat -seq 6 -set-init-zero -prove-asserts -verify equiv + +# pipelined mul (signed) +design -load src +hierarchy -top mul_pipe_s +proc +design -stash gold +design -load src +hierarchy -top mul_pipe_s +synth_nexus -family lifcl -noiopad +select -assert-count 1 t:MULT18X18 r:REGINPUTA=REGISTER r:REGINPUTB=REGISTER r:REGOUTPUT=REGISTER +read_verilog +/lattice/cells_sim_nexus.v +hierarchy -top mul_pipe_s +flatten +proc +design -stash gate +design -copy-from gold -as gold mul_pipe_s +design -copy-from gate -as gate mul_pipe_s +miter -equiv -make_assert -flatten gold gate equiv +sat -seq 6 -set-init-zero -prove-asserts -verify equiv From 5e59deecc3b6ad6e42272381aa083ab5d2c2bd4d Mon Sep 17 00:00:00 2001 From: nella Date: Fri, 24 Jul 2026 12:07:10 +0200 Subject: [PATCH 5/6] Fix test desc. --- tests/arch/nexus/dsp_equiv.ys | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/arch/nexus/dsp_equiv.ys b/tests/arch/nexus/dsp_equiv.ys index 61b0df443..a79ae2cf4 100644 --- a/tests/arch/nexus/dsp_equiv.ys +++ b/tests/arch/nexus/dsp_equiv.ys @@ -96,7 +96,7 @@ design -copy-from gate -as gate mac_wide_s miter -equiv -make_assert -flatten gold gate equiv sat -seq 1 -set-init-zero -prove-asserts -verify equiv -# y = a*b - c (minuend mul) +# y = a*b - c (product is the minuend, cannot map to C - A*B, must not fuse) design -load src hierarchy -top mac_subrev_u proc From 526ab7aab3fab15361f41cdd22c5fabd2a8d4410 Mon Sep 17 00:00:00 2001 From: nella Date: Fri, 24 Jul 2026 12:10:07 +0200 Subject: [PATCH 6/6] Use Nexus DSP pipelined registers. --- techlibs/lattice/dsp_map_nexus.v | 2 +- techlibs/lattice/lattice_dsp_nexus.cc | 6 +- techlibs/lattice/lattice_dsp_nexus.pmg | 358 +++++++++++++------------ 3 files changed, 194 insertions(+), 172 deletions(-) diff --git a/techlibs/lattice/dsp_map_nexus.v b/techlibs/lattice/dsp_map_nexus.v index ef9beba44..0489345cc 100644 --- a/techlibs/lattice/dsp_map_nexus.v +++ b/techlibs/lattice/dsp_map_nexus.v @@ -182,4 +182,4 @@ module \$__NX_MAC9X9WIDE_4LANE (input [8:0] A0, B0, A1, B1, A2, B2, A3, B3, outp .ADDSUB(4'b0000), .Z(Y) ); -endmodule \ No newline at end of file +endmodule diff --git a/techlibs/lattice/lattice_dsp_nexus.cc b/techlibs/lattice/lattice_dsp_nexus.cc index b0aaa5f17..8ce71276d 100644 --- a/techlibs/lattice/lattice_dsp_nexus.cc +++ b/techlibs/lattice/lattice_dsp_nexus.cc @@ -27,8 +27,8 @@ struct LatticeDspNexusPass : public Pass { for (auto module : design->selected_modules()) { lattice_dsp_nexus_pm pm(module, module->cells()); - - pm.run_nexus_mac9_4lane(); + + pm.run_nexus_mac9_4lane(); pm.run_nexus_mac18(); pm.run_nexus_preadd18(); pm.run_nexus_mul_reg(); @@ -36,4 +36,4 @@ struct LatticeDspNexusPass : public Pass { } } LatticeDspNexusPass; -PRIVATE_NAMESPACE_END \ No newline at end of file +PRIVATE_NAMESPACE_END diff --git a/techlibs/lattice/lattice_dsp_nexus.pmg b/techlibs/lattice/lattice_dsp_nexus.pmg index 13e82e389..0ff871af9 100644 --- a/techlibs/lattice/lattice_dsp_nexus.pmg +++ b/techlibs/lattice/lattice_dsp_nexus.pmg @@ -220,6 +220,14 @@ endcode pattern nexus_mul_reg +state clk +state argQ argD +state ffA ffB ffY + +udata dff +udata dffclk +udata dffD dffQ + match mul select mul->type.in($mul) select GetSize(port(mul, \A)) <= 36 @@ -227,199 +235,213 @@ match mul select GetSize(port(mul, \Y)) <= 72 endmatch -match ffA - select ffA->type.in($dff, $dffe, $sdff, $sdffe) - select param(ffA, \CLK_POLARITY).as_bool() - filter ffA->type.in($dff, $dffe) || param(ffA, \SRST_VALUE).is_fully_zero() - filter GetSize(port(ffA, \Q)) == GetSize(port(mul, \A)) - index port(ffA, \Q) === port(mul, \A) - optional -endmatch +// Absorb the pipeline FF on each mul input +code argQ ffA clk + argQ = port(mul, \A); + subpattern(in_ff); + if (dff) { + ffA = dff; + clk = dffclk; + } +endcode -match ffB - select ffB->type.in($dff, $dffe, $sdff, $sdffe) - select param(ffB, \CLK_POLARITY).as_bool() - filter ffB->type.in($dff, $dffe) || param(ffB, \SRST_VALUE).is_fully_zero() - filter GetSize(port(ffB, \Q)) == GetSize(port(mul, \B)) - index port(ffB, \Q) === port(mul, \B) - optional -endmatch +code argQ ffB clk + argQ = port(mul, \B); + subpattern(in_ff); + if (dff) { + ffB = dff; + clk = dffclk; + } +endcode -match ffY - select ffY->type.in($dff, $dffe, $sdff, $sdffe) - select param(ffY, \CLK_POLARITY).as_bool() - filter ffY->type.in($dff, $dffe) || param(ffY, \SRST_VALUE).is_fully_zero() - filter GetSize(port(ffY, \D)) == GetSize(port(mul, \Y)) - index port(ffY, \D) === port(mul, \Y) - optional -endmatch +// Absorb the FF on the mul output +code argD ffY clk + argD = port(mul, \Y); + subpattern(out_ff); + if (dff) { + ffY = dff; + clk = dffclk; + } +endcode code - Cell *fa = ffA; - Cell *fb = ffB; - Cell *fy = ffY; - - if (!fa && !fb && !fy) + if (!ffA && !ffB && !ffY) reject; { - // Drop any FF whose Q carries a non-zero power-up value, since the DSP reg powers up at 0 - auto bad_init = [&](Cell *ff) -> bool { - SigSpec q = port(ff, \Q); + int aw = GetSize(port(mul, \A)); + int bw = GetSize(port(mul, \B)); + IdString prim; + int A_PINS; + int B_PINS; - for (auto bit : sigmap(q)) { - if (!bit.wire) - continue; - - auto it = bit.wire->attributes.find(\init); - if (it == bit.wire->attributes.end()) - continue; - - Const init = it->second; - int off = bit.offset; - - if (off < GetSize(init)) { - State s = init[off]; - if (s != State::Sx && s != State::S0) - return true; - } - } - - return false; - }; - - if (fa && bad_init(fa)) fa = nullptr; - if (fb && bad_init(fb)) fb = nullptr; - if (fy && bad_init(fy)) fy = nullptr; - - // All absorbed FFs must share one clock domain - SigBit clk; - bool have_clk = false; - - { - Cell *ffs[3] = { fa, fb, fy }; - for (Cell *ff : ffs) { - if (!ff) continue; - - SigBit c = sigmap(port(ff, \CLK)[0]); - - if (!have_clk) { - clk = c; - have_clk = true; - continue; - } - - if (c != clk) { - if (ff == fa) fa = nullptr; - else if (ff == fb) fb = nullptr; - else fy = nullptr; - } - } + if (aw <= 18 && bw <= 18) { + prim = "$__NX_MUL18X18"; + A_PINS = 18; + B_PINS = 18; + } else if (aw <= 36 && bw <= 18) { + prim = "$__NX_MUL36X18"; + A_PINS = 36; + B_PINS = 18; + } else if (aw <= 18 && bw <= 36) { + prim = "$__NX_MUL36X18"; + A_PINS = 36; + B_PINS = 18; + } else { + prim = "$__NX_MUL36X36"; + A_PINS = 36; + B_PINS = 36; } - // If every candidate was filtered out, don't build a DSP reg cell - if (fa || fb || fy) { - int aw = GetSize(port(mul, \A)); - int bw = GetSize(port(mul, \B)); - IdString prim; - int A_PINS; - int B_PINS; + bool a_signed = mul->getParam(\A_SIGNED).as_bool(); + bool b_signed = mul->getParam(\B_SIGNED).as_bool(); + // Drive the DSP from the FF's D + SigSpec sigA = ffA ? port(ffA, \D) : port(mul, \A); + SigSpec sigB = ffB ? port(ffB, \D) : port(mul, \B); + SigSpec sigY = ffY ? port(ffY, \Q) : port(mul, \Y); - if (aw <= 18 && bw <= 18) { - prim = "$__NX_MUL18X18"; - A_PINS = 18; - B_PINS = 18; - } else if (aw <= 36 && bw <= 18) { - prim = "$__NX_MUL36X18"; - A_PINS = 36; - B_PINS = 18; - } else if (aw <= 18 && bw <= 36) { - prim = "$__NX_MUL36X18"; - A_PINS = 36; - B_PINS = 18; - } else { - prim = "$__NX_MUL36X36"; - A_PINS = 36; - B_PINS = 36; + // 36X18 -> wide operand goes to A + Cell *ffa = ffA; + Cell *ffb = ffB; + if (prim == "$__NX_MUL36X18" && aw <= 18 && bw <= 36) { + std::swap(sigA, sigB); + std::swap(a_signed, b_signed); + std::swap(ffa, ffb); + } + + sigA.extend_u0(A_PINS, a_signed); + sigB.extend_u0(B_PINS, b_signed); + + Cell *cell = module->addCell(NEW_ID, prim); + cell->setPort(\A, sigA); + cell->setPort(\B, sigB); + cell->setPort(\Y, sigY); + cell->setParam(\A_WIDTH, A_PINS); + cell->setParam(\B_WIDTH, B_PINS); + cell->setParam(\Y_WIDTH, GetSize(sigY)); + cell->setParam(\A_SIGNED, a_signed ? State::S1 : State::S0); + cell->setParam(\B_SIGNED, b_signed ? State::S1 : State::S0); + cell->setParam(\RESETMODE, std::string("SYNC")); + + auto ce_of = [&](Cell *ff) -> SigBit { + if (!ff) return State::S0; + + if (ff->type.in($dffe, $sdffe)) { + SigBit e = port(ff, \EN)[0]; + if (!ff->getParam(\EN_POLARITY).as_bool()) + e = module->NotGate(NEW_ID, e); + + return e; } - bool a_signed = mul->getParam(\A_SIGNED).as_bool(); - bool b_signed = mul->getParam(\B_SIGNED).as_bool(); - // Drive the DSP from the FF's D - SigSpec sigA = fa ? port(fa, \D) : port(mul, \A); - SigSpec sigB = fb ? port(fb, \D) : port(mul, \B); - SigSpec sigY = fy ? port(fy, \Q) : port(mul, \Y); + return State::S1; // Always enabled + }; - // 36X18 -> wide operand goes to A - Cell *ffA_use = fa; - Cell *ffB_use = fb; - if (prim == "$__NX_MUL36X18" && aw <= 18 && bw <= 36) { - std::swap(sigA, sigB); - std::swap(a_signed, b_signed); - std::swap(ffA_use, ffB_use); + auto rst_of = [&](Cell *ff) -> SigBit { + if (!ff) return State::S0; + + if (ff->type.in($sdff, $sdffe)) { + SigBit r = port(ff, \SRST)[0]; + if (!ff->getParam(\SRST_POLARITY).as_bool()) + r = module->NotGate(NEW_ID, r); + + return r; } - sigA.extend_u0(A_PINS, a_signed); - sigB.extend_u0(B_PINS, b_signed); + return State::S0; // No reset + }; - Cell *cell = module->addCell(NEW_ID, prim); - cell->setPort(\A, sigA); - cell->setPort(\B, sigB); - cell->setPort(\Y, sigY); - cell->setParam(\A_WIDTH, A_PINS); - cell->setParam(\B_WIDTH, B_PINS); - cell->setParam(\Y_WIDTH, GetSize(sigY)); - cell->setParam(\A_SIGNED, a_signed ? State::S1 : State::S0); - cell->setParam(\B_SIGNED, b_signed ? State::S1 : State::S0); - cell->setParam(\RESETMODE, std::string("SYNC")); + cell->setPort(\CLK, clk != SigBit() ? clk : State::S0); + cell->setPort(\CEA, ce_of(ffa)); + cell->setPort(\RSTA, rst_of(ffa)); + cell->setPort(\CEB, ce_of(ffb)); + cell->setPort(\RSTB, rst_of(ffb)); + cell->setPort(\CEOUT, ce_of(ffY)); + cell->setPort(\RSTOUT, rst_of(ffY)); + cell->setParam(\REGINPUTA, ffa ? std::string("REGISTER") : std::string("BYPASS")); + cell->setParam(\REGINPUTB, ffb ? std::string("REGISTER") : std::string("BYPASS")); + cell->setParam(\REGOUTPUT, ffY ? std::string("REGISTER") : std::string("BYPASS")); - auto ce_of = [&](Cell *ff) -> SigBit { - if (!ff) return State::S0; + if (ffa) autoremove(ffa); + if (ffb && ffb != ffa) autoremove(ffb); + if (ffY) autoremove(ffY); + autoremove(mul); - if (ff->type.in($dffe, $sdffe)) { - SigBit e = port(ff, \EN)[0]; - if (!ff->getParam(\EN_POLARITY).as_bool()) - e = module->NotGate(NEW_ID, e); + accept; + } +endcode - return e; - } +// Find the input pipeline FF whose Q feeds argQ +subpattern in_ff +arg argQ clk - return State::S1; // Always enabled - }; +code + dff = nullptr; +endcode - auto rst_of = [&](Cell *ff) -> SigBit { - if (!ff) return State::S0; +match ff + select ff->type.in($dff, $dffe, $sdff, $sdffe) + select param(ff, \CLK_POLARITY).as_bool() + filter ff->type.in($dff, $dffe) || param(ff, \SRST_VALUE).is_fully_zero() + filter GetSize(port(ff, \Q)) == GetSize(argQ) + index port(ff, \Q) === argQ +endmatch - if (ff->type.in($sdff, $sdffe)) { - SigBit r = port(ff, \SRST)[0]; - if (!ff->getParam(\SRST_POLARITY).as_bool()) - r = module->NotGate(NEW_ID, r); +code + if (clk != SigBit() && sigmap(port(ff, \CLK)[0]) != clk) + reject; - return r; - } - - return State::S0; // No reset - }; - - cell->setPort(\CLK, have_clk ? clk : State::S0); - cell->setPort(\CEA, ce_of(ffA_use)); - cell->setPort(\RSTA, rst_of(ffA_use)); - cell->setPort(\CEB, ce_of(ffB_use)); - cell->setPort(\RSTB, rst_of(ffB_use)); - cell->setPort(\CEOUT, ce_of(fy)); - cell->setPort(\RSTOUT, rst_of(fy)); - cell->setParam(\REGINPUTA, ffA_use ? std::string("REGISTER") : std::string("BYPASS")); - cell->setParam(\REGINPUTB, ffB_use ? std::string("REGISTER") : std::string("BYPASS")); - cell->setParam(\REGOUTPUT, fy ? std::string("REGISTER") : std::string("BYPASS")); - - if (ffA_use) autoremove(ffA_use); - if (ffB_use && ffB_use != ffA_use) autoremove(ffB_use); - if (fy) autoremove(fy); - autoremove(mul); - - accept; + for (auto bit : sigmap(port(ff, \Q))) { + if (!bit.wire) + continue; + auto it = bit.wire->attributes.find(\init); + if (it == bit.wire->attributes.end()) + continue; + if (bit.offset < GetSize(it->second)) { + State s = it->second[bit.offset]; + if (s != State::Sx && s != State::S0) + reject; } } - reject; + dff = ff; + dffD = port(ff, \D); + dffclk = sigmap(port(ff, \CLK)[0]); +endcode + +subpattern out_ff +arg argD clk + +code + dff = nullptr; +endcode + +match ff + select ff->type.in($dff, $dffe, $sdff, $sdffe) + select param(ff, \CLK_POLARITY).as_bool() + filter ff->type.in($dff, $dffe) || param(ff, \SRST_VALUE).is_fully_zero() + filter GetSize(port(ff, \D)) == GetSize(argD) + index port(ff, \D) === argD +endmatch + +code + if (clk != SigBit() && sigmap(port(ff, \CLK)[0]) != clk) + reject; + + for (auto bit : sigmap(port(ff, \Q))) { + if (!bit.wire) + continue; + auto it = bit.wire->attributes.find(\init); + if (it == bit.wire->attributes.end()) + continue; + if (bit.offset < GetSize(it->second)) { + State s = it->second[bit.offset]; + if (s != State::Sx && s != State::S0) + reject; + } + } + + dff = ff; + dffQ = port(ff, \Q); + dffclk = sigmap(port(ff, \CLK)[0]); endcode