Infer MULTADDSUB36X36.

This commit is contained in:
nella
2026-09-24 14:47:53 +02:00
parent 371467f164
commit ec83e47b0f
5 changed files with 332 additions and 131 deletions
+31 -9
View File
@@ -105,14 +105,9 @@ module \$__NX_MUL9X9 (input [8:0] A, input [8:0] B, output [17:0] Y);
);
endmodule
module \$__NX_MAC18X18 (input [17:0] A, input [17:0] B, input [47:0] C, output [53:0] Y);
module \$__NX_MAC18X18 (input [17:0] A, input [17:0] B, input [53:0] C, output [53:0] Y);
parameter A_WIDTH = 18;
parameter B_WIDTH = 18;
parameter C_WIDTH = 48;
parameter Y_WIDTH = 48;
parameter A_SIGNED = 0;
parameter B_SIGNED = 0;
parameter SUBTRACT = 0;
MULTADDSUB18X18 #(
@@ -128,7 +123,34 @@ module \$__NX_MAC18X18 (input [17:0] A, input [17:0] B, input [47:0] C, output [
) _TECHMAP_REPLACE_ (
.A(A),
.B(B),
.C({6'b0, C}),
.C(C),
.SIGNED(A_SIGNED ? 1'b1 : 1'b0),
.ADDSUB(SUBTRACT ? 1'b1 : 1'b0),
.LOADC(1'b1),
.CIN(1'b0),
.Z(Y)
);
endmodule
module \$__NX_MAC36X36 (input [35:0] A, input [35:0] B, input [107:0] C, output [107:0] Y);
parameter A_SIGNED = 0;
parameter SUBTRACT = 0;
MULTADDSUB36X36 #(
.REGINPUTA("BYPASS"),
.REGINPUTB("BYPASS"),
.REGINPUTC("BYPASS"),
.REGADDSUB("BYPASS"),
.REGLOADC("BYPASS"),
.REGLOADC2("BYPASS"),
.REGCIN("BYPASS"),
.REGPIPELINE("BYPASS"),
.REGOUTPUT("BYPASS")
) _TECHMAP_REPLACE_ (
.A(A),
.B(B),
.C(C),
.SIGNED(A_SIGNED ? 1'b1 : 1'b0),
.ADDSUB(SUBTRACT ? 1'b1 : 1'b0),
.LOADC(1'b1),
@@ -161,7 +183,7 @@ module \$__NX_PREADD18X18 (input [17:0] A, input [17:0] B, input [17:0] C, input
);
endmodule
module \$__NX_MAC9X9WIDE_4LANE (input [8:0] A0, B0, A1, B1, A2, B2, A3, B3, output [53:0] Y);
module \$__NX_MAC9X9WIDE_4LANE (input [8:0] A0, B0, A1, B1, A2, B2, A3, B3, input [53:0] C, output [53:0] Y);
parameter SIGNED = 0;
@@ -181,7 +203,7 @@ module \$__NX_MAC9X9WIDE_4LANE (input [8:0] A0, B0, A1, B1, A2, B2, A3, B3, outp
.A1(A1), .B1(B1),
.A2(A2), .B2(B2),
.A3(A3), .B3(B3),
.C(54'b0),
.C(C),
.SIGNED(SIGNED ? 1'b1 : 1'b0),
.ADDSUB(4'b0000),
.LOADC(1'b1),
+37 -5
View File
@@ -4,6 +4,38 @@
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
// $add and $sub are signed only if both operands are
bool nexus_add_signed(Cell *add) { return add->getParam(ID::A_SIGNED).as_bool() && add->getParam(ID::B_SIGNED).as_bool(); }
// Bits [0, width) of the exact product of mul, or empty if its Y cuts them off
SigSpec nexus_product_bits(Cell *mul, const SigSpec &mul_y, int width)
{
int full_width = GetSize(mul->getPort(ID::A)) + GetSize(mul->getPort(ID::B));
if (GetSize(mul_y) < width && GetSize(mul_y) < full_width)
return SigSpec();
SigSpec bits = mul_y;
bits.extend_u0(width, mul->getParam(ID::A_SIGNED).as_bool());
return bits;
}
// Bits [0, width) of a partial sum, or empty if it is narrower
SigSpec nexus_sum_bits(const SigSpec &sum_y, int width)
{
if (GetSize(sum_y) < width)
return SigSpec();
return sum_y.extract(0, width);
}
// True if an adder operand, extended as the adder does, is value
bool nexus_operand_is(SigSpec operand, bool add_signed, const SigSpec &value)
{
if (value.empty())
return false;
operand.extend_u0(GetSize(value), add_signed);
return operand == value;
}
#include "techlibs/lattice/lattice_dsp_nexus_pm.h"
struct LatticeDspNexusPass : public Pass {
@@ -14,10 +46,10 @@ struct LatticeDspNexusPass : public Pass {
log("\n");
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, 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("Infer Lattice Nexus sysDSP macrocells (MULTADDSUB18X18, MULTADDSUB36X36,\n");
log("MULTPREADD18X18, MULTADDSUB9X9WIDE) from MAC and dot-product patterns, and\n");
log("absorb the pipeline flip-flops around bare MULT18X18 / MULT36X36 multipliers\n");
log("into the hardened DSP input and output registers.\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
@@ -29,7 +61,7 @@ struct LatticeDspNexusPass : public Pass {
lattice_dsp_nexus_pm pm(module, module->cells());
pm.run_nexus_mac9_4lane();
pm.run_nexus_mac18();
pm.run_nexus_mac();
pm.run_nexus_preadd18();
pm.run_nexus_mul_reg();
}
+186 -113
View File
@@ -1,59 +1,218 @@
pattern nexus_mac18
pattern nexus_mac9_4lane
// maccmap -unmap adds the addends first, then one product per $add:
// (((C + P0) + P1) + P2) + P3, or ((P0 + P1) + P2) + P3 without C
match add3
select add3->type == $add
select GetSize(port(add3, \Y)) <= 54
endmatch
match mul3
select mul3->type == $mul
select GetSize(port(mul3, \A)) <= 9 && GetSize(port(mul3, \B)) <= 9
select nusers(port(mul3, \Y)) == 2
index <SigBit> port(mul3, \Y)[0] === port(add3, \B)[0]
endmatch
match add2
select add2->type == $add
select nusers(port(add2, \Y)) == 2
index <SigBit> port(add2, \Y)[0] === port(add3, \A)[0]
endmatch
match mul2
select mul2->type == $mul
select GetSize(port(mul2, \A)) <= 9 && GetSize(port(mul2, \B)) <= 9
select nusers(port(mul2, \Y)) == 2
index <SigBit> port(mul2, \Y)[0] === port(add2, \B)[0]
endmatch
match add1
select add1->type == $add
select nusers(port(add1, \Y)) == 2
index <SigBit> port(add1, \Y)[0] === port(add2, \A)[0]
endmatch
match mul1
select mul1->type == $mul
select GetSize(port(mul1, \A)) <= 9 && GetSize(port(mul1, \B)) <= 9
select nusers(port(mul1, \Y)) == 2
index <SigBit> port(mul1, \Y)[0] === port(add1, \B)[0]
endmatch
// C + P0
match add0
select add0->type == $add
select nusers(port(add0, \Y)) == 2
index <SigBit> port(add0, \Y)[0] === port(add1, \A)[0]
optional
endmatch
match mul0
select mul0->type == $mul
select GetSize(port(mul0, \A)) <= 9 && GetSize(port(mul0, \B)) <= 9
select nusers(port(mul0, \Y)) == 2
index <SigBit> port(mul0, \Y)[0] === (add0 ? port(add0, \B)[0] : port(add1, \A)[0])
endmatch
code
{
int width = GetSize(port(add3, \Y));
bool is_signed = param(mul0, \A_SIGNED).as_bool();
for (auto mul : {mul0, mul1, mul2, mul3})
if (param(mul, \A_SIGNED).as_bool() != is_signed || param(mul, \B_SIGNED).as_bool() != is_signed)
reject;
// Each adder must see the exact products and partial sums in the width of the top one
SigSpec p0 = nexus_product_bits(mul0, port(mul0, \Y), width);
SigSpec p1 = nexus_product_bits(mul1, port(mul1, \Y), width);
SigSpec p2 = nexus_product_bits(mul2, port(mul2, \Y), width);
SigSpec p3 = nexus_product_bits(mul3, port(mul3, \Y), width);
SigSpec s1 = add0 ? nexus_sum_bits(port(add0, \Y), width) : p0;
SigSpec s2 = nexus_sum_bits(port(add1, \Y), width);
SigSpec s3 = nexus_sum_bits(port(add2, \Y), width);
if (!nexus_operand_is(port(add3, \A), nexus_add_signed(add3), s3) || !nexus_operand_is(port(add3, \B), nexus_add_signed(add3), p3))
reject;
if (!nexus_operand_is(port(add2, \A), nexus_add_signed(add2), s2) || !nexus_operand_is(port(add2, \B), nexus_add_signed(add2), p2))
reject;
if (!nexus_operand_is(port(add1, \A), nexus_add_signed(add1), s1) || !nexus_operand_is(port(add1, \B), nexus_add_signed(add1), p1))
reject;
if (add0 && !nexus_operand_is(port(add0, \B), nexus_add_signed(add0), p0))
reject;
SigSpec sigC;
if (add0) {
sigC = port(add0, \A);
sigC.extend_u0(width, nexus_add_signed(add0));
// C must not read cells that are removed
for (auto cell : {mul0, mul1, mul2, mul3, add0, add1, add2, add3})
if (!sigC.extract(port(cell, \Y)).empty())
reject;
}
sigC.extend_u0(54, false);
SigSpec sigY = port(add3, \Y);
if (width < 54)
sigY.append(module->addWire(NEW_ID, 54 - width));
Cell *mac = module->addCell(NEW_ID, "$__NX_MAC9X9WIDE_4LANE");
int i = 0;
for (auto mul : {mul0, mul1, mul2, mul3}) {
SigSpec sigA = port(mul, \A);
SigSpec sigB = port(mul, \B);
sigA.extend_u0(9, is_signed);
sigB.extend_u0(9, is_signed);
mac->setPort(stringf("\\A%d", i), sigA);
mac->setPort(stringf("\\B%d", i), sigB);
i++;
}
mac->setPort(\C, sigC);
mac->setPort(\Y, sigY);
mac->setParam(\SIGNED, is_signed ? State::S1 : State::S0);
for (auto cell : {mul0, mul1, mul2, mul3, add0, add1, add2, add3})
autoremove(cell);
}
accept;
endcode
pattern nexus_mac
state <IdString> add_AB add_C mux_add mux_mul
match mul
select mul->type.in($mul)
select GetSize(port(mul, \A)) <= 18
select GetSize(port(mul, \B)) <= 18
select GetSize(port(mul, \Y)) <= 48
select param(mul, \A_SIGNED).as_bool() == param(mul, \B_SIGNED).as_bool()
select GetSize(port(mul, \A)) <= 36
select GetSize(port(mul, \B)) <= 36
endmatch
match add
select add->type.in($add, $sub)
select GetSize(port(add, \Y)) <= 48
select GetSize(port(add, \Y)) <= 108
choice <IdString> AB {\A, \B}
define <IdString> BA (AB == \A ? \B : \A)
filter add->type == $add || AB == \B
index <SigBit> port(add, AB)[0] === port(mul, \Y)[0]
set add_AB AB
set add_C BA
endmatch
// Clear: S ? A * B : C + A * B becomes (S ? 0 : C) + A * B
match mux
select mux->type == $mux
choice <IdString> AB {\A, \B}
define <IdString> BA (AB == \A ? \B : \A)
index <SigSpec> port(mux, AB) === port(add, \Y)
set mux_add AB
set mux_mul BA
optional
endmatch
code
if (mul->getParam(\A_SIGNED).as_bool() != mul->getParam(\B_SIGNED).as_bool()) {
reject;
}
{
SigSpec mul_out = port(mul, \Y);
IdString add_AB;
int width = GetSize(port(add, \Y));
bool add_signed = nexus_add_signed(add);
SigSpec product = nexus_product_bits(mul, port(mul, \Y), width);
if (!nexus_operand_is(port(add, add_AB), add_signed, product))
reject;
if (GetSize(port(add, \A)) >= GetSize(mul_out) && port(add, \A).extract(0, GetSize(mul_out)) == mul_out) {
add_AB = \A;
} else if (GetSize(port(add, \B)) >= GetSize(mul_out) && port(add, \B).extract(0, GetSize(mul_out)) == mul_out) {
add_AB = \B;
} else {
SigSpec sigC = port(add, add_C);
if (!sigC.extract(port(mul, \Y)).empty())
reject;
if (mux) {
if (add->type != $add || port(mux, mux_mul) != product)
reject;
// Don't read removed cells
SigSpec sel = port(mux, \S);
if (!sel.extract(port(mul, \Y)).empty() || !sel.extract(port(add, \Y)).empty())
reject;
if (nusers(port(mul, \Y)) != 3 || nusers(port(add, \Y)) != 2)
reject;
} else if (nusers(port(mul, \Y)) != 2) {
reject;
}
if (add->type == $sub && add_AB == \A)
bool small = GetSize(port(mul, \A)) <= 18 && GetSize(port(mul, \B)) <= 18;
// Narrow muls don't take a whole 36x36 block for a wide sum
if (small && width > 54)
reject;
int ab_width = small ? 18 : 36;
int z_width = small ? 54 : 108;
Cell *mac = module->addCell(NEW_ID, "$__NX_MAC18X18");
IdString add_C = (add_AB == \A) ? \B : \A;
bool ab_signed = mul->getParam(\A_SIGNED).as_bool();
bool ab_signed = param(mul, \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);
SigSpec sigY = mux ? port(mux, \Y) : port(add, \Y);
sigA.extend_u0(ab_width, ab_signed);
sigB.extend_u0(ab_width, ab_signed);
sigC.extend_u0(width, add_signed);
if (mux) {
SigSpec zero(State::S0, width);
SigSpec sel = port(mux, \S);
if (mux_add == \A)
sigC = module->Mux(NEW_ID, sigC, zero, sel);
else
sigC = module->Mux(NEW_ID, zero, sigC, sel);
}
sigC.extend_u0(z_width, false);
if (width < z_width)
sigY.append(module->addWire(NEW_ID, z_width - width));
Cell *mac = module->addCell(NEW_ID, small ? "$__NX_MAC18X18" : "$__NX_MAC36X36");
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->setPort(\Y, sigY);
mac->setParam(\A_SIGNED, param(mul, \A_SIGNED));
mac->setParam(\SUBTRACT, add->type == $sub ? State::S1 : State::S0);
autoremove(mul);
autoremove(add);
autoremove(mux);
}
accept;
@@ -134,92 +293,6 @@ code
accept;
endcode
pattern nexus_mac9_4lane
match add_top
select add_top->type == $add
endmatch
match add_mid
select add_mid->type == $add
index <SigBit> port(add_mid, \Y)[0] === port(add_top, \A)[0]
endmatch
match add_bot
select add_bot->type == $add
index <SigBit> port(add_bot, \Y)[0] === port(add_mid, \A)[0]
endmatch
match mul3
select mul3->type == $mul
select GetSize(port(mul3, \A)) <= 9 && GetSize(port(mul3, \B)) <= 9
index <SigBit> port(mul3, \Y)[0] === port(add_top, \B)[0]
endmatch
match mul2
select mul2->type == $mul
select GetSize(port(mul2, \A)) <= 9 && GetSize(port(mul2, \B)) <= 9
index <SigBit> port(mul2, \Y)[0] === port(add_mid, \B)[0]
endmatch
match mul1
select mul1->type == $mul
select GetSize(port(mul1, \A)) <= 9 && GetSize(port(mul1, \B)) <= 9
index <SigBit> port(mul1, \Y)[0] === port(add_bot, \B)[0]
endmatch
match mul0
select mul0->type == $mul
select GetSize(port(mul0, \A)) <= 9 && GetSize(port(mul0, \B)) <= 9
index <SigBit> port(mul0, \Y)[0] === port(add_bot, \A)[0]
endmatch
code
bool is_signed = mul0->getParam(\A_SIGNED).as_bool();
if (
mul0->getParam(\B_SIGNED).as_bool() != is_signed ||
mul1->getParam(\A_SIGNED).as_bool() != is_signed ||
mul1->getParam(\B_SIGNED).as_bool() != is_signed ||
mul2->getParam(\A_SIGNED).as_bool() != is_signed ||
mul2->getParam(\B_SIGNED).as_bool() != is_signed ||
mul3->getParam(\A_SIGNED).as_bool() != is_signed ||
mul3->getParam(\B_SIGNED).as_bool() != is_signed
) {
reject;
}
{
Cell *mac = module->addCell(NEW_ID, "$__NX_MAC9X9WIDE_4LANE");
auto ext9 = [&](SigSpec s) {
s.extend_u0(9, is_signed);
return s;
};
mac->setPort(\A0, ext9(port(mul0, \A)));
mac->setPort(\B0, ext9(port(mul0, \B)));
mac->setPort(\A1, ext9(port(mul1, \A)));
mac->setPort(\B1, ext9(port(mul1, \B)));
mac->setPort(\A2, ext9(port(mul2, \A)));
mac->setPort(\B2, ext9(port(mul2, \B)));
mac->setPort(\A3, ext9(port(mul3, \A)));
mac->setPort(\B3, ext9(port(mul3, \B)));
mac->setPort(\Y, port(add_top, \Y));
mac->setParam(\SIGNED, is_signed ? State::S1 : State::S0);
autoremove(add_top);
autoremove(add_mid);
autoremove(add_bot);
autoremove(mul0);
autoremove(mul1);
autoremove(mul2);
autoremove(mul3);
}
accept;
endcode
pattern nexus_mul_reg
state <SigBit> clk
+31 -2
View File
@@ -55,8 +55,8 @@ module dot4 (
assign p = p_r;
endmodule
// Oversized 24x24 MAC
module neg_mac24 (input clk, clear, input [23:0] a, b, output [47:0] p);
// 24x24 MAC
module mac24 (input clk, clear, input [23:0] a, b, output [47:0] p);
reg [23:0] a_r, b_r; reg [47:0] p_r; reg clear_r;
always_ff @(posedge clk) begin
a_r <= a; b_r <= b; clear_r <= clear;
@@ -75,6 +75,35 @@ module neg_dot_mixed (input clk, input [8:0] a0,b0,a1,b1, input [17:0] a2, b2, o
assign p = p_r;
endmodule
// https://github.com/YosysHQ/yosys/issues/6233
module mac36 (input [35:0] a, b, input [107:0] c, output [107:0] y);
assign y = c + a * b;
endmodule
module msub36 (input [35:0] a, b, input [107:0] c, output [107:0] y);
assign y = c - a * b;
endmodule
module neg_mulsub36 (input [35:0] a, b, input [107:0] c, output [107:0] y);
assign y = a * b - c;
endmodule
module mac16 (input [15:0] a, b, input [51:0] c, output [51:0] y);
assign y = c + a * b;
endmodule
module dot4_acc (input [8:0] a0, b0, a1, b1, a2, b2, a3, b3, input [53:0] c, output [53:0] y);
assign y = a0*b0 + a1*b1 + a2*b2 + a3*b3 + c;
endmodule
// The mux select reads the product
module mac_sel (input [3:0] a, b, input [5:0] c, output [5:0] y);
wire [7:0] p = a * b;
wire [5:0] s = c + p[5:0];
assign y = p[7] ? p[5:0] : s;
endmodule
// The pre-adder result is also an output
module preadd_fanout (input [5:0] a, b, d, output [15:0] p, output [6:0] q);
assign q = a + d;
+47 -2
View File
@@ -9,6 +9,11 @@ synth_nexus -family lifcl -top mac
select -assert-count 1 t:MULTADDSUB18X18
select -assert-count 0 t:CCU2
design -load pristine
hierarchy -top mac;
synth_nexus -family lifcl -top mac -run begin:map_ram
check -assert
# 18x18 pre-add MAC
design -load pristine
hierarchy -top madd_pre;
@@ -23,9 +28,10 @@ select -assert-count 1 t:MULTADDSUB9X9WIDE
# 24x24 MAC
design -load pristine
hierarchy -top neg_mac24;
synth_nexus -family lifcl -top neg_mac24
hierarchy -top mac24;
synth_nexus -family lifcl -top mac24
select -assert-count 0 t:MULTADDSUB18X18
select -assert-count 1 t:MULTADDSUB36X36
# mixed
design -load pristine
@@ -34,7 +40,46 @@ synth_nexus -family lifcl -top neg_dot_mixed
select -assert-count 0 t:MULTADDSUB9X9WIDE
select -assert-count 2 t:MULTADDSUB18X18
# 36x36 MAC
design -load pristine
hierarchy -top mac36;
synth_nexus -family lifcl -top mac36
select -assert-count 1 t:MULTADDSUB36X36
select -assert-count 0 t:CCU2
# 36x36 C - A*B
design -load pristine
hierarchy -top msub36;
synth_nexus -family lifcl -top msub36
select -assert-count 1 t:MULTADDSUB36X36
select -assert-count 0 t:CCU2
# A*B - C is not a DSP operation
design -load pristine
hierarchy -top neg_mulsub36;
synth_nexus -family lifcl -top neg_mulsub36
select -assert-count 0 t:MULTADDSUB36X36
# 16x16 MAC with a 52-bit sum stays on the 18x18 cell
design -load pristine
hierarchy -top mac16;
synth_nexus -family lifcl -top mac16
select -assert-count 1 t:MULTADDSUB18X18
select -assert-count 0 t:MULTADDSUB36X36
# 4-lane 9x9 dot product with addend
design -load pristine
hierarchy -top dot4_acc;
synth_nexus -family lifcl -top dot4_acc
select -assert-count 1 t:MULTADDSUB9X9WIDE
select -assert-count 0 t:CCU2
# Cells read elsewhere must not be removed
design -load pristine
hierarchy -top mac_sel;
proc
equiv_opt -assert -map +/lattice/cells_sim_nexus.v synth_nexus -family lifcl -top mac_sel -run begin:map_ram
design -load pristine
hierarchy -top preadd_fanout;
proc