From 26379d2b8004b95bcf13cac3c2da9650b7be4813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Tue, 3 Nov 2026 09:06:29 +0100 Subject: [PATCH] pdn: connect the supply pins of dont_touch instances MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit global_connect skips dont_touch instances (ODB-0383, deliberate in odb), so an instance made dont_touch before the PDN step keeps its VDD and VSS pins on no net through final. A register file built by generate_ram and dissolved into its parent after macro placement is that case: its core is FIRM and dont_touch by the PDN step. 896 of riscv32i's 7,089 instances with its register file generated, 406,272 cells of XiangShan's XSTile, were left unpowered. The M1 rails touch them, so timing and routing were unaffected; the netlist, LVS and power analysis saw them unpowered. The PDN step lifts dont_touch from every dont_touch instance with a supply pin on no net while the platform's PDN script makes its global connections, and restores it after: one connection, no ODB-0383 warning per instance. A supply pin already on a net is left alone. flow/test/test_pdn_dont_touch_supplies.py checks that global_connect still skips a dont_touch instance, that only the dont_touch instances with a supply pin on no net are named, and that they are connected and dont_touch again afterwards. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Øyvind Harboe --- flow/scripts/pdn.tcl | 14 +++ flow/scripts/util.tcl | 22 ++++ flow/test/test_pdn_dont_touch_supplies.py | 116 ++++++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 flow/test/test_pdn_dont_touch_supplies.py diff --git a/flow/scripts/pdn.tcl b/flow/scripts/pdn.tcl index 1ffbadd9ba..4daf8d0021 100644 --- a/flow/scripts/pdn.tcl +++ b/flow/scripts/pdn.tcl @@ -3,7 +3,21 @@ erase_non_stage_variables floorplan load_design 2_3_floorplan_tapcell.odb 2_1_floorplan.sdc source_step_tcl PRE PDN +# A supply pin on no net changes no logic when it is connected: lift +# dont_touch for the platform's global connections, restore it after. +set lifted [dont_touch_unsupplied] +foreach inst $lifted { + $inst setDoNotTouch 0 +} source $::env(PDN_TCL) +foreach inst $lifted { + $inst setDoNotTouch 1 +} +if { [llength $lifted] > 0 } { + utl::info FLW 18 "[llength $lifted] dont_touch instances had a supply pin\ + on no net; connected by the global connections" +} + pdngen source_step_tcl POST PDN diff --git a/flow/scripts/util.tcl b/flow/scripts/util.tcl index 293572e39e..a8aeb27083 100644 --- a/flow/scripts/util.tcl +++ b/flow/scripts/util.tcl @@ -506,3 +506,25 @@ proc synth_verilog_files { } { } return $copies } + +# global_connect skips dont_touch instances (ODB-0383, a warning each), +# so an instance made dont_touch before the PDN step keeps its supply +# pins on no net: unpowered in the netlist, LVS and power analysis. The +# dont_touch instances with a supply pin on no net, for the PDN step to +# lift dont_touch from while it makes the global connections. +proc dont_touch_unsupplied { } { + set insts {} + foreach inst [[ord::get_db_block] getInsts] { + if { ![$inst isDoNotTouch] } { + continue + } + foreach iterm [$inst getITerms] { + set type [[$iterm getMTerm] getSigType] + if { ($type eq "POWER" || $type eq "GROUND") && [$iterm getNet] eq "NULL" } { + lappend insts $inst + break + } + } + } + return $insts +} diff --git a/flow/test/test_pdn_dont_touch_supplies.py b/flow/test/test_pdn_dont_touch_supplies.py new file mode 100644 index 0000000000..74c87b2881 --- /dev/null +++ b/flow/test/test_pdn_dont_touch_supplies.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +"""The PDN step connects the supply pins of dont_touch instances. + +global_connect skips a dont_touch instance, so an instance made +dont_touch before the PDN step kept its supply pins on no net. +dont_touch_unsupplied (util.tcl) names those instances, the PDN step +lifts dont_touch from them for the global connections and restores it +after; an instance whose supply pins are already on a net, or that is +not dont_touch, is not named. + +Skipped when no openroad is available: OPENROAD_EXE or openroad on PATH. +""" + +import os +import shutil +import subprocess +import tempfile +import unittest + +FLOW = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..") +UTIL_TCL = os.path.join(FLOW, "scripts", "util.tcl") +LEF = os.path.join(FLOW, "platforms", "nangate45", "lef") + +DESIGN = """\ +module top (input a, output y0, output y1, output y2); + INV_X1 kept (.A(a), .ZN(y0)); + INV_X1 free (.A(a), .ZN(y1)); + INV_X1 supplied (.A(a), .ZN(y2)); +endmodule +""" + +SETUP = f"""\ +set ::env(REPORTS_DIR) [pwd] +source {{{UTIL_TCL}}} +read_lef {{{os.path.join(LEF, "NangateOpenCellLibrary.tech.lef")}}} +read_lef {{{os.path.join(LEF, "NangateOpenCellLibrary.macro.mod.lef")}}} +read_verilog top.v +link_design top +set block [ord::get_db_block] +proc supply_net {{ name }} {{ + set net [[[ord::get_db_block] findInst $name] findITerm VDD] + set net [$net getNet] + if {{ $net eq "NULL" }} {{ + return none + }} + return [$net getName] +}} +foreach pin {{VDD VSS}} {{ + [[$block findInst supplied] findITerm $pin] connect \\ + [odb::dbNet_create $block supplied_[string tolower $pin]] +}} +[$block findInst supplied] setDoNotTouch 1 +[$block findInst kept] setDoNotTouch 1 +""" + +# The platform's PDN script: add_global_connection connects at once. +GLOBAL_CONNECTIONS = """\ +add_global_connection -net VDD -pin_pattern {^VDD$} -power +add_global_connection -net VSS -pin_pattern {^VSS$} -ground +global_connect +""" + + +def openroad_command(): + openroad = os.environ.get("OPENROAD_EXE") or shutil.which("openroad") + return [openroad, "-no_init", "-no_splash", "-exit"] if openroad else None + + +class TestPdnDontTouchSupplies(unittest.TestCase): + def run_openroad(self, body): + cmd = openroad_command() + if cmd is None: + self.skipTest("no openroad") + with tempfile.TemporaryDirectory() as tmp: + with open(os.path.join(tmp, "top.v"), "w") as f: + f.write(DESIGN) + with open(os.path.join(tmp, "test.tcl"), "w") as f: + f.write(SETUP + body) + result = subprocess.run( + cmd + ["test.tcl"], cwd=tmp, capture_output=True, text=True + ) + output = result.stdout + result.stderr + self.assertEqual(result.returncode, 0, output) + return output + + def test_global_connect_skips_dont_touch(self): + # The problem the PDN step works around, so this test notices + # when global_connect stops skipping dont_touch instances. + output = self.run_openroad( + GLOBAL_CONNECTIONS + + 'puts "kept=[supply_net kept] free=[supply_net free]"\n' + ) + self.assertIn("kept=none free=VDD", output) + + def test_names_only_unsupplied_dont_touch(self): + output = self.run_openroad( + 'puts "lifted=[lmap i [dont_touch_unsupplied] { $i getName }]"\n' + ) + self.assertIn("lifted=kept\n", output) + + def test_lifted_for_global_connections(self): + # pdn.tcl's sequence around the platform's PDN script + output = self.run_openroad( + "set lifted [dont_touch_unsupplied]\n" + "foreach inst $lifted { $inst setDoNotTouch 0 }\n" + + GLOBAL_CONNECTIONS + + "foreach inst $lifted { $inst setDoNotTouch 1 }\n" + 'puts "kept=[supply_net kept] supplied=[supply_net supplied]"\n' + 'puts "dont_touch=[[$block findInst kept] isDoNotTouch]"\n' + ) + self.assertIn("kept=VDD supplied=supplied_vdd", output) + self.assertIn("dont_touch=1", output) + + +if __name__ == "__main__": + unittest.main()