From 13ab5d4a674bde777d6188704cd5a096fd7060db Mon Sep 17 00:00:00 2001 From: Alain Dargelas Date: Tue, 12 May 2026 18:49:55 -0700 Subject: [PATCH] verilog backend: preserve `signed` on wire and port declarations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `dump_wire` had no code path that emits the `signed` keyword for wires/ports whose RTLIL `is_signed` flag is set. Reading module top(input signed [9:0] in, output signed [31:0] o); assign o = in; endmodule and writing it back via `write_verilog` produced input [9:0] in; output [31:0] o; losing the declared signedness even though `wire->is_signed` was tracked correctly in RTLIL throughout the round trip. The IEEE 1364-2001 grammar (Annex A.2.1.2 / A.2.1.3) allows `signed` after the direction / net-type keyword, which is the dialect `write_verilog` targets by default — so the fix is to emit ` signed` between the direction/net-type and the range when `wire->is_signed`. Closes the half of chipsalliance/synlig#2425 that lives in Yosys: the SystemVerilog frontend correctly produces a signed wire for `output int`, but the Verilog backend dropped it on write. Adds `tests/various/write_verilog_signed_port.ys`, which round-trips a module with `signed` inputs, outputs, and an internal wire and greps for the keyword on each declaration — fails without the fix, passes with it. --- backends/verilog/verilog_backend.cc | 15 ++++++---- tests/various/write_verilog_signed_port.ys | 33 ++++++++++++++++++++++ 2 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 tests/various/write_verilog_signed_port.ys diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc index 73ffcbf3e..057c8b46b 100644 --- a/backends/verilog/verilog_backend.cc +++ b/backends/verilog/verilog_backend.cc @@ -456,21 +456,26 @@ void dump_wire(std::ostream &f, std::string indent, RTLIL::Wire *wire) if (wire->attributes.count(ID::single_bit_vector)) range = stringf(" [%d:%d]", wire->start_offset, wire->start_offset); } + // Emit `signed` for wires/ports whose RTLIL is_signed flag is set. + // Without this, an `output signed [N:0] o` was silently demoted to plain + // `output [N:0] o` on write, losing the declared signedness in a + // read_verilog -> write_verilog round-trip. + const char *signed_kw = wire->is_signed ? " signed" : ""; if (wire->port_input && !wire->port_output) - f << stringf("%s" "input%s %s;\n", indent, range, id(wire->name)); + f << stringf("%s" "input%s%s %s;\n", indent, signed_kw, range, id(wire->name)); if (!wire->port_input && wire->port_output) - f << stringf("%s" "output%s %s;\n", indent, range, id(wire->name)); + f << stringf("%s" "output%s%s %s;\n", indent, signed_kw, range, id(wire->name)); if (wire->port_input && wire->port_output) - f << stringf("%s" "inout%s %s;\n", indent, range, id(wire->name)); + f << stringf("%s" "inout%s%s %s;\n", indent, signed_kw, range, id(wire->name)); if (reg_wires.count(wire->name)) { - f << stringf("%s" "reg%s %s", indent, range, id(wire->name)); + f << stringf("%s" "reg%s%s %s", indent, signed_kw, range, id(wire->name)); if (wire->attributes.count(ID::init)) { f << stringf(" = "); dump_const(f, wire->attributes.at(ID::init)); } f << stringf(";\n"); } else - f << stringf("%s" "wire%s %s;\n", indent, range, id(wire->name)); + f << stringf("%s" "wire%s%s %s;\n", indent, signed_kw, range, id(wire->name)); #endif } diff --git a/tests/various/write_verilog_signed_port.ys b/tests/various/write_verilog_signed_port.ys new file mode 100644 index 000000000..1d534cbd9 --- /dev/null +++ b/tests/various/write_verilog_signed_port.ys @@ -0,0 +1,33 @@ +# Roundtrip a module with `signed` ports / wires through read_verilog +# and write_verilog and verify the `signed` keyword survives. Before +# the fix, write_verilog silently dropped `signed` on every wire / port +# declaration, even though wire->is_signed was tracked correctly +# through the RTLIL. The IEEE 1364-2001 grammar (Annex A.2.1.2 / +# A.2.1.3) allows `signed` after the direction / net-type, which is +# the dialect write_verilog targets by default. + +! mkdir -p temp +read_verilog <