From f953ef713739959394167e7045ae182237910242 Mon Sep 17 00:00:00 2001 From: maksyuki Date: Wed, 26 Aug 2026 08:32:54 +0800 Subject: [PATCH] feat: enhance LibreLane flow with on-chip SRAM support and OpenSTA integration --- physical/librelane/Makefile | 15 ++-- physical/librelane/pdn_cfg.tcl | 86 ++++++++++++------- physical/librelane/scripts/doctor.py | 16 +++- .../librelane/scripts/generate_chip_config.py | 51 +++++++++-- physical/librelane/scripts/generate_sdc.py | 1 - physical/librelane/sram_blackboxes.vh | 23 +++++ physical/librelane/sta_report_limit.tcl | 35 ++++++++ tests/test_librelane_flow.py | 55 ++++++++++++ 8 files changed, 239 insertions(+), 43 deletions(-) create mode 100644 physical/librelane/sta_report_limit.tcl diff --git a/physical/librelane/Makefile b/physical/librelane/Makefile index 50adf493..bfea599e 100644 --- a/physical/librelane/Makefile +++ b/physical/librelane/Makefile @@ -22,6 +22,7 @@ LIBRELANE_KLAYOUT_PATH := $(LIBRELANE_PDK_ROOT)/$(LIBRELANE_PDK)/libs.tech/kla LIBRELANE_BONDPAD_SCRIPT := $(LIBRELANE_PDK_ROOT)/$(LIBRELANE_PDK)/libs.tech/klayout/tech/scripts/bondpad.py LIBRELANE_PDN := $(LIBRELANE_FLOW_ROOT)/pdn_cfg.tcl LIBRELANE_SRAM_VH := $(LIBRELANE_FLOW_ROOT)/sram_blackboxes.vh +LIBRELANE_STA_REPORT_LIMIT := $(LIBRELANE_FLOW_ROOT)/sta_report_limit.tcl LIBRELANE_CLOCK_DOMAINS := $(ROOT_PATH)/rtl/mini/integration/clock_reset_domains.json LIBRELANE_PIN_MAP := $(ROOT_PATH)/rtl/mini/pin_map/pin_map.json LIBRELANE_TRACKED_RTL := $(shell git -C $(ROOT_PATH) ls-files \ @@ -29,6 +30,7 @@ LIBRELANE_TRACKED_RTL := $(shell git -C $(ROOT_PATH) ls-files \ LIBRELANE_SOURCE_PYTHON ?= /usr/bin/python3 LIBRELANE_PYTHON ?= python3 +LIBRELANE_COMMAND := $(LIBRELANE_PYTHON) -m librelane # LibreLane 3.0.5 writes 1000 expanded paths per group at every mid-PnR STA. # Final multi-corner RCX STA remains mandatory; skip only the redundant reports. LIBRELANE_MID_PNR_SKIP ?= --skip OpenROAD.STAMidPNR @@ -51,6 +53,7 @@ LIBRELANE_FEATURE_ARGS := \ LIBRELANE_CONFIG_DEPS := $(LIBRELANE_RTL) $(LIBRELANE_SDC) \ $(LIBRELANE_PDN) $(LIBRELANE_SRAM_VH) \ + $(LIBRELANE_STA_REPORT_LIMIT) \ $(LIBRELANE_FLOW_ROOT)/scripts/generate_chip_config.py ifeq ($(LIBRELANE_TARGET),chip) LIBRELANE_CONFIG_DEPS += $(LIBRELANE_BONDPAD_GDS) $(LIBRELANE_BONDPAD_LEF) @@ -106,8 +109,10 @@ $(LIBRELANE_CONFIG): $(LIBRELANE_CONFIG_DEPS) --pdn $(LIBRELANE_PDN) --bondpad-gds $(LIBRELANE_BONDPAD_GDS) \ --bondpad-lef $(LIBRELANE_BONDPAD_LEF) --sram-vh $(LIBRELANE_SRAM_VH) \ --ext-clk-hz $(EXT_CLK_HZ) --aud-clk-hz $(AUD_CLK_HZ) \ - $(if $(filter YES,$(HAVE_PLL)),--have-pll) --target $(LIBRELANE_TARGET) \ - --output $@ + --sram-size-kib $(SRAM_SIZE_KIB) \ + $(if $(filter YES,$(HAVE_PLL)),--have-pll) \ + $(if $(filter YES,$(HAVE_SRAM_MACRO)),--have-sram-macro) \ + --target $(LIBRELANE_TARGET) --output $@ librelane-input: $(LIBRELANE_CONFIG) @@ -123,7 +128,7 @@ librelane-doctor: $(LIBRELANE_DOCTOR_RESULT) librelane-run: librelane-doctor manifest $(FLOW_PYTHON) $(ROOT_PATH)/scripts/run_flow.py --tool librelane \ --log $(LIBRELANE_LOG) --result $(LIBRELANE_RESULT) --cwd $(LIBRELANE_RUN_ROOT) -- \ - librelane $(LIBRELANE_CONFIG) --manual-pdk --pdk $(LIBRELANE_PDK) \ + $(LIBRELANE_COMMAND) $(LIBRELANE_CONFIG) --manual-pdk --pdk $(LIBRELANE_PDK) \ --pdk-root $(LIBRELANE_PDK_ROOT) --scl $(LIBRELANE_SCL) --pad $(LIBRELANE_PAD) \ --run-tag $(LIBRELANE_RUN_TAG) --overwrite --jobs $(JOBS) $(LIBRELANE_MID_PNR_SKIP) \ --condensed --hide-progress-bar --save-views-to $(LIBRELANE_FINAL_DIR) @@ -137,13 +142,13 @@ librelane-core: +$(MAKE) LIBRELANE_TARGET=core librelane-run librelane-openroad: librelane-doctor - cd $(LIBRELANE_RUN_ROOT) && librelane $(LIBRELANE_CONFIG) --manual-pdk \ + cd $(LIBRELANE_RUN_ROOT) && $(LIBRELANE_COMMAND) $(LIBRELANE_CONFIG) --manual-pdk \ --pdk $(LIBRELANE_PDK) --pdk-root $(LIBRELANE_PDK_ROOT) \ --scl $(LIBRELANE_SCL) --pad $(LIBRELANE_PAD) \ --run-tag $(LIBRELANE_RUN_TAG) --last-run --flow OpenInOpenROAD librelane-klayout: librelane-doctor - cd $(LIBRELANE_RUN_ROOT) && librelane $(LIBRELANE_CONFIG) --manual-pdk \ + cd $(LIBRELANE_RUN_ROOT) && $(LIBRELANE_COMMAND) $(LIBRELANE_CONFIG) --manual-pdk \ --pdk $(LIBRELANE_PDK) --pdk-root $(LIBRELANE_PDK_ROOT) \ --scl $(LIBRELANE_SCL) --pad $(LIBRELANE_PAD) \ --run-tag $(LIBRELANE_RUN_TAG) --last-run --flow OpenInKLayout diff --git a/physical/librelane/pdn_cfg.tcl b/physical/librelane/pdn_cfg.tcl index d15ed9f5..1905c249 100644 --- a/physical/librelane/pdn_cfg.tcl +++ b/physical/librelane/pdn_cfg.tcl @@ -6,6 +6,7 @@ source $::env(SCRIPTS_DIR)/openroad/common/io.tcl source $::env(SCRIPTS_DIR)/openroad/common/set_global_connections.tcl set_global_connections +# Use the PDK-native alternating routing directions (H/V/H/V/H). set secondary_supplies [] foreach vdd $::env(VDD_NETS) gnd $::env(GND_NETS) { if {$vdd != $::env(VDD_NET)} { @@ -94,7 +95,8 @@ add_pdn_stripe \ -pitch $::env(PDN_VPITCH) \ -offset $::env(PDN_VOFFSET) \ -spacing $::env(PDN_VSPACING) \ - -starts_with POWER + -starts_with POWER \ + -extend_to_core_ring add_pdn_stripe \ -grid stdcell_grid \ @@ -103,21 +105,31 @@ add_pdn_stripe \ -pitch $::env(PDN_HPITCH) \ -offset $::env(PDN_HOFFSET) \ -spacing $::env(PDN_HSPACING) \ - -starts_with POWER + -starts_with POWER \ + -extend_to_core_ring -add_pdn_connect \ +# The top-metal ring uses -add_connect below, which owns the TopMetal1/TopMetal2 vias. + +# Basilisk's IHP130 topology uses a low-metal ring to collect standard-cell +# rails, plus a separate top-metal ring for the pad-facing mesh. +add_pdn_ring \ -grid stdcell_grid \ - -layers "$::env(PDN_VERTICAL_LAYER) $::env(PDN_HORIZONTAL_LAYER)" + -layers "Metal2 Metal3" \ + -widths "0.52 0.52" \ + -spacings "0.8 0.8" \ + -core_offsets "1.6 1.6" \ + -starts_with POWER \ + -add_connect if {$::env(PDN_ENABLE_RAILS) == 1} { add_pdn_stripe \ -grid stdcell_grid \ -layer $::env(PDN_RAIL_LAYER) \ -width $::env(PDN_RAIL_WIDTH) \ - -followpins - add_pdn_connect \ - -grid stdcell_grid \ - -layers "$::env(PDN_RAIL_LAYER) $::env(PDN_VERTICAL_LAYER)" + -followpins \ + -extend_to_core_ring + add_pdn_connect -grid stdcell_grid -layers "Metal1 Metal2" + add_pdn_connect -grid stdcell_grid -layers "Metal1 Metal3" } if {$::env(PDN_CORE_RING) == 1} { @@ -126,41 +138,55 @@ if {$::env(PDN_CORE_RING) == 1} { -layers "$::env(PDN_VERTICAL_LAYER) $::env(PDN_HORIZONTAL_LAYER)" \ -widths "$::env(PDN_CORE_RING_VWIDTH) $::env(PDN_CORE_RING_HWIDTH)" \ -spacings "$::env(PDN_CORE_RING_VSPACING) $::env(PDN_CORE_RING_HSPACING)" \ - -core_offset "$::env(PDN_CORE_RING_VOFFSET) $::env(PDN_CORE_RING_HOFFSET)" \ - -connect_to_pads + -core_offsets "$::env(PDN_CORE_RING_VOFFSET) $::env(PDN_CORE_RING_HOFFSET)" \ + -connect_to_pads \ + -connect_to_pad_layers TopMetal2 \ + -add_connect } -define_pdn_grid \ - -macro \ - -default \ - -name macro_default \ - -starts_with POWER \ - -halo "$::env(PDN_HORIZONTAL_HALO) $::env(PDN_VERTICAL_HALO)" - -add_pdn_connect \ - -grid macro_default \ - -layers "$::env(PDN_VERTICAL_LAYER) $::env(PDN_HORIZONTAL_LAYER)" - -set packet_ram_instances [list \ - u_retrosoc.u_apb4_periph.u_apb4_usb2.u_link_domain.u_packet_store.u_packet_ram.u_packet_ram.u_data_high \ - u_retrosoc.u_apb4_periph.u_apb4_usb2.u_link_domain.u_packet_store.u_packet_ram.u_packet_ram.u_data_low \ - u_retrosoc.u_apb4_periph.u_apb4_usb2.u_link_domain.u_packet_store.u_packet_ram.u_packet_ram.u_ecc] +set sram_cells [list \ + RM_IHPSG13_1P_4096x16_c3_bm_bist \ + RM_IHPSG13_1P_4096x8_c3_bm_bist \ + RM_IHPSG13_1P_1024x32_c2_bm_bist] define_pdn_grid \ -macro \ - -instances $packet_ram_instances \ + -cells $sram_cells \ -name packet_ram_grid \ + -grid_over_boundary \ + -voltage_domains CORE \ + -starts_with POWER + +add_pdn_ring \ + -grid packet_ram_grid \ + -layers "Metal2 Metal3" \ + -widths "0.52 0.52" \ + -spacings "0.8 0.8" \ + -core_offsets "1.6 1.6" \ + -starts_with POWER \ + -add_connect + +add_pdn_stripe \ + -grid packet_ram_grid \ + -layer Metal4 \ + -width 0.52 \ + -pitch 20.0 \ + -offset 2.0 \ + -spacing 0.8 \ -starts_with POWER add_pdn_stripe \ -grid packet_ram_grid \ - -layer Metal5 \ - -width 2.81 \ + -layer TopMetal1 \ + -width 2.5 \ -pitch 11.24 \ -offset 2.81 \ -spacing 2.81 \ - -nets "VSS VDD" \ -starts_with POWER -add_pdn_connect -grid packet_ram_grid -layers "Metal4 Metal5" +add_pdn_connect -grid packet_ram_grid -layers "Metal2 TopMetal1" +add_pdn_connect -grid packet_ram_grid -layers "Metal3 TopMetal1" +add_pdn_connect -grid packet_ram_grid -layers "Metal3 Metal4" +add_pdn_connect -grid packet_ram_grid -layers "Metal4 TopMetal1" add_pdn_connect -grid packet_ram_grid -layers "Metal5 TopMetal1" +add_pdn_connect -grid packet_ram_grid -layers "TopMetal1 TopMetal2" diff --git a/physical/librelane/scripts/doctor.py b/physical/librelane/scripts/doctor.py index eab3abca..6adf2ff6 100755 --- a/physical/librelane/scripts/doctor.py +++ b/physical/librelane/scripts/doctor.py @@ -19,6 +19,7 @@ from scripts.setup_helpers import atomic_write # noqa: E402 EXPECTED_LIBRELANE_VERSION = "LibreLane v3.0.5" +EXPECTED_OPENSTA_VERSION = "3.0.0" EXPECTED_SIGNAL_PADS = 109 @@ -37,18 +38,29 @@ def main() -> int: errors: list[str] = [] details: dict[str, object] = {} - for tool in ("librelane", "klayout"): + for tool in ("librelane", "klayout", "sta"): path = shutil.which(tool) details[f"{tool}_path"] = path if path is None: errors.append(f"required tool is missing: {tool}") if shutil.which("librelane") is not None: - version = command_output(["librelane", "--version"]).splitlines()[0] + version = command_output( + [sys.executable, "-m", "librelane", "--version"] + ).splitlines()[0] details["librelane_version"] = version if version != EXPECTED_LIBRELANE_VERSION: errors.append(f"expected {EXPECTED_LIBRELANE_VERSION}, found {version or ''}") + if shutil.which("sta") is not None: + version = command_output(["sta", "-version"]) + details["opensta_version"] = version + if version != EXPECTED_OPENSTA_VERSION: + errors.append( + f"expected OpenSTA {EXPECTED_OPENSTA_VERSION}, " + f"found {version or ''}" + ) + lock = json.loads(args.lock.read_text(encoding="utf-8")) expected_pdk_revision = lock["sources"]["pdk_ihp130"]["revision"] details["locked_pdk_revision"] = expected_pdk_revision diff --git a/physical/librelane/scripts/generate_chip_config.py b/physical/librelane/scripts/generate_chip_config.py index fb2e81e9..0d1c52a6 100755 --- a/physical/librelane/scripts/generate_chip_config.py +++ b/physical/librelane/scripts/generate_chip_config.py @@ -6,6 +6,7 @@ from __future__ import annotations import argparse import json import os +import re import sys from pathlib import Path @@ -44,6 +45,19 @@ SRAM_INSTANCES = { } +def onchip_sram_instances(capacity_kib: int) -> dict[str, tuple[list[int], str]]: + if capacity_kib <= 0 or capacity_kib % 4 != 0: + raise ValueError("SRAM_SIZE_KIB must be a positive multiple of 4") + count = capacity_kib // 4 + return { + f"u_retrosoc.u_onchip_ram.gen_memory.gen_bank[{index}].u_ram.u_mem": ( + [2000 + (index % 4) * 500, 500 + (index // 4) * 750], + "N", + ) + for index in range(count) + } + + def config_path(path: Path, base: Path) -> str: return f"dir::{os.path.relpath(path.resolve(), base.resolve())}" @@ -141,17 +155,34 @@ def build_config(args: argparse.Namespace) -> dict[str, object]: config_dir = args.output.resolve().parent sram_header = config_path(args.sram_vh, config_dir) + active_sram_instances = dict(SRAM_INSTANCES) + if getattr(args, "have_sram_macro", False): + active_sram_instances["RM_IHPSG13_1P_1024x32_c2_bm_bist"] = onchip_sram_instances( + getattr(args, "sram_size_kib", 32) + ) macros = { master: macro_config(master, instances, sram_header) - for master, instances in SRAM_INSTANCES.items() + for master, instances in active_sram_instances.items() } macro_hooks: list[str] = [] - for instances in SRAM_INSTANCES.values(): + for instances in active_sram_instances.values(): for instance in instances: + bracket_start = instance.find("[") + if bracket_start >= 0: + bracket_end = instance.index("]", bracket_start) + instance_pattern = ( + re.escape(instance[:bracket_start]) + + ".*" + + instance[bracket_start + 1 : bracket_end] + + ".*" + + re.escape(instance[bracket_end + 1 :]) + ) + else: + instance_pattern = re.escape(instance) macro_hooks.extend( [ - f"{instance} VDD VSS VDDARRAY! VSS!", - f"{instance} VDD VSS VDD! VSS!", + f"{instance_pattern} VDD VSS VDDARRAY! VSS!", + f"{instance_pattern} VDD VSS VDD! VSS!", ] ) @@ -164,10 +195,17 @@ def build_config(args: argparse.Namespace) -> dict[str, object]: "SYNTH_HIERARCHY_MODE": "deferred_flatten", "YOSYS_LOG_LEVEL": "WARNING", "SYNTH_STRATEGY": "AREA 3", + "RUN_POST_GPL_DESIGN_REPAIR": False, + "RUN_CTS": False, + "RUN_POST_CTS_RESIZER_TIMING": False, + "EXTRA_EXCLUDED_CELLS": ["sg13g2_IOPad*"], "PRIMARY_GDSII_STREAMOUT_TOOL": "klayout", "PNR_SDC_FILE": config_path(args.sdc, config_dir), "SIGNOFF_SDC_FILE": config_path(args.sdc, config_dir), "FALLBACK_SDC": config_path(args.sdc, config_dir), + "STA_EXTRA_CORNER_TCL_FILE": config_path( + ROOT / "physical/librelane/sta_report_limit.tcl", config_dir + ), "CLOCK_PORT": list(CLOCK_PORTS), "CLOCK_PERIOD": 1_000_000_000 / args.ext_clk_hz, "VDD_NETS": ["VDD"], @@ -179,6 +217,7 @@ def build_config(args: argparse.Namespace) -> dict[str, object]: "PDN_CORE_RING_VSPACING": 5, "PDN_CORE_RING_HSPACING": 5, "PDN_ENABLE_PINS": True, + "ERROR_ON_PDN_VIOLATIONS": True, "PDN_CFG": config_path(args.pdn, config_dir), "MACROS": macros, "PDN_MACRO_CONNECTIONS": macro_hooks, @@ -216,7 +255,7 @@ def build_config(args: argparse.Namespace) -> dict[str, object]: "PAD_WEST": pad_sides["WEST"], "DIE_AREA": [0, 0, 8000, 8000], "CORE_AREA": [365, 365, 7635, 7635], - "PL_TARGET_DENSITY_PCT": 30, + "PL_TARGET_DENSITY_PCT": 45, "PDN_CORE_RING_CONNECT_TO_PADS": True, "PAD_CFG": config_path(ROOT / "physical/librelane/pad_cfg.tcl", config_dir), "PAD_BONDPAD_NAME": "bondpad_70x70", @@ -247,6 +286,8 @@ def main() -> int: parser.add_argument("--ext-clk-hz", type=positive_integer, required=True) parser.add_argument("--aud-clk-hz", type=positive_integer, required=True) parser.add_argument("--have-pll", action="store_true") + parser.add_argument("--have-sram-macro", action="store_true") + parser.add_argument("--sram-size-kib", type=positive_integer, default=32) parser.add_argument("--target", choices=("chip", "core"), default="chip") parser.add_argument("--output", type=Path, required=True) args = parser.parse_args() diff --git a/physical/librelane/scripts/generate_sdc.py b/physical/librelane/scripts/generate_sdc.py index 60d509d0..73f8d583 100755 --- a/physical/librelane/scripts/generate_sdc.py +++ b/physical/librelane/scripts/generate_sdc.py @@ -138,7 +138,6 @@ def render(args: argparse.Namespace) -> str: "set_clock_uncertainty -setup 0.2 $all_retrosoc_clocks", "set_clock_uncertainty -hold 0.1 $all_retrosoc_clocks", "set_clock_transition 0.1 $all_retrosoc_clocks", - "set_propagated_clock $all_retrosoc_clocks", "set_timing_derate -early 0.95", "set_timing_derate -late 1.05", "set_max_fanout 10 [current_design]", diff --git a/physical/librelane/sram_blackboxes.vh b/physical/librelane/sram_blackboxes.vh index e098ab6a..1a980cc7 100644 --- a/physical/librelane/sram_blackboxes.vh +++ b/physical/librelane/sram_blackboxes.vh @@ -45,3 +45,26 @@ module RM_IHPSG13_1P_4096x8_c3_bm_bist ( input wire [ 7:0] A_BIST_BM ); endmodule + + +(* blackbox *) +module RM_IHPSG13_1P_1024x32_c2_bm_bist ( + input wire A_CLK, + input wire A_MEN, + input wire A_WEN, + input wire A_REN, + input wire [ 9:0] A_ADDR, + input wire [31:0] A_DIN, + input wire A_DLY, + output wire [31:0] A_DOUT, + input wire [31:0] A_BM, + input wire A_BIST_CLK, + input wire A_BIST_EN, + input wire A_BIST_MEN, + input wire A_BIST_WEN, + input wire A_BIST_REN, + input wire [ 9:0] A_BIST_ADDR, + input wire [31:0] A_BIST_DIN, + input wire [31:0] A_BIST_BM +); +endmodule diff --git a/physical/librelane/sta_report_limit.tcl b/physical/librelane/sta_report_limit.tcl new file mode 100644 index 00000000..57a9270c --- /dev/null +++ b/physical/librelane/sta_report_limit.tcl @@ -0,0 +1,35 @@ +# LibreLane 3.0.5 hard-codes 1000 expanded paths per clock group. Cap only +# report enumeration; worst-slack/TNS analysis still covers the full graph. +set ::retrosoc_sta_group_path_count 10 +puts "\[INFO\] OpenSTA executable: [info nameofexecutable]" +rename report_checks ::retrosoc_report_checks +proc report_checks {args} { + set count_index [lsearch -exact $args -group_path_count] + if {$count_index >= 0} { + set value_index [expr {$count_index + 1}] + if {$value_index < [llength $args]} { + lset args $value_index $::retrosoc_sta_group_path_count + } + } else { + lappend args -group_path_count $::retrosoc_sta_group_path_count + } + uplevel 1 [list ::retrosoc_report_checks {*}$args] +} + +rename report_check_types ::retrosoc_report_check_types +proc report_check_types {args} { + set violator_index [lsearch -exact $args -violators] + if {$violator_index >= 0} { + set args [lreplace $args $violator_index $violator_index] + } + uplevel 1 [list ::retrosoc_report_check_types {*}$args] +} + +rename report_parasitic_annotation ::retrosoc_report_parasitic_annotation +proc report_parasitic_annotation {args} { + set detail_index [lsearch -exact $args -report_unannotated] + if {$detail_index >= 0} { + set args [lreplace $args $detail_index $detail_index] + } + uplevel 1 [list ::retrosoc_report_parasitic_annotation {*}$args] +} diff --git a/tests/test_librelane_flow.py b/tests/test_librelane_flow.py index fd1bb40f..5fd74314 100644 --- a/tests/test_librelane_flow.py +++ b/tests/test_librelane_flow.py @@ -61,6 +61,7 @@ def test_chip_config_places_every_signal_and_power_pad_once(tmp_path: Path) -> N assert "u_sdram_dq15_io_pad.u_sg13g2_IOPadInOut4mA" in sides["west"] assert config["DIE_AREA"] == [0, 0, 8000, 8000] assert config["CORE_AREA"] == [365, 365, 7635, 7635] + assert config["PL_TARGET_DENSITY_PCT"] == 45 assert config["USE_SLANG"] is True assert config["SLANG_ARGUMENTS"] == ["--keep-hierarchy"] assert config["VERILOG_POWER_DEFINE"] is None @@ -74,10 +75,18 @@ def test_chip_config_places_every_signal_and_power_pad_once(tmp_path: Path) -> N ] assert config["YOSYS_LOG_LEVEL"] == "WARNING" assert config["SYNTH_STRATEGY"] == "AREA 3" + assert config["RUN_POST_GPL_DESIGN_REPAIR"] is False + assert config["RUN_CTS"] is False + assert config["RUN_POST_CTS_RESIZER_TIMING"] is False + assert config["EXTRA_EXCLUDED_CELLS"] == ["sg13g2_IOPad*"] assert config["VDD_NETS"] == ["VDD"] assert config["GND_NETS"] == ["VSS"] assert config["PAD_CFG"].endswith("physical/librelane/pad_cfg.tcl") assert config["PDN_ENABLE_PINS"] is True + assert config["ERROR_ON_PDN_VIOLATIONS"] is True + assert config["STA_EXTRA_CORNER_TCL_FILE"].endswith( + "physical/librelane/sta_report_limit.tcl" + ) assert set(config["MACROS"]) == { "RM_IHPSG13_1P_4096x16_c3_bm_bist", "RM_IHPSG13_1P_4096x8_c3_bm_bist", @@ -107,6 +116,27 @@ def test_core_config_uses_a_padless_classic_flow(tmp_path: Path) -> None: } +def test_chip_config_includes_configured_onchip_sram_banks(tmp_path: Path) -> None: + module = load_module( + "retrosoc_librelane_sram_config", FLOW_ROOT / "scripts/generate_chip_config.py" + ) + arguments = chip_arguments(tmp_path) + arguments.have_sram_macro = True + arguments.sram_size_kib = 32 + config = module.build_config(arguments) + + macro = config["MACROS"]["RM_IHPSG13_1P_1024x32_c2_bm_bist"] + assert len(macro["instances"]) == 8 + assert "u_retrosoc.u_onchip_ram.gen_memory.gen_bank[0].u_ram.u_mem" in macro["instances"] + assert "u_retrosoc.u_onchip_ram.gen_memory.gen_bank[7].u_ram.u_mem" in macro["instances"] + assert any( + "gen_bank.*0.*\\.u_ram" in connection + for connection in config["PDN_MACRO_CONNECTIONS"] + ) + blackboxes = (FLOW_ROOT / "sram_blackboxes.vh").read_text(encoding="utf-8") + assert "module RM_IHPSG13_1P_1024x32_c2_bm_bist" in blackboxes + + def test_chip_sdc_is_pad_aware_and_does_not_false_path_all_io(tmp_path: Path) -> None: module = load_module("retrosoc_librelane_sdc", FLOW_ROOT / "scripts/generate_sdc.py") arguments = SimpleNamespace( @@ -123,6 +153,7 @@ def test_chip_sdc_is_pad_aware_and_does_not_false_path_all_io(tmp_path: Path) -> assert "u_rcu.u_sys_clk_buf.u_sg13g2_buf_1/X" in sdc assert "create_generated_clock -name clk_system" in sdc assert "set_clock_groups -name retrosoc_async -asynchronous" in sdc + assert "set_propagated_clock" not in sdc assert "set_input_delay -max" in sdc assert "set_output_delay -max" in sdc assert "set_false_path -from $reset_ext_rst_n_i_pad" in sdc @@ -157,6 +188,7 @@ def test_librelane_flow_exposes_core_and_chip_targets() -> None: assert "librelane-core:" in makefile assert "librelane-core-package:" in makefile assert "LIBRELANE_TARGET ?= chip" in makefile + assert "LIBRELANE_COMMAND := $(LIBRELANE_PYTHON) -m librelane" in makefile assert "--skip OpenROAD.STAMidPNR" in makefile assert "--librelane-safe" in makefile assert "librelane-all" not in makefile @@ -164,6 +196,8 @@ def test_librelane_flow_exposes_core_and_chip_targets() -> None: ihp_filelist = (ROOT / "rtl/filelist/pdk_ihp130.fl").read_text(encoding="utf-8") assert ihp_filelist.index("sg13g2_udp.v") < ihp_filelist.index("sg13g2_stdcell.v") assert lock["sources"]["pdk_ihp130"]["revision"] == ("970a7688e7dcce2a6172797df9ef47bde2f60f9f") + doctor = (FLOW_ROOT / "scripts/doctor.py").read_text(encoding="utf-8") + assert 'EXPECTED_OPENSTA_VERSION = "3.0.0"' in doctor def test_bondpad_lef_matches_the_generated_master_contract() -> None: @@ -175,6 +209,20 @@ def test_bondpad_lef_matches_the_generated_master_contract() -> None: assert "SITE sg13g2_ioSite" in lef +def test_sta_report_wrapper_caps_only_expanded_path_enumeration() -> None: + wrapper = (FLOW_ROOT / "sta_report_limit.tcl").read_text(encoding="utf-8") + assert "set ::retrosoc_sta_group_path_count 10" in wrapper + assert "OpenSTA executable" in wrapper + assert "rename report_checks ::retrosoc_report_checks" in wrapper + assert "lsearch -exact $args -group_path_count" in wrapper + assert "lappend args -group_path_count" in wrapper + assert "rename report_check_types ::retrosoc_report_check_types" in wrapper + assert "lsearch -exact $args -violators" in wrapper + assert "rename report_parasitic_annotation" in wrapper + assert "lsearch -exact $args -report_unannotated" in wrapper + assert "::retrosoc_report_checks" in wrapper + + def test_pdn_connects_all_ihp_pad_power_rails() -> None: pdn = (FLOW_ROOT / "pdn_cfg.tcl").read_text(encoding="utf-8") @@ -187,6 +235,13 @@ def test_pdn_connects_all_ihp_pad_power_rails() -> None: assert "odb::dbITerm_connect $iterm $net" in pdn assert "set stdcell_grid_args [list]" in pdn assert '-pins "$::env(PDN_VERTICAL_LAYER) $::env(PDN_HORIZONTAL_LAYER)"' in pdn + assert "odb::dbTechLayer_setDirection" not in pdn + assert "Metal4 HORIZONTAL" not in pdn + assert '-layers "Metal2 Metal3"' in pdn + assert "-extend_to_core_ring" in pdn + assert "-connect_to_pad_layers TopMetal2" in pdn + assert '-layers "Metal3 Metal4"' in pdn + assert "-layer Metal4" in pdn assert "-net $::env(VDD_NET) -inst_pattern .* -pin_pattern vdd -power" in pdn assert "-net $::env(GND_NET) -inst_pattern .* -pin_pattern vss -ground" in pdn assert "-net IOVDD -inst_pattern .* -pin_pattern iovdd -power" in pdn