`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.
This makes the Verilog backend handle the $connect and $input_port
cells. This represents the undirected $connect cell using the `tran`
primitive, so we also extend the frontend to support this.
Checking only happens at compile time if -std=c++20 (or greater) is enabled. Otherwise
the checking happens at run time.
This requires the format string to be a compile-time constant (when compiling with
C++20), so fix a few places where that isn't true.
The format string behavior is a bit more lenient than C printf. For %d/%u
you can pass any integer type and it will be converted and output without
truncating bits, i.e. any length specifier is ignored and the conversion is
always treated as 'll'. Any truncation needs to be done by casting the argument itself.
For %f/%g you can pass anything that converts to double, including integers.
Performance results with clang 19 -O3 on Linux:
```
hyperfine './yosys -dp "read_rtlil /usr/local/google/home/rocallahan/Downloads/jpeg.synth.il; dump"'
```
C++17 before: Time (mean ± σ): 101.3 ms ± 0.8 ms [User: 85.6 ms, System: 15.6 ms]
C++17 after: Time (mean ± σ): 98.4 ms ± 1.2 ms [User: 82.1 ms, System: 16.1 ms]
C++20 before: Time (mean ± σ): 100.9 ms ± 1.1 ms [User: 87.0 ms, System: 13.8 ms]
C++20 after: Time (mean ± σ): 97.8 ms ± 1.4 ms [User: 83.1 ms, System: 14.7 ms]
The generated code is reasonably efficient. E.g. with clang 19, `stringf()` with a format
with no %% escapes and no other parameters (a weirdly common case) often compiles to a fully
inlined `std::string` construction. In general the format string parsing is often (not always)
compiled away.
If you have a large design with a lot of modules and you use the Verilog
backend to emit modules one at a time to separate files, performance is
very low. The problem is that the Verilog backend calls `design->sort()`
every time, which sorts the contents of all modules, and this is slow
even when everything is already sorted.
We can easily fix this by only sorting the contents of modules that
we're actually going to emit.
Processes without sync rules correspond to simple decision trees that
directly correspond to `always @*` or `always_comb` blocks in Verilog,
and do not need a warning.
This removes the need to suppress warnings during the RTLIL-to-Verilog
conversion performed by Amaranth.
These were previously not being converted correctly leading to yosys
internal cells being written to my netlist.
Signed-off-by: Ethan Mahintorabi <ethanmoon@google.com>
This change only matters for processes that weren't processed by
`proc_rmdead` for which follow-up cases after a default case are treated
differently in Verilog and RTLIL semantics.
The Verilog grammar does not allow an empty case. Most synthesis tools
are quite permissive about this, but Quartus is not. This causes
problems for amaranth with Quartus (see amaranth-lang/amaranth#931).