From b695a2d7db2a6c34bb8aeeaad293af048dceaafd Mon Sep 17 00:00:00 2001 From: appleweiping Date: Thu, 3 Sep 2026 15:30:35 -0500 Subject: [PATCH 1/2] Do not emit an input max_transition below the library default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `write_addr_bus()` and `write_wmask_bus()` set each input pin's `max_transition` to `self.slews[-1]`, the largest slew used during characterization. For the default slew set that is 0.04ns, an order of magnitude tighter than the `default_max_transition : 0.5` the same file declares in the library header. Downstream STA and P&R reject the result as electrically infeasible, which is what #298 reports. The header's 0.5 was also hardcoded separately from the value used for the buses, so the two could drift. This lifts it to a `default_max_transition` class attribute, writes the header from it, and emits `max(default_max_transition, self.slews[-1])` for both buses, so a characterization that legitimately uses slower slews still widens the limit instead of being clamped. Golden .lib fixtures are regenerated for freepdk45 and scn4m_subm; the addr and wmask `max_transition` entries move from 0.04/0.4 to 0.5. Adds an assertion to 23_lib_sram_test.py that drives `write_addr_bus()` and `write_wmask_bus()` against a stubbed writer and checks both emitted limits equal `default_max_transition`. Verified against origin/dev with freepdk45: the new assertion passes with this change and fails without it, emitting the 0.04 limit. The full 23_lib_sram_test.py run does not complete in my environment — SPICE characterization aborts at "Unable to open spice output file" — but it aborts identically on unmodified dev, so it is an environment limitation rather than a regression. 23_lib_sram_model_test.py likewise fails identically before and after the change. Signed-off-by: appleweiping Co-Authored-By: Claude Opus 5 --- compiler/characterizer/lib.py | 10 +++++++--- compiler/tests/23_lib_sram_test.py | 15 ++++++++++++++- ...am_2_16_1_freepdk45_FF_1p0V_25C_analytical.lib | 2 +- ...am_2_16_1_freepdk45_SS_1p0V_25C_analytical.lib | 2 +- .../golden/sram_2_16_1_freepdk45_TT_1p0V_25C.lib | 2 +- ...am_2_16_1_freepdk45_TT_1p0V_25C_analytical.lib | 2 +- .../sram_2_16_1_freepdk45_TT_1p0V_25C_pruned.lib | 2 +- ...m_2_16_1_scn4m_subm_FF_5p0V_25C_analytical.lib | 2 +- ...m_2_16_1_scn4m_subm_SS_5p0V_25C_analytical.lib | 2 +- .../golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C.lib | 2 +- ...m_2_16_1_scn4m_subm_TT_5p0V_25C_analytical.lib | 2 +- .../sram_2_16_1_scn4m_subm_TT_5p0V_25C_pruned.lib | 2 +- 12 files changed, 31 insertions(+), 14 deletions(-) diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py index b8880717f..4afefc476 100644 --- a/compiler/characterizer/lib.py +++ b/compiler/characterizer/lib.py @@ -21,6 +21,8 @@ class lib: """ lib file generation.""" + default_max_transition = 0.5 + def __init__(self, out_dir, sram, sp_file, use_model=OPTS.analytical_delay): try: @@ -281,7 +283,7 @@ def write_defaults(self): self.lib.write(" default_input_pin_cap : 1.0 ;\n") self.lib.write(" default_inout_pin_cap : 1.0 ;\n") self.lib.write(" default_output_pin_cap : 0.0 ;\n") - self.lib.write(" default_max_transition : 0.5 ;\n") + self.lib.write(" default_max_transition : {} ;\n".format(self.default_max_transition)) self.lib.write(" default_fanout_load : 1.0 ;\n") self.lib.write(" default_max_fanout : 4.0 ;\n") self.lib.write(" default_connection_class : universal ;\n\n") @@ -484,7 +486,8 @@ def write_addr_bus(self, port): self.lib.write(" bus_type : addr; \n") self.lib.write(" direction : input; \n") self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"]/1000)) - self.lib.write(" max_transition : {0};\n".format(self.slews[-1])) + max_transition = max(self.default_max_transition, self.slews[-1]) + self.lib.write(" max_transition : {0};\n".format(max_transition)) self.lib.write(" pin(addr{0}[{1}:0])".format(port,self.sram.addr_size-1)) self.lib.write("{\n") @@ -499,7 +502,8 @@ def write_wmask_bus(self, port): self.lib.write(" bus_type : wmask; \n") self.lib.write(" direction : input; \n") self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"] / 1000)) - self.lib.write(" max_transition : {0};\n".format(self.slews[-1])) + max_transition = max(self.default_max_transition, self.slews[-1]) + self.lib.write(" max_transition : {0};\n".format(max_transition)) self.lib.write(" pin(wmask{0}[{1}:0])".format(port, self.sram.num_wmasks - 1)) self.lib.write("{\n") diff --git a/compiler/tests/23_lib_sram_test.py b/compiler/tests/23_lib_sram_test.py index 4595d0e5e..30d3147bd 100755 --- a/compiler/tests/23_lib_sram_test.py +++ b/compiler/tests/23_lib_sram_test.py @@ -6,7 +6,8 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import sys, os, re +import sys, os, re, io +import types import unittest from testutils import * @@ -69,6 +70,18 @@ def runTest(self): golden = "{0}/golden/{1}".format(os.path.dirname(os.path.realpath(__file__)),filename) self.assertTrue(self.isapproxdiff(libname,golden,0.40)) + writer = lib.__new__(lib) + writer.lib = io.StringIO() + writer.sram = types.SimpleNamespace(addr_size=4, num_wmasks=2) + writer.slews = [0.00125, 0.005, 0.04] + writer.write_FF_setuphold = lambda port: None + writer.write_addr_bus(0) + writer.write_wmask_bus(0) + input_limits = re.findall(r"(?m)^\s+max_transition\s*:\s*([0-9.eE+-]+)", + writer.lib.getvalue()) + self.assertEqual([float(value) for value in input_limits], + [lib.default_max_transition, lib.default_max_transition]) + reload(characterizer) openram.end_openram() diff --git a/compiler/tests/golden/sram_2_16_1_freepdk45_FF_1p0V_25C_analytical.lib b/compiler/tests/golden/sram_2_16_1_freepdk45_FF_1p0V_25C_analytical.lib index b3ef0e0a3..c237dc7b3 100644 --- a/compiler/tests/golden/sram_2_16_1_freepdk45_FF_1p0V_25C_analytical.lib +++ b/compiler/tests/golden/sram_2_16_1_freepdk45_FF_1p0V_25C_analytical.lib @@ -177,7 +177,7 @@ cell (sram_2_16_1_freepdk45){ bus_type : addr; direction : input; capacitance : 0.00020910000000000001; - max_transition : 0.04; + max_transition : 0.5; pin(addr0[3:0]){ timing(){ timing_type : setup_rising; diff --git a/compiler/tests/golden/sram_2_16_1_freepdk45_SS_1p0V_25C_analytical.lib b/compiler/tests/golden/sram_2_16_1_freepdk45_SS_1p0V_25C_analytical.lib index 34be4fe4e..88328de88 100644 --- a/compiler/tests/golden/sram_2_16_1_freepdk45_SS_1p0V_25C_analytical.lib +++ b/compiler/tests/golden/sram_2_16_1_freepdk45_SS_1p0V_25C_analytical.lib @@ -177,7 +177,7 @@ cell (sram_2_16_1_freepdk45){ bus_type : addr; direction : input; capacitance : 0.00020910000000000001; - max_transition : 0.04; + max_transition : 0.5; pin(addr0[3:0]){ timing(){ timing_type : setup_rising; diff --git a/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C.lib b/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C.lib index cca9c1ed6..ce56ff8dd 100644 --- a/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C.lib +++ b/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C.lib @@ -177,7 +177,7 @@ cell (sram_2_16_1_freepdk45){ bus_type : addr; direction : input; capacitance : 0.00020910000000000001; - max_transition : 0.04; + max_transition : 0.5; pin(addr0[3:0]){ timing(){ timing_type : setup_rising; diff --git a/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_analytical.lib b/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_analytical.lib index 260288929..78f8b8410 100644 --- a/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_analytical.lib +++ b/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_analytical.lib @@ -177,7 +177,7 @@ cell (sram_2_16_1_freepdk45){ bus_type : addr; direction : input; capacitance : 0.00020910000000000001; - max_transition : 0.04; + max_transition : 0.5; pin(addr0[3:0]){ timing(){ timing_type : setup_rising; diff --git a/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_pruned.lib b/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_pruned.lib index 5817211b2..90ff2449e 100644 --- a/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_pruned.lib +++ b/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_pruned.lib @@ -165,7 +165,7 @@ cell (sram_2_16_1_freepdk45){ bus_type : addr; direction : input; capacitance : 0.2091; - max_transition : 0.04; + max_transition : 0.5; pin(addr0[3:0]){ timing(){ timing_type : setup_rising; diff --git a/compiler/tests/golden/sram_2_16_1_scn4m_subm_FF_5p0V_25C_analytical.lib b/compiler/tests/golden/sram_2_16_1_scn4m_subm_FF_5p0V_25C_analytical.lib index 6912aec73..850e3e99e 100644 --- a/compiler/tests/golden/sram_2_16_1_scn4m_subm_FF_5p0V_25C_analytical.lib +++ b/compiler/tests/golden/sram_2_16_1_scn4m_subm_FF_5p0V_25C_analytical.lib @@ -177,7 +177,7 @@ cell (sram_2_16_1_scn4m_subm){ bus_type : addr; direction : input; capacitance : 0.0098242; - max_transition : 0.4; + max_transition : 0.5; pin(addr0[3:0]){ timing(){ timing_type : setup_rising; diff --git a/compiler/tests/golden/sram_2_16_1_scn4m_subm_SS_5p0V_25C_analytical.lib b/compiler/tests/golden/sram_2_16_1_scn4m_subm_SS_5p0V_25C_analytical.lib index a7605cb36..5762fa8ee 100644 --- a/compiler/tests/golden/sram_2_16_1_scn4m_subm_SS_5p0V_25C_analytical.lib +++ b/compiler/tests/golden/sram_2_16_1_scn4m_subm_SS_5p0V_25C_analytical.lib @@ -177,7 +177,7 @@ cell (sram_2_16_1_scn4m_subm){ bus_type : addr; direction : input; capacitance : 0.0098242; - max_transition : 0.4; + max_transition : 0.5; pin(addr0[3:0]){ timing(){ timing_type : setup_rising; diff --git a/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C.lib b/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C.lib index 8bec74c37..909fd826d 100644 --- a/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C.lib +++ b/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C.lib @@ -177,7 +177,7 @@ cell (sram_2_16_1_scn4m_subm){ bus_type : addr; direction : input; capacitance : 0.0098242; - max_transition : 0.4; + max_transition : 0.5; pin(addr0[3:0]){ timing(){ timing_type : setup_rising; diff --git a/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_analytical.lib b/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_analytical.lib index 8bec74c37..909fd826d 100644 --- a/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_analytical.lib +++ b/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_analytical.lib @@ -177,7 +177,7 @@ cell (sram_2_16_1_scn4m_subm){ bus_type : addr; direction : input; capacitance : 0.0098242; - max_transition : 0.4; + max_transition : 0.5; pin(addr0[3:0]){ timing(){ timing_type : setup_rising; diff --git a/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_pruned.lib b/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_pruned.lib index 7b649d0dd..fd2967874 100644 --- a/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_pruned.lib +++ b/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_pruned.lib @@ -165,7 +165,7 @@ cell (sram_2_16_1_scn4m_subm){ bus_type : addr; direction : input; capacitance : 9.8242; - max_transition : 0.4; + max_transition : 0.5; pin(addr0[3:0]){ timing(){ timing_type : setup_rising; From c770dc6fd25c0a44791ae54bc442e4d843d1228b Mon Sep 17 00:00:00 2001 From: appleweiping Date: Fri, 4 Sep 2026 18:02:24 -0500 Subject: [PATCH 2/2] Make the input max_transition floor a configurable option Addresses review feedback on #306. The floor was a hardcoded class attribute, which clamped every technology to 0.5 ns regardless of what it can drive. It is now `OPTS.max_transition`, declared in options.py and defaulted in set_default_corner() alongside slew_scales and load_scales, so a config can set it per design and per technology. The library header is written from the same value, so the header and the per-pin limits can no longer drift apart. Also drops the unit test added to 23_lib_sram_test.py. The criticism was correct: it asserted against the constant itself, so it could not catch a wrong value, and it never exercised the branch where slews[-1] exceeds the floor. The goldens already encode the emitted limits. Verified against origin/dev with freepdk45: the default resolves to 0.5; a characterized range topping out at 0.04 emits 0.5; a range topping out at 0.9 emits 0.9 rather than being clamped; and setting OPTS.max_transition to 0.25 emits 0.25. Signed-off-by: appleweiping --- compiler/characterizer/lib.py | 8 +++----- compiler/globals.py | 6 ++++++ compiler/options.py | 1 + compiler/tests/23_lib_sram_test.py | 15 +-------------- 4 files changed, 11 insertions(+), 19 deletions(-) diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py index 4afefc476..ae94a200f 100644 --- a/compiler/characterizer/lib.py +++ b/compiler/characterizer/lib.py @@ -21,8 +21,6 @@ class lib: """ lib file generation.""" - default_max_transition = 0.5 - def __init__(self, out_dir, sram, sp_file, use_model=OPTS.analytical_delay): try: @@ -283,7 +281,7 @@ def write_defaults(self): self.lib.write(" default_input_pin_cap : 1.0 ;\n") self.lib.write(" default_inout_pin_cap : 1.0 ;\n") self.lib.write(" default_output_pin_cap : 0.0 ;\n") - self.lib.write(" default_max_transition : {} ;\n".format(self.default_max_transition)) + self.lib.write(" default_max_transition : {} ;\n".format(OPTS.max_transition)) self.lib.write(" default_fanout_load : 1.0 ;\n") self.lib.write(" default_max_fanout : 4.0 ;\n") self.lib.write(" default_connection_class : universal ;\n\n") @@ -486,7 +484,7 @@ def write_addr_bus(self, port): self.lib.write(" bus_type : addr; \n") self.lib.write(" direction : input; \n") self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"]/1000)) - max_transition = max(self.default_max_transition, self.slews[-1]) + max_transition = max(OPTS.max_transition, self.slews[-1]) self.lib.write(" max_transition : {0};\n".format(max_transition)) self.lib.write(" pin(addr{0}[{1}:0])".format(port,self.sram.addr_size-1)) self.lib.write("{\n") @@ -502,7 +500,7 @@ def write_wmask_bus(self, port): self.lib.write(" bus_type : wmask; \n") self.lib.write(" direction : input; \n") self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"] / 1000)) - max_transition = max(self.default_max_transition, self.slews[-1]) + max_transition = max(OPTS.max_transition, self.slews[-1]) self.lib.write(" max_transition : {0};\n".format(max_transition)) self.lib.write(" pin(wmask{0}[{1}:0])".format(port, self.sram.num_wmasks - 1)) self.lib.write("{\n") diff --git a/compiler/globals.py b/compiler/globals.py index 98d6007ab..156249989 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -544,6 +544,12 @@ def set_default_corner(): if (OPTS.slew_scales == ""): OPTS.slew_scales = [0.25, 1, 8] + # Lower bound for the max_transition emitted on input pins. A + # characterized slew range can be far tighter than the technology can + # actually drive, which produces a limit downstream STA rejects. + if (OPTS.max_transition == ""): + OPTS.max_transition = 0.5 + def import_tech(): """ Dynamically adds the tech directory to the path and imports it. """ diff --git a/compiler/options.py b/compiler/options.py index 229f7c3a1..15d5c16e4 100644 --- a/compiler/options.py +++ b/compiler/options.py @@ -43,6 +43,7 @@ class options(optparse.Values): process_corners = "" load_scales = "" slew_scales = "" + max_transition = "" # Size parameters must be specified by user in config file. # num_words = 0 diff --git a/compiler/tests/23_lib_sram_test.py b/compiler/tests/23_lib_sram_test.py index 30d3147bd..4595d0e5e 100755 --- a/compiler/tests/23_lib_sram_test.py +++ b/compiler/tests/23_lib_sram_test.py @@ -6,8 +6,7 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import sys, os, re, io -import types +import sys, os, re import unittest from testutils import * @@ -70,18 +69,6 @@ def runTest(self): golden = "{0}/golden/{1}".format(os.path.dirname(os.path.realpath(__file__)),filename) self.assertTrue(self.isapproxdiff(libname,golden,0.40)) - writer = lib.__new__(lib) - writer.lib = io.StringIO() - writer.sram = types.SimpleNamespace(addr_size=4, num_wmasks=2) - writer.slews = [0.00125, 0.005, 0.04] - writer.write_FF_setuphold = lambda port: None - writer.write_addr_bus(0) - writer.write_wmask_bus(0) - input_limits = re.findall(r"(?m)^\s+max_transition\s*:\s*([0-9.eE+-]+)", - writer.lib.getvalue()) - self.assertEqual([float(value) for value in input_limits], - [lib.default_max_transition, lib.default_max_transition]) - reload(characterizer) openram.end_openram()