diff --git a/.gitignore b/.gitignore index 92a9eb8e..6bae6ebb 100644 --- a/.gitignore +++ b/.gitignore @@ -77,3 +77,4 @@ socver.* !/rtl/managed/clusterip/setup.py /rtl/managed/mpw/ /rtl/managed/third_party/ +*result.json \ No newline at end of file diff --git a/physical/README.md b/physical/README.md index 4ffe5598..d3fb75a7 100644 --- a/physical/README.md +++ b/physical/README.md @@ -7,7 +7,7 @@ drivers. - `pdk/` contains the locked PDK setup helpers and managed PDK checkouts. - `smoke/syn/` contains Yosys synthesis and source-export integration. - `smoke/sta/` contains OpenSTA constraints and timing integration. -- `librelane/` contains the single-level open-source IHP130 full-chip pad-ring flow. +- `librelane/` contains the open-source IHP130 core-hardening and full-chip pad-ring flows. - `commercial/` contains licensed-tool orchestration and flow logic. PDKs, foundry decks, commercial libraries, site configuration, and results remain outside Git. diff --git a/physical/librelane/Makefile b/physical/librelane/Makefile index 49e0f1a5..83e5de7e 100644 --- a/physical/librelane/Makefile +++ b/physical/librelane/Makefile @@ -1,16 +1,18 @@ LIBRELANE_FLOW_ROOT := $(ROOT_PATH)/physical/librelane -LIBRELANE_RUN_ROOT := $(VARIANT_ROOT)/physical/librelane/chip +LIBRELANE_TARGET ?= chip +LIBRELANE_RUN_ROOT := $(VARIANT_ROOT)/physical/librelane/$(LIBRELANE_TARGET) LIBRELANE_INPUT_DIR := $(LIBRELANE_RUN_ROOT)/input LIBRELANE_RTL := $(LIBRELANE_INPUT_DIR)/retrosoc_asic_sources.sv -LIBRELANE_SDC := $(LIBRELANE_INPUT_DIR)/retrosoc_asic.sdc +LIBRELANE_SDC := $(LIBRELANE_INPUT_DIR)/retrosoc_$(LIBRELANE_TARGET).sdc LIBRELANE_BONDPAD_GDS := $(LIBRELANE_INPUT_DIR)/bondpad_70x70.gds LIBRELANE_BONDPAD_LEF := $(LIBRELANE_FLOW_ROOT)/bondpad/bondpad_70x70.lef -LIBRELANE_CHIP_CONFIG := $(LIBRELANE_RUN_ROOT)/config.json +LIBRELANE_CONFIG := $(LIBRELANE_RUN_ROOT)/config.json LIBRELANE_LOG := $(LIBRELANE_RUN_ROOT)/librelane.log LIBRELANE_RESULT := $(LIBRELANE_RUN_ROOT)/result.json -LIBRELANE_META_RESULT := $(META_DIR)/librelane-chip.json -LIBRELANE_DOCTOR_RESULT := $(META_DIR)/librelane-doctor.json +LIBRELANE_META_RESULT := $(META_DIR)/librelane-$(LIBRELANE_TARGET).json +LIBRELANE_DOCTOR_RESULT := $(META_DIR)/librelane-$(LIBRELANE_TARGET)-doctor.json LIBRELANE_FINAL_DIR := $(LIBRELANE_RUN_ROOT)/final +LIBRELANE_ARCHIVE := $(LIBRELANE_RUN_ROOT)/retrosoc-ihp130-$(LIBRELANE_TARGET).tar.gz LIBRELANE_RUN_TAG ?= current LIBRELANE_PDK_ROOT := $(ROOT_PATH)/physical/pdk/IHP-Open-PDK LIBRELANE_PDK := ihp-sg13g2 @@ -27,9 +29,17 @@ LIBRELANE_TRACKED_RTL := $(shell git -C $(ROOT_PATH) ls-files \ LIBRELANE_SOURCE_PYTHON ?= /usr/bin/python3 LIBRELANE_PYTHON ?= python3 +# 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 -ifneq ($(filter librelane-input librelane-doctor librelane-chip librelane-openroad \ - librelane-klayout librelane-package,$(MAKECMDGOALS)),) +ifneq ($(filter-out chip core,$(LIBRELANE_TARGET)),) +$(error LIBRELANE_TARGET must be chip or core, got '$(LIBRELANE_TARGET)') +endif + +ifneq ($(filter librelane-input librelane-doctor librelane-run librelane-chip \ + librelane-core librelane-openroad librelane-klayout librelane-package \ + librelane-core-package librelane-package-run,$(MAKECMDGOALS)),) FLOW_PYTHON := $(LIBRELANE_SOURCE_PYTHON) endif @@ -39,14 +49,22 @@ LIBRELANE_FEATURE_ARGS := \ $(if $(filter YES,$(HAVE_SRAM_MACRO)),--have-sram-macro) \ $(if $(filter YES,$(HAVE_SVA)),--have-sva) -.PHONY: librelane-check-pdk librelane-input librelane-doctor librelane-chip \ - librelane-openroad librelane-klayout librelane-package +LIBRELANE_CONFIG_DEPS := $(LIBRELANE_RTL) $(LIBRELANE_SDC) \ + $(LIBRELANE_PDN) $(LIBRELANE_SRAM_VH) \ + $(LIBRELANE_FLOW_ROOT)/scripts/generate_chip_config.py +ifeq ($(LIBRELANE_TARGET),chip) +LIBRELANE_CONFIG_DEPS += $(LIBRELANE_BONDPAD_GDS) $(LIBRELANE_BONDPAD_LEF) +endif + +.PHONY: librelane-check-pdk librelane-input librelane-doctor librelane-run \ + librelane-chip librelane-core librelane-openroad librelane-klayout \ + librelane-package librelane-core-package librelane-package-run librelane-check-pdk: @test '$(PDK)' = IHP130 || { \ echo 'physical/librelane currently implements only PDK=IHP130' >&2; exit 2; } @test '$(HAVE_PLL)' = NO || { \ - echo 'the IHP130 LibreLane pad-ring flow has no qualified crystal PAD' >&2; exit 2; } + echo 'the IHP130 LibreLane flow has no qualified crystal PAD' >&2; exit 2; } $(LIBRELANE_RTL): $(MPW_VARIANT_STAMP) $(FILELIST_STAMP) $(LIBRELANE_TRACKED_RTL) \ $(ROOT_PATH)/physical/smoke/syn/tools/export_soc_sources.py | librelane-check-pdk @@ -72,7 +90,8 @@ $(LIBRELANE_SDC): $(LIBRELANE_FLOW_ROOT)/scripts/generate_sdc.py \ $(FLOW_PYTHON) $(LIBRELANE_FLOW_ROOT)/scripts/generate_sdc.py \ --domains $(LIBRELANE_CLOCK_DOMAINS) --pin-map $(LIBRELANE_PIN_MAP) \ --ext-clk-hz $(EXT_CLK_HZ) --aud-clk-hz $(AUD_CLK_HZ) \ - $(if $(filter YES,$(HAVE_PLL)),--have-pll) --output $@ + $(if $(filter YES,$(HAVE_PLL)),--have-pll) --target $(LIBRELANE_TARGET) \ + --output $@ $(LIBRELANE_BONDPAD_GDS): $(LIBRELANE_BONDPAD_SCRIPT) | librelane-check-pdk @mkdir -p $(@D) @@ -81,51 +100,62 @@ $(LIBRELANE_BONDPAD_GDS): $(LIBRELANE_BONDPAD_SCRIPT) | librelane-check-pdk -rd output=$@ @test -s $@ -$(LIBRELANE_CHIP_CONFIG): $(LIBRELANE_RTL) $(LIBRELANE_SDC) \ - $(LIBRELANE_BONDPAD_GDS) $(LIBRELANE_BONDPAD_LEF) $(LIBRELANE_PDN) \ - $(LIBRELANE_SRAM_VH) $(LIBRELANE_FLOW_ROOT)/scripts/generate_chip_config.py +$(LIBRELANE_CONFIG): $(LIBRELANE_CONFIG_DEPS) $(FLOW_PYTHON) $(LIBRELANE_FLOW_ROOT)/scripts/generate_chip_config.py \ --pin-map $(LIBRELANE_PIN_MAP) --rtl $(LIBRELANE_RTL) --sdc $(LIBRELANE_SDC) \ --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) \ + --ext-clk-hz $(EXT_CLK_HZ) --aud-clk-hz $(AUD_CLK_HZ) \ + $(if $(filter YES,$(HAVE_PLL)),--have-pll) --target $(LIBRELANE_TARGET) \ --output $@ -librelane-input: $(LIBRELANE_CHIP_CONFIG) +librelane-input: $(LIBRELANE_CONFIG) -$(LIBRELANE_DOCTOR_RESULT): $(LIBRELANE_CHIP_CONFIG) \ +$(LIBRELANE_DOCTOR_RESULT): $(LIBRELANE_CONFIG) \ $(LIBRELANE_FLOW_ROOT)/scripts/doctor.py @mkdir -p $(@D) $(LIBRELANE_PYTHON) $(LIBRELANE_FLOW_ROOT)/scripts/doctor.py \ - --root $(ROOT_PATH) --config $(LIBRELANE_CHIP_CONFIG) --lock $(LOCK_FILE) \ + --root $(ROOT_PATH) --config $(LIBRELANE_CONFIG) --lock $(LOCK_FILE) \ --pdk-root $(LIBRELANE_PDK_ROOT) --output $@ librelane-doctor: $(LIBRELANE_DOCTOR_RESULT) -librelane-chip: librelane-doctor manifest +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_CHIP_CONFIG) --manual-pdk --pdk $(LIBRELANE_PDK) \ + librelane $(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) --condensed \ - --hide-progress-bar --save-views-to $(LIBRELANE_FINAL_DIR) + --run-tag $(LIBRELANE_RUN_TAG) --overwrite --jobs $(JOBS) $(LIBRELANE_MID_PNR_SKIP) \ + --condensed --hide-progress-bar --save-views-to $(LIBRELANE_FINAL_DIR) @mkdir -p $(META_DIR) @cp $(LIBRELANE_RESULT) $(LIBRELANE_META_RESULT) +librelane-chip: + +$(MAKE) LIBRELANE_TARGET=chip librelane-run + +librelane-core: + +$(MAKE) LIBRELANE_TARGET=core librelane-run + librelane-openroad: librelane-doctor - cd $(LIBRELANE_RUN_ROOT) && librelane $(LIBRELANE_CHIP_CONFIG) --manual-pdk \ + cd $(LIBRELANE_RUN_ROOT) && librelane $(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_CHIP_CONFIG) --manual-pdk \ + cd $(LIBRELANE_RUN_ROOT) && librelane $(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 -librelane-package: librelane-chip +librelane-package-run: librelane-run $(FLOW_PYTHON) $(LIBRELANE_FLOW_ROOT)/scripts/package.py \ --root $(ROOT_PATH) --variant-root $(VARIANT_ROOT) \ - --run-root $(LIBRELANE_RUN_ROOT) --output $(LIBRELANE_RUN_ROOT)/retrosoc-ihp130-chip.tar.gz \ No newline at end of file + --run-root $(LIBRELANE_RUN_ROOT) --target $(LIBRELANE_TARGET) \ + --output $(LIBRELANE_ARCHIVE) + +librelane-package: + +$(MAKE) LIBRELANE_TARGET=chip librelane-package-run + +librelane-core-package: + +$(MAKE) LIBRELANE_TARGET=core librelane-package-run diff --git a/physical/librelane/README.md b/physical/librelane/README.md index aa6bf92b..2194c546 100644 --- a/physical/librelane/README.md +++ b/physical/librelane/README.md @@ -1,29 +1,45 @@ -# LibreLane Full-Chip Flow +# LibreLane IHP130 Flows -This directory owns the open-source, single-level IHP130 pad-ring implementation -for `retrosoc_asic`. It does not harden or deliver a separate digital-core macro. -The three USB packet SRAMs remain PDK macros inside the single chip run. +This directory owns the open-source IHP130 implementation entry points for two +independent `retrosoc` deliverables: -The flow consumes the committed `configs/ci/ihp130.mk` profile, the canonical -SoC pin map, and the IHP-Open-PDK revision in -`dependencies/dependencies.lock.json`. LibreLane reads the upstream -`ihp-sg13g2/libs.tech/librelane` configuration directly through manual-PDK -mode. Generated RTL, SDC, configuration, run databases, reports, and final -views are written below the selected `build//physical/librelane/chip/` -directory. +- `librelane-core` hardens `retrosoc_core`, a padless logical macro with the + canonical external pin contract and the three USB packet SRAM macros. +- `librelane-chip` implements the bare-die `retrosoc_asic` package-facing SoC, + including its pad ring, bondpads, fillers, corners, seal ring, and the same + SRAM macros. + +These are separate single-level runs. The chip flow does not consume the core +run as a hierarchical macro, so either artifact can be developed and reviewed +without a two-level integration dependency. + +The flow consumes the committed `configs/ci/ihp130.mk` profile, canonical SoC +pin map, and IHP-Open-PDK revision in `dependencies/dependencies.lock.json`. +LibreLane reads the upstream `ihp-sg13g2/libs.tech/librelane` configuration +directly through manual-PDK mode. Generated RTL, SDC, configuration, run +databases, reports, and final views are written below the selected +`build//physical/librelane/{core,chip}/` directory. Use the LibreLane 3.0.5 environment supplied by the development shell: ```sh make CONFIG=configs/ci/ihp130.mk librelane-doctor +make CONFIG=configs/ci/ihp130.mk librelane-core make CONFIG=configs/ci/ihp130.mk librelane-chip +make CONFIG=configs/ci/ihp130.mk librelane-core-package make CONFIG=configs/ci/ihp130.mk librelane-package ``` -The initial bare-die plan contains all 109 signal pads, 80 core/IO power and -ground pads, four corners, I/O fillers, 70 um bondpads, and a seal ring. It is -not a QFN/BGA package definition. `PDK=GF180`, `PDK=SKY130`, and `PDK=ICS55` -remain explicit future adapter work and are rejected by this flow. +The chip target places all 109 signal pads, 80 core/IO power and ground pads, +four corners, I/O fillers, and 70 um bondpads. It is a bare-die plan, not a +QFN/BGA package definition. The SDC constrains the external clock at 72 MHz; +mid-PnR timing report passes are skipped because LibreLane 3.0.5 emits 1000 +expanded paths per group, while final post-RCX multi-corner STA remains enabled. + +`PDK=GF180`, `PDK=SKY130`, and `PDK=ICS55` are intentionally rejected until +their technology-specific LibreLane adapters, pad cells, macro collateral, and +signoff decks are qualified. The target split is technology-neutral so those +adapters can reuse the core/chip interface when they are ready. IHP SG13G2 does not currently provide pad cells implementing retroSoC's programmable pull-up, pull-down, or Schmitt controls. The IHP implementation diff --git a/physical/librelane/pad_cfg.tcl b/physical/librelane/pad_cfg.tcl new file mode 100644 index 00000000..4a857cd6 --- /dev/null +++ b/physical/librelane/pad_cfg.tcl @@ -0,0 +1,16 @@ +# IHP SG13G2 supply pads are physical-only cells and are not retained by Yosys. +foreach {kind master count} { + vdd sg13g2_IOPadVdd 24 + vss sg13g2_IOPadVss 24 + iovdd sg13g2_IOPadIOVdd 16 + iovss sg13g2_IOPadIOVss 16 +} { + for {set index 0} {$index < $count} {incr index} { + set instance_name [format {%s_pads[%d].%s_pad} $kind $index $kind] + set db_block [ord::get_db_block] + if {[$db_block findInst $instance_name] == "NULL"} { + make_instance $instance_name $master + } + } +} +source $::env(SCRIPTS_DIR)/openroad/common/pad_cfg.tcl diff --git a/physical/librelane/pdn_cfg.tcl b/physical/librelane/pdn_cfg.tcl index 2d8e63a5..d15ed9f5 100644 --- a/physical/librelane/pdn_cfg.tcl +++ b/physical/librelane/pdn_cfg.tcl @@ -28,6 +28,16 @@ foreach vdd $::env(VDD_NETS) gnd $::env(GND_NETS) { } } +# I/O rails are pad-ring internal and are absent from the synthesized top-level. +foreach {net signal_type} {IOVDD POWER IOVSS GROUND} { + set db_net [[ord::get_db_block] findNet $net] + if {$db_net == "NULL"} { + set db_net [odb::dbNet_create [ord::get_db_block] $net] + $db_net setSpecial + $db_net setSigType $signal_type + } +} + # IHP IO cells carry both core and external-IO power rails on lower-case pins. add_global_connection -net $::env(VDD_NET) -inst_pattern .* -pin_pattern vdd -power add_global_connection -net $::env(GND_NET) -inst_pattern .* -pin_pattern vss -ground @@ -35,13 +45,47 @@ add_global_connection -net IOVDD -inst_pattern .* -pin_pattern iovdd -power add_global_connection -net IOVSS -inst_pattern .* -pin_pattern iovss -ground global_connect -verbose +# connect_by_abutment assigns IHP pad rails to corner-local *_RING nets. Rebind +# those physical pad and filler pins so the PDN knows they are VDD/VSS terminals. +proc reconnect_ihp_padring_rails {block} { + foreach {net_name pin_name} { + VDD vdd + VSS vss + IOVDD iovdd + IOVSS iovss + } { + set net [$block findNet $net_name] + foreach inst [$block getInsts] { + set master_name [[$inst getMaster] getName] + if {![string match "sg13g2_IOPad*" $master_name] && + ![string match "sg13g2_Filler*" $master_name] && + ![string match "sg13g2_Corner*" $master_name]} { + continue + } + foreach iterm [$inst getITerms] { + if {[[$iterm getMTerm] getName] != $pin_name} { + continue + } + odb::dbITerm_disconnect $iterm + odb::dbITerm_connect $iterm $net + } + } + } +} +reconnect_ihp_padring_rails [ord::get_db_block] + set_voltage_domain -name CORE -power $::env(VDD_NET) -ground $::env(GND_NET) \ -secondary_power $secondary_supplies +set stdcell_grid_args [list] +if {$::env(PDN_ENABLE_PINS) == 1} { + lappend stdcell_grid_args -pins "$::env(PDN_VERTICAL_LAYER) $::env(PDN_HORIZONTAL_LAYER)" +} define_pdn_grid \ -name stdcell_grid \ -starts_with POWER \ - -voltage_domain CORE + -voltage_domain CORE \ + {*}$stdcell_grid_args add_pdn_stripe \ -grid stdcell_grid \ diff --git a/physical/librelane/scripts/doctor.py b/physical/librelane/scripts/doctor.py index 820fe27d..eab3abca 100755 --- a/physical/librelane/scripts/doctor.py +++ b/physical/librelane/scripts/doctor.py @@ -73,9 +73,23 @@ def main() -> int: if not path.is_file(): errors.append(f"required LibreLane/IHP file is missing: {path}") + config: dict[str, object] = {} + flow_name = "" + if args.config.is_file(): + try: + config = json.loads(args.config.read_text(encoding="utf-8")) + meta = config.get("meta", {}) + if not isinstance(meta, dict) or not isinstance(meta.get("flow"), str): + raise ValueError("config meta.flow must name a LibreLane flow") + flow_name = meta["flow"] + except (OSError, ValueError, json.JSONDecodeError) as error: + errors.append(f"failed to read LibreLane configuration: {error}") + if args.config.is_file() and not errors: try: - flow_class = Flow.factory.get("Chip") + flow_class = Flow.factory.get(flow_name) + if flow_class is None: + raise ValueError(f"unknown LibreLane flow: {flow_name}") flow = flow_class( str(args.config.resolve()), pdk="ihp-sg13g2", @@ -84,26 +98,29 @@ def main() -> int: pad="sg13g2_io", design_dir=str(args.config.resolve().parent), ) + details["flow"] = flow_name details["config_design"] = flow.config["DESIGN_NAME"] details["drc_runset"] = str(flow.config["KLAYOUT_DRC_RUNSET"]) details["lvs_setup"] = str(flow.config["NETGEN_SETUP"]) details["pad_spice"] = [str(path) for path in flow.config["PAD_SPICE_MODELS"]] except Exception as error: # LibreLane reports structured configuration exceptions. - errors.append(f"LibreLane Chip configuration failed to load: {error}") + errors.append(f"LibreLane {flow_name or 'unknown'} configuration failed to load: {error}") - if args.config.is_file(): - config = json.loads(args.config.read_text(encoding="utf-8")) + if config: placed = [ item for side in ("PAD_SOUTH", "PAD_EAST", "PAD_NORTH", "PAD_WEST") for item in config.get(side, []) ] - expected = EXPECTED_SIGNAL_PADS + sum(IHP130_POWER_PAD_COUNTS.values()) details["placed_pad_count"] = len(placed) - if len(placed) != expected or len(placed) != len(set(placed)): - errors.append( - f"expected {expected} unique placed signal/power PADs, found {len(placed)}" - ) + if flow_name == "Chip": + expected = EXPECTED_SIGNAL_PADS + sum(IHP130_POWER_PAD_COUNTS.values()) + if len(placed) != expected or len(placed) != len(set(placed)): + errors.append( + f"expected {expected} unique placed signal/power PADs, found {len(placed)}" + ) + elif flow_name == "Classic" and placed: + errors.append("core hardening configuration must not define pad-ring placement") result = { "schema_version": 1, diff --git a/physical/librelane/scripts/generate_chip_config.py b/physical/librelane/scripts/generate_chip_config.py index 5626ec58..fb2e81e9 100755 --- a/physical/librelane/scripts/generate_chip_config.py +++ b/physical/librelane/scripts/generate_chip_config.py @@ -64,11 +64,11 @@ def power_instances(side_index: int) -> list[str]: for local_index in range(6): for kind in ("vdd", "vss"): index = (side_index * 6) + local_index - result.append(f"{kind}_pads\\[{index}\\].{kind}_pad") + result.append(f"{kind}_pads[{index}].{kind}_pad") if local_index < 4: for kind in ("iovdd", "iovss"): index = (side_index * 4) + local_index - result.append(f"{kind}_pads\\[{index}\\].{kind}_pad") + result.append(f"{kind}_pads[{index}].{kind}_pad") return result @@ -106,6 +106,9 @@ def macro_config( def build_config(args: argparse.Namespace) -> dict[str, object]: + target = getattr(args, "target", "chip") + if target not in {"chip", "core"}: + raise ValueError("target must be chip or core") pads, _ = read_map(args.pin_map) active_pads = [pad for pad in pads if pad.feature is None or args.have_pll] signal_instances: dict[str, list[str]] = {side: [] for side in SIDE_ORDER} @@ -152,44 +155,31 @@ def build_config(args: argparse.Namespace) -> dict[str, object]: ] ) - return { - "meta": {"version": 3, "flow": "Chip"}, - "DESIGN_NAME": "retrosoc_asic", + common = { "VERILOG_FILES": [config_path(args.rtl, config_dir)], "USE_SLANG": True, "SLANG_ARGUMENTS": ["--keep-hierarchy"], + "VERILOG_POWER_DEFINE": None, "SYNTH_SHARE_RESOURCES": False, "SYNTH_HIERARCHY_MODE": "deferred_flatten", "YOSYS_LOG_LEVEL": "WARNING", + "SYNTH_STRATEGY": "AREA 3", "PRIMARY_GDSII_STREAMOUT_TOOL": "klayout", - "PAD_SOUTH": pad_sides["SOUTH"], - "PAD_EAST": pad_sides["EAST"], - "PAD_NORTH": pad_sides["NORTH"], - "PAD_WEST": pad_sides["WEST"], "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), "CLOCK_PORT": list(CLOCK_PORTS), "CLOCK_PERIOD": 1_000_000_000 / args.ext_clk_hz, - "VDD_NETS": ["VDD", "IOVDD"], - "GND_NETS": ["VSS", "IOVSS"], + "VDD_NETS": ["VDD"], + "GND_NETS": ["VSS"], "FP_SIZING": "absolute", - "DIE_AREA": [0, 0, 8000, 8000], - "CORE_AREA": [365, 365, 7635, 7635], - "PL_TARGET_DENSITY_PCT": 30, "PDN_CORE_RING": True, "PDN_CORE_RING_VWIDTH": 15, "PDN_CORE_RING_HWIDTH": 15, "PDN_CORE_RING_VSPACING": 5, "PDN_CORE_RING_HSPACING": 5, - "PDN_CORE_RING_CONNECT_TO_PADS": True, - "PDN_ENABLE_PINS": False, + "PDN_ENABLE_PINS": True, "PDN_CFG": config_path(args.pdn, config_dir), - "PAD_BONDPAD_NAME": "bondpad_70x70", - "EXTRA_GDS": [config_path(args.bondpad_gds, config_dir)], - "EXTRA_LEFS": [config_path(args.bondpad_lef, config_dir)], - "IGNORE_DISCONNECTED_MODULES": ["bondpad_70x70"], - "MAGIC_EXT_UNIQUE": "notopports", "MACROS": macros, "PDN_MACRO_CONNECTIONS": macro_hooks, "MAGIC_GDS_FLATGLOB": [ @@ -201,6 +191,41 @@ def build_config(args: argparse.Namespace) -> dict[str, object]: "*_CELL_SUB", ], } + if target == "core": + return { + "meta": {"version": 3, "flow": "Classic"}, + "DESIGN_NAME": "retrosoc_core", + "DIE_AREA": [0, 0, 6000, 6000], + "CORE_AREA": [120, 120, 5880, 5880], + "PL_TARGET_DENSITY_PCT": 45, + "PDN_CORE_RING_CONNECT_TO_PADS": False, + **common, + } + return { + "meta": {"version": 3, "flow": "Chip"}, + "DESIGN_NAME": "retrosoc_asic", + "SYNTH_KEEP_HIERARCHY_MODULES": [ + "sg13g2_IOPadVdd", + "sg13g2_IOPadVss", + "sg13g2_IOPadIOVdd", + "sg13g2_IOPadIOVss", + ], + "PAD_SOUTH": pad_sides["SOUTH"], + "PAD_EAST": pad_sides["EAST"], + "PAD_NORTH": pad_sides["NORTH"], + "PAD_WEST": pad_sides["WEST"], + "DIE_AREA": [0, 0, 8000, 8000], + "CORE_AREA": [365, 365, 7635, 7635], + "PL_TARGET_DENSITY_PCT": 30, + "PDN_CORE_RING_CONNECT_TO_PADS": True, + "PAD_CFG": config_path(ROOT / "physical/librelane/pad_cfg.tcl", config_dir), + "PAD_BONDPAD_NAME": "bondpad_70x70", + "EXTRA_GDS": [config_path(args.bondpad_gds, config_dir)], + "EXTRA_LEFS": [config_path(args.bondpad_lef, config_dir)], + "IGNORE_DISCONNECTED_MODULES": ["bondpad_70x70"], + "MAGIC_EXT_UNIQUE": "notopports", + **common, + } def positive_integer(value: str) -> int: @@ -222,6 +247,7 @@ 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("--target", choices=("chip", "core"), default="chip") parser.add_argument("--output", type=Path, required=True) args = parser.parse_args() try: diff --git a/physical/librelane/scripts/generate_sdc.py b/physical/librelane/scripts/generate_sdc.py index ecf0f111..60d509d0 100755 --- a/physical/librelane/scripts/generate_sdc.py +++ b/physical/librelane/scripts/generate_sdc.py @@ -24,6 +24,7 @@ CLOCK_PADS = { "usb2_ulpi": "usb2_ulpi_clk_i_pad", } RESET_PADS = {"ext_rst_n_i_pad", "jtag_trst_n_i_pad"} +IHP130_SYSTEM_CLOCK_PIN = "u_rcu.u_sys_clk_buf.u_sg13g2_buf_1/X" def positive_integer(value: str) -> int: @@ -58,7 +59,9 @@ def logical_directions(pad: Pad) -> tuple[bool, bool]: def render(args: argparse.Namespace) -> str: validate(args.domains, ROOT) - document = json.loads(args.domains.read_text(encoding="utf-8")) + target = getattr(args, "target", "chip") + if target not in {"chip", "core"}: + raise ValueError("target must be chip or core") pads, _ = read_map(args.pin_map) active = [pad for pad in pads if pad.feature is None or args.have_pll] by_name = {pad.name: pad for pad in active} @@ -106,19 +109,21 @@ def render(args: argparse.Namespace) -> str: ] clock_vars: dict[str, str] = {} for name, pad_name in CLOCK_PADS.items(): - instance = ihp130_pad_instance(by_name[pad_name]) - if instance is None: - raise ValueError(f"clock PAD has no IHP130 physical instance: {pad_name}") variable = f"clk_{name}_pin" - lines.append(f'set {variable} [require_pins "clock {name}" {{{instance}/p2c}}]') + if target == "chip": + instance = ihp130_pad_instance(by_name[pad_name]) + if instance is None: + raise ValueError(f"clock PAD has no IHP130 physical instance: {pad_name}") + lines.append(f'set {variable} [require_pins "clock {name}" {{{instance}/p2c}}]') + else: + lines.append(f'set {variable} [require_ports "clock {name}" {{{pad_name}}}]') lines.append(f"create_clock -name clk_{name} -period {periods[name]:.12g} ${variable}") lines.append("") clock_vars[name] = variable - system_pin = document["domains"][1]["sta"]["pin"] lines.extend( [ - f'set clk_system_pin [require_pins "clock system" {{{system_pin}}}]', + f'set clk_system_pin [require_pins "clock system" {{{IHP130_SYSTEM_CLOCK_PIN}}}]', "create_generated_clock -name clk_system -source $clk_external_pin " "-divide_by 1 $clk_system_pin", "", @@ -175,6 +180,7 @@ 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("--target", choices=("chip", "core"), default="chip") parser.add_argument("--output", type=Path, required=True) args = parser.parse_args() try: diff --git a/physical/librelane/scripts/package.py b/physical/librelane/scripts/package.py index e1e014b5..b7e310b7 100755 --- a/physical/librelane/scripts/package.py +++ b/physical/librelane/scripts/package.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Create a checksummed LibreLane full-chip delivery archive.""" +"""Create a checksummed LibreLane implementation delivery archive.""" from __future__ import annotations @@ -24,6 +24,7 @@ def main() -> int: parser.add_argument("--root", type=Path, required=True) parser.add_argument("--variant-root", type=Path, required=True) parser.add_argument("--run-root", type=Path, required=True) + parser.add_argument("--target", choices=("chip", "core"), required=True) parser.add_argument("--output", type=Path, required=True) args = parser.parse_args() root = args.root.resolve() @@ -35,8 +36,8 @@ def main() -> int: run_root / "final", run_root / "runs/current", variant_root / "meta/manifest.json", - variant_root / "meta/librelane-doctor.json", - variant_root / "meta/librelane-chip.json", + variant_root / f"meta/librelane-{args.target}-doctor.json", + variant_root / f"meta/librelane-{args.target}.json", root / "dependencies/dependencies.lock.json", ) missing = [str(path) for path in required if not path.exists()] @@ -45,19 +46,19 @@ def main() -> int: args.output.parent.mkdir(parents=True, exist_ok=True) with tempfile.TemporaryDirectory(prefix="retrosoc-librelane-package-") as temporary: - staging = Path(temporary) / "retrosoc-ihp130-chip" + staging = Path(temporary) / f"retrosoc-ihp130-{args.target}" shutil.copytree(run_root / "final", staging / "final") shutil.copytree(run_root / "runs/current", staging / "evidence/run") shutil.copy2(run_root / "config.json", staging / "evidence/config.json") shutil.copy2(run_root / "result.json", staging / "evidence/result.json") shutil.copy2(variant_root / "meta/manifest.json", staging / "evidence/manifest.json") shutil.copy2( - variant_root / "meta/librelane-doctor.json", + variant_root / f"meta/librelane-{args.target}-doctor.json", staging / "evidence/librelane-doctor.json", ) shutil.copy2( - variant_root / "meta/librelane-chip.json", - staging / "evidence/librelane-chip.json", + variant_root / f"meta/librelane-{args.target}.json", + staging / f"evidence/librelane-{args.target}.json", ) shutil.copy2( root / "dependencies/dependencies.lock.json", diff --git a/physical/pdk/README.md b/physical/pdk/README.md index 2621de3f..494aa8ea 100644 --- a/physical/pdk/README.md +++ b/physical/pdk/README.md @@ -26,11 +26,12 @@ at TT and slow corners. Derived Liberty files are stored below the locked HD library submodule; the remaining upstream libraries are outside the SoC integration boundary. -The IHP130 dependency is also the technology source for the single-level -`physical/librelane/` full-chip flow. The locked checkout includes the upstream -`libs.tech/librelane` standard-cell, pad, extraction, DRC, LVS, filler, bondpad, -and seal-ring configuration; LibreLane consumes it directly in manual-PDK mode. -The other three open PDKs do not yet have project-owned LibreLane adapters. +The IHP130 dependency is also the technology source for the independent +`physical/librelane/` core-hardening and full-chip pad-ring flows. The locked +checkout includes the upstream `libs.tech/librelane` standard-cell, pad, +extraction, DRC, LVS, filler, bondpad, and seal-ring configuration; LibreLane +consumes it directly in manual-PDK mode. The other three open PDKs do not yet +have project-owned LibreLane adapters. All four PDK profiles run slow-corner OpenSTA core timing in CI. The analysis uses the PDK standard-cell Liberty plus the linked IO-cell model required to diff --git a/tests/test_librelane_flow.py b/tests/test_librelane_flow.py index 1afff08d..fd1bb40f 100644 --- a/tests/test_librelane_flow.py +++ b/tests/test_librelane_flow.py @@ -1,4 +1,4 @@ -"""Tests for the single-level IHP130 LibreLane pad-ring flow.""" +"""Tests for IHP130 LibreLane core and pad-ring flows.""" from __future__ import annotations @@ -52,10 +52,10 @@ def test_chip_config_places_every_signal_and_power_pad_once(tmp_path: Path) -> N "north": 48, "west": 59, } - assert sum(item.startswith("vdd_pads\\[") for item in placed) == 24 - assert sum(item.startswith("vss_pads\\[") for item in placed) == 24 - assert sum(item.startswith("iovdd_pads\\[") for item in placed) == 16 - assert sum(item.startswith("iovss_pads\\[") for item in placed) == 16 + assert sum(item.startswith("vdd_pads[") for item in placed) == 24 + assert sum(item.startswith("vss_pads[") for item in placed) == 24 + assert sum(item.startswith("iovdd_pads[") for item in placed) == 16 + assert sum(item.startswith("iovss_pads[") for item in placed) == 16 assert "u_extclk_i_pad.u_sg13g2_IOPadIn" in sides["south"] assert "u_gpio_31_io_pad.u_sg13g2_IOPadInOut4mA" in sides["east"] assert "u_sdram_dq15_io_pad.u_sg13g2_IOPadInOut4mA" in sides["west"] @@ -63,11 +63,21 @@ def test_chip_config_places_every_signal_and_power_pad_once(tmp_path: Path) -> N assert config["CORE_AREA"] == [365, 365, 7635, 7635] assert config["USE_SLANG"] is True assert config["SLANG_ARGUMENTS"] == ["--keep-hierarchy"] + assert config["VERILOG_POWER_DEFINE"] is None assert config["SYNTH_SHARE_RESOURCES"] is False assert config["SYNTH_HIERARCHY_MODE"] == "deferred_flatten" + assert config["SYNTH_KEEP_HIERARCHY_MODULES"] == [ + "sg13g2_IOPadVdd", + "sg13g2_IOPadVss", + "sg13g2_IOPadIOVdd", + "sg13g2_IOPadIOVss", + ] assert config["YOSYS_LOG_LEVEL"] == "WARNING" - assert config["VDD_NETS"] == ["VDD", "IOVDD"] - assert config["GND_NETS"] == ["VSS", "IOVSS"] + assert config["SYNTH_STRATEGY"] == "AREA 3" + 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 set(config["MACROS"]) == { "RM_IHPSG13_1P_4096x16_c3_bm_bist", "RM_IHPSG13_1P_4096x8_c3_bm_bist", @@ -75,6 +85,28 @@ def test_chip_config_places_every_signal_and_power_pad_once(tmp_path: Path) -> N assert len(config["PDN_MACRO_CONNECTIONS"]) == 6 +def test_core_config_uses_a_padless_classic_flow(tmp_path: Path) -> None: + module = load_module( + "retrosoc_librelane_core_config", FLOW_ROOT / "scripts/generate_chip_config.py" + ) + arguments = chip_arguments(tmp_path) + arguments.target = "core" + config = module.build_config(arguments) + + assert config["meta"] == {"version": 3, "flow": "Classic"} + assert config["DESIGN_NAME"] == "retrosoc_core" + assert config["DIE_AREA"] == [0, 0, 6000, 6000] + assert config["CORE_AREA"] == [120, 120, 5880, 5880] + assert config["PDN_CORE_RING_CONNECT_TO_PADS"] is False + assert "PAD_CFG" not in config + assert not any(key.startswith("PAD_") for key in config) + assert "PAD_BONDPAD_NAME" not in config + assert set(config["MACROS"]) == { + "RM_IHPSG13_1P_4096x16_c3_bm_bist", + "RM_IHPSG13_1P_4096x8_c3_bm_bist", + } + + 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( @@ -88,6 +120,7 @@ def test_chip_sdc_is_pad_aware_and_does_not_false_path_all_io(tmp_path: Path) -> assert "u_extclk_i_pad.u_sg13g2_IOPadIn/p2c" in sdc assert "u_usb2_ulpi_clk_i_pad.u_sg13g2_IOPadIn/p2c" in sdc + 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_input_delay -max" in sdc @@ -97,14 +130,35 @@ def test_chip_sdc_is_pad_aware_and_does_not_false_path_all_io(tmp_path: Path) -> assert "set_false_path -to [all_outputs]" not in sdc -def test_librelane_flow_is_single_level_chip_only() -> None: +def test_core_sdc_constrains_logical_top_ports(tmp_path: Path) -> None: + module = load_module("retrosoc_librelane_core_sdc", FLOW_ROOT / "scripts/generate_sdc.py") + arguments = SimpleNamespace( + domains=DOMAINS, + pin_map=PIN_MAP, + ext_clk_hz=72_000_000, + aud_clk_hz=18_432_000, + have_pll=False, + target="core", + ) + sdc = module.render(arguments) + + assert 'require_ports "clock external" {extclk_i_pad}' in sdc + assert 'require_ports "clock usb2_ulpi" {usb2_ulpi_clk_i_pad}' in sdc + assert "u_extclk_i_pad.u_sg13g2_IOPadIn/p2c" not in sdc + assert "u_rcu.u_sys_clk_buf.u_sg13g2_buf_1/X" in sdc + + +def test_librelane_flow_exposes_core_and_chip_targets() -> None: makefile = (FLOW_ROOT / "Makefile").read_text(encoding="utf-8") top_makefile = (ROOT / "Makefile").read_text(encoding="utf-8") lock = json.loads((ROOT / "dependencies/dependencies.lock.json").read_text()) assert "librelane-chip:" in makefile + assert "librelane-core:" in makefile + assert "librelane-core-package:" in makefile + assert "LIBRELANE_TARGET ?= chip" in makefile + assert "--skip OpenROAD.STAMidPNR" in makefile assert "--librelane-safe" in makefile - assert "librelane-core" not in makefile assert "librelane-all" not in makefile assert "include physical/librelane/Makefile" in top_makefile ihp_filelist = (ROOT / "rtl/filelist/pdk_ihp130.fl").read_text(encoding="utf-8") @@ -126,6 +180,13 @@ def test_pdn_connects_all_ihp_pad_power_rails() -> None: assert "findNet $vdd" in pdn assert "findNet $gnd" in pdn + assert "foreach {net signal_type} {IOVDD POWER IOVSS GROUND}" in pdn + pad_cfg = (FLOW_ROOT / "pad_cfg.tcl").read_text(encoding="utf-8") + assert "make_instance $instance_name $master" in pad_cfg + assert "proc reconnect_ihp_padring_rails" in pdn + 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 "-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 diff --git a/tests/test_pin_map.py b/tests/test_pin_map.py index c3dbd3ac..aa5ed1c4 100644 --- a/tests/test_pin_map.py +++ b/tests/test_pin_map.py @@ -44,6 +44,8 @@ def test_pin_map_generates_asic_and_platform_bindings(tmp_path: Path) -> None: ports = (tmp_path / "rtl/retrosoc_asic_ports.svh").read_text(encoding="utf-8") pads = (tmp_path / "rtl/retrosoc_asic_pad_bindings.svh").read_text(encoding="utf-8") + core_ports = (tmp_path / "rtl/retrosoc_core_ports.svh").read_text(encoding="utf-8") + core_bindings = (tmp_path / "rtl/retrosoc_core_bindings.svh").read_text(encoding="utf-8") fpga = (tmp_path / "rtl/retrosoc_asic_fpga_mini_bindings.svh").read_text(encoding="utf-8") testbench = (tmp_path / "rtl/retrosoc_asic_tb_bindings.svh").read_text(encoding="utf-8") verilator = (tmp_path / "rtl/retrosoc_asic_verilator_bindings.svh").read_text(encoding="utf-8") @@ -52,6 +54,11 @@ def test_pin_map_generates_asic_and_platform_bindings(tmp_path: Path) -> None: assert "inout wire IOVSS," in ports assert "inout wire VDD," in ports assert "inout wire VSS," in ports + assert "inout wire VDD," not in core_ports + assert "inout extclk_i_pad," in core_ports + assert "assign s_ext_clk = extclk_i_pad;" in core_bindings + assert "assign gpio_0_io_pad = u_gpio_if.oe_o[0] ? u_gpio_if.do_o[0] : 1'bz;" in core_bindings + assert "assign u_sdram_if.dq_i[15] = sdram_dq15_io_pad;" in core_bindings assert "inout extclk_i_pad," in ports assert "input xi_i_pad," in ports assert "inout sdram_dq15_io_pad" in ports @@ -78,6 +85,7 @@ def test_pin_map_generates_asic_and_platform_bindings(tmp_path: Path) -> None: assert "tc_io_in_pad u_usb2_ulpi_clk_i_pad" in pads assert ".p2c(s_usb2_ulpi_clk)" in pads assert "`define RETROSOC_PAD_POWER_CONNECTIONS" in pads + assert "sg13g2_IOPadIOVdd iovdd_pad" in pads assert "for (genvar i = 0; i < 24; i++) begin : vdd_pads" in pads assert "for (genvar i = 0; i < 24; i++) begin : vss_pads" in pads assert "for (genvar i = 0; i < 16; i++) begin : iovdd_pads" in pads