From 6503ad91c92af3347ad2a04d8fde3a28c33311de Mon Sep 17 00:00:00 2001 From: Natalia Date: Wed, 14 Jan 2026 01:43:55 -0800 Subject: [PATCH] splitnets: add -ports_only and -top_only options Add two new options to the splitnets pass: - `-ports_only`: Split only module ports, not internal signals. This is useful when you want to split ports for interface compatibility while keeping internal signals as multi-bit wires for better readability. - `-top_only`: Apply splitting only at the top module level, not in submodules. This is helpful for hierarchical designs where you need split signals only at the top-level interface. These options can be combined with existing flags: - `splitnets -ports_only`: Split all ports in all modules - `splitnets -ports_only -top_only`: Split ports only in top module - `splitnets -ports -top_only`: Split both ports and nets only in top Add comprehensive tests that verify wire/port counts for all flag combinations using a hierarchical design. --- passes/cmds/splitnets.cc | 42 +++++++++++++++------ tests/various/test_splitnets.tcl | 63 ++++++++++++++++++++++++++++++++ tests/various/test_splitnets.v | 13 +++++++ tests/various/test_splitnets.ys | 1 + 4 files changed, 107 insertions(+), 12 deletions(-) create mode 100644 tests/various/test_splitnets.tcl create mode 100644 tests/various/test_splitnets.v create mode 100644 tests/various/test_splitnets.ys diff --git a/passes/cmds/splitnets.cc b/passes/cmds/splitnets.cc index d61a99ec0..8dabb747c 100644 --- a/passes/cmds/splitnets.cc +++ b/passes/cmds/splitnets.cc @@ -113,6 +113,12 @@ struct SplitnetsPass : public Pass { log(" -ports\n"); log(" also split module ports. per default only internal signals are split.\n"); log("\n"); + log(" -ports_only\n"); + log(" split module ports, but not the internal signals.\n"); + log("\n"); + log(" -top_only\n"); + log(" split module ports/nets, only at the top level of hierarchy.\n"); + log("\n"); log(" -driver\n"); log(" don't blindly split nets in individual bits. instead look at the driver\n"); log(" and split nets so that no driver drives only part of a net.\n"); @@ -122,6 +128,8 @@ struct SplitnetsPass : public Pass { { bool flag_ports = false; bool flag_driver = false; + bool flag_ports_only = false; + bool flag_top_only = false; std::string format = "[]:"; log_header(design, "Executing SPLITNETS pass (splitting up multi-bit signals).\n"); @@ -137,6 +145,14 @@ struct SplitnetsPass : public Pass { flag_ports = true; continue; } + if (args[argidx] == "-ports_only") { + flag_ports_only = true; + continue; + } + if (args[argidx] == "-top_only") { + flag_top_only = true; + continue; + } if (args[argidx] == "-driver") { flag_driver = true; continue; @@ -152,11 +168,12 @@ struct SplitnetsPass : public Pass { { if (module->has_processes_warn()) continue; - + if (flag_top_only && (design->top_module() != module)) { + continue; + } SplitnetsWorker worker; - if (flag_ports) - { + if (flag_ports || flag_ports_only) { int normalized_port_factor = 0; for (auto wire : module->wires()) @@ -187,7 +204,8 @@ struct SplitnetsPass : public Pass { for (auto &chunk : sig.chunks()) { if (chunk.wire == NULL) continue; - if (chunk.wire->port_id == 0 || flag_ports) { + if ((flag_ports_only && (chunk.wire->port_id != 0)) || + (!flag_ports_only && (chunk.wire->port_id == 0 || flag_ports))) { if (chunk.offset != 0) split_wires_at[chunk.wire].insert(chunk.offset); if (chunk.offset + chunk.width < chunk.wire->width) @@ -204,11 +222,12 @@ struct SplitnetsPass : public Pass { } worker.append_wire(module, it.first, cursor, it.first->width - cursor, format); } - } - else - { + } else { for (auto wire : module->wires()) { - if (((wire->width > 1) || (wire->has_attribute(ID::single_bit_vector))) + if (flag_ports_only) { + if (wire->width > 1 && (wire->port_id != 0) && design->selected(module, wire)) + worker.splitmap[wire] = std::vector(); + } else if (((wire->width > 1) || (wire->has_attribute(ID::single_bit_vector))) && (wire->port_id == 0 || flag_ports) && design->selected(module, wire)) { wire->attributes.erase(ID::single_bit_vector); @@ -223,9 +242,8 @@ struct SplitnetsPass : public Pass { module->rewrite_sigspecs(worker); - if (flag_ports) - { - for (auto wire : module->wires()) + if (flag_ports || flag_ports_only) { + for (auto wire : module->wires()) { if (wire->port_id == 0) continue; @@ -248,7 +266,7 @@ struct SplitnetsPass : public Pass { delete_wires.insert(it.first); module->remove(delete_wires); - if (flag_ports) + if (flag_ports || flag_ports_only) module->fixup_ports(); } diff --git a/tests/various/test_splitnets.tcl b/tests/various/test_splitnets.tcl new file mode 100644 index 000000000..9cb2b4305 --- /dev/null +++ b/tests/various/test_splitnets.tcl @@ -0,0 +1,63 @@ +yosys -import + +proc read_stats { file } { + set fid [open $file] + set result [read $fid] + close $fid + set ports 0 + set nets 0 + foreach line [split $result "\n"] { + if [regexp {Number of wires:[ \t]+([0-9]+)} $line tmp n] { + set nets [expr $nets + $n] + } + if [regexp {Number of ports:[ \t]+([0-9]+)} $line tmp n] { + set ports [expr $ports + $n] + } + } + return [list $nets $ports] +} + +proc assert_count { type actual expected } { + if {$actual != $expected} { + puts "Error, $type count: $actual vs $expected expected" + exit 1 + } +} + +read_verilog test_splitnets.v +hierarchy -auto-top +procs +design -save "pre" + +splitnets -ports_only -top_only +write_verilog -noexpr "ports_only_in_top.v" +tee -o "ports_only_in_top.txt" stat +foreach {nets ports} [read_stats "ports_only_in_top.txt"] {} +assert_count "nets" $nets 26 +assert_count "ports" $ports 16 + +design -load "pre" +splitnets -ports_only +write_verilog -noexpr "ports_only_in_all.v" +tee -o "ports_only_in_all.txt" stat +foreach {nets ports} [read_stats "ports_only_in_all.txt"] {} +assert_count "nets" $nets 30 +assert_count "ports" $ports 20 + +design -load "pre" +splitnets -ports -top_only +write_verilog -noexpr "ports_nets_in_top.v" +tee -o "ports_nets_in_top.txt" stat +foreach {nets ports} [read_stats "ports_nets_in_top.txt"] {} +assert_count "nets" $nets 30 +assert_count "ports" $ports 16 + +design -load "pre" +splitnets -ports +write_verilog -noexpr "ports_nets_in_all.v" +tee -o "ports_nets_in_all.txt" stat +foreach {nets ports} [read_stats "ports_nets_in_all.txt"] {} +assert_count "nets" $nets 40 +assert_count "ports" $ports 20 + +exit 0 diff --git a/tests/various/test_splitnets.v b/tests/various/test_splitnets.v new file mode 100644 index 000000000..692d20b0f --- /dev/null +++ b/tests/various/test_splitnets.v @@ -0,0 +1,13 @@ +module bottom(input clk, input wire [1:0] i, output reg [1:0] q); + reg [1:0] q1; + always @(posedge clk) begin + q1 <= i; + q <= q1; + end +endmodule + +module top(input clk, input wire [1:0] i, output wire [1:0] q); + wire [1:0] q1; + bottom u1 (.clk(clk), .i(i), .q(q1)); + assign q = ~q1; +endmodule diff --git a/tests/various/test_splitnets.ys b/tests/various/test_splitnets.ys new file mode 100644 index 000000000..2af787d4f --- /dev/null +++ b/tests/various/test_splitnets.ys @@ -0,0 +1 @@ +tcl test_splitnets.tcl