The-OpenROAD-Project / The-OpenROAD-Project/OpenSTA

Verilog round trip: 18 reader/writer defects found by equivalence checking, grouped by failure mode

Open
#399 1 comment 0 reactions 1 assignee View on GitHub

@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_veriloglink_designwrite_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::makeInstPinmakeModuleInstBodyConcreteNetwork::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 digitwire 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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.