From 98656e284b4840417cc0b93e5578bb03e05b658d Mon Sep 17 00:00:00 2001 From: Johan Olby Date: Sun, 12 Jul 2026 21:00:21 +0200 Subject: [PATCH] gowin: infer DSP multipliers for the GW5A family synth_gowin guarded the mul2dsp + dsp_map path to gw1n/gw2a, so GW5A multiplies expanded to LUTs. Add a gw5a branch mapping the GW5A's three multiplier widths: $__MUL27X18 (M0 27x18) for larger multiplies, $__MUL12X12 (M1 12x12) for <=12x12 (which pack two-per-block via M0+M1), and $__MUL27X36 (27x36) for wide (B>18) multiplies. The blocks are signed-only (no per-operand sign control), so DSP_SIGNEDONLY is mandatory without it a 32x16 -1*-1 mapping yields 0x08000001 instead of 1. --- techlibs/gowin/CMakeLists.txt | 1 + techlibs/gowin/dsp_map_gw5a.v | 95 +++++++++++++++++++++++++++++++++++ techlibs/gowin/synth_gowin.cc | 16 ++++++ tests/arch/gowin/mul_gw5a.v | 11 ++++ tests/arch/gowin/mul_gw5a.ys | 72 ++++++++++++++++++++++++++ 5 files changed, 195 insertions(+) create mode 100644 techlibs/gowin/dsp_map_gw5a.v create mode 100644 tests/arch/gowin/mul_gw5a.v create mode 100644 tests/arch/gowin/mul_gw5a.ys diff --git a/techlibs/gowin/CMakeLists.txt b/techlibs/gowin/CMakeLists.txt index f74305818..d7b2e3288 100644 --- a/techlibs/gowin/CMakeLists.txt +++ b/techlibs/gowin/CMakeLists.txt @@ -52,4 +52,5 @@ yosys_pass(synth_gowin lutrams_map.v lutrams.txt dsp_map.v + dsp_map_gw5a.v ) diff --git a/techlibs/gowin/dsp_map_gw5a.v b/techlibs/gowin/dsp_map_gw5a.v new file mode 100644 index 000000000..ed8a4e6cc --- /dev/null +++ b/techlibs/gowin/dsp_map_gw5a.v @@ -0,0 +1,95 @@ +module \$__MUL27X18 (input [26:0] A, input [17:0] B, output [44:0] Y); + + parameter A_WIDTH = 27; + parameter B_WIDTH = 18; + parameter Y_WIDTH = 45; + parameter A_SIGNED = 0; + parameter B_SIGNED = 0; + + wire [47:0] dout_w; + + MULTALU27X18 #( + .MULT12X12_EN("FALSE"), + .MULT_RESET_MODE("SYNC"), + .AREG_CLK("BYPASS"), + .BREG_CLK("BYPASS"), + .PREG_CLK("BYPASS"), + .OREG_CLK("BYPASS") + ) __TECHMAP_REPLACE__ ( + .DOUT(dout_w), + .CASO(), + .SOA(), + .A(A), + .SIA(27'd0), + .B(B), + .C(48'd0), + .D(26'd0), + .CASI(48'd0), + .ACCSEL(1'b0), + .PSEL(1'b0), + .ASEL(1'b0), + .PADDSUB(1'b0), + .CSEL(1'b0), + .CASISEL(1'b0), + .ADDSUB(2'b00), + .CLK(2'b00), + .CE(2'b00), + .RESET(2'b00) + ); + + assign Y = dout_w[44:0]; + +endmodule + +module \$__MUL12X12 (input [11:0] A, input [11:0] B, output [23:0] Y); + + parameter A_WIDTH = 12; + parameter B_WIDTH = 12; + parameter Y_WIDTH = 24; + parameter A_SIGNED = 0; + parameter B_SIGNED = 0; + + MULT12X12 #( + .MULT_RESET_MODE("SYNC"), + .AREG_CLK("BYPASS"), + .BREG_CLK("BYPASS"), + .PREG_CLK("BYPASS"), + .OREG_CLK("BYPASS") + ) __TECHMAP_REPLACE__ ( + .DOUT(Y), + .A(A), + .B(B), + .CLK(2'b00), + .CE(2'b00), + .RESET(2'b00) + ); + +endmodule + +module \$__MUL27X36 (input [26:0] A, input [35:0] B, output [62:0] Y); + + parameter A_WIDTH = 27; + parameter B_WIDTH = 36; + parameter Y_WIDTH = 63; + parameter A_SIGNED = 0; + parameter B_SIGNED = 0; + + MULT27X36 #( + .MULT_RESET_MODE("SYNC"), + .AREG_CLK("BYPASS"), + .BREG_CLK("BYPASS"), + .PREG_CLK("BYPASS"), + .OREG_CLK("BYPASS") + ) __TECHMAP_REPLACE__ ( + .DOUT(Y), + .A(A), + .B(B), + .D(26'd0), + .PSEL(1'b0), + .PADDSUB(1'b0), + .CLK(2'b00), + .CE(2'b00), + .RESET(2'b00) + ); + +endmodule diff --git a/techlibs/gowin/synth_gowin.cc b/techlibs/gowin/synth_gowin.cc index d5ebdafea..f76f49deb 100644 --- a/techlibs/gowin/synth_gowin.cc +++ b/techlibs/gowin/synth_gowin.cc @@ -44,6 +44,12 @@ struct SynthGowinPass : public ScriptPass {9, 9, 4, 4, "$__MUL9X9"}, }; + const std::vector gw5a_dsp_rules = { + {27, 36, 2, 19, "$__MUL27X36"}, + {27, 18, 13, 13, "$__MUL27X18"}, + {12, 12, 2, 2, "$__MUL12X12"}, + }; + void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| @@ -285,6 +291,8 @@ struct SynthGowinPass : public ScriptPass if (help_mode) { run("techmap -map +/mul2dsp.v [...]", "(unless -nodsp and if -family gw1n or gw2a)"); run("techmap -map +/gowin/dsp_map.v", "(unless -nodsp and if -family gw1n or gw2a)"); + run("techmap -map +/mul2dsp.v [...]", "(unless -nodsp and if -family gw5a)"); + run("techmap -map +/gowin/dsp_map_gw5a.v", "(unless -nodsp and if -family gw5a)"); } else if (!nodsp && (family == "gw1n" || family == "gw2a")) { for (const auto &rule : dsp_rules) { run(stringf("techmap -map +/mul2dsp.v -D DSP_A_MAXWIDTH=%d -D DSP_B_MAXWIDTH=%d -D DSP_A_MINWIDTH=%d -D DSP_B_MINWIDTH=%d -D DSP_NAME=%s", @@ -292,6 +300,14 @@ struct SynthGowinPass : public ScriptPass run("chtype -set $mul t:$__soft_mul"); } run("techmap -map +/gowin/dsp_map.v"); + } else if (!nodsp && family == "gw5a") { + for (const auto &rule : gw5a_dsp_rules) { + run(stringf("techmap -map +/mul2dsp.v -D DSP_A_MAXWIDTH=%d -D DSP_B_MAXWIDTH=%d " + "-D DSP_A_MINWIDTH=%d -D DSP_B_MINWIDTH=%d -D DSP_SIGNEDONLY -D DSP_NAME=%s", + rule.a_maxwidth, rule.b_maxwidth, rule.a_minwidth, rule.b_minwidth, rule.prim)); + run("chtype -set $mul t:$__soft_mul"); + } + run("techmap -map +/gowin/dsp_map_gw5a.v"); } run("alumacc"); diff --git a/tests/arch/gowin/mul_gw5a.v b/tests/arch/gowin/mul_gw5a.v new file mode 100644 index 000000000..24ae8c4fa --- /dev/null +++ b/tests/arch/gowin/mul_gw5a.v @@ -0,0 +1,11 @@ +// Signed multiply for GW5A DSP boundary tests (the GW5A multipliers are +// signed-only, so signed operands exercise the routing boundaries cleanly). +module top +#(parameter X_WIDTH=8, Y_WIDTH=8, A_WIDTH=16) +( + input signed [X_WIDTH-1:0] x, + input signed [Y_WIDTH-1:0] y, + output signed [A_WIDTH-1:0] A +); + assign A = x * y; +endmodule diff --git a/tests/arch/gowin/mul_gw5a.ys b/tests/arch/gowin/mul_gw5a.ys new file mode 100644 index 000000000..a8b0c2e7d --- /dev/null +++ b/tests/arch/gowin/mul_gw5a.ys @@ -0,0 +1,72 @@ +# GW5A DSP multiply boundary tests. Probes each routing edge: +# 12x12 small-cell max (fits MULT12X12) +# 13x13 MINWIDTH=13 cutoff (-> MULTALU27X18) +# 27x18 large-cell exact fit (B<=18 -> MULTALU27X18) +# 27x19 B overflows 18 -> MULT27X36 cascade +# 28x18 A overflows 27 -> decomposition + +# 12x12 -> 1 MULT12X12 (small cell, below MINWIDTH=13) +read_verilog mul_gw5a.v +chparam -set X_WIDTH 12 -set Y_WIDTH 12 -set A_WIDTH 24 +hierarchy -top top +proc +synth_gowin -family gw5a +cd top +select -assert-count 1 t:MULT12X12 + + +# 13x13 -> 1 MULTALU27X18 (MINWIDTH=13 cutoff routes to the large cell) +design -reset +read_verilog mul_gw5a.v +chparam -set X_WIDTH 13 -set Y_WIDTH 13 -set A_WIDTH 26 +hierarchy -top top +proc +synth_gowin -family gw5a +cd top +select -assert-count 1 t:MULTALU27X18 + + +# 27x18 -> 1 MULTALU27X18 (exact fit of the large cell) +design -reset +read_verilog mul_gw5a.v +chparam -set X_WIDTH 27 -set Y_WIDTH 18 -set A_WIDTH 45 +hierarchy -top top +proc +synth_gowin -family gw5a +cd top +select -assert-count 1 t:MULTALU27X18 + + +# 28x18 -> A overflows 27: 1 MULTALU27X18 + MULT12X12 partials +design -reset +read_verilog mul_gw5a.v +chparam -set X_WIDTH 28 -set Y_WIDTH 18 -set A_WIDTH 46 +hierarchy -top top +proc +synth_gowin -family gw5a +cd top +select -assert-count 1 t:MULTALU27X18 +select -assert-count 2 t:MULT12X12 + + +# 27x19 -> B overflows 18: routes to MULT27X36 (2 cascaded DSPs) +design -reset +read_verilog mul_gw5a.v +chparam -set X_WIDTH 27 -set Y_WIDTH 19 -set A_WIDTH 46 +hierarchy -top top +proc +synth_gowin -family gw5a +cd top +select -assert-count 1 t:MULT27X36 + + +# Make sure that DSPs are not inferred with -nodsp option +design -reset +read_verilog mul_gw5a.v +chparam -set X_WIDTH 27 -set Y_WIDTH 18 -set A_WIDTH 45 +hierarchy -top top +proc +synth_gowin -family gw5a -nodsp +cd top +select -assert-none t:MULTALU27X18 +select -assert-none t:MULT12X12