The-OpenROAD-Project / The-OpenROAD-Project/OpenSTA
Verilog round trip: 18 reader/writer defects found by equivalence checking, grouped by failure mode
@dsengupta0628 is already working on this.
Since Aug 10, 2026.
- Dominant language
- Verilog
- Stars
- 619
- Forks
- 270
- Avg merge
- 5d 15h
- Merged PRs (30d)
- 4
Description
Running read_verilog → link_design → write_verilog over a systematically
generated netlist corpus and checking each result against its input with a
formal equivalence checker turned up 18 distinct defects in OpenSTA's Verilog
front end. This issue is an overview and a map; each one has a self-contained
reproducer and can be split out into its own issue if you prefer.
Tested against: OpenSTA 3.1.0 (dc5ccd2d69), built standalone. Liberty only
(Nangate45); no LEF, no external tooling required to reproduce.
Why report them together: they are not 18 unrelated bugs. They cluster into
five mechanisms, and several share a fix. Two of them (11 and 26) have the same
root cause in one function; two more (08 and 27) are separate faults in the same
_NC filler machinery; three (01, 09, 10) are all the reader binding a
connection to the wrong port object.
What is not here: findings that turned out to be OpenROAD-side rather than
OpenSTA's are excluded, as is one defect that lives in a revision OpenSTA does
not currently ship. Every item below was re-reproduced from scratch against the
standalone binary above.
A. Crashes and hangs
A legal netlist should never take the tool down without a diagnostic.
| # | Defect |
|---|---|
| 02 | link_design segfaults on a Verilog-2001 explicit named header port (module sub (.px(b), .py(a));) reached by an ordered connection list. read_verilog succeeds; the crash is in VerilogReader::makeInstPin ← makeModuleInstBody ← ConcreteNetwork::linkNetwork (backtrace attached in the issue). |
| 09 | A concatenation port_expression in a module header (module sub ({a,b}, z);) creates no port at all. In the top module this segfaults; in a submodule it silently shifts every positional connection onto the wrong port, leaving a top-level output driven by nothing. |
| 22 | link_design hangs forever — no diagnostic, no timeout — on two mutually aliasing continuous assignments. Confirmed a spin loop rather than a block or an allocation blowup: child CPU accumulates for the whole run. |
B. The reader binds a connection to the wrong port
The common shape: a connection is accepted, then attached to something other
than the port the source named. Nothing is reported, and the netlist is wrong.
| # | Defect |
|---|---|
| 01 | Ordered (positional) connections on a liberty cell bind to the cell's pg_pin ports: INV_X1 u (a, y); puts a on VDD and y on VSS, and emits INV_X1 u (); — every signal connection dropped, zero diagnostics. |
| 10 | A multi-bit net on a scalar cell pin binds to the first declared index rather than being truncated to the LSB: [1:0] a gives a[1], [0:1] a gives a[0]. The LRM prescribes right-aligned truncation. |
| 07 | A width-mismatched port connection is discarded whole instead of truncated or zero-extended, so even the bits that did line up are lost. A Warning mentions the size difference but never says the connection was dropped. |
| 05 | An explicit named header port corrupts the emitted interface: internal names leak out as ports, bundle names appear as extra inouts, the body is left reading undeclared identifiers — and in one case a port is declared twice, which OpenSTA's own reader then warns about. |
C. Values and constants are silently altered
| # | Defect |
|---|---|
| 31 | 4'sd5 reads as 0000; x and z digits read as 0. A sized literal with the sign modifier silently loses its value — the same file with 4'b0101 gives a different netlist. |
| 24 | Every integer token is truncated to 32 bits, silently re-indexing bit selects and bus bounds: w[4294967296] becomes w[0], and wire [2147483648:0] becomes wire [0:-2147483648]. |
| 03 | Constant literals are modelled as nets named zero_/one_ in the user namespace: write_verilog emits references to them without declaring or driving them, and a user wire legitimately named zero_ is captured, rewiring a tie to live logic. |
| 19 | supply0/supply1 declarations are dropped while their connections are kept, so the emitted netlist references undeclared, undriven nets and the logic constants are gone. |
D. The writer emits netlists OpenSTA cannot read back
Each of these produces output that is not legal Verilog — the round trip is not
closed, and re-reading fails or, worse, silently succeeds with different logic.
| # | Defect |
|---|---|
| 11 | Escapes are dropped from identifiers spelling Verilog keywords: \assign → INV_X1 assign (...), \wire → wire wire;, \module → module module (. Re-reading the output fails with a syntax error. |
| 26 | Same fault, different lexical class: escapes dropped from identifiers starting with a digit — wire 1w;, module 1top (, INV_X1 1g (. (Same root cause as 11; one fix should cover both.) |
| 08 | _NC filler wires invented for an open vector formal are never checked against the module namespace: duplicate wire declarations, and in one case the filler aliases a live net, giving it a second driver. OpenSTA then re-reads its own illegal output without complaint, laundering it into a legal-but-wrong netlist. |
| 27 | _NC fillers are numbered from a counter never reset per module, so every module after the first references fillers it does not declare — and can silently give a user's net a second driver. |
| 30 | Every internal bus declaration is rebuilt as [max_bit:0] descending: phantom bits are invented (wire [5:5] comes back as wire [5:0]) and the declared bit order is discarded. |
E. Legal input rejected
| # | Defect |
|---|---|
| 32 | Four legal Verilog-2005 constructs are rejected: the replication operator {2{x}}, an omitted ordered connection sub u (a, , y);, an instance array h2 u [1:0] (...), and an unsized literal 'b0. Three give a coded, located diagnostic; the unsized literal escapes as a bare C++ exception message — Error: stol — with no error number, no file and no line. Verible, Icarus and Verilator accept all four. |
Reproducing
Every issue ships a self-contained directory: its own liberty, the testcase
netlists with negative controls, the .tcl scripts, and a run.sh that needs
no arguments and no environment variables.
cd 01_positional_cell_binding_offset_two && ./run.sh
Each run ends with a verdict block computed from that run — exit status, greps
over the emitted netlist, OpenSTA's own diagnostics — so a script reports the
truth once its defect is fixed rather than repeating a stored claim:
EXPECTED (conforming tool): ...
OBSERVED (this run): ...
Variants and negative controls have their own wrappers (./run_case_nand2.sh,
./run_ctl_named.sh); ./run_all.sh -a runs the whole set and prints one line
per script. Point STA= at your own build. Every invocation is capped with
ulimit -v and timeout, because a few of these inputs declare vectors near
2^31 bits.
Suggested triage order
The three in A are the ones a user cannot work around: a crash or a hang
ends the session. The items in B and C are the most dangerous, because
the tool exits 0 and hands back a netlist that is quietly wrong — 01 and 03 in
particular corrupt ordinary, idiomatic input. D is mostly one namespace
function (11, 26) plus one filler routine (08, 27). E is the cheapest: three
grammar gaps and one missing try/catch.
Files:
issues.tar.gz
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.