odb: insert_buffer on two ports that share a net leaves the driver net named after one port; write_verilog then shorts the buffer · Issue #11789 · The-OpenROAD-Project/OpenROAD · GitHub
Skip to content

odb: insert_buffer on two ports that share a net leaves the driver net named after one port; write_verilog then shorts the buffer #11789

Closed

Description

@akashlevy

Describe the bug

insert_buffer (dbInsertBuffer::createNewFlatNet) can leave the original net named after an output port it no longer connects to.

This happens when the moved loads are two output ports that share a net, which is what remove_buffers leaves when two output ports are driven by the same signal:

  • the new net takes the name of the first BTerm among the moved loads;
  • the original net is renamed only if its name equals that BTerm's name;
  • if the original net is named after the other port, it keeps that name, although both ports now sit on the new net.

The database is still consistent, but write_verilog then writes the driver's net under the port's name and adds an assign between the two ports. In Verilog a net named like a port is that port, so the written netlist shorts the buffer's input to its output. Re-reading it gives a net with two drivers and a combinational loop, and STA on the re-read netlist reports infinite slews there.

We hit this with ORFS placement repair (repair_design inserting a buffer in front of two such ports) on several open-source cores (opene902, opene906, veer, cva6). The in-database results were fine, but the written netlist and SPEF are not.

This is the multi-port case of #9675, which made createNewFlatNet name the new net after the BTerm and rename a colliding original net or modnet. The comment in createNewFlatNet describes exactly this case ("remove_buffers merges two port-named nets, leaving a BTerm on a net with a different port's name"). The check only compares the original net's name with the first BTerm found, though, so it depends on which port that is. The code is unchanged on master since 2026-07-21.

Expected Behavior

After insert_buffer, no net that isn't connected to a port has that port's name. Concretely, the original net should be renamed whenever its name equals the name of any BTerm moved onto the new net. Equivalently, write_verilog should never write a non-port net under a port's name.

The re-read netlist should be equivalent to the database: drv/ZN drives rbuf1/A and ld/A, and rbuf1/Z drives p and q.

Environment

OpenROAD 27288f5c90265ae5296b2f5a9fa6e8714fa191ad (2026-10-01), from the ORFS image openroad/orfs:26Q4-4-g214988303
Ubuntu 22.04.5 LTS

To Reproduce

Run from OpenROAD's test/ directory with openroad -exit port_alias.tcl, then openroad -exit reread.tcl.

port_alias.v: two output ports driven by one signal, each through its own buffer.

module top (a, b, p, q, y);
  input a, b;
  output p, q, y;
  wire n;
  NAND2_X1 drv (.A1(a), .A2(b), .ZN(n));
  BUF_X1 ob1 (.A(n), .Z(q));
  BUF_X1 ob2 (.A(n), .Z(p));
  INV_X1 ld (.A(n), .ZN(y));
endmodule

port_alias.tcl:

read_lef Nangate45/Nangate45.lef
read_liberty Nangate45/Nangate45_typ.lib
read_verilog port_alias.v
link_design top
initialize_floorplan -die_area {0 0 40 40} -core_area {5 5 35 35} \
  -site FreePDK45_38x28_10R_NP_162NW_34O
place_inst -name drv -location {10 10}
place_inst -name ob1 -location {12 10}
place_inst -name ob2 -location {14 10}
place_inst -name ld -location {16 10}

proc show_nets {tag} {
  puts "== $tag"
  foreach net [[ord::get_db_block] getNets] {
    set drv {}
    set lds {}
    foreach it [$net getITerms] {
      set pin [[$it getInst] getName]/[[$it getMTerm] getName]
      if { [$it isOutputSignal] } { lappend drv $pin } else { lappend lds $pin }
    }
    puts [format "  net %-6s ports {%s} drivers {%s} loads {%s}" [$net getName] \
            [lmap b [$net getBTerms] {$b getName}] $drv $lds]
  }
}

# remove_buffers leaves p and q on one net, named after one of them (q)
remove_buffers
show_nets "after remove_buffers"

# A buffer between the driver and both ports, as repair_design inserts one
insert_buffer -buffer_cell BUF_X1 -net [get_nets -of_objects [get_pins drv/ZN]] \
  -load_pins [get_ports {p q}] -location {20 10} -buffer_name rbuf
show_nets "after insert_buffer"
write_verilog written.v

reread.tcl:

read_lef Nangate45/Nangate45.lef
read_liberty Nangate45/Nangate45_typ.lib
read_verilog written.v
link_design top
foreach net [[ord::get_db_block] getNets] {
  set drivers [lmap it [$net getITerms] {
    if { [$it isOutputSignal] } { string cat [[$it getInst] getName]/[[$it getMTerm] getName] } else continue
  }]
  if { [llength $drivers] > 1 } {
    puts "net [$net getName] has [llength $drivers] drivers: $drivers"
  }
}

Relevant log output

== after remove_buffers
  net a      ports {a} drivers {} loads {drv/A1}
  net b      ports {b} drivers {} loads {drv/A2}
  net q      ports {p q} drivers {drv/ZN} loads {ld/A}
  net y      ports {y} drivers {ld/ZN} loads {}
== after insert_buffer
  net a      ports {a} drivers {} loads {drv/A1}
  net b      ports {b} drivers {} loads {drv/A2}
  net p      ports {q p} drivers {rbuf1/Z} loads {}
  net q      ports {} drivers {drv/ZN} loads {rbuf1/A ld/A}
  net y      ports {y} drivers {ld/ZN} loads {}

written.v (the driver's net is written as the port q, and assign q = p closes the loop):

 NAND2_X1 drv (.A1(a),
    .A2(b),
    .ZN(q));
 INV_X1 ld (.A(q),
    .ZN(y));
 BUF_X1 rbuf1 (.A(q),
    .Z(p));
 assign q = p;

reread.tcl:

net p has 2 drivers: rbuf1/Z drv/ZN

If the buffers are swapped (ob1 drives p), remove_buffers names the merged net p. The first BTerm then matches, the original net is renamed net1, and the written netlist is correct. So the outcome depends on which port the merged net was named after.

Additional Context

  • Our workaround. Before writing the netlist and SPEF, we rename every net whose name equals a port (or bus port base) name it isn't connected to. Afterwards we check the written Verilog for nets with more than one driver.
  • The equivalence check doesn't catch it. In a slightly larger version of this reproducer, kepler-formal reported the broken netlist as identical to the original, but compared only 7 of its 9 outputs.
  • A separate crash. In the same script, insert_buffer without -buffer_name segfaults (Signal 11 received) on this build. With -buffer_name rbuf it runs as shown above.

Activity

  1. github-actions commented on Oct 16, 2026

    @github-actions
    Contributor

    Welcome to OpenROAD! Thanks for opening your first issue.
    To get started:

    Please search existing issues before submitting to avoid duplicates.
    A maintainer will get back to you soon!

  2. added a commit that references this issue on Oct 22, 2026
    481acf6
  3. akashlevy commented on Oct 23, 2026

    @akashlevy
    Author

    Thank you @maliberty and @jinchuntan!

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

Metadata

Metadata

Assignees

Labels

odbOpenDBrszResizer

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions