From 7db8f29c042295e68e714e616c7398e39d389b41 Mon Sep 17 00:00:00 2001 From: Akash Levy Date: Fri, 1 May 2026 19:57:00 -0700 Subject: [PATCH 1/2] opt_boundary --- passes/silimate/Makefile.inc | 1 + passes/silimate/opt_boundary.cc | 291 +++++++++ tests/silimate/opt_boundary.ys | 851 +++++++++++++++++++++++++ tests/silimate/opt_boundary_random.tcl | 61 ++ 4 files changed, 1204 insertions(+) create mode 100644 passes/silimate/opt_boundary.cc create mode 100644 tests/silimate/opt_boundary.ys create mode 100644 tests/silimate/opt_boundary_random.tcl diff --git a/passes/silimate/Makefile.inc b/passes/silimate/Makefile.inc index ceeb33469..2cdc24045 100644 --- a/passes/silimate/Makefile.inc +++ b/passes/silimate/Makefile.inc @@ -18,6 +18,7 @@ OBJS += passes/silimate/splitnetlist.o OBJS += passes/silimate/opt_timing_balance.o OBJS += passes/silimate/cone_partition.o OBJS += passes/silimate/clkmerge.o +OBJS += passes/silimate/opt_boundary.o OBJS += passes/silimate/opt_vps.o OBJS += passes/silimate/opt_expand.o diff --git a/passes/silimate/opt_boundary.cc b/passes/silimate/opt_boundary.cc new file mode 100644 index 000000000..a36c75804 --- /dev/null +++ b/passes/silimate/opt_boundary.cc @@ -0,0 +1,291 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2026 Akash Levy + * + * 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/celltypes.h" +#include "kernel/register.h" +#include "kernel/rtlil.h" +#include "kernel/sigtools.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct BoundaryConeWorker { + Module *child, *parent; + Cell *instance; + SigMap child_sigmap; + int max_cells; + + dict bit_driver; + dict input_map; + dict copied_bits; + dict copied_cells; + std::vector created_wires; + std::vector created_cells; + pool active_cells; + int copied_cell_count = 0; + bool failed = false; + + BoundaryConeWorker(Module *child, Module *parent, Cell *instance, int max_cells) + : child(child), parent(parent), instance(instance), child_sigmap(child), max_cells(max_cells) + { + for (auto wire : child->wires()) { + if (!wire->port_input || wire->port_output) + continue; + if (!instance->connections_.count(wire->name)) + continue; + SigSpec conn = instance->connections_.at(wire->name); + for (int i = 0; i < wire->width; i++) + input_map[child_sigmap(SigBit(wire, i))] = conn[i]; + } + + for (auto cell : child->cells()) { + if (!yosys_celltypes.cell_evaluable(cell->type) || cell->has_keep_attr()) + continue; + for (auto &conn : cell->connections()) { + if (!yosys_celltypes.cell_output(cell->type, conn.first)) + continue; + for (auto bit : conn.second) + if (bit.is_wire()) + bit_driver[child_sigmap(bit)] = cell; + } + } + } + + SigSpec materialize(SigSpec sig) + { + SigSpec result; + for (auto bit : sig) + result.append(materialize(bit)); + return result; + } + + SigBit materialize(SigBit bit) + { + bit = child_sigmap(bit); + + if (!bit.is_wire()) + return bit; + + if (input_map.count(bit)) + return input_map.at(bit); + + if (copied_bits.count(bit)) + return copied_bits.at(bit); + + if (!bit_driver.count(bit)) { + failed = true; + return RTLIL::Sx; + } + + Cell *driver = bit_driver.at(bit); + if (active_cells.count(driver) || copied_cell_count >= max_cells) { + failed = true; + return RTLIL::Sx; + } + + copy_driver(driver); + if (failed || !copied_bits.count(bit)) { + failed = true; + return RTLIL::Sx; + } + return copied_bits.at(bit); + } + + void copy_driver(Cell *driver) + { + if (copied_cells.count(driver)) + return; + + active_cells.insert(driver); + dict new_connections; + + for (auto &conn : driver->connections()) { + if (yosys_celltypes.cell_input(driver->type, conn.first)) { + SigSpec mapped = materialize(conn.second); + if (failed) + break; + new_connections[conn.first] = mapped; + continue; + } + + if (yosys_celltypes.cell_output(driver->type, conn.first)) { + Wire *wire = parent->addWire(NEW_ID_SUFFIX("opt_boundary"), GetSize(conn.second)); + created_wires.push_back(wire); + SigSpec mapped = wire; + new_connections[conn.first] = mapped; + for (int i = 0; i < GetSize(conn.second); i++) { + if (conn.second[i].is_wire()) + copied_bits[child_sigmap(conn.second[i])] = mapped[i]; + } + continue; + } + + failed = true; + break; + } + + if (!failed && copied_cell_count >= max_cells) + failed = true; + + if (!failed) { + Cell *copy = parent->addCell(NEW_ID_SUFFIX("opt_boundary"), driver); + for (auto &conn : new_connections) + copy->setPort(conn.first, conn.second); + copied_cells[driver] = copy; + created_cells.push_back(copy); + copied_cell_count++; + } + + active_cells.erase(driver); + } + + void rollback() + { + for (auto it = created_cells.rbegin(); it != created_cells.rend(); ++it) + parent->remove(*it); + for (auto it = created_wires.rbegin(); it != created_wires.rend(); ++it) + parent->remove(pool{*it}); + created_cells.clear(); + created_wires.clear(); + copied_cells.clear(); + copied_bits.clear(); + copied_cell_count = 0; + } +}; + +static bool protected_module(Module *module) +{ + return module->get_blackbox_attribute() || + module->get_bool_attribute(ID::keep) || + module->get_bool_attribute(ID::keep_hierarchy); +} + +struct OptBoundaryPass : Pass { + OptBoundaryPass() : Pass("opt_boundary", "perform conservative parent-side cross-boundary cone optimization") {} + + void help() override + { + log("\n"); + log(" opt_boundary [options] [selection]\n"); + log("\n"); + log("This pass performs a conservative form of hierarchical boundary optimization.\n"); + log("For each selected parent module, it looks through instances of non-blackbox,\n"); + log("non-keep child modules and copies small evaluable combinational cones that\n"); + log("drive child output ports into the parent. The original child module body is\n"); + log("left unchanged; optimized instance outputs are disconnected only after an\n"); + log("equivalent parent-side cone has been created.\n"); + log("\n"); + log(" -max_cells \n"); + log(" maximum number of child cells to copy for one output bit. Default: 8.\n"); + log("\n"); + log(" -no_disconnect\n"); + log(" copy eligible cones into the parent but leave instance output ports\n"); + log(" connected to their original nets.\n"); + log("\n"); + } + + void execute(std::vector args, RTLIL::Design *design) override + { + log_header(design, "Executing OPT_BOUNDARY pass.\n"); + + int max_cells = 8; + bool no_disconnect = false; + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-max_cells" && argidx + 1 < args.size()) { + max_cells = atoi(args[++argidx].c_str()); + continue; + } + if (args[argidx] == "-no_disconnect") { + no_disconnect = true; + continue; + } + break; + } + extra_args(args, argidx, design); + + if (max_cells < 1) + log_cmd_error("The -max_cells value must be positive.\n"); + + bool did_something = false; + for (auto parent : design->selected_modules(RTLIL::SELECT_WHOLE_ONLY, RTLIL::SB_UNBOXED_CMDERR)) { + if (protected_module(parent)) + continue; + + for (auto instance : parent->cells().to_vector()) { + if (instance->has_keep_attr()) + continue; + + Module *child = design->module(instance->type); + if (child == nullptr || protected_module(child)) + continue; + + for (auto &conn : instance->connections_) { + Wire *port = child->wire(conn.first); + if (port == nullptr || !port->port_output || port->port_input) + continue; + if (port->width != GetSize(conn.second)) + log_error("Port %s connected on instance %s not found in module %s or width is not matching\n", + log_id(conn.first), log_id(instance), log_id(child)); + + SigSpec new_conn = conn.second; + bool changed_port = false; + for (int i = 0; i < port->width; i++) { + if (!conn.second[i].is_wire()) + continue; + + BoundaryConeWorker worker(child, parent, instance, max_cells); + SigBit replacement = worker.materialize(SigBit(port, i)); + if (worker.failed) { + worker.rollback(); + continue; + } + if (replacement == conn.second[i]) { + worker.rollback(); + continue; + } + + if (!no_disconnect) { + parent->connect(conn.second[i], replacement); + Wire *dummy = parent->addWire(NEW_ID_SUFFIX("opt_boundary_output")); + new_conn[i] = SigBit(dummy, 0); + changed_port = true; + } + did_something = true; + + if (worker.copied_cell_count > 0) + log("Copied %d cells from cone driving %s[%d] of instance '%s' (type '%s') into '%s'\n", + worker.copied_cell_count, log_id(port), i, log_id(instance), log_id(instance->type), log_id(parent)); + else + log("Bypassed cone driving %s[%d] of instance '%s' (type '%s') in '%s'\n", + log_id(port), i, log_id(instance), log_id(instance->type), log_id(parent)); + } + + if (changed_port) + conn.second = new_conn; + } + } + } + + if (did_something) + design->scratchpad_set_bool("opt.did_something", true); + } +} OptBoundaryPass; + +PRIVATE_NAMESPACE_END diff --git a/tests/silimate/opt_boundary.ys b/tests/silimate/opt_boundary.ys new file mode 100644 index 000000000..fc80b4aca --- /dev/null +++ b/tests/silimate/opt_boundary.ys @@ -0,0 +1,851 @@ +################################################################### +# Boundary optimization test cases +################################################################### + +log -header "Copy simple child cone into parent" +log -push +design -reset +read_verilog < Date: Fri, 1 May 2026 22:50:43 -0700 Subject: [PATCH 2/2] Improvements --- passes/silimate/opt_boundary.cc | 128 +++++++++++++++++++++++-- tests/silimate/opt_boundary.ys | 82 ++++++++++++++++ tests/silimate/opt_boundary_random.tcl | 11 ++- 3 files changed, 210 insertions(+), 11 deletions(-) diff --git a/passes/silimate/opt_boundary.cc b/passes/silimate/opt_boundary.cc index a36c75804..715644b17 100644 --- a/passes/silimate/opt_boundary.cc +++ b/passes/silimate/opt_boundary.cc @@ -27,9 +27,9 @@ PRIVATE_NAMESPACE_BEGIN struct BoundaryConeWorker { Module *child, *parent; - Cell *instance; SigMap child_sigmap; int max_cells; + int max_bits; dict bit_driver; dict input_map; @@ -39,10 +39,11 @@ struct BoundaryConeWorker { std::vector created_cells; pool active_cells; int copied_cell_count = 0; + int materialized_bit_count = 0; bool failed = false; - BoundaryConeWorker(Module *child, Module *parent, Cell *instance, int max_cells) - : child(child), parent(parent), instance(instance), child_sigmap(child), max_cells(max_cells) + BoundaryConeWorker(Module *child, Module *parent, Cell *instance, int max_cells, int max_bits) + : child(child), parent(parent), child_sigmap(child), max_cells(max_cells), max_bits(max_bits) { for (auto wire : child->wires()) { if (!wire->port_input || wire->port_output) @@ -50,6 +51,10 @@ struct BoundaryConeWorker { if (!instance->connections_.count(wire->name)) continue; SigSpec conn = instance->connections_.at(wire->name); + if (GetSize(conn) != wire->width) { + failed = true; + continue; + } for (int i = 0; i < wire->width; i++) input_map[child_sigmap(SigBit(wire, i))] = conn[i]; } @@ -70,8 +75,15 @@ struct BoundaryConeWorker { SigSpec materialize(SigSpec sig) { SigSpec result; - for (auto bit : sig) + for (auto bit : sig) { + if (++materialized_bit_count > max_bits) { + failed = true; + break; + } result.append(materialize(bit)); + if (failed) + break; + } return result; } @@ -166,6 +178,7 @@ struct BoundaryConeWorker { copied_cells.clear(); copied_bits.clear(); copied_cell_count = 0; + materialized_bit_count = 0; } }; @@ -176,6 +189,54 @@ static bool protected_module(Module *module) module->get_bool_attribute(ID::keep_hierarchy); } +struct ParentUsage { + Design *design; + SigMap sigmap; + SigPool used; + + ParentUsage(Module *module, Design *design) : design(design), sigmap(module) + { + auto count_usage = [&](const SigSpec &signal) { + for (auto bit : signal) + used.add(sigmap(bit)); + }; + + for (auto wire : module->wires()) { + if (wire->port_output) + count_usage(wire); + } + + for (auto [_, process] : module->processes) + process->rewrite_sigspecs(count_usage); + + for (auto cell : module->cells()) { + Module *cell_module = design->module(cell->type); + for (auto &conn : cell->connections()) { + if (yosys_celltypes.cell_known(cell->type)) { + if (yosys_celltypes.cell_input(cell->type, conn.first)) + count_usage(conn.second); + continue; + } + + if (cell_module != nullptr) { + Wire *port = cell_module->wire(conn.first); + if (port != nullptr && port->port_input) + count_usage(conn.second); + continue; + } + + // Unknown cells may observe any connection. + count_usage(conn.second); + } + } + } + + bool check(SigBit bit) + { + return bit.is_wire() && used.check(sigmap(bit)); + } +}; + struct OptBoundaryPass : Pass { OptBoundaryPass() : Pass("opt_boundary", "perform conservative parent-side cross-boundary cone optimization") {} @@ -194,6 +255,9 @@ struct OptBoundaryPass : Pass { log(" -max_cells \n"); log(" maximum number of child cells to copy for one output bit. Default: 8.\n"); log("\n"); + log(" -max_bits \n"); + log(" maximum number of child cone bits to inspect for one output bit. Default: 4096.\n"); + log("\n"); log(" -no_disconnect\n"); log(" copy eligible cones into the parent but leave instance output ports\n"); log(" connected to their original nets.\n"); @@ -205,6 +269,7 @@ struct OptBoundaryPass : Pass { log_header(design, "Executing OPT_BOUNDARY pass.\n"); int max_cells = 8; + int max_bits = 4096; bool no_disconnect = false; size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { @@ -212,6 +277,10 @@ struct OptBoundaryPass : Pass { max_cells = atoi(args[++argidx].c_str()); continue; } + if (args[argidx] == "-max_bits" && argidx + 1 < args.size()) { + max_bits = atoi(args[++argidx].c_str()); + continue; + } if (args[argidx] == "-no_disconnect") { no_disconnect = true; continue; @@ -222,19 +291,35 @@ struct OptBoundaryPass : Pass { if (max_cells < 1) log_cmd_error("The -max_cells value must be positive.\n"); + if (max_bits < 1) + log_cmd_error("The -max_bits value must be positive.\n"); bool did_something = false; for (auto parent : design->selected_modules(RTLIL::SELECT_WHOLE_ONLY, RTLIL::SB_UNBOXED_CMDERR)) { - if (protected_module(parent)) + if (protected_module(parent)) { + log_debug("opt_boundary: skipping protected parent module %s\n", log_id(parent)); continue; + } + + ParentUsage parent_usage(parent, design); for (auto instance : parent->cells().to_vector()) { - if (instance->has_keep_attr()) + if (instance->has_keep_attr()) { + log_debug("opt_boundary: skipping kept instance %s in %s\n", log_id(instance), log_id(parent)); continue; + } Module *child = design->module(instance->type); - if (child == nullptr || protected_module(child)) + if (child == nullptr) { + log_debug("opt_boundary: skipping non-module cell %s (type %s) in %s\n", + log_id(instance), log_id(instance->type), log_id(parent)); continue; + } + if (protected_module(child)) { + log_debug("opt_boundary: skipping protected child module %s for instance %s in %s\n", + log_id(child), log_id(instance), log_id(parent)); + continue; + } for (auto &conn : instance->connections_) { Wire *port = child->wire(conn.first); @@ -246,20 +331,45 @@ struct OptBoundaryPass : Pass { SigSpec new_conn = conn.second; bool changed_port = false; + log_debug("opt_boundary: checking output port %s (%d bits) on instance %s in %s\n", + log_id(conn.first), port->width, log_id(instance), log_id(parent)); for (int i = 0; i < port->width; i++) { - if (!conn.second[i].is_wire()) + if (!conn.second[i].is_wire()) { + log_debug("opt_boundary: skipping %s[%d] on %s because parent connection is constant\n", + log_id(port), i, log_id(instance)); continue; + } + if (!parent_usage.check(conn.second[i])) { + log_debug("opt_boundary: skipping %s[%d] on %s because parent net %s is unobserved\n", + log_id(port), i, log_id(instance), log_signal(conn.second[i])); + continue; + } - BoundaryConeWorker worker(child, parent, instance, max_cells); + BoundaryConeWorker worker(child, parent, instance, max_cells, max_bits); SigBit replacement = worker.materialize(SigBit(port, i)); if (worker.failed) { + log_debug("opt_boundary: failed to materialize %s[%d] of instance %s after inspecting %d bits; rolling back\n", + log_id(port), i, log_id(instance), worker.materialized_bit_count); worker.rollback(); continue; } if (replacement == conn.second[i]) { + log_debug("opt_boundary: skipping %s[%d] on %s because replacement is identical\n", + log_id(port), i, log_id(instance)); worker.rollback(); continue; } + if (parent_usage.sigmap(replacement) == parent_usage.sigmap(conn.second[i])) { + log_debug("opt_boundary: skipping %s[%d] on %s because replacement is already equivalent in parent\n", + log_id(port), i, log_id(instance)); + worker.rollback(); + continue; + } + if (no_disconnect && worker.copied_cell_count == 0) { + log_debug("opt_boundary: skipping zero-cell bypass for %s[%d] on %s in -no_disconnect mode\n", + log_id(port), i, log_id(instance)); + continue; + } if (!no_disconnect) { parent->connect(conn.second[i], replacement); diff --git a/tests/silimate/opt_boundary.ys b/tests/silimate/opt_boundary.ys index fc80b4aca..78d42688c 100644 --- a/tests/silimate/opt_boundary.ys +++ b/tests/silimate/opt_boundary.ys @@ -135,6 +135,32 @@ select -assert-count 1 top/t:m design -reset log -pop +log -header "Honor max_bits limit on wide cone" +log -push +design -reset +read_verilog <