From 9a14f318d6de379fef1d000565a258f418660c2f Mon Sep 17 00:00:00 2001 From: kris j Date: Thu, 20 Nov 2025 06:57:05 +0000 Subject: [PATCH] feat(precheck): check if power pins have `USE` in definition (#139) * feat(precheck): check if power pins have `USE` in definition * fix: expect zero or more spaces, instead of one or more * feat(precheck): check for missing `USE POWER` * feat(precheck): check for missing `USE GROUND` * feat(precheck): add simple README with link to LEF/DEF reference manual --- precheck/README.md | 4 ++ precheck/precheck.py | 27 +++++++++- precheck/test_precheck.py | 106 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 precheck/README.md diff --git a/precheck/README.md b/precheck/README.md new file mode 100644 index 0000000..600d4cf --- /dev/null +++ b/precheck/README.md @@ -0,0 +1,4 @@ +# Precheck + +## Documentation reference +- [LEF/DEF Language Reference (cadence) (November 2009)](https://www.ispd.cc/contests/18/lefdefref.pdf) ([web archive](https://web.archive.org/web/20250809120606/https://www.ispd.cc/contests/18/lefdefref.pdf)) \ No newline at end of file diff --git a/precheck/precheck.py b/precheck/precheck.py index 41a95eb..4a29d1b 100644 --- a/precheck/precheck.py +++ b/precheck/precheck.py @@ -154,7 +154,7 @@ def boundary_check(gds: str, tech: str): def power_pin_check(verilog: str, lef: str, uses_3v3: bool): - """Ensure that VPWR / VGND are present, + """Ensure that VPWR / VGND are present and have USE definitions, and that VAPWR is present if and only if 'uses_3v3' is set.""" verilog_s = open(verilog).read().replace("VPWR", "VDPWR") lef_s = open(lef).read().replace("VPWR", "VDPWR") @@ -163,6 +163,12 @@ def power_pin_check(verilog: str, lef: str, uses_3v3: bool): verilog_s = re.sub("//.*", "", verilog_s) verilog_s = re.sub("/\\*.*?\\*/", "", verilog_s, flags=(re.DOTALL | re.MULTILINE)) + # this looks for a line beginning with "PIN", captures the name of the pin and the body up until its "END" + PIN_PATTERN = re.compile( + r"^\s*PIN (VPWR|VDPWR|VAPWR|VGND)\s*([\s\S]+?(?=^\s*END \1))", + flags=re.MULTILINE, + ) + for ft, s in (("Verilog", verilog_s), ("LEF", lef_s)): for pwr, ex in (("VGND", True), ("VDPWR", True), ("VAPWR", uses_3v3)): if (pwr in s) and not ex: @@ -170,6 +176,25 @@ def power_pin_check(verilog: str, lef: str, uses_3v3: bool): if not (pwr in s) and ex: raise PrecheckFailure(f"{ft} doesn't contain {pwr}") + for match in PIN_PATTERN.finditer(lef_s): + pin, definition = match.groups() + + match pin: + case "VPWR" | "VDPWR" | "VAPWR": + if "USE POWER" not in definition: + raise PrecheckFailure( + f"{pin} does not have a corresponding 'USE POWER ;'" + ) + + case "VGND": + if "USE GROUND" not in definition: + raise PrecheckFailure( + f"{pin} does not have a corresponding 'USE GROUND ;'" + ) + + case _: + raise PrecheckFailure(f"unhandled {pin}") + def layer_check(gds: str, tech: str): """Check that there are no invalid layers in the GDS file.""" diff --git a/precheck/test_precheck.py b/precheck/test_precheck.py index 6e043b8..b2c6871 100644 --- a/precheck/test_precheck.py +++ b/precheck/test_precheck.py @@ -167,6 +167,94 @@ def verilog_lef_wrong_power_pins(tmp_path_factory: pytest.TempPathFactory): return str(verilog_file), str(lef_file) +@pytest.fixture(scope="session") +def verilog_lef_missing_use_power(tmp_path_factory: pytest.TempPathFactory): + """Creates a Verilog & LEF file with wrong power pins.""" + verilog_file = tmp_path_factory.mktemp("verilog") / "TEST_missing_use_power.v" + verilog_data = """ + `default_nettype none + module TEST_wrong_power_pins ( + input wire VGND, + input wire VDPWR + ); + endmodule + """ + open(verilog_file, "w").write(textwrap.dedent(verilog_data)) + lef_file = tmp_path_factory.mktemp("lef") / "TEST_missing_use_power.lef" + lef_data = """ + VERSION 5.7 ; + NOWIREEXTENSIONATPIN ON ; + DIVIDERCHAR "/" ; + BUSBITCHARS "[]" ; + MACRO TEST_missing_use_power + CLASS BLOCK ; + FOREIGN TEST_missing_use_power ; + ORIGIN 0.000 0.000 ; + SIZE 161.000 BY 111.520 ; + PIN VGND + DIRECTION INOUT ; + USE GROUND ; + PORT + LAYER met4 ; + RECT 21.580 2.480 23.180 109.040 ; + END + END VGND + PIN VDPWR + DIRECTION INOUT ; + PORT + LAYER met4 ; + RECT 18.280 2.480 19.880 109.040 ; + END + END VDPWR + """ + open(lef_file, "w").write(textwrap.dedent(lef_data)) + return str(verilog_file), str(lef_file) + + +@pytest.fixture(scope="session") +def verilog_lef_missing_use_ground(tmp_path_factory: pytest.TempPathFactory): + """Creates a Verilog & LEF file with wrong power pins.""" + verilog_file = tmp_path_factory.mktemp("verilog") / "TEST_missing_use_ground.v" + verilog_data = """ + `default_nettype none + module TEST_wrong_power_pins ( + input wire VGND, + input wire VDPWR + ); + endmodule + """ + open(verilog_file, "w").write(textwrap.dedent(verilog_data)) + lef_file = tmp_path_factory.mktemp("lef") / "TEST_missing_use_ground.lef" + lef_data = """ + VERSION 5.7 ; + NOWIREEXTENSIONATPIN ON ; + DIVIDERCHAR "/" ; + BUSBITCHARS "[]" ; + MACRO TEST_missing_use_ground + CLASS BLOCK ; + FOREIGN TEST_missing_use_ground ; + ORIGIN 0.000 0.000 ; + SIZE 161.000 BY 111.520 ; + PIN VGND + DIRECTION INOUT ; + PORT + LAYER met4 ; + RECT 21.580 2.480 23.180 109.040 ; + END + END VGND + PIN VDPWR + DIRECTION INOUT ; + USE POWER ; + PORT + LAYER met4 ; + RECT 18.280 2.480 19.880 109.040 ; + END + END VDPWR + """ + open(lef_file, "w").write(textwrap.dedent(lef_data)) + return str(verilog_file), str(lef_file) + + @pytest.fixture(scope="session") def gds_invalid_layer(tmp_path_factory: pytest.TempPathFactory): """Creates a GDS with a layer/datatype not defined in sky130.""" @@ -531,6 +619,24 @@ def test_wrong_power_pins_2(verilog_lef_wrong_power_pins: tuple[str, str]): precheck.power_pin_check(verilog_file, lef_file, uses_3v3=True) +def test_missing_use_power(verilog_lef_missing_use_power: tuple[str, str]): + verilog_file, lef_file = verilog_lef_missing_use_power + with pytest.raises( + precheck.PrecheckFailure, + match="VDPWR does not have a corresponding 'USE POWER ;'", + ): + precheck.power_pin_check(verilog_file, lef_file, uses_3v3=False) + + +def test_missing_use_ground(verilog_lef_missing_use_ground: tuple[str, str]): + verilog_file, lef_file = verilog_lef_missing_use_ground + with pytest.raises( + precheck.PrecheckFailure, + match="VGND does not have a corresponding 'USE GROUND ;'", + ): + precheck.power_pin_check(verilog_file, lef_file, uses_3v3=False) + + def test_invalid_layer(gds_invalid_layer: str): with pytest.raises(precheck.PrecheckFailure, match="Invalid layers in GDS"): precheck.layer_check(gds_invalid_layer, "sky130A")