Global placement with `-timing_driven` results in critical error
Describe the bug
When performing global placement for a full chip design on gf180mcuD, I get the following error:
[CRITICAL RSZ-2008] buffering pin gf180mcu_fd_io__in_s_clk/Y: wire step options empty
If I omit -timing_driven, placement completes successfully.
Expected Behavior
Global placement should complete
Environment
Recent OpenROAD from 2025-09-01 (c47bc3b81431e1ce5f305ab75bad126c3adb2858)
To Reproduce
Here is a standalone reproducible:
Relevant log output
Screenshots
No response
Additional Context
No response
@povik And this one as well :)
This is an interesting case. The issue is downstream of the IO cell specifying max fanout of 1 in the .lib. It will need some adjustment to the buffering algorithm to handle this.
If this is a case of the .lib having unrealistic constraints, I think you can ignore it an issue a warning. Even if it's a commercial IP. It seems like the gf180 IO cells are not exactly the best.
The gf180mcu I/O cells not being the best is an understatement. Turns out despite two separate voltage domains, you cannot have a HV and LV domain due to a lack of level shifters 😆
A warning would be a good enough solution I think.
Hi, just checking in if a warning could be added with relatively little effort? I'm currently blocked on this for the gf180mcu-project-template.
As a workaround, I tried increasing the fanout to 2 in the Liberty files for the I/O cells. However, that was not quite enough, I still got the same error. Increasing the fanout to 5 got me through the error.
Should OpenROAD not be able to add a buffer directly after the I/O cell output?
As a workaround, I tried increasing the fanout to 2 in the Liberty files for the I/O cells.
I'm surprised increasing to 2 wasn't enough but there will be some internal reason for it.
Should OpenROAD not be able to add a buffer directly after the I/O cell output?
That's what it should do but isn't smart enough to do yet. Another workaround would be to insert the buffer into the incoming netlist. It would need a (* dont_touch *) attribute to not get removed during optimization steps.
I see! That would actually be a great workaround, if it worked ^^
// Normal input
gf180mcu_fd_io__in_c rst_n_pad (
.Y (rst_n_PAD2CORE),
.PAD (rst_n_PAD),
.PU (1'b0),
.PD (1'b0)
);
(* keep, dont_touch *)
gf180mcu_fd_sc_mcu7t5v0__buf_1 buf_rst_n (
.I (rst_n_PAD2CORE),
.Z (rst_n_PAD2CORE_buf)
);
Unfortunately I still get:
[CRITICAL RSZ-2008] buffering pin rst_n_pad/Y: wire step options empty
Through the OpenROAD GUI, I can confirm that buf_rst_n is not being removed.
@mole99 I wanted to see why the workaround doesn't work but it seems to work for me. I've used the following script to patch the design in your reproducible.
set db [ord::get_db]
set block [ord::get_db_block]
set i 1
foreach pad_inst [$block getInsts] {
if {[[$pad_inst getMaster] getName] == "gf180mcu_fd_io__in_s" || [[$pad_inst getMaster] getName] == "gf180mcu_fd_io__in_c"} {
puts "visiting [$pad_inst getName]"
set buf [$db findMaster gf180mcu_fd_sc_mcu7t5v0__buf_1]
set buf_in [$buf findMTerm I]
set buf_out [$buf findMTerm Z]
set pad_out [[$pad_inst getMaster] findMTerm Y]
set pad_pin [$pad_inst getITerm $pad_out]
set n1 [odb::dbNet_create $block "fixup_net$i"]
set i1 [odb::dbInst_create $block $buf "fixup_buf$i"]
set i [expr {$i + 1}]
[$i1 getITerm $buf_in] connect $n1
[$i1 getITerm $buf_out] connect [$pad_pin getNet]
$i1 setDoNotTouch true
$pad_pin connect $n1
}
}
Yours and mine way of applying the workaround are different so it's possible one works but not the other. If you package a reproducible, I'd be interested to take a look as it could suggest a different issue with the buffering algorithm than the one originally reported.
Hi Martin, thank you for digging into this.
The latest reproducible is here: gf180mcu_reproducible.zip
I added a buffer directly after the input pad:
(* keep, dont_touch *)
gf180mcu_fd_sc_mcu7t5v0__buf_1 buf_rst_n (
.I (rst_n_PAD2CORE),
.Z (rst_n_PAD2CORE_buf)
);
I confirmed its existence in the design through the GUI.
Using this approach, the error remains:
[CRITICAL RSZ-2008] buffering pin rst_n_pad/Y: wire step options empty
Thanks Leo.
It looks like the attributes haven't propagated into a "dont touch" flag inside odb. If I add a command set_dont_touch buf_rst_n the error no longer shows up on rst_n_pad/Y but on another pin which doesn't have the buffer. Let me look into this more.
Sorry Martin! That errors seems to be on my side. The netlist synthesized by yosys does not seem to keep any of the attributes in LibreLane. Let me look into this as well.
That's alright. Are you perhaps passing -noattr to write_verilog in your yosys script?
Yes, that's what happens, but I think on purpose.
I can confirm that by manually injecting the attribute into the synthesized netlist and starting the flow after synthesis, it successfully runs through.
(* dont_touch *)
gf180mcu_fd_sc_mcu7t5v0__buf_1 buf_rst_n (
.I(rst_n_PAD2CORE),
.Z(\i_chip_core.rst_n )
);
However, I also get:
WARNING [ODB-0383] buf_rst_n is marked do not touch and will be skipped in global connections.
Which is why adding the dont_touch attribute to the Verilog might not be the best approach.
I just remembered that LibreLane has a RSZ_DONT_TOUCH_LIST variable that it uses to apply the dont_touch attribute before operations such as resizing, and removes it afterwards.
However, RSZ_DONT_TOUCH_LIST is not used for global placement due to this issue: https://github.com/librelane/librelane/issues/695
I'm setting both:
RSZ_DONT_TOUCH_LIST:
- buf_rst_n
PL_KEEP_RESIZE_BELOW_OVERFLOW: 0
But it still gets stuck at global placement. Maybe a wrong configuration from my side...
It would be really helpful to hear your thoughts about this dont_touch situation, what would be the best approach to it?
To me, it looks like a dont_resize option might be useful.
It seems a similar feature was proposed here: https://github.com/The-OpenROAD-Project/OpenROAD/issues/478
I've opened a discussion thread
I encountered this issue in another design, where it does not happen on the I/O pads, but on a stdcell:
...
[INFO RSZ-0034] Found 3 slew violations.
[INFO RSZ-0035] Found 46 fanout violations.
[INFO RSZ-0038] Inserted 232 buffers in 46 nets.
Iter | Area | Removed | Inserted | Pins
| | Buffers | Buffers | Remaining
-------------------------------------------------------
0 | +0.0% | 0 | 0 | 468
46 | +0.0% | 0 | 0 | 422
92 | +0.0% | 0 | 0 | 376
[CRITICAL RSZ-2008] buffering pin _545_/ZN: wire step options empty
(I also found out how to package a reproducible in LibreLane, which doesn't help much since it's packaged for LibreLane, but it's slightly faster than doing it completely manually.)
Simply run: ./run.sh
@mole99 In LibreLane it's librelane.steps create-reproducible to create the LibreLane reproducible (for us as maintainers) and then ./run_ol.sh eject to create a shell-only reproducible (to report issues to tools upstream.)
The issue persists btw. More up-to-date reproducible in #8752
EDIT: Wait, duh. That value only controls what is kept from the resizing, not whether the resizer is run at all.
~~Incidentally -keep_resize_below_overflow 0 as recommended in https://github.com/The-OpenROAD-Project/OpenROAD/issues/6643#issuecomment-2637837736 does not appear to disable the resizer either:~~
+ global_placement -density 0.5 -timing_driven -routability_driven -pad_right 0 -pad_left 0 -init_wirelength_coef 0.25 -keep_resize_below_overflow 0
[INFO GPL-0005] Execute conjugate gradient initial placement.
[INFO GPL-0002] DBU: 2000
[INFO GPL-0003] SiteSize: ( 0.560 3.920 ) um
[INFO GPL-0004] CoreBBox: ( 6.720 15.680 ) ( 153.440 160.720 ) um
[INFO GPL-0032] Initializing region: Top-level
[INFO GPL-0006] Number of instances: 430
[INFO GPL-0007] Movable instances: 287
[INFO GPL-0008] Fixed instances: 137
[INFO GPL-0009] Dummy instances: 6
[INFO GPL-0010] Number of nets: 322
[INFO GPL-0011] Number of pins: 958
[INFO GPL-0012] Die BBox: ( 0.000 0.000 ) ( 160.600 178.520 ) um
[INFO GPL-0013] Core BBox: ( 6.720 15.680 ) ( 153.440 160.720 ) um
[INFO GPL-0016] Core area: 21280.269 um^2
[INFO GPL-0014] Region name: top-level.
[INFO GPL-0015] Region area: 21280.269 um^2
[INFO GPL-0017] Fixed instances area: 1088.819 um^2
[INFO GPL-0018] Movable instances area: 8662.259 um^2
[INFO GPL-0019] Utilization: 42.901 %
[INFO GPL-0020] Standard cells area: 8662.259 um^2
[INFO GPL-0021] Large instances area: 0.000 um^2
[InitialPlace] Iter: 1 conjugate gradient residual: 0.00000008 HPWL: 28631920
[InitialPlace] Iter: 2 conjugate gradient residual: 0.00000341 HPWL: 19050441
[InitialPlace] Iter: 3 conjugate gradient residual: 0.00000024 HPWL: 15292196
[InitialPlace] Iter: 4 conjugate gradient residual: 0.00000012 HPWL: 14493627
[InitialPlace] Iter: 5 conjugate gradient residual: 0.00000011 HPWL: 14360746
[INFO GPL-0033] Initializing Nesterov region: Top-level
[INFO GPL-0023] Placement target density: 0.5000
[INFO GPL-0024] Movable insts average area: 30.182 um^2
[INFO GPL-0025] Ideal bin area: 60.364 um^2
[INFO GPL-0026] Ideal bin count: 352
[INFO GPL-0027] Total bin area: 21280.269 um^2
[INFO GPL-0028] Bin count (X, Y): 16 , 16
[INFO GPL-0029] Bin size (W * H): 9.170 * 9.065 um
[INFO GPL-0030] Number of bins: 256
[INFO GPL-0007] Execute nesterov global placement.
[INFO GPL-0031] HPWL: Half-Perimeter Wirelength
Iteration | Overflow | HPWL (um) | HPWL(%) | Penalty | Group
---------------------------------------------------------------
0 | 0.8615 | 3.393388e+03 | +0.00% | 2.59e-15 |
10 | 0.8518 | 3.585352e+03 | +5.66% | 4.21e-15 |
20 | 0.8581 | 3.743566e+03 | +4.41% | 6.86e-15 |
30 | 0.8601 | 3.761171e+03 | +0.47% | 1.12e-14 |
40 | 0.8589 | 3.732109e+03 | -0.77% | 1.82e-14 |
50 | 0.8586 | 3.728040e+03 | -0.11% | 2.96e-14 |
60 | 0.8592 | 3.741616e+03 | +0.36% | 4.83e-14 |
70 | 0.8591 | 3.743905e+03 | +0.06% | 7.86e-14 |
80 | 0.8587 | 3.740039e+03 | -0.10% | 1.28e-13 |
90 | 0.8584 | 3.744221e+03 | +0.11% | 2.09e-13 |
100 | 0.8581 | 3.755209e+03 | +0.29% | 3.40e-13 |
110 | 0.8572 | 3.766849e+03 | +0.31% | 5.54e-13 |
120 | 0.8552 | 3.783177e+03 | +0.43% | 9.02e-13 |
130 | 0.8511 | 3.810751e+03 | +0.73% | 1.47e-12 |
140 | 0.8440 | 3.849413e+03 | +1.01% | 2.39e-12 |
150 | 0.8083 | 3.891445e+03 | +1.09% | 3.90e-12 |
160 | 0.7984 | 3.931663e+03 | +1.03% | 6.35e-12 |
170 | 0.8060 | 4.050019e+03 | +3.01% | 1.03e-11 |
180 | 0.7561 | 4.248249e+03 | +4.89% | 1.68e-11 |
190 | 0.7443 | 4.344758e+03 | +2.27% | 2.74e-11 |
200 | 0.7105 | 4.589575e+03 | +5.63% | 4.47e-11 |
210 | 0.6394 | 4.680180e+03 | +1.97% | 7.28e-11 |
[INFO GPL-0100] Timing-driven iteration 1/2, virtual: true.
[INFO GPL-0101] Iter: 212, overflow: 0.633, keep resizer changes at: 0, HPWL: 9367148
Iteration | Area | Resized | Buffers | Nets repaired | Remaining
---------------------------------------------------------------------
0 | +0.0% | 0 | 0 | 0 | 322
final | +33.6% | 0 | 39 | 2 | 0
---------------------------------------------------------------------
[INFO RSZ-0034] Found 1 slew violations.
[INFO RSZ-0035] Found 2 fanout violations.
[INFO RSZ-0038] Inserted 39 buffers in 2 nets.
Iter | Area | Removed | Inserted | Pins
| | Buffers | Buffers | Remaining
-------------------------------------------------------
0 | +0.0% | 0 | 0 | 289
28 | +0.0% | 0 | 0 | 261
56 | +0.0% | 0 | 0 | 233
84 | +0.0% | 0 | 0 | 205
112 | +0.0% | 0 | 0 | 177
140 | +0.0% | 0 | 0 | 149
168 | +0.0% | 0 | 0 | 121
196 | +0.0% | 0 | 0 | 93
224 | +0.0% | 0 | 0 | 65
252 | +0.0% | 0 | 0 | 37
280 | +0.0% | 0 | 0 | 9
[CRITICAL RSZ-2008] buffering pin fanout39/Z: wire step options empty
Hi @povik, is there any news on this issue? Can we provide additional information to help resolve it?
As you can see from the latest reproducibles, this appears to be a more general issue that also occurs with standard cells and not just with I/O cells.
I tried your test case and it is also mispackaged:
[ERROR ORD-0007] /home/leo/Repositories/ws-submission-2025/example_project/runs/latest/26-odb-customioplacement/user_project_example.odb does not exist.
I'm not going to fix again as I did in #8642
@maliberty I'm really sorry about this. I was under the impression that LibreLane would rewrite the entire reproducible. However, there were some absolute and wrong paths left under _env.tcl. I have fixed them and grepped for any remaining full paths that I may have missed, but it seems I have caught them all now. Please let me know right away if you encounter any further issues.
Here is the updated reproducible: reproducible.zip
./run.sh invokes OpenROAD to reproduce the issue:
...
[INFO RSZ-0038] Inserted 230 buffers in 46 nets.
Iter | Area | Removed | Inserted | Pins
| | Buffers | Buffers | Remaining
-------------------------------------------------------
0 | +0.0% | 0 | 0 | 468
46 | +0.0% | 0 | 0 | 422
92 | +0.0% | 0 | 0 | 376
[CRITICAL RSZ-2008] buffering pin _545_/ZN: wire step options empty