Skip to content

dbSta: escape brackets in scalar net/port names (not bus selects)#10760

Open
oharboe wants to merge 1 commit into
The-OpenROAD-Project:masterfrom
oharboe:oharboe/dbnetwork-escape-scalar-brackets
Open

dbSta: escape brackets in scalar net/port names (not bus selects)#10760
oharboe wants to merge 1 commit into
The-OpenROAD-Project:masterfrom
oharboe:oharboe/dbnetwork-escape-scalar-brackets

Conversation

@oharboe

@oharboe oharboe commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Problem

write_verilog emits bus-select syntax (foo[7]) for any net/port whose name parses as a clean, unescaped bus member. That is correct when foo is a declared bus (input [hi:lo] foo; / wire [hi:lo] foo;), but wrong for a single-bit escaped-scalar signal declared as input \foo[7] ; with no bus declaration.

The two name renderers disagree on such a name:

  • portVerilogName escapes the brackets → the port dcl is input \foo[7] ;
  • netVerilogName treats them as a bus subscript → the reference is foo[7]

Verilator then implicitly declares a 1-bit logic foo and rejects the bit-select:

%Error: Illegal bit or array select; type does not have a bit range

These per-bit escaped-scalar boundary signals arise from partition synthesis and from repair_design buffer insertion across module boundaries.

Fix

Following the upstream guidance on parallaxsw/OpenSTA#453"The problem is in the upstream name. foo[7] should be foo\[7\] if it is not doing a bus select." — this fixes the name dbNetwork hands to STA rather than patching the writer.

dbNetwork::name(const Port*) / name(const Net*) now escape the brackets of a clean bus-member name only when, in the owning module:

  1. a per-bit module boundary terminal (dbModBTerm) of that exact name exists (positive evidence it is a scalar port, not a pure bus select), and
  2. the bus prefix has no bus-aggregate dbModBTerm (isBusPort()).

Genuine bus members are left untouched: sub-module bus bits carry a sibling bus aggregate, and top-level bus bits have no per-bit dbModBTerm at all, so neither path escapes them. This is the bracket analogue of the recent escaped-slash fix in dbNetwork::name(Port*) / stripParentPrefix (hier_escape_port).

This supersedes the write-side workaround proposed in parallaxsw/OpenSTA#453.

Testing

  • New dbsta_unittest regression TestDbSta.EscapeScalarBracketName: covers scalar port escaping, scalar-net escaping (net sharing a name with a scalar boundary port), and the genuine bus-member negative cases (member port and member net stay unescaped). Confirmed to fail without the fix and pass with it.
  • Full //src/dbSta/test:all (74 tests) and //src/rsz/test:all (254 tests) pass — including the hierarchical write-verilog goldens (hierwrite, hier3, hierclock) that exercise real top-level and sub-module buses.

🤖 Generated with Claude Code

write_verilog emits bus-select syntax (foo[7]) for any net/port whose
name parses as a clean, unescaped bus member.  That is correct when foo
is a declared bus, but wrong for a single-bit "escaped-scalar" signal
declared as `input \foo[7] ;` with no bus declaration: the port dcl
escapes the brackets (portVerilogName) while the net reference renders
them as a bus select (netVerilogName), so Verilator sees a bit-select on
an undeclared 1-bit signal and errors with "Illegal bit or array select;
type does not have a bit range".

Such per-bit escaped-scalar boundary signals are produced by partition
synthesis and by repair_design buffer insertion across module
boundaries.

Per the upstream discussion (jjcherry56 on parallaxsw/OpenSTA#453: "The
problem is in the upstream name.  foo[7] should be foo\[7\] if it is not
doing a bus select."), fix the name dbNetwork hands to STA rather than
the writer.  dbNetwork::name() now escapes the brackets of a clean
bus-member name when, in the owning module, a per-bit module boundary
terminal (dbModBTerm) of that exact name exists but the bus prefix has
no bus-aggregate dbModBTerm.  Genuine bus members are left untouched:
sub-module bus bits carry a sibling bus aggregate, and top-level bus
bits have no per-bit dbModBTerm at all, so neither is escaped.  This is
the bracket analogue of the recent escaped-slash fix in
dbNetwork::name(Port*)/stripParentPrefix.

Add a dbsta_unittest regression (TestDbSta.EscapeScalarBracketName)
covering scalar port and net escaping and the genuine-bus-member
negative cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
@oharboe oharboe requested a review from a team as a code owner June 26, 2026 13:58
@oharboe oharboe requested a review from dsengupta0628 June 26, 2026 13:58
@oharboe

oharboe commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator Author

@jjcherry56 FYI - if this doesn't do it, then I'll see about creating a confidential issue for @maliberty

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces escapeBracketsIfScalar in dbNetwork to escape brackets in scalar signal names (e.g., foo[7] to foo\[7\]) when they do not belong to a declared bus, preventing incorrect bus select outputs in write_verilog. A regression test was also added to verify this behavior. The feedback suggests optimizing this new function with a fast-path check using std::string::find to avoid unnecessary overhead for names without brackets.

Comment on lines +810 to +813
if (scope == nullptr) {
return name;
}
bool is_bus = false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since escapeBracketsIfScalar is called on every port and net name lookup when modbterm or modnet is present, we can optimize the common case where names do not contain brackets. Adding a fast-path check using std::string::find avoids the overhead of calling parseBusName and allocating std::string bus_name for non-bus names.

  if (scope == nullptr) {
    return name;
  }
  if (name.find('[') == std::string::npos) {
    return name;
  }
  bool is_bus = false;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant