From ec83e47b0f9201f245acbff2a8ecb0d7c5d6df24 Mon Sep 17 00:00:00 2001 From: nella Date: Thu, 24 Sep 2026 14:47:53 +0200 Subject: [PATCH] Infer MULTADDSUB36X36. --- techlibs/lattice/dsp_map_nexus.v | 40 +++- techlibs/lattice/lattice_dsp_nexus.cc | 42 +++- techlibs/lattice/lattice_dsp_nexus.pmg | 299 +++++++++++++++---------- tests/arch/nexus/fuse_mac.sv | 33 ++- tests/arch/nexus/fuse_mac.ys | 49 +++- 5 files changed, 332 insertions(+), 131 deletions(-) diff --git a/techlibs/lattice/dsp_map_nexus.v b/techlibs/lattice/dsp_map_nexus.v index d745b7aa3..c161d2342 100644 --- a/techlibs/lattice/dsp_map_nexus.v +++ b/techlibs/lattice/dsp_map_nexus.v @@ -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), diff --git a/techlibs/lattice/lattice_dsp_nexus.cc b/techlibs/lattice/lattice_dsp_nexus.cc index 8ce71276d..748b89b6e 100644 --- a/techlibs/lattice/lattice_dsp_nexus.cc +++ b/techlibs/lattice/lattice_dsp_nexus.cc @@ -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 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(); } diff --git a/techlibs/lattice/lattice_dsp_nexus.pmg b/techlibs/lattice/lattice_dsp_nexus.pmg index 47c3df22f..d2eb88673 100644 --- a/techlibs/lattice/lattice_dsp_nexus.pmg +++ b/techlibs/lattice/lattice_dsp_nexus.pmg @@ -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 port(mul3, \Y)[0] === port(add3, \B)[0] +endmatch + +match add2 + select add2->type == $add + select nusers(port(add2, \Y)) == 2 + index 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 port(mul2, \Y)[0] === port(add2, \B)[0] +endmatch + +match add1 + select add1->type == $add + select nusers(port(add1, \Y)) == 2 + index 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 port(mul1, \Y)[0] === port(add1, \B)[0] +endmatch + +// C + P0 +match add0 + select add0->type == $add + select nusers(port(add0, \Y)) == 2 + index 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 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 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 AB {\A, \B} + define BA (AB == \A ? \B : \A) + filter add->type == $add || AB == \B index 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 AB {\A, \B} + define BA (AB == \A ? \B : \A) + index 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 port(add_mid, \Y)[0] === port(add_top, \A)[0] -endmatch - -match add_bot - select add_bot->type == $add - index 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 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 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 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 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 clk diff --git a/tests/arch/nexus/fuse_mac.sv b/tests/arch/nexus/fuse_mac.sv index b8b26b7bc..614fb3642 100644 --- a/tests/arch/nexus/fuse_mac.sv +++ b/tests/arch/nexus/fuse_mac.sv @@ -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; diff --git a/tests/arch/nexus/fuse_mac.ys b/tests/arch/nexus/fuse_mac.ys index 094b16768..e0701ca4a 100644 --- a/tests/arch/nexus/fuse_mac.ys +++ b/tests/arch/nexus/fuse_mac.ys @@ -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