From 46ba89059ad6e26e37c46ca3ae64a65a4f9d7616 Mon Sep 17 00:00:00 2001 From: Mohamed Gaber Date: Thu, 15 May 2025 17:45:08 +0300 Subject: [PATCH] splitlarge: new pass to split wide arithmetic operators Adds a new pass, `splitlarge`, that recursively divides $add/$sub cells into smaller cells until each cell's width doesn't exceed a given max_width (128 by default.) An $add/$sub cell's width for this purpose is defined as the higher of the widths of its two inputs. A test was written in Tcl for it, which tests this matrix: - cell: $add/$sub - b: unsigned, signed - a: unsigned, signed This is the first test for a Silimate pass in Tcl and thus `run-test.sh` was modified to include it. --- passes/silimate/Makefile.inc | 1 + passes/silimate/splitlarge.cc | 203 ++++++++++++++++++++++++++++++++++ tests/silimate/run-test.sh | 4 + tests/silimate/wide_op.tcl | 70 ++++++++++++ tests/silimate/wide_op.v | 39 +++++++ 5 files changed, 317 insertions(+) create mode 100644 passes/silimate/splitlarge.cc create mode 100644 tests/silimate/wide_op.tcl create mode 100644 tests/silimate/wide_op.v diff --git a/passes/silimate/Makefile.inc b/passes/silimate/Makefile.inc index 27988cb52..038224e8f 100644 --- a/passes/silimate/Makefile.inc +++ b/passes/silimate/Makefile.inc @@ -12,6 +12,7 @@ OBJS += passes/silimate/obs_clean.o OBJS += passes/silimate/opt_balance_tree.o OBJS += passes/silimate/segv.o OBJS += passes/silimate/splitfanout.o +OBJS += passes/silimate/splitlarge.o OBJS += passes/silimate/splitnetlist.o OBJS += passes/silimate/opt_expand.o diff --git a/passes/silimate/splitlarge.cc b/passes/silimate/splitlarge.cc new file mode 100644 index 000000000..f38cf76a7 --- /dev/null +++ b/passes/silimate/splitlarge.cc @@ -0,0 +1,203 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * 2025 Silimate Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + + #include "kernel/yosys.h" + #include "kernel/sigtools.h" + #include "kernel/utils.h" + + #include + + USING_YOSYS_NAMESPACE + PRIVATE_NAMESPACE_BEGIN + + struct SplitlargeWorker + { + Module *module; + int max_width; + + SplitlargeWorker(Module *module, int max_width) : module(module), max_width(max_width) + { + } + + void split_spec(int high_width, int low_width, SigSpec &high, SigSpec &low, const SigSpec &splittable, bool sign_extend) + { + SigSpec splittable_ext = splittable; + splittable_ext.extend_u0(high_width + low_width, sign_extend); + + high = splittable_ext.extract(low_width, high_width); + low = splittable_ext.extract(0, low_width); + } + + int width_addsub(Cell *cell) + { + int a_width = cell->parameters[ID::A_WIDTH].as_int(); + int b_width = cell->parameters[ID::B_WIDTH].as_int(); + return max(a_width, b_width); + } + + void split_addsub(Cell *cell, std::queue& q) + { + auto width = width_addsub(cell); + auto width_low = width / 2; + auto width_high = width - width_low; // Handle odd widths + + auto a = cell->getPort(ID::A); + auto b = cell->getPort(ID::B); + bool aSigned = cell->parameters[ID::A_SIGNED].as_bool(); + bool bSigned = cell->parameters[ID::B_SIGNED].as_bool(); + SigSpec aHigh, aLow, bHigh, bLow; + + split_spec(width_high, width_low, aHigh, aLow, cell->getPort(ID::A), cell->getParam(ID::A_SIGNED).as_bool()); + split_spec(width_high, width_low, bHigh, bLow, cell->getPort(ID::B), cell->getParam(ID::B_SIGNED).as_bool()); + + std::string yHighName = cell->name.str() + "_splitres1"; + std::string yCarryName = cell->name.str() + "_splitresc"; + std::string yLowName = cell->name.str() + "_splitres0"; + std::string nameHigh = cell->name.str() + "_split1"; + std::string nameLow = cell->name.str() + "_split0"; + std::string nameCarry = cell->name.str() + "_splitc"; + + auto yCarry = module->addWire(yCarryName, width_high + 1); + auto yHigh = module->addWire(yHighName, width_high + 1); + auto yLow = module->addWire(yLowName, width_low + 1); + + auto backupResultSpec = cell->getPort(ID::Y); + + // Modify existing adder to be low + module->rename(cell, nameLow); + cell->setPort(ID::A, aLow); + cell->setPort(ID::B, bLow); + cell->setPort(ID::Y, yLow); + cell->parameters[ID::A_WIDTH] = width_low; + cell->parameters[ID::B_WIDTH] = width_low; + cell->parameters[ID::Y_WIDTH] = width_low + 1; + cell->parameters[ID::A_SIGNED] = 0; + cell->parameters[ID::B_SIGNED] = 0; + if (width_low > max_width) { + q.emplace(cell); + } + + // Create high adder + auto highCell = module->addCell(nameHigh, cell); // copy type and parameters + highCell->setPort(ID::A, aHigh); + highCell->setPort(ID::B, bHigh); + highCell->setPort(ID::Y, yHigh); + highCell->parameters[ID::A_WIDTH] = width_high; + highCell->parameters[ID::B_WIDTH] = width_high; + highCell->parameters[ID::Y_WIDTH] = width_high + 1; + highCell->parameters[ID::A_SIGNED] = aSigned; + highCell->parameters[ID::B_SIGNED] = bSigned; + if (width_high > max_width) { + q.emplace(highCell); + } + + // Create carry adder + auto carryCell = module->addCell(nameCarry, highCell); // copy type and parameters + auto carryBit = SigSpec(yLow).extract(width_low, 1); + carryBit.extend_u0(2); + + carryCell->setPort(ID::A, yHigh); + carryCell->parameters[ID::A_WIDTH] = width_high + 1; + carryCell->parameters[ID::A_SIGNED] = aSigned || bSigned; + carryCell->setPort(ID::B, carryBit); + carryCell->parameters[ID::B_WIDTH] = 2; + carryCell->parameters[ID::B_SIGNED] = aSigned || bSigned; + carryCell->setPort(ID::Y, yCarry); + if (width_high + 1 > max_width) { + q.emplace(carryCell); + } + + // Concatenate + auto ySpec = SigSpec({yCarry, SigSpec(yLow).extract(0, width_low)}); + module->connect(backupResultSpec, ySpec.extract(0, backupResultSpec.size())); + } + + void split() + { + std::queue q; + for (auto pair: module->cells_) { + auto& cell = pair.second; + if (cell->type == ID($add) && width_addsub(cell) > max_width) { + q.emplace(cell); + } else if (cell->type == ID($sub) && width_addsub(cell) > max_width) { + q.emplace(cell); + } + } + + while (!q.empty()) { + auto cell = q.front(); + q.pop(); + + if (cell->type == ID($add)) { + split_addsub(cell, q); + } + else if (cell->type == ID($sub)) { + split_addsub(cell, q); + } + } + } + + }; + + struct SplitlargePass : public Pass { + SplitlargePass() : Pass("splitlarge", "splitting large arithmetic operators") { } + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" splitlarge [selection]\n"); + log("\n"); + log(" splits arithmetic operators (specifically $add, $sub) into a number of\n"); + log(" operators with smaller widths. should be run after techmap/simplemap\n"); + log("\n"); + log("\n"); + log(" -max_width n\n"); + log(" max tolerable width of an $add or $sub operator.\n"); + log(" * The width of $add/$sub is defined as max(a_width, b_width)\n"); + log(" * If unset, 128 is used as a default value.\n"); + log("\n"); + } + void execute(std::vector args, RTLIL::Design *design) override + { + int max_width = 128; + log_header(design, "Executing SPLITLARGE pass (splitting wide $add/$sub operators).\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) + { + if (args[argidx] == "-max_width" && argidx+1 < args.size()) { + max_width = std::stoul(args[++argidx]); + continue; + } + break; + } + extra_args(args, argidx, design); + + for (auto module : design->selected_modules()) + { + SplitlargeWorker worker(module, max_width); + worker.split(); + } + + Pass::call(design, "clean *"); + } + } SplitfanoutPass; + + PRIVATE_NAMESPACE_END diff --git a/tests/silimate/run-test.sh b/tests/silimate/run-test.sh index e9698386e..22a40145e 100755 --- a/tests/silimate/run-test.sh +++ b/tests/silimate/run-test.sh @@ -4,3 +4,7 @@ for x in *.ys; do echo "Running $x.." ../../yosys -ql ${x%.ys}.log $x done +for x in *.tcl; do + echo "Running $x.." + ../../yosys -ql ${x%.tcl}.log -c $x +done diff --git a/tests/silimate/wide_op.tcl b/tests/silimate/wide_op.tcl new file mode 100644 index 000000000..af3f4a431 --- /dev/null +++ b/tests/silimate/wide_op.tcl @@ -0,0 +1,70 @@ +proc op_name {op_number} { + set op [expr $op_number >> 2] + set b_signed [expr {$op_number & 1 ? "signed": "unsigned"}] + set a_signed [expr {(($op_number & 2) >> 1) ? "signed": "unsigned"}] + set cell "unknown" + if { "$op" == "0" } { + set cell "\$add" + } elseif { "$op" == "1" } { + set cell "\$sub" + } + return "$cell (a $a_signed, b $b_signed)" +} + +proc predict_adder_count {in_width max_width op_number} { + set b_signed [expr {$op_number & 1}] + set a_signed [expr {($op_number & 2) >> 1}] + set a_width [expr {$in_width + $a_signed}] + set b_width [expr {$in_width + $b_signed}] + + set adder_width [expr {max($a_width, $b_width)}] + set adder_queue [list] + + if {$adder_width > $max_width} { + lappend adder_queue $adder_width + } + + set adder_count 1 + + while {[llength $adder_queue] > 0} { + set current [lindex $adder_queue end] + set adder_queue [lrange $adder_queue 0 end-1] + + incr adder_count 2 ; # one changed adder, two new adders + + set low [expr {$current / 2}] + set high [expr {$current - $low}] + set carry [expr {$high + 1}] + + if {$low > $max_width} { + lappend adder_queue $low + } + if {$high > $max_width} { + lappend adder_queue $high + } + if {$carry > $max_width} { + lappend adder_queue $carry + } + } + return $adder_count +} + +yosys -import +log -header "splitlarge" +log -push +for {set i 0} {$i < 8} {incr i} { + log -header "[op_name $i]" + log -push + design -reset + read_verilog wide_op.v + hierarchy -top wide_op + chparam -set width 1024 -set op $i wide_op + yosys proc + simplemap + equiv_opt -post -assert splitlarge -max_width 128 + yosys select -assert-none r:A_WIDTH>128 + yosys select -assert-none r:B_WIDTH>128 + yosys select -assert-count [predict_adder_count 1024 128 $i] r:A_WIDTH + log -pop +} +log -pop diff --git a/tests/silimate/wide_op.v b/tests/silimate/wide_op.v new file mode 100644 index 000000000..52b54e83a --- /dev/null +++ b/tests/silimate/wide_op.v @@ -0,0 +1,39 @@ + + module wide_op( + a, b, y, c + ); + parameter width = 1024; + + // ADD/SUB: 0/4 + (0 unsigned+unsigned, 1 unsigned+signed, 2 signed+unsigned, 3 signed+signed) + // reserved for MUL: 8 + parameter op = 0; + + localparam ywidth = (op == 8) ? width * 2 : width; + input[width-1:0] a; + input[width-1:0] b; + output [width-1:0] y; + output c; + + generate + if (op == 0) + assign {c, y} = a + b; + else if (op == 1) + assign {c, y} = a + $signed(b); + else if (op == 2) + assign {c, y} = $signed(a) + b; + else if (op == 3) + assign {c, y} = $signed(a) + $signed(b); + else if (op == 4) + assign {c, y} = a - b; + else if (op == 5) + assign {c, y} = a - $signed(b); + else if (op == 6) + assign {c, y} = $signed(a) - b; + else if (op == 7) + assign {c, y} = $signed(a) - $signed(b); + else if (op == 8) begin + assign c = 0; + assign y = a * b; + end + endgenerate + endmodule