This commit is contained in:
nella 2026-06-09 18:19:34 +02:00 committed by GitHub
commit 590130c0f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 480 additions and 15 deletions

View File

@ -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

View File

@ -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<std::string> 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;

View File

@ -205,4 +205,209 @@ code
}
accept;
endcode
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 <SigSpec> 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 <SigSpec> 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 <SigSpec> 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

View File

@ -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

View File

@ -0,0 +1,74 @@
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 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 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 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 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 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 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 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 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 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 r:REGINPUTA=BYPASS r:REGINPUTB=BYPASS
select -assert-min 1 t:FD1P3*