diff --git a/.agents/skills/retrosoc-physical-flow/references/commercial.md b/.agents/skills/retrosoc-physical-flow/references/commercial.md index eed88f0f..0b2683b3 100644 --- a/.agents/skills/retrosoc-physical-flow/references/commercial.md +++ b/.agents/skills/retrosoc-physical-flow/references/commercial.md @@ -3,15 +3,22 @@ Read the [commercial guide](../../../../physical/commercial/README.md), its [Makefile](../../../../physical/commercial/Makefile), and the selected local configuration without copying site-sensitive values into tracked material. -The current flow is Mini/ICS55, not a generic adapter for every product/PDK. +The flow is parameterized per product through `SOC` (tracked +`config/products/.mk` + `tcl/common/products/.tcl`): Mini +(`retrosoc_asic`) and Tiny (`retrosoc_tiny_asic`, SAFE24 with the PLL parked) +are supported on ICS55; future series plug into the same layer. It is not a +generic adapter for arbitrary designs or other PDKs. ## Inputs and execution boundary The development zone creates the production RTL package from the committed -`configs/cluster/ics55.mk` profile with the guide's PLL/SRAM settings. The EDA -zone consumes that archive through a real, ignored `LOCAL_CONFIG` and `RUN_ID`. -Validate the source/configuration/clock inventory and archive identity before -using it. An inspection request must not generate the package or submit jobs. +product profile — `configs/cluster/ics55.mk` with the guide's PLL/SRAM +settings for Mini, `configs/ci/ics55-tiny.mk` for Tiny. The EDA zone consumes +that archive through a real, ignored `LOCAL_CONFIG` (default +`local/ics55-.mk`) and `RUN_ID`. Validate the source/configuration/clock +inventory and archive identity before using it; the packaged +`rtl/contracts/commercial_timing_contract.tcl` must match the product domain +policy. An inspection request must not generate the package or submit jobs. For authorized execution, use the repository Make targets and site-selected LSF mechanism. Never invoke licensed tools directly on the development host @@ -27,21 +34,24 @@ There are two different acceptance boundaries: Its top-level I/O exclusions must be visible in the summary; it is not input qualification for Innovus/PrimeTime implementation. - Full implementation uses the strict `doctor` and the selected stages up to - `signoff`. Qualified I/O budgets, local pad timing hook, library/corner and - extraction coverage, and zero-threshold verification rules remain mandatory. - Never set `IO_TIMING_QUALIFIED=YES` without the supporting reviewed budgets. + `signoff`. Qualified I/O budgets, the product pad timing hook when required + (Mini), library/corner and extraction coverage, and zero-threshold + verification rules remain mandatory. Never set `IO_TIMING_QUALIFIED=YES` + without the supporting reviewed budgets. Follow the current stage graph through synthesis/equivalence, APR, -post-route equivalence, extraction/STA/ECO, and physical verification only as -far as requested. The guide and executable targets, not a skill-maintained -copy of the graph, define dependencies and required evidence. +post-route equivalence, extraction/STA/ECO, and physical verification +(including the optional macro-level LVS/ERC stage) only as far as requested. +The guide and executable targets, not a skill-maintained copy of the graph, +define dependencies and required evidence. ## Resume and handoff -Runs live under `build/commercial/ics55/`. Before resuming, compare -original input/configuration hashes and the source identity, then check each -completed stage's result, required outputs, and verdict marker against its -stamp. Stamps alone are insufficient, especially after input or tool changes. +Runs live under `build/commercial/ics55//`. Before resuming, +compare original input/configuration hashes and the source identity, then +check each completed stage's result, required outputs, and verdict marker +against its stamp. Stamps alone are insufficient, especially after input or +tool changes. For unchanged valid inputs, repeating the requested endpoint with the same local configuration and run ID uses the existing first-missing-stage behavior. diff --git a/README.md b/README.md index 9edacec2..dfab1cbb 100644 --- a/README.md +++ b/README.md @@ -257,3 +257,13 @@ for its current reporting information. Thanks to everyone contributing to retroSoC. [![retroSoC contributors](https://contrib.rocks/image?repo=retroSoC/retroSoC)](https://github.com/retroSoC/retroSoC/graphs/contributors) + +## Star History + + + + + + Star History Chart + + \ No newline at end of file diff --git a/physical/README.md b/physical/README.md index 34617244..54e0b213 100644 --- a/physical/README.md +++ b/physical/README.md @@ -12,9 +12,12 @@ drivers. and `librelane/tiny/` (Tiny full-chip pad ring). - `ecc/` contains the ICS55 padless-core adapter and the official ECC `latest` installer entrypoint. Installation does not qualify physical-flow compatibility. -- `commercial/` contains licensed-tool orchestration and flow logic. PDKs, - foundry decks, commercial libraries, site configuration, and results remain - outside Git. +- `commercial/` contains licensed-tool orchestration and flow logic for the + ICS55 implementation flow, parameterized per product line (`SOC=MINI` and + `SOC=TINY`, extensible to future series). PDKs, foundry decks, commercial + libraries, site configuration, and results remain outside Git; see + [commercial/README.md](commercial/README.md) for the three-layer + configuration model and stage graph. - `sdf/`, when supplied by an implementation flow, contains post-layout netlist and SDF collateral consumed by post-layout simulation. diff --git a/physical/commercial/Makefile b/physical/commercial/Makefile index 986778ac..dea8c5eb 100644 --- a/physical/commercial/Makefile +++ b/physical/commercial/Makefile @@ -15,17 +15,46 @@ SHELL := /bin/sh FLOW_ROOT := $(abspath $(dir $(firstword $(MAKEFILE_LIST)))) ROOT_PATH := $(abspath $(FLOW_ROOT)/../..) -LOCAL_CONFIG ?= $(FLOW_ROOT)/local/ics55-production.mk +LOCAL_CONFIG ?= + +SOC ?= MINI +TECHNOLOGY ?= ICS55 +SOC_LOWER := $(shell echo '$(SOC)' | tr 'A-Z' 'a-z') +TECH_LOWER := $(shell echo '$(TECHNOLOGY)' | tr 'A-Z' 'a-z') + +TECH_CONFIG := $(FLOW_ROOT)/config/technology/$(TECH_LOWER).mk +PRODUCT_CONFIG := $(FLOW_ROOT)/config/products/$(SOC_LOWER).mk +ifeq ($(wildcard $(TECH_CONFIG)),) +$(error unsupported TECHNOLOGY=$(TECHNOLOGY): missing $(TECH_CONFIG)) +endif +ifeq ($(wildcard $(PRODUCT_CONFIG)),) +$(error unsupported SOC=$(SOC): missing $(PRODUCT_CONFIG)) +endif +include $(TECH_CONFIG) +include $(PRODUCT_CONFIG) + +ifeq ($(LOCAL_CONFIG),) +LOCAL_CONFIG := $(FLOW_ROOT)/local/$(TECH_LOWER)-$(SOC_LOWER).mk +endif -include $(LOCAL_CONFIG) -TOP ?= retrosoc_asic -TECHNOLOGY ?= ICS55 FLOW_PYTHON ?= python TCLSH ?= tclsh DOCTOR_ARGS ?= RUN_ID ?= current BUILD_ROOT ?= $(ROOT_PATH)/build -RUN_ROOT ?= $(abspath $(BUILD_ROOT))/commercial/ics55/$(RUN_ID) +RUN_ROOT ?= $(abspath $(BUILD_ROOT))/commercial/$(TECH_LOWER)/$(SOC_LOWER)/$(RUN_ID) + +# Optional ECO subflows and Calibre LVS controls; the local configuration may +# override them. All optional subflows default off (legacy +# pd_data/pteco/scr/flow/{swap,size_down,remove_buffer}.tcl semantics). +ECO_ENABLE_VT_SWAP ?= NO +ECO_ENABLE_SIZE_DOWN ?= NO +ECO_ENABLE_REMOVE_BUFFER ?= NO +ECO_PBA_MODE ?= none +ECO_VT_PATTERN_PRIORITY ?= +MACRO_LVS_CELLS ?= +CALIBRE_LVS_ARGS ?= INPUT_DIR := $(RUN_ROOT)/input META_DIR := $(RUN_ROOT)/meta @@ -47,10 +76,11 @@ ECO_STAMP := $(STAMP_DIR)/eco.stamp APR_ECO_STAMP := $(STAMP_DIR)/apr-eco.stamp REEXTRACT_STAMP := $(STAMP_DIR)/reextract.stamp RESTA_STAMP := $(STAMP_DIR)/resta.stamp -PV_MERGE_STAMP := $(STAMP_DIR)/pv-merge.stamp -PV_DRC_STAMP := $(STAMP_DIR)/pv-drc.stamp -PV_ANT_STAMP := $(STAMP_DIR)/pv-antenna.stamp -PV_LVS_STAMP := $(STAMP_DIR)/pv-lvs.stamp +PV_MERGE_STAMP := $(STAMP_DIR)/pv-merge.stamp +PV_DRC_STAMP := $(STAMP_DIR)/pv-drc.stamp +PV_ANT_STAMP := $(STAMP_DIR)/pv-antenna.stamp +PV_LVS_STAMP := $(STAMP_DIR)/pv-lvs.stamp +PV_MACRO_LVS_STAMP := $(STAMP_DIR)/pv-macro-lvs.stamp RUN_STAGE := $(FLOW_PYTHON) $(FLOW_ROOT)/scripts/run_stage.py SUBMIT_JOB := $(FLOW_PYTHON) $(FLOW_ROOT)/scripts/submit_job.py @@ -66,7 +96,7 @@ export .PHONY: help check-config doctor doctor-syn boundary-check input syn fm-rtl2syn \ apr-initialize apr-floorplan apr-preplace apr-place apr-cts apr-route \ fm-syn2pr extract sta eco apr-eco reextract resta pv-merge pv-drc \ - pv-antenna pv-lvs signoff clean-stage list-stages + pv-antenna pv-lvs pv-macro-lvs signoff clean-stage list-stages help: @printf '%s\n' \ @@ -83,25 +113,29 @@ help: ' apr-eco apply ECO changes and incrementally route' \ ' reextract extract the ECO design' \ ' resta sign off the ECO design' \ - ' pv-* merge GDS and run Calibre DRC/antenna/LVS' \ + ' pv-* merge GDS and run Calibre DRC/antenna/LVS/macro-LVS' \ ' signoff run the complete strict stage graph' \ ' clean-stage remove STAGE and all downstream stage results' \ '' \ - 'Set LOCAL_CONFIG=local/ics55-production.mk and optionally RUN_ID=.' + 'Set SOC=MINI|TINY (default MINI) and TECHNOLOGY=ICS55, point' \ + 'LOCAL_CONFIG at the ignored local configuration (default' \ + 'local/-.mk), and optionally set RUN_ID=.' list-stages: @printf '%s\n' input syn fm-rtl2syn apr-initialize apr-floorplan \ apr-preplace apr-place apr-cts apr-route fm-syn2pr extract sta eco \ - apr-eco reextract resta pv-merge pv-drc pv-antenna pv-lvs + apr-eco reextract resta pv-merge pv-drc pv-antenna pv-lvs pv-macro-lvs check-config: @test -f '$(LOCAL_CONFIG)' || { \ echo "missing ignored local configuration: $(LOCAL_CONFIG)" >&2; \ - echo "copy config/ics55.example.mk to local/ics55-production.mk" >&2; \ + echo "copy config/$(TECH_LOWER)-$(SOC_LOWER).example.mk to $(LOCAL_CONFIG)" >&2; \ + exit 2; \ + } + @test -n '$(TOP)' || { \ + echo "TOP is not set by config/products/$(SOC_LOWER).mk" >&2; \ exit 2; \ } - @test '$(TECHNOLOGY)' = ICS55 - @test '$(TOP)' = retrosoc_asic doctor: check-config @mkdir -p $(META_DIR) @@ -394,8 +428,20 @@ $(PV_LVS_STAMP): $(PV_ANT_STAMP) pv-lvs: $(PV_LVS_STAMP) +$(PV_MACRO_LVS_STAMP): $(PV_LVS_STAMP) + @mkdir -p $(RUN_ROOT)/pv/macro_lvs/work $(RUN_ROOT)/pv/macro_lvs/log $(RUN_ROOT)/pv/macro_lvs/reports $(RUN_ROOT)/pv/macro_lvs/output + PV_MODE=macro_lvs $(TCLSH) $(FLOW_ROOT)/tcl/pv/generate.tcl + $(RUN_STAGE) --stage pv-macro-lvs --cwd $(RUN_ROOT)/pv/macro_lvs/work \ + --log $(RUN_ROOT)/pv/macro_lvs/log/lsf.log \ + --result $(RUN_ROOT)/pv/macro_lvs/result.json --stamp $@ \ + --expect $(RUN_ROOT)/pv/macro_lvs/output/verdict.pass -- \ + $(call submit-job,LSF_PV_ARGS,$(RUN_ROOT)/pv/macro_lvs/log/tool.log) \ + $(TCLSH) $(RUN_ROOT)/pv/macro_lvs/work/macro_lvs_driver.tcl + +pv-macro-lvs: $(PV_MACRO_LVS_STAMP) + signoff: doctor - $(MAKE) pv-lvs LOCAL_CONFIG=$(LOCAL_CONFIG) RUN_ID=$(RUN_ID) + $(MAKE) pv-macro-lvs LOCAL_CONFIG=$(LOCAL_CONFIG) RUN_ID=$(RUN_ID) @printf 'commercial signoff passed: %s\n' '$(RUN_ROOT)' clean-stage: diff --git a/physical/commercial/README.md b/physical/commercial/README.md index 8bc0f93b..ba2bf84d 100644 --- a/physical/commercial/README.md +++ b/physical/commercial/README.md @@ -1,15 +1,44 @@ # Commercial Physical Design This directory owns the self-developed integration for the licensed ICS55 -implementation flow. It does not contain the PDK, foundry rule decks, standard -cell or macro libraries, commercial tool installations, or generated results. +implementation flow. It contains only flow logic and configuration policy: the +PDK, foundry rule decks, standard-cell or macro libraries, commercial tool +installations, site configuration, and generated results all remain outside +Git. + +## Configuration layers + +Configuration is resolved in three layers; later layers override earlier ones. + +1. `config/technology/.mk` (tracked) — technology policy shared by + every product: corner/scenario names, OCV derate values, DRV limits, CTS + targets, and library-family naming conventions. +2. `config/products/.mk` (tracked) — product policy: `TOP`, the canonical + clock-domain list, qualified-I/O interface groups, PLL mode policy + (`qualified` or `parked`), and non-sensitive floorplan/timing defaults. + `tcl/common/products/.tcl` mirrors it with the product clock overlays + and I/O port mapping. Adding a product (for example Std/Pro) means adding + these two files plus one ignored local configuration; the Makefile and the + shared Tcl do not change. +3. `local/-.mk` (ignored) — site values: tool commands and + LSF queues, Liberty/DB/LEF/GDS/CDL/RC/deck paths, PDK cell lists, routing + layers, and reviewed I/O timing budgets. Copy the matching + `config/-.example.mk` template and replace every + `REQUIRED` value. + +Run outputs land below `build/commercial////`. The +`` path component was added with product parameterization; runs created +at `build/commercial/ics55//` predate it and are not migrated. A +pre-parameterization `local/ics55-production.mk` remains valid for Mini when +passed explicitly through `LOCAL_CONFIG`; rename it to `local/ics55-mini.mk` +to use the default lookup. ## Execution boundary The development and EDA zones share the repository and build directories through a site-mounted shared filesystem, but have different responsibilities: -1. In the development zone, generate a production RTL package: +1. In the development zone, generate a production RTL package. For Mini: ```sh make CONFIG=configs/cluster/ics55.mk \ @@ -17,30 +46,41 @@ through a site-mounted shared filesystem, but have different responsibilities: commercial-package ``` -2. In the EDA zone, create the ignored local configuration from - `config/ics55.example.mk`, point `RTL_ARCHIVE` at the generated - `retrosoc_mini_sources.tar.gz`, and choose one of two entry points. - For synthesis with internal timing only: + For Tiny (the committed `configs/ci/ics55-tiny.mk` profile already carries + the required PLL/SRAM settings): ```sh - make -C physical/commercial \ - LOCAL_CONFIG=local/ics55-production.mk doctor-syn - make -C physical/commercial \ - LOCAL_CONFIG=local/ics55-production.mk RUN_ID= syn - make -C physical/commercial \ - LOCAL_CONFIG=local/ics55-production.mk RUN_ID= fm-rtl2syn + make CONFIG=configs/ci/ics55-tiny.mk commercial-package + ``` + + Both packages carry `rtl/filelist.fl` and the generated + `rtl/contracts/commercial_timing_contract.tcl`; Tiny additionally carries + `contracts/tiny_core.sdc` for the smoke STA flow. Packages created before + the nine-domain Mini clock inventory (2026-08-30) are incompatible with + the current constraint stack and must be regenerated. + +2. In the EDA zone, create the ignored local configuration from the matching + example template and point `RTL_ARCHIVE` at the generated + `retrosoc__sources.tar.gz`. For synthesis with internal timing + only: + + ```sh + make -C physical/commercial SOC=MINI doctor-syn + make -C physical/commercial SOC=MINI RUN_ID= syn + make -C physical/commercial SOC=MINI RUN_ID= fm-rtl2syn ``` For production implementation, first populate every interface timing - budget, set `IO_TIMING_QUALIFIED=YES`, and run: + budget of the selected product, set `IO_TIMING_QUALIFIED=YES`, and run: ```sh - make -C physical/commercial \ - LOCAL_CONFIG=local/ics55-production.mk RUN_ID= doctor - make -C physical/commercial \ - LOCAL_CONFIG=local/ics55-production.mk RUN_ID= signoff + make -C physical/commercial SOC=MINI RUN_ID= doctor + make -C physical/commercial SOC=MINI RUN_ID= signoff ``` + `SOC` defaults to `MINI`; `LOCAL_CONFIG` defaults to + `local/-.mk`. + Commercial tools are never executed directly by this Makefile. Every tool stage is submitted as an independent blocking LSF job. `LSF_MODE=batch` uses `bsub -K`; sites that expose only an interactive queue use @@ -61,9 +101,10 @@ site-specific values, including: - tool commands and LSF queues/resources; - Liberty/DB, LEF, GDS, CDL, and RC technology files; - stream maps and Calibre DRC, antenna, and LVS decks; -- PDK cell lists, routing layers, sites, and power nets. +- PDK cell lists, routing layers, sites, and power nets; - reviewed min/max board and external-device I/O timing budgets; -- an audited ICS55 pad-mode timing hook when bidirectional arcs require it. +- an audited pad-mode timing hook when the product requires one (Mini does; + Tiny does not). Do not add absolute PDK/library paths to tracked Make, Tcl, Python, shell, or documentation files. Run `python3 scripts/audit_boundary.py --root ../..` @@ -82,20 +123,89 @@ input -> fm-syn2pr -> extract -> sta -> eco -> apr-eco -> reextract -> resta - -> pv-merge -> pv-drc -> pv-antenna -> pv-lvs + -> pv-merge -> pv-drc -> pv-antenna -> pv-lvs -> pv-macro-lvs ``` Each stage writes `log/`, `reports/`, `output/`, a result JSON, and a success -stamp below: +stamp below the run root. A stamp is created only when LSF reports success and +every required output, including the stage verdict marker, exists. Re-running +`signoff` resumes from the first missing stamp. Use `force-` to rerun +one stage and downstream dependencies; use `clean-stage STAGE=` for +targeted cleanup. -```text -build/commercial/ics55// -``` +`pv-macro-lvs` verifies the macros listed in `MACRO_LVS_CELLS` (for example +`PLL_TOP`) individually against `MACRO_GDS`/`MACRO_CDL` with LVS+ERC; with an +empty list the stage records a harmless skip. -A stamp is created only when LSF reports success and every required output, -including the stage verdict marker, exists. Re-running `signoff` resumes from -the first missing stamp. Use `force-` to rerun one stage and downstream -dependencies; use `clean-stage STAGE=` for targeted cleanup. +## Timing constraint model + +Constraints are generated from the canonical per-product inventory +(`rtl//integration/clock_reset_domains.json` + `pin_map.json`) into +the packaged `commercial_timing_contract.tcl` by +`scripts/generate_timing_contract.py --soc `, which rejects any drift +from the product domain list in `config/products/.mk`. The shared Tcl +(`tcl/common/clocks.tcl`) creates one master clock per source-port domain at +its contract observation and period, a `-divide_by 1` generated clock for +domains derived from another domain, groups clocks by the contract +`async_group`, applies uncertainty/transition from configuration, and +false-paths the canonical reset ports. Product overlays +(`tcl/common/products/.tcl`) own the rest: + +- Mini (`mini.tcl`): the LP root mux is modeled with physically exclusive + generated clocks — `clk_lp_ext`/`clk_pclk_ext` from the 24 MHz reference + (bypass) and `clk_lp_pll`/`clk_pclk_pll` from `clk_pll` (PLL performance + mode, master on `u_rcu/u_clock_reset_subsystem/u_tc_pll/u_PLL_TOP/CKOUT1`). + Qualified I/O covers JTAG, DVP (GPIO10-20), ULPI, SDRAM, SDIO, XPI, and + asynchronous pads (UART0/1, remaining GPIO); a reviewed + `TIMING_IO_MODE_HOOK` is mandatory in qualified mode. +- Tiny (`tiny.tcl`): SAFE24 boot — `clk_system` (external 24 MHz) and + `clk_jtag` only. The PLL_TOP macro is parked (EN=0), is not a clock source, + and has no characterized timing arcs yet (see `docs/ip/tiny-soc.md` + TINY-055); its DB/LEF/GDS/CDL views are still required for link, placement, + and LVS. Qualified I/O covers JTAG, XPI, and asynchronous pads. + +## Legacy-flow parity + +This implementation re-expresses the legacy CX55 flow +(DC + Formality + Innovus + StarRC + PrimeTime/PT-ECO + Calibre) and carries +over every capability the legacy project actually exercised on CX55: + +- OCV derate policy in PrimeTime per scenario PVT and in Innovus per stage, + with the legacy per-stage setup uncertainty tightening + (`flow::apply_signoff_derate`, `flow::apply_apr_derate`, + `flow::apply_apr_stage_uncertainty` in `tcl/common/corners.tcl`); +- DC compile policy: critical range, high-effort TNS optimization, path + groups, latch-based clock-gating style with synchronizer exclusion, + `set_max_area 0`, `set_wire_load_mode top`, macro `set_dont_touch`, and the + legacy hdlin/suppress-message subset; +- PrimeTime CRPR, signal-integrity analysis settings, the full legacy + `check_timing` include list, PBA-exhaustive reports, optional SAIF power + analysis (`STA_SAIF_FILE`), and a configurable SDF scenario + (`STA_SDF_SCENARIO`); +- Innovus engine settings (OCV analysis, CPPR, postRoute extraction, + SI/timing-driven routing), per-stage DRV limits, macro preplacement + (`APR_MACRO_LOC_FILE`) with halos, CTS NDR with trunk shielding and ccopt + targets, full `streamOut` options, a reviewed pad-order file slot + (`APR_IO_ORDER_FILE`), and optional multi-power-net mapping + (`APR_POWER_PIN_MAP`/`APR_GROUND_PIN_MAP`, for example a separate PLL AVDD); +- PrimeTime ECO over all 13 restored DMSA scenarios with merged changes, plus + optional VT-swap, size-down, and buffer-removal sub-flows + (`ECO_ENABLE_*`, off by default) and `freeze_silicon` physical mode; +- Calibre merge with an `OTHER_GDS` slot (seal ring), macro-level LVS/ERC, + and `CALIBRE_LVS_ARGS` performance options. + +Capabilities the legacy framework carried but never exercised on CX55 are +deliberately not migrated: DCG/DCNXT physical synthesis (only ever configured +for tsmc28), the Cadence Genus setup (SMIC110 remnant), DFT/scan hooks +(inactive in both flows), UPF low-power, ETM hardening and the hierarchical +harden tree, IP/library preparation helpers (`fake_db.py`, `itf_to_ict.pl`, +`lib2db`, `std_wrapper_gen.py`, `gen_memory.sh`, `timed_run.py`), the legacy +`func/gdsout.tcl` (replaced by the calibredrv merge), `formate_pt2edi.pl` +(replaced by `scripts/translate_eco.py`), the `bes_data_zcn` snapshot, and the +legacy automatic I/O delay decorator (replaced by the `doctor-syn` internal-QoR +mode plus qualified budget review). The legacy multi-design entry points +(`run_syn -d ...`) are superseded by the `SOC` product layer; external +non-retroSoC designs are out of scope. ## Strict verdict @@ -113,19 +223,12 @@ The default policy is blocking: - Calibre DRC and antenna counts must be zero and LVS must be clean. The production `doctor` requires every violation threshold to remain zero. -It also requires qualified JTAG, DVP, ULPI, SDRAM, SDIO, XPI, and asynchronous -I/O budgets and an audited local hook that selects GPIO10-20 DVP input mode -and cuts invalid bidirectional-pad feedback arcs. `doctor-syn` is the only -relaxed entry point: Design Compiler then false-paths all top-level I/O and -reports internal sequential QoR. Its summary carries `io_qualified=no`, and -Innovus and PrimeTime reject it. - -The canonical six-domain clock inventory is generated from -`rtl/mini/integration/clock_reset_domains.json` into the commercial RTL -package. Commercial constraints add the crystal and PLL overlays and model -the system clock mux as physically exclusive external-source and PLL-source -generated clocks. The asynchronous relationships cover system, audio, -crystal, JTAG, DVP, and ULPI domains. +It also requires the product's qualified I/O budgets and, for Mini, an audited +local hook that selects GPIO10-20 DVP input mode and cuts invalid +bidirectional-pad feedback arcs. `doctor-syn` is the only relaxed entry point: +Design Compiler then false-paths all top-level I/O and reports internal +sequential QoR. Its summary carries `io_qualified=no`, and Innovus and +PrimeTime reject it. The standard-cell family is LLSC H7C across DB, Liberty, LEF, GDS, and CDL. Design Compiler links one TYP set and targets only H7CR (SVT) plus H7CL @@ -141,15 +244,6 @@ syn/output/synthesis.path_groups.tsv Detailed clock, exception, setup/hold, QoR, design-rule, library-binding, and reference reports are written below `syn/reports/`. -## Legacy-flow audit - -The legacy source contains valid DC, Formality, Innovus, StarRC, PrimeTime, -PrimeTime ECO, Calibre, and `calibredrv` behavior, but also contains unrelated -process branches, old top-level names, copied RTL, absolute site paths, and -many generated sessions and reports. This implementation re-expresses only -the ICS55/`retrosoc_asic` behavior and does not source or execute legacy -scripts. - The common corner contract is: | PVT | RC views | @@ -158,7 +252,23 @@ The common corner contract is: | `TYP` | `TYP` | | `MIN`, `ML` | `Cworst`, `RCworst`, `Cbest`, `RCbest` | -The old flow used a single typical Innovus analysis view and delegated broad -corner coverage to separate StarRC/PrimeTime runs. The new flow derives APR, -extraction, STA, and ECO scenarios from one configuration and rejects missing -views. +APR, extraction, STA, and ECO scenarios all derive from this single +configuration, and the flow rejects missing views. + +## Known gaps and risks + +- The APR-and-later stages have not yet executed on any product; only + `input`, `syn`, and `fm-rtl2syn` have run to completion. Expect first-run + issues in later stages and retain logs when they surface. +- PLL_TOP ships without characterized timing arcs, so PLL output timing is + modeled by the product overlays, not by library arcs. Tiny additionally + parks the PLL (EN=0). Timing closure is mandatory only in the final + complete-product campaign (`docs/ip/tiny-soc.md` policy). +- Without `APR_IO_ORDER_FILE`, the floorplan stage distributes pads + round-robin at a fixed pitch; production runs must supply a reviewed pad + order. The stage records `io_order=file|round_robin` in + `apr/floorplan/output/floorplan.io.json`. +- The default power hook maps every `APR_POWER_PINS` entry (including the PLL + analog supplies) onto the single digital power net; use + `APR_POWER_PIN_MAP`/`APR_GROUND_PIN_MAP` when a separate PLL supply domain + is required. diff --git a/physical/commercial/config/ics55.example.mk b/physical/commercial/config/ics55-mini.example.mk similarity index 75% rename from physical/commercial/config/ics55.example.mk rename to physical/commercial/config/ics55-mini.example.mk index a257b9e2..1530cefe 100644 --- a/physical/commercial/config/ics55.example.mk +++ b/physical/commercial/config/ics55-mini.example.mk @@ -8,11 +8,11 @@ # MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. # See the Mulan PSL v2 for more details. -# Copy this file to ../local/ics55-production.mk and replace every REQUIRED -# value there. The local copy is ignored by Git. +# Mini/ICS55 local configuration template. Copy this file to +# ../local/ics55-mini.mk and replace every REQUIRED value there. The local +# copy is ignored by Git. Product and technology policy defaults come from +# ../config/products/mini.mk and ../config/technology/ics55.mk. -TOP := retrosoc_asic -TECHNOLOGY := ICS55 RTL_ARCHIVE := REQUIRED BUILD_ROOT := $(ROOT_PATH)/build @@ -104,6 +104,7 @@ STARRC_MAP := REQUIRED STD_GDS := REQUIRED IO_GDS := REQUIRED MACRO_GDS := REQUIRED +OTHER_GDS := STD_CDL := REQUIRED IO_CDL := REQUIRED MACRO_CDL := REQUIRED @@ -149,21 +150,20 @@ ECO_HOLD_MARGIN_NS := 0.0 ECO_MAX_PROCESSES := 8 ECO_PHYSICAL_MODE := open_site -# Non-sensitive design intent. Units are micrometres and nanoseconds. -DIE_WIDTH := 2400 -DIE_HEIGHT := 2400 -CORE_MARGIN_LEFT := 120 -CORE_MARGIN_BOTTOM := 120 -CORE_MARGIN_RIGHT := 120 -CORE_MARGIN_TOP := 120 -CORE_UTILIZATION := 0.60 -XTAL_CLK_PERIOD_NS := 41.666666667 -PLL_OUTPUT_PERIOD_NS := 13.888888889 -CLOCK_SETUP_UNCERTAINTY_NS := 0.20 -CLOCK_HOLD_UNCERTAINTY_NS := 0.10 -CLOCK_TRANSITION_NS := 0.10 -MAX_TRANSITION_NS := 0.50 -MAX_FANOUT := 32 +# Optional product-intent overrides (defaults in ../config/products/mini.mk). +# DIE_WIDTH := 2400 +# DIE_HEIGHT := 2400 +# CORE_MARGIN_LEFT := 258 +# CORE_MARGIN_BOTTOM := 258 +# CORE_MARGIN_RIGHT := 258 +# CORE_MARGIN_TOP := 258 +# CORE_UTILIZATION := 0.60 +# PLL_OUTPUT_PERIOD_NS := 13.888888889 +# CLOCK_SETUP_UNCERTAINTY_NS := 0.20 +# CLOCK_HOLD_UNCERTAINTY_NS := 0.10 +# CLOCK_TRANSITION_NS := 0.10 +# MAX_TRANSITION_NS := 0.50 +# MAX_FANOUT := 32 # Board/device timing is local qualification data. Set YES only when every # interface budget below is taken from reviewed board and component timing. @@ -219,10 +219,9 @@ ASYNC_OUTPUT_DELAY_MAX_NS := REQUIRED ASYNC_OUTPUT_DELAY_MIN_NS := REQUIRED ASYNC_OUTPUT_LOAD_PF := REQUIRED -# The qualified ICS55 PLL mode is fixed. Other runtime selections fail to lock. -ICS55_PLL_SUPPORTED_SEL := 0 -ICS55_PLL_N := 2 -ICS55_PLL_OD := 2 +# The qualified ICS55 PLL mode for Mini is fixed by +# ../config/products/mini.mk (SEL=0, N=2, OD=2). Other runtime selections +# fail to lock. # TIMING_IO_MODE_HOOK is required when IO_TIMING_QUALIFIED=YES. It must select # the reviewed GPIO10-20 DVP mode and cut invalid bidirectional pad feedback. @@ -235,9 +234,30 @@ APR_ROUTE_HOOK := TIMING_IO_MODE_HOOK := REQUIRED TIMING_EXCEPTION_HOOK := +# Optional flow behavior slots (technology defaults live in +# ../config/technology/ics55.mk; see README.md for the full reference). +# STA_SAIF_FILE := # SAIF for per-scenario report_power +# STA_SDF_SCENARIO := func_TYP_TYP_25 +# SYN_CLOCK_GATING_EXCLUDE_PATTERNS := # clock-gating exclusion name globs +# APR_IO_ORDER_FILE := # reviewed pad order; default round-robin +# APR_MACRO_LOC_FILE := # fixed macro coordinates (place_macro) +# APR_MACRO_HALO_UM := 5 +# APR_NETLIST_EXCLUDE_CELLS := # e.g. seal-ring cells excluded from netlist +# APR_POWER_PIN_MAP := # "PLL_AVDD:AVDD PLL_AVDD_DRV:DVDD_DRV" +# APR_GROUND_PIN_MAP := # "PLL_AVSS:AVSS PLL_AVSS_DRV:DVSS_DRV" +# APR_EARLY_GLOBAL_MIN_LAYER := +# APR_EARLY_GLOBAL_MAX_LAYER := +# ECO_ENABLE_VT_SWAP := NO +# ECO_VT_PATTERN_PRIORITY := # required when ECO_ENABLE_VT_SWAP=YES +# ECO_ENABLE_SIZE_DOWN := NO +# ECO_ENABLE_REMOVE_BUFFER := NO +# ECO_PBA_MODE := none +# MACRO_LVS_CELLS := PLL_TOP # macro-level LVS/ERC (legacy CX55 practice) +# CALIBRE_LVS_ARGS := -turbo -hyper + # Required strict thresholds. MAX_SETUP_VIOLATIONS := 0 MAX_HOLD_VIOLATIONS := 0 MAX_DRV_VIOLATIONS := 0 MAX_DRC_RESULTS := 0 -MAX_ANTENNA_RESULTS := 0 \ No newline at end of file +MAX_ANTENNA_RESULTS := 0 diff --git a/physical/commercial/config/ics55-tiny.example.mk b/physical/commercial/config/ics55-tiny.example.mk new file mode 100644 index 00000000..c01a0aee --- /dev/null +++ b/physical/commercial/config/ics55-tiny.example.mk @@ -0,0 +1,234 @@ +# Copyright (c) 2023-2026 Yuchi Miao +# retroSoC is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. + +# Tiny/ICS55 local configuration template. Copy this file to +# ../local/ics55-tiny.mk and replace every REQUIRED value there. The local +# copy is ignored by Git. Product and technology policy defaults come from +# ../config/products/tiny.mk and ../config/technology/ics55.mk. +# +# Tiny boots SAFE24: the PLL_TOP macro is instantiated in the netlist but +# parked off (EN=0) and is not a clock source. Its DB/LEF/GDS/CDL views are +# still required below because the macro must link, place, and verify. + +RTL_ARCHIVE := REQUIRED +BUILD_ROOT := $(ROOT_PATH)/build + +FLOW_PYTHON := python +TCLSH := tclsh +LSF_COMMAND := bsub +LSF_MODE := batch +LSF_COMMON_ARGS := +LSF_SYN_ARGS := REQUIRED +LSF_FM_ARGS := REQUIRED +LSF_APR_ARGS := REQUIRED +LSF_EXTRACT_ARGS := REQUIRED +LSF_STA_ARGS := REQUIRED +LSF_ECO_ARGS := REQUIRED +LSF_PV_ARGS := REQUIRED + +DC_SHELL := dc_shell +FM_SHELL := fm_shell +INNOVUS := innovus +STARRC := StarXtract +PT_SHELL := pt_shell +CALIBRE := calibre +CALIBREDRV := calibredrv +V2LVS := v2lvs + +# Synopsys DB views. Every list must be explicit; wildcards are rejected. +STD_DB_MAX := REQUIRED +STD_DB_WCL := REQUIRED +STD_DB_TYP := REQUIRED +STD_DB_MIN := REQUIRED +STD_DB_ML := REQUIRED +# DC target subset: H7CR (SVT) and H7CL (LVT) TYP DBs only. +SYN_STD_DB_TYP := REQUIRED +IO_DB_MAX := REQUIRED +IO_DB_WCL := REQUIRED +IO_DB_TYP := REQUIRED +IO_DB_MIN := REQUIRED +IO_DB_ML := REQUIRED +SRAM_DB_MAX := REQUIRED +SRAM_DB_WCL := REQUIRED +SRAM_DB_TYP := REQUIRED +SRAM_DB_MIN := REQUIRED +SRAM_DB_ML := REQUIRED +PLL_DB := REQUIRED + +# Liberty views consumed by Innovus MMMC. +STD_LIB_MAX := REQUIRED +STD_LIB_WCL := REQUIRED +STD_LIB_TYP := REQUIRED +STD_LIB_MIN := REQUIRED +STD_LIB_ML := REQUIRED +IO_LIB_MAX := REQUIRED +IO_LIB_WCL := REQUIRED +IO_LIB_TYP := REQUIRED +IO_LIB_MIN := REQUIRED +IO_LIB_ML := REQUIRED +SRAM_LIB_MAX := REQUIRED +SRAM_LIB_WCL := REQUIRED +SRAM_LIB_TYP := REQUIRED +SRAM_LIB_MIN := REQUIRED +SRAM_LIB_ML := REQUIRED +PLL_LIB := REQUIRED + +# Exact internal name from the selected H7CR TYP DB. +SYN_OPERATING_CONDITION_LIBRARY := REQUIRED +SYN_OPERATING_CONDITION := REQUIRED + +# Innovus physical inputs. +TECH_LEF := REQUIRED +STD_LEFS := REQUIRED +IO_LEFS := REQUIRED +MACRO_LEFS := REQUIRED +CAP_TABLE_CWORST := REQUIRED +CAP_TABLE_RCWORST := REQUIRED +CAP_TABLE_CBEST := REQUIRED +CAP_TABLE_RCBEST := REQUIRED +CAP_TABLE_TYP := REQUIRED +STREAM_MAP := REQUIRED + +# StarRC technology inputs. +NXTGRD_CWORST := REQUIRED +NXTGRD_RCWORST := REQUIRED +NXTGRD_CBEST := REQUIRED +NXTGRD_RCBEST := REQUIRED +NXTGRD_TYP := REQUIRED +STARRC_MAP := REQUIRED + +# Calibre and merge inputs. +STD_GDS := REQUIRED +IO_GDS := REQUIRED +MACRO_GDS := REQUIRED +OTHER_GDS := +STD_CDL := REQUIRED +IO_CDL := REQUIRED +MACRO_CDL := REQUIRED +CALIBRE_DRC_DECK := REQUIRED +CALIBRE_ANT_DECK := REQUIRED +CALIBRE_LVS_DECK := REQUIRED +STARRC_CORES := 8 + +# PDK cell/site/layer names belong in this ignored local configuration. +APR_SITE := REQUIRED +APR_CORE_FILLERS := REQUIRED +APR_IO_FILLERS := REQUIRED +APR_SIGNAL_PAD_CELLS := REQUIRED +APR_IO_CORNER_CELL := REQUIRED +APR_IO_POWER_CELLS := REQUIRED +APR_IO_OFFSET := REQUIRED +APR_IO_PITCH := REQUIRED +APR_ENDCAP_CELLS := REQUIRED +APR_TIE_HIGH_CELL := REQUIRED +APR_TIE_LOW_CELL := REQUIRED +APR_CTS_BUFFER_CELLS := REQUIRED +APR_CTS_INVERTER_CELLS := REQUIRED +APR_CLOCK_ROUTING_LAYERS := REQUIRED +APR_SIGNAL_MIN_LAYER := REQUIRED +APR_SIGNAL_MAX_LAYER := REQUIRED +APR_POWER_NET := REQUIRED +APR_GROUND_NET := REQUIRED +APR_POWER_PINS := REQUIRED +APR_GROUND_PINS := REQUIRED +APR_RING_LAYERS := REQUIRED +APR_STRIPE_LAYER := REQUIRED +APR_RING_WIDTH := REQUIRED +APR_RING_SPACING := REQUIRED +APR_RING_OFFSET := REQUIRED +APR_STRIPE_WIDTH := REQUIRED +APR_STRIPE_SPACING := REQUIRED +APR_STRIPE_PITCH := REQUIRED +SYN_DONT_USE := REQUIRED +ECO_SETUP_BUFFERS := REQUIRED +ECO_HOLD_BUFFERS := REQUIRED +ECO_SETUP_MARGIN_NS := 0.0 +ECO_HOLD_MARGIN_NS := 0.0 +ECO_MAX_PROCESSES := 8 +ECO_PHYSICAL_MODE := open_site + +# Tiny has no tracked die defaults yet; size the pad ring and core from the +# product floorplan review before implementation. +DIE_WIDTH := REQUIRED +DIE_HEIGHT := REQUIRED +CORE_MARGIN_LEFT := REQUIRED +CORE_MARGIN_BOTTOM := REQUIRED +CORE_MARGIN_RIGHT := REQUIRED +CORE_MARGIN_TOP := REQUIRED +CORE_UTILIZATION := REQUIRED +# CLOCK_SETUP_UNCERTAINTY_NS := 0.20 +# CLOCK_HOLD_UNCERTAINTY_NS := 0.10 +# CLOCK_TRANSITION_NS := 0.10 +# MAX_TRANSITION_NS := 0.50 +# MAX_FANOUT := 32 + +# Board/device timing is local qualification data. Set YES only when every +# interface budget below is taken from reviewed board and component timing. +IO_TIMING_QUALIFIED := NO + +JTAG_INPUT_DELAY_MAX_NS := REQUIRED +JTAG_INPUT_DELAY_MIN_NS := REQUIRED +JTAG_INPUT_TRANSITION_NS := REQUIRED +JTAG_OUTPUT_DELAY_MAX_NS := REQUIRED +JTAG_OUTPUT_DELAY_MIN_NS := REQUIRED +JTAG_OUTPUT_LOAD_PF := REQUIRED + +XPI_CLOCK_PERIOD_NS := REQUIRED +XPI_INPUT_DELAY_MAX_NS := REQUIRED +XPI_INPUT_DELAY_MIN_NS := REQUIRED +XPI_INPUT_TRANSITION_NS := REQUIRED +XPI_OUTPUT_DELAY_MAX_NS := REQUIRED +XPI_OUTPUT_DELAY_MIN_NS := REQUIRED +XPI_OUTPUT_LOAD_PF := REQUIRED + +ASYNC_CLOCK_PERIOD_NS := REQUIRED +ASYNC_INPUT_DELAY_MAX_NS := REQUIRED +ASYNC_INPUT_DELAY_MIN_NS := REQUIRED +ASYNC_INPUT_TRANSITION_NS := REQUIRED +ASYNC_OUTPUT_DELAY_MAX_NS := REQUIRED +ASYNC_OUTPUT_DELAY_MIN_NS := REQUIRED +ASYNC_OUTPUT_LOAD_PF := REQUIRED + +# Optional hooks. +APR_FLOORPLAN_HOOK := +APR_POWER_HOOK := +APR_PLACE_HOOK := +APR_CTS_HOOK := +APR_ROUTE_HOOK := +TIMING_IO_MODE_HOOK := +TIMING_EXCEPTION_HOOK := + +# Optional flow behavior slots (technology defaults live in +# ../config/technology/ics55.mk; see README.md for the full reference). +# STA_SAIF_FILE := # SAIF for per-scenario report_power +# STA_SDF_SCENARIO := func_TYP_TYP_25 +# SYN_CLOCK_GATING_EXCLUDE_PATTERNS := # clock-gating exclusion name globs +# APR_IO_ORDER_FILE := # reviewed pad order; default round-robin +# APR_MACRO_LOC_FILE := # fixed macro coordinates (place_macro) +# APR_MACRO_HALO_UM := 5 +# APR_NETLIST_EXCLUDE_CELLS := # e.g. seal-ring cells excluded from netlist +# APR_POWER_PIN_MAP := # "PLL_AVDD:AVDD" (PLL parked: tie to VDD) +# APR_GROUND_PIN_MAP := # "PLL_AVSS:AVSS" +# APR_EARLY_GLOBAL_MIN_LAYER := +# APR_EARLY_GLOBAL_MAX_LAYER := +# ECO_ENABLE_VT_SWAP := NO +# ECO_VT_PATTERN_PRIORITY := # required when ECO_ENABLE_VT_SWAP=YES +# ECO_ENABLE_SIZE_DOWN := NO +# ECO_ENABLE_REMOVE_BUFFER := NO +# ECO_PBA_MODE := none +# MACRO_LVS_CELLS := # e.g. PLL_TOP for macro-level LVS/ERC +# CALIBRE_LVS_ARGS := -turbo -hyper + +# Required strict thresholds. +MAX_SETUP_VIOLATIONS := 0 +MAX_HOLD_VIOLATIONS := 0 +MAX_DRV_VIOLATIONS := 0 +MAX_DRC_RESULTS := 0 +MAX_ANTENNA_RESULTS := 0 diff --git a/physical/commercial/config/products/mini.mk b/physical/commercial/config/products/mini.mk new file mode 100644 index 00000000..59e62cff --- /dev/null +++ b/physical/commercial/config/products/mini.mk @@ -0,0 +1,75 @@ +# Copyright (c) 2023-2026 Yuchi Miao +# retroSoC is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. + +# retroSoC Mini product policy for the commercial flow. Non-sensitive design +# intent only; site values live in the ignored local configuration. + +SOC := MINI +TOP := retrosoc_asic + +# Canonical clock domains, mirrored by tcl/common/products/mini.tcl and +# validated against rtl/mini/integration/clock_reset_domains.json when the +# commercial timing contract is generated. +COMMERCIAL_CLOCK_DOMAINS := aon lp hp pclk memory audio jtag dvp usb2_ulpi + +# Qualified-I/O interface groups and the budget variables each group requires. +COMMERCIAL_IO_INTERFACES := JTAG DVP ULPI SDRAM SDIO XPI ASYNC +COMMERCIAL_IO_BUDGET_VARIABLES := \ + JTAG_INPUT_DELAY_MAX_NS JTAG_INPUT_DELAY_MIN_NS \ + JTAG_INPUT_TRANSITION_NS JTAG_OUTPUT_DELAY_MAX_NS \ + JTAG_OUTPUT_DELAY_MIN_NS JTAG_OUTPUT_LOAD_PF \ + DVP_INPUT_DELAY_MAX_NS DVP_INPUT_DELAY_MIN_NS \ + DVP_INPUT_TRANSITION_NS \ + ULPI_INPUT_DELAY_MAX_NS ULPI_INPUT_DELAY_MIN_NS \ + ULPI_INPUT_TRANSITION_NS ULPI_OUTPUT_DELAY_MAX_NS \ + ULPI_OUTPUT_DELAY_MIN_NS ULPI_OUTPUT_LOAD_PF \ + SDRAM_CLOCK_PERIOD_NS SDRAM_INPUT_DELAY_MAX_NS \ + SDRAM_INPUT_DELAY_MIN_NS SDRAM_INPUT_TRANSITION_NS \ + SDRAM_OUTPUT_DELAY_MAX_NS SDRAM_OUTPUT_DELAY_MIN_NS \ + SDRAM_OUTPUT_LOAD_PF \ + SDIO_CLOCK_PERIOD_NS SDIO_INPUT_DELAY_MAX_NS \ + SDIO_INPUT_DELAY_MIN_NS SDIO_INPUT_TRANSITION_NS \ + SDIO_OUTPUT_DELAY_MAX_NS SDIO_OUTPUT_DELAY_MIN_NS \ + SDIO_OUTPUT_LOAD_PF \ + XPI_CLOCK_PERIOD_NS XPI_INPUT_DELAY_MAX_NS \ + XPI_INPUT_DELAY_MIN_NS XPI_INPUT_TRANSITION_NS \ + XPI_OUTPUT_DELAY_MAX_NS XPI_OUTPUT_DELAY_MIN_NS \ + XPI_OUTPUT_LOAD_PF \ + ASYNC_CLOCK_PERIOD_NS ASYNC_INPUT_DELAY_MAX_NS \ + ASYNC_INPUT_DELAY_MIN_NS ASYNC_INPUT_TRANSITION_NS \ + ASYNC_OUTPUT_DELAY_MAX_NS ASYNC_OUTPUT_DELAY_MIN_NS \ + ASYNC_OUTPUT_LOAD_PF + +# Qualified Mini I/O requires a reviewed pad-mode hook that selects the +# GPIO10-20 DVP input mode and cuts invalid bidirectional-pad feedback arcs. +PRODUCT_REQUIRES_IO_MODE_HOOK := YES + +# The Mini RCU clocks the system from PLL_TOP in the qualified production +# mode; doctor enforces the reviewed SEL/N/OD values below. +PRODUCT_PLL_MODE := qualified +ICS55_PLL_SUPPORTED_SEL ?= 0 +ICS55_PLL_N ?= 2 +ICS55_PLL_OD ?= 2 + +# Non-sensitive floorplan/timing defaults; the local configuration may +# override them. +DIE_WIDTH ?= 2400 +DIE_HEIGHT ?= 2400 +CORE_MARGIN_LEFT ?= 258 +CORE_MARGIN_BOTTOM ?= 258 +CORE_MARGIN_RIGHT ?= 258 +CORE_MARGIN_TOP ?= 258 +CORE_UTILIZATION ?= 0.60 +PLL_OUTPUT_PERIOD_NS ?= 13.888888889 +CLOCK_SETUP_UNCERTAINTY_NS ?= 0.20 +CLOCK_HOLD_UNCERTAINTY_NS ?= 0.10 +CLOCK_TRANSITION_NS ?= 0.10 +MAX_TRANSITION_NS ?= 0.50 +MAX_FANOUT ?= 32 diff --git a/physical/commercial/config/products/tiny.mk b/physical/commercial/config/products/tiny.mk new file mode 100644 index 00000000..85a0f2c3 --- /dev/null +++ b/physical/commercial/config/products/tiny.mk @@ -0,0 +1,50 @@ +# Copyright (c) 2023-2026 Yuchi Miao +# retroSoC is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. + +# retroSoC Tiny product policy for the commercial flow. Non-sensitive design +# intent only; site values live in the ignored local configuration. + +SOC := TINY +TOP := retrosoc_tiny_asic + +# Canonical clock domains, mirrored by tcl/common/products/tiny.tcl and +# validated against rtl/tiny/integration/clock_reset_domains.json when the +# commercial timing contract is generated. Tiny boots SAFE24: the PLL_TOP +# macro is present in the netlist but parked off and is not a clock source. +COMMERCIAL_CLOCK_DOMAINS := system jtag + +# Qualified-I/O interface groups and the budget variables each group requires. +COMMERCIAL_IO_INTERFACES := JTAG XPI ASYNC +COMMERCIAL_IO_BUDGET_VARIABLES := \ + JTAG_INPUT_DELAY_MAX_NS JTAG_INPUT_DELAY_MIN_NS \ + JTAG_INPUT_TRANSITION_NS JTAG_OUTPUT_DELAY_MAX_NS \ + JTAG_OUTPUT_DELAY_MIN_NS JTAG_OUTPUT_LOAD_PF \ + XPI_CLOCK_PERIOD_NS XPI_INPUT_DELAY_MAX_NS \ + XPI_INPUT_DELAY_MIN_NS XPI_INPUT_TRANSITION_NS \ + XPI_OUTPUT_DELAY_MAX_NS XPI_OUTPUT_DELAY_MIN_NS \ + XPI_OUTPUT_LOAD_PF \ + ASYNC_CLOCK_PERIOD_NS ASYNC_INPUT_DELAY_MAX_NS \ + ASYNC_INPUT_DELAY_MIN_NS ASYNC_INPUT_TRANSITION_NS \ + ASYNC_OUTPUT_DELAY_MAX_NS ASYNC_OUTPUT_DELAY_MIN_NS \ + ASYNC_OUTPUT_LOAD_PF + +# Tiny has no bidirectional pad-mode selection requirement. +PRODUCT_REQUIRES_IO_MODE_HOOK := NO + +# The Tiny PLL_TOP macro is parked (EN=0) and has no characterized timing +# arcs; doctor skips the qualified PLL-mode checks for this product. See +# docs/ip/tiny-soc.md (TINY-055) for the qualification boundary. +PRODUCT_PLL_MODE := parked + +CLOCK_SETUP_UNCERTAINTY_NS ?= 0.20 +CLOCK_HOLD_UNCERTAINTY_NS ?= 0.10 +CLOCK_TRANSITION_NS ?= 0.10 +MAX_TRANSITION_NS ?= 0.50 +MAX_FANOUT ?= 32 diff --git a/physical/commercial/config/technology/ics55.mk b/physical/commercial/config/technology/ics55.mk new file mode 100644 index 00000000..4bd15b37 --- /dev/null +++ b/physical/commercial/config/technology/ics55.mk @@ -0,0 +1,68 @@ +# Copyright (c) 2023-2026 Yuchi Miao +# retroSoC is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. + +# ICS55 (ICsprout 55LLULP1233) technology policy: non-sensitive naming and +# corner conventions shared by every product on this technology. Library +# paths, cell lists, and deck locations remain in the ignored local +# configuration. + +# PVT corner set used across synthesis, extraction, STA, and ECO. +COMMERCIAL_PVT_CORNERS := MAX WCL TYP MIN ML + +# StarRC extraction corners written by the extract stage; check_outputs.py +# requires one SPEF per entry. +COMMERCIAL_EXTRACTION_CORNERS := \ + Cworst_m40 Cworst_125 \ + RCworst_m40 RCworst_125 \ + Cbest_m40 Cbest_125 \ + RCbest_m40 RCbest_125 \ + TYP_25 + +# H7C standard-cell family naming policy: cell-name suffix tokens feed the +# synthesis VT utilization report, and library-name glob patterns bind the +# threshold-voltage groups in the TYP link set. +SYN_VT_SUFFIX_MAP := HVT H7H LVT H7L SVT H7R +SYN_VT_LIBRARY_MAP := HVT *H7CH* LVT *H7CL* SVT *H7CR* + +# OCV derate policy (legacy bes_data/common/signoff_table.tcl, CX55 section; +# PrimeTime-only — legacy DC synthesis applied no derate): +TIMING_DERATE_SETUP_CELL_CLOCK_EARLY := 0.95 +TIMING_DERATE_SETUP_NET_CLOCK_EARLY := 0.95 +TIMING_DERATE_HOLD_CELL_CLOCK_EARLY := 0.90 +TIMING_DERATE_HOLD_NET_CLOCK_EARLY := 0.90 +TIMING_DERATE_DEFAULT_EARLY := 0.95 + +# Innovus derate and per-stage setup uncertainty (legacy +# pd_data/pr/scr/CL1/set_derate_uncertainty.tcl). +APR_DERATE_CLOCK_EARLY := 0.95 +APR_DERATE_CLOCK_LATE := 1.10 +APR_DERATE_DATA_LATE := 1.10 +APR_SETUP_UNCERTAINTY_PLACE_NS := 0.25 +APR_SETUP_UNCERTAINTY_CTS_NS := 0.225 +APR_SETUP_UNCERTAINTY_ROUTE_NS := 0.20 + +# Innovus per-stage DRV limits (legacy pd_data/pr/scr/CL1/update_sdc.tcl) and +# CTS targets (legacy pd_data/pr/scr/setting/cts_setting.tcl). +APR_DESIGN_PROCESS ?= 55 +APR_MAX_FANOUT ?= 32 +APR_MAX_TRANSITION_NS ?= 0.08 +APR_MAX_CAPACITANCE_PF ?= 0.15 +APR_CTS_NDR_WIDTH_UM ?= 0.2 +APR_CTS_NDR_SPACING_UM ?= 0.2 +APR_CTS_TARGET_SKEW_NS ?= 0.08 +APR_CTS_TARGET_MAX_TRANS_LEAF_NS ?= 0.78 +APR_CTS_TARGET_MAX_TRANS_TRUNK_NS ?= 0.78 +APR_CTS_TARGET_INSERTION_DELAY_NS ?= 0.05 +APR_CTS_MAX_FANOUT ?= 4 + +# Synthesis compile policy (legacy bes_data/syn/scr/flow_com/ +# syn_common_flow.tcl). +SYN_CRITICAL_RANGE_NS ?= 0.2 +SYN_COMPILE_CORES ?= 16 diff --git a/physical/commercial/scripts/check_outputs.py b/physical/commercial/scripts/check_outputs.py index 627894ab..5f2f62ac 100644 --- a/physical/commercial/scripts/check_outputs.py +++ b/physical/commercial/scripts/check_outputs.py @@ -22,17 +22,21 @@ def read_text(path): return handle.read() -SPEF_CORNERS = ( - "Cworst_m40", - "Cworst_125", - "RCworst_m40", - "RCworst_125", - "Cbest_m40", - "Cbest_125", - "RCbest_m40", - "RCbest_125", - "TYP_25", -) +def spef_corners(): + configured = os.environ.get("COMMERCIAL_EXTRACTION_CORNERS", "").split() + if configured: + return configured + return [ + "Cworst_m40", + "Cworst_125", + "RCworst_m40", + "RCworst_125", + "Cbest_m40", + "Cbest_125", + "RCbest_m40", + "RCbest_125", + "TYP_25", + ] def find_files(root, suffixes): @@ -63,15 +67,15 @@ def apply_check(kind, root, limit, top=None, minimum_mtime=None): else: files = [ os.path.join(root, "{0}.{1}.spef.gz".format(top, corner)) - for corner in SPEF_CORNERS + for corner in spef_corners() ] missing = [path for path in files if not os.path.isfile(path)] if missing: - error = "missing ICS55 SPEF corners: {0}".format( + error = "missing SPEF corners: {0}".format( ", ".join(os.path.basename(path) for path in missing) ) elif any(os.path.getsize(path) == 0 for path in files): - error = "an ICS55 SPEF corner is empty" + error = "an SPEF corner is empty" elif kind in ("calibre-drc", "calibre-antenna"): reports = find_files(root, (".summary", ".sum", ".rpt")) files = reports diff --git a/physical/commercial/scripts/clean_stage.py b/physical/commercial/scripts/clean_stage.py index 4cfd9014..fc12ab3a 100644 --- a/physical/commercial/scripts/clean_stage.py +++ b/physical/commercial/scripts/clean_stage.py @@ -38,6 +38,7 @@ STAGES = ( ("pv-drc", "pv/drc"), ("pv-antenna", "pv/antenna"), ("pv-lvs", "pv/lvs"), + ("pv-macro-lvs", "pv/macro_lvs"), ) diff --git a/physical/commercial/scripts/doctor.py b/physical/commercial/scripts/doctor.py index 1e2311b5..8930b12a 100644 --- a/physical/commercial/scripts/doctor.py +++ b/physical/commercial/scripts/doctor.py @@ -14,6 +14,7 @@ from __future__ import print_function import argparse import json import os +import re import shlex import subprocess import sys @@ -68,6 +69,18 @@ SYNTHESIS_FILE_VARIABLES = ( "PLL_DB", ) +# Optional file lists are validated only when the local configuration sets +# them; they never become required. +OPTIONAL_FILE_LIST_VARIABLES = ( + "OTHER_GDS", +) + +OPTIONAL_SWITCH_VARIABLES = ( + "ECO_ENABLE_VT_SWAP", + "ECO_ENABLE_SIZE_DOWN", + "ECO_ENABLE_REMOVE_BUFFER", +) + VALUE_VARIABLES = ( "TOP", "TECHNOLOGY", "RTL_ARCHIVE", "LSF_MODE", "APR_SITE", "APR_CORE_FILLERS", "APR_IO_FILLERS", "APR_SIGNAL_PAD_CELLS", "APR_IO_CORNER_CELL", @@ -98,32 +111,7 @@ SYNTHESIS_VALUE_VARIABLES = ( "SYN_OPERATING_CONDITION", ) -IO_BUDGET_VARIABLES = ( - "JTAG_INPUT_DELAY_MAX_NS", "JTAG_INPUT_DELAY_MIN_NS", - "JTAG_INPUT_TRANSITION_NS", "JTAG_OUTPUT_DELAY_MAX_NS", - "JTAG_OUTPUT_DELAY_MIN_NS", "JTAG_OUTPUT_LOAD_PF", - "DVP_INPUT_DELAY_MAX_NS", "DVP_INPUT_DELAY_MIN_NS", - "DVP_INPUT_TRANSITION_NS", - "ULPI_INPUT_DELAY_MAX_NS", "ULPI_INPUT_DELAY_MIN_NS", - "ULPI_INPUT_TRANSITION_NS", "ULPI_OUTPUT_DELAY_MAX_NS", - "ULPI_OUTPUT_DELAY_MIN_NS", "ULPI_OUTPUT_LOAD_PF", - "SDRAM_CLOCK_PERIOD_NS", "SDRAM_INPUT_DELAY_MAX_NS", - "SDRAM_INPUT_DELAY_MIN_NS", "SDRAM_INPUT_TRANSITION_NS", - "SDRAM_OUTPUT_DELAY_MAX_NS", "SDRAM_OUTPUT_DELAY_MIN_NS", - "SDRAM_OUTPUT_LOAD_PF", - "SDIO_CLOCK_PERIOD_NS", "SDIO_INPUT_DELAY_MAX_NS", - "SDIO_INPUT_DELAY_MIN_NS", "SDIO_INPUT_TRANSITION_NS", - "SDIO_OUTPUT_DELAY_MAX_NS", "SDIO_OUTPUT_DELAY_MIN_NS", - "SDIO_OUTPUT_LOAD_PF", - "XPI_CLOCK_PERIOD_NS", "XPI_INPUT_DELAY_MAX_NS", - "XPI_INPUT_DELAY_MIN_NS", "XPI_INPUT_TRANSITION_NS", - "XPI_OUTPUT_DELAY_MAX_NS", "XPI_OUTPUT_DELAY_MIN_NS", - "XPI_OUTPUT_LOAD_PF", - "ASYNC_CLOCK_PERIOD_NS", "ASYNC_INPUT_DELAY_MAX_NS", - "ASYNC_INPUT_DELAY_MIN_NS", "ASYNC_INPUT_TRANSITION_NS", - "ASYNC_OUTPUT_DELAY_MAX_NS", "ASYNC_OUTPUT_DELAY_MIN_NS", - "ASYNC_OUTPUT_LOAD_PF", -) +IO_BUDGET_VARIABLES = () H7C_PVT_TOKENS = { "MAX": "_ss_rcworst_1p08_125", @@ -208,18 +196,24 @@ def main(): if name in required_values and (not value or value == "REQUIRED"): errors.append("{0} is not configured".format(name)) - qualified_pll = { - "ICS55_PLL_SUPPORTED_SEL": "0", - "ICS55_PLL_N": "2", - "ICS55_PLL_OD": "2", - } - for name, expected in qualified_pll.items(): - value = os.environ.get(name, "").strip() - values[name] = value - if value != expected: - errors.append( - "{0} must match the qualified value {1}".format(name, expected) - ) + pll_mode = os.environ.get("PRODUCT_PLL_MODE", "").strip().lower() + if pll_mode == "qualified": + qualified_pll = { + "ICS55_PLL_SUPPORTED_SEL": "0", + "ICS55_PLL_N": "2", + "ICS55_PLL_OD": "2", + } + for name, expected in qualified_pll.items(): + value = os.environ.get(name, "").strip() + values[name] = value + if value != expected: + errors.append( + "{0} must match the qualified value {1}".format(name, expected) + ) + elif pll_mode == "parked": + pass + else: + errors.append("PRODUCT_PLL_MODE must be qualified or parked") if not args.allow_internal_qor: for name in ( "MAX_SETUP_VIOLATIONS", @@ -236,14 +230,46 @@ def main(): raise ValueError except ValueError: errors.append("ECO_MAX_PROCESSES must be a positive integer") - if values.get("ECO_PHYSICAL_MODE") not in ("open_site", "occupied_site"): - errors.append("ECO_PHYSICAL_MODE must be open_site or occupied_site") + if values.get("ECO_PHYSICAL_MODE") not in ( + "open_site", + "occupied_site", + "freeze_silicon", + ): + errors.append( + "ECO_PHYSICAL_MODE must be open_site, occupied_site, or freeze_silicon" + ) + for name in OPTIONAL_SWITCH_VARIABLES: + value = os.environ.get(name, "").strip().upper() + if value and value not in ("YES", "NO"): + errors.append("{0} must be YES or NO".format(name)) + if ( + os.environ.get("ECO_ENABLE_VT_SWAP", "").strip().upper() == "YES" + and not split_value(os.environ.get("ECO_VT_PATTERN_PRIORITY", "")) + ): + errors.append( + "ECO_VT_PATTERN_PRIORITY is required when ECO_ENABLE_VT_SWAP is YES" + ) + pba_mode = os.environ.get("ECO_PBA_MODE", "").strip().lower() + if pba_mode and pba_mode not in ("none", "path", "exhaustive"): + errors.append("ECO_PBA_MODE must be none, path, or exhaustive") + for cell in split_value(os.environ.get("MACRO_LVS_CELLS", "")): + if re.match(r"^[A-Za-z0-9_]+$", cell) is None: + errors.append( + "MACRO_LVS_CELLS contains an invalid cell name: {0}".format(cell) + ) if values.get("LSF_MODE") not in ("batch", "interactive"): errors.append("LSF_MODE must be batch or interactive") io_mode = os.environ.get("IO_TIMING_QUALIFIED", "").strip().upper() if io_mode == "YES": - for name in IO_BUDGET_VARIABLES: + budget_variables = split_value( + os.environ.get("COMMERCIAL_IO_BUDGET_VARIABLES", "") + ) + if not budget_variables: + errors.append( + "COMMERCIAL_IO_BUDGET_VARIABLES is not set by the product configuration" + ) + for name in budget_variables: value = os.environ.get(name, "").strip() if not value or value == "REQUIRED": errors.append("{0} is not configured".format(name)) @@ -252,10 +278,15 @@ def main(): float(value) except ValueError: errors.append("{0} must be numeric".format(name)) + hook_required = ( + os.environ.get("PRODUCT_REQUIRES_IO_MODE_HOOK", "").strip().upper() == "YES" + ) io_hook = os.environ.get("TIMING_IO_MODE_HOOK", "").strip() - if not io_hook or io_hook == "REQUIRED": + if hook_required and (not io_hook or io_hook == "REQUIRED"): errors.append("TIMING_IO_MODE_HOOK is required for qualified I/O") - elif not os.path.isfile(io_hook) or not os.access(io_hook, os.R_OK): + elif io_hook and io_hook != "REQUIRED" and ( + not os.path.isfile(io_hook) or not os.access(io_hook, os.R_OK) + ): errors.append("TIMING_IO_MODE_HOOK is not readable: {0}".format(io_hook)) elif io_mode == "NO" and args.allow_internal_qor: pass @@ -298,6 +329,18 @@ def main(): elif not os.path.isfile(path) or not os.access(path, os.R_OK): errors.append("{0} is not readable: {1}".format(name, path)) + for name in OPTIONAL_FILE_LIST_VARIABLES: + value = os.environ.get(name, "").strip() + paths = split_value(value) if value and value != "REQUIRED" else [] + checked_files[name] = paths + if args.allow_internal_qor: + continue + for path in paths: + if any(token in path for token in ("*", "?", "[")): + errors.append("{0} contains a wildcard: {1}".format(name, path)) + elif not os.path.isfile(path) or not os.access(path, os.R_OK): + errors.append("{0} is not readable: {1}".format(name, path)) + for prefix in ("STD", "IO", "SRAM"): for pvt in ("MAX", "WCL", "TYP", "MIN", "ML"): if args.allow_internal_qor and pvt != "TYP": @@ -394,6 +437,8 @@ def main(): result = { "schema_version": 1, "status": "failed" if errors else "passed", + "soc": os.environ.get("SOC", ""), + "technology": os.environ.get("TECHNOLOGY", ""), "runtime": runtime, "tools": tools, "checked_file_variables": checked_files, diff --git a/physical/commercial/scripts/generate_timing_contract.py b/physical/commercial/scripts/generate_timing_contract.py index cd0f9938..2fff175c 100644 --- a/physical/commercial/scripts/generate_timing_contract.py +++ b/physical/commercial/scripts/generate_timing_contract.py @@ -17,17 +17,23 @@ from scripts.check_clock_reset_domains import validate # noqa: E402 from scripts.setup_helpers import atomic_write # noqa: E402 -EXPECTED_DOMAINS = { - "aon", - "lp", - "hp", - "pclk", - "memory", - "audio", - "jtag", - "dvp", - "usb2_ulpi", -} +def expected_domains(soc: str) -> set[str]: + """Read the product clock-domain policy from config/products/.mk.""" + config = ROOT / "physical/commercial/config/products" / f"{soc.lower()}.mk" + if not config.is_file(): + raise ValueError(f"unsupported SOC {soc!r}: missing {config}") + text = "" + for line in config.read_text(encoding="utf-8").splitlines(): + line = line.rstrip() + if line.endswith("\\"): + text += line[:-1] + " " + continue + text += line + if text.lstrip().startswith("COMMERCIAL_CLOCK_DOMAINS"): + _, _, value = text.partition("=") + return set(value.split()) + text = "" + raise ValueError(f"{config} does not define COMMERCIAL_CLOCK_DOMAINS") def tcl_value(value: object) -> str: @@ -35,7 +41,9 @@ def tcl_value(value: object) -> str: return "{" + text.replace("\\", "\\\\").replace("}", "\\}") + "}" -def load_contract(domains_path: Path, pin_map_path: Path) -> tuple[list[dict[str, Any]], list[str]]: +def load_contract( + domains_path: Path, pin_map_path: Path, expected: set[str] +) -> tuple[list[dict[str, Any]], list[str]]: validate(domains_path, ROOT) document = json.loads(domains_path.read_text(encoding="utf-8")) domains = document.get("domains") @@ -43,7 +51,7 @@ def load_contract(domains_path: Path, pin_map_path: Path) -> tuple[list[dict[str if not isinstance(domains, list) or not isinstance(reset_ports, list): raise ValueError("clock/reset inventory has an invalid shape") names = {domain.get("name") for domain in domains if isinstance(domain, dict)} - if names != EXPECTED_DOMAINS: + if names != expected: raise ValueError( "commercial clock domains differ from the canonical set: " + ", ".join(sorted(str(name) for name in names)) @@ -85,10 +93,10 @@ def load_contract(domains_path: Path, pin_map_path: Path) -> tuple[list[dict[str return parsed, parsed_resets -def render(domains: list[dict[str, Any]], reset_ports: list[str]) -> str: +def render(soc: str, domains: list[dict[str, Any]], reset_ports: list[str]) -> str: lines = [ "# Generated by physical/commercial/scripts/generate_timing_contract.py.", - "# Source: rtl/mini/integration/clock_reset_domains.json and pin_map.json.", + f"# Source: rtl/{soc.lower()}/integration/clock_reset_domains.json and pin_map.json.", "set flow::canonical_clock_domains [dict create \\", ] for domain in domains: @@ -120,13 +128,16 @@ def render(domains: list[dict[str, Any]], reset_ports: list[str]) -> str: def main() -> int: parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--soc", required=True, choices=("MINI", "TINY")) parser.add_argument("--domains", type=Path, required=True) parser.add_argument("--pin-map", type=Path, required=True) parser.add_argument("--output", type=Path, required=True) args = parser.parse_args() try: - domains, reset_ports = load_contract(args.domains, args.pin_map) - atomic_write(args.output, render(domains, reset_ports)) + domains, reset_ports = load_contract( + args.domains, args.pin_map, expected_domains(args.soc) + ) + atomic_write(args.output, render(args.soc, domains, reset_ports)) except (OSError, ValueError, json.JSONDecodeError) as error: parser.error(str(error)) return 0 diff --git a/physical/commercial/scripts/prepare_input.py b/physical/commercial/scripts/prepare_input.py index 56414763..c4789279 100644 --- a/physical/commercial/scripts/prepare_input.py +++ b/physical/commercial/scripts/prepare_input.py @@ -66,6 +66,13 @@ def main(): filelist = os.path.join(output_dir, "rtl", "filelist.fl") if not os.path.isfile(filelist): parser.error("RTL archive does not contain rtl/filelist.fl") + contract = os.path.join( + output_dir, "rtl", "contracts", "commercial_timing_contract.tcl" + ) + if not os.path.isfile(contract): + parser.error( + "RTL archive does not contain rtl/contracts/commercial_timing_contract.tcl" + ) # Tar extraction preserves the development-zone timestamp. Refresh the # staged entry point so the stage runner can distinguish this extraction # from output left by an earlier run. diff --git a/physical/commercial/scripts/translate_eco.py b/physical/commercial/scripts/translate_eco.py index be4b70d9..e9ff82cd 100644 --- a/physical/commercial/scripts/translate_eco.py +++ b/physical/commercial/scripts/translate_eco.py @@ -21,8 +21,12 @@ CURRENT = re.compile(r"^current_instance(?:\s+\{([^}]*)\})?\s*$") SIZE = re.compile(r"^size_cell\s+\{([^}]+)\}\s+\{([^}]+)\}\s*$") INSERT = re.compile( r"^insert_buffer\s+\[get_pins\s+\{([^}]+)\}\]\s+(\S+)" - r"\s+-new_net_names\s+\{([^}]+)\}\s+-new_cell_names\s+\{([^}]+)\}\s*$" + r"\s+-new_net_names\s+\{([^}]+)\}\s+-new_cell_names\s+\{([^}]+)\}" + r"(?:\s+-location\s+\{([^}]+)\})?\s*$" ) +# Legacy pd_data/pteco/rpt/formate_pt2edi.pl maps a single-cell buffer +# removal to the Innovus ecoDeleteRepeater command. +REMOVE = re.compile(r"^remove_buffer\s+\[get_cells\s+\{([^\s}]+)\}\]\s*$") def full_name(parent, name): @@ -54,7 +58,7 @@ def translate(paths): continue match = INSERT.match(line) if match: - output.append( + command = ( "ecoAddRepeater -term {{{0}}} -cell {{{1}}} " "-newNetName {{{2}}} -name {{{3}}}".format( full_name(current, match.group(1)), @@ -63,13 +67,32 @@ def translate(paths): match.group(4), ) ) + if match.group(5) is not None: + command += " -loc {{{0}}}".format(match.group(5).strip()) + output.append(command) + continue + match = REMOVE.match(line) + if match: + output.append( + "ecoDeleteRepeater -inst {{{0}}}".format( + full_name(current, match.group(1)) + ) + ) continue raise ValueError( "{0}:{1}: unsupported PrimeTime ECO command: {2}".format( path, number, line ) ) - return output + # DMSA exports one change list per scenario; identical changes repeat in + # every scenario, so keep the first occurrence of each translated command. + unique = [] + seen = set() + for command in output: + if command not in seen: + seen.add(command) + unique.append(command) + return unique def main(): diff --git a/physical/commercial/tcl/apr/main.tcl b/physical/commercial/tcl/apr/main.tcl index 5fdd96ab..66dd973e 100644 --- a/physical/commercial/tcl/apr/main.tcl +++ b/physical/commercial/tcl/apr/main.tcl @@ -33,14 +33,60 @@ proc restore_previous {run_root top stage} { restoreDesign $database $top } +proc connect_pin_map {map type} { + foreach group $map { + set fields [split $group :] + if {[llength $fields] != 2} { + flow::fail "invalid APR pin map group (expect NET:pin1,pin2): $group" + } + set net [string trim [lindex $fields 0]] + set pins [string trim [lindex $fields 1]] + if {$net eq "" || $pins eq ""} { + flow::fail "invalid APR pin map group (expect NET:pin1,pin2): $group" + } + # Create the physical net when the netlist does not carry it, mirroring + # legacy CL1/user_add_pg.tcl:4-15. + if {[dbGet top.nets.name $net] eq "0x0"} { + if {$type eq "power"} { + addNet $net -physical -power + } else { + addNet $net -physical -ground + } + setNet -type special -net $net + } + foreach pin [split $pins ,] { + set pin [string trim $pin] + if {$pin eq ""} { + continue + } + globalNetConnect $net -type pgpin -pin $pin -all -verbose + } + } +} + +# Legacy CL1/user_add_pg.tcl connects every power domain to its own pin group +# (independent PLL_AVDD/PLL_AVSS pairs for the PLL macro at lines 22-23 and +# 32-33). APR_POWER_PIN_MAP / APR_GROUND_PIN_MAP express the same grouping as +# "NET:pin1,pin2 NET2:pin3"; when a map is empty every configured pin joins +# the single APR_POWER_NET / APR_GROUND_NET as before. proc connect_power_nets {} { set power [flow::env APR_POWER_NET] set ground [flow::env APR_GROUND_NET] - foreach pin [flow::env APR_POWER_PINS] { - globalNetConnect $power -type pgpin -pin $pin -all -verbose + set power_map [string trim [flow::env APR_POWER_PIN_MAP ""]] + set ground_map [string trim [flow::env APR_GROUND_PIN_MAP ""]] + if {$power_map ne ""} { + connect_pin_map $power_map power + } else { + foreach pin [flow::env APR_POWER_PINS] { + globalNetConnect $power -type pgpin -pin $pin -all -verbose + } } - foreach pin [flow::env APR_GROUND_PINS] { - globalNetConnect $ground -type pgpin -pin $pin -all -verbose + if {$ground_map ne ""} { + connect_pin_map $ground_map ground + } else { + foreach pin [flow::env APR_GROUND_PINS] { + globalNetConnect $ground -type pgpin -pin $pin -all -verbose + } } globalNetConnect $power -type tiehi -all -verbose globalNetConnect $ground -type tielo -all -verbose @@ -140,6 +186,113 @@ proc write_io_side {handle side signals power_records offset pitch length} { puts $handle " )" } +# Innovus engine modes from legacy setting/common_setting.tcl (lines 17 and +# 21-22). Session modes are not preserved across restoreDesign, so every +# stage re-applies them; the APR stages run in a fresh Innovus process, so the +# legacy "-reset" prelude is not needed. +proc apply_engine_settings {} { + setDesignMode -process [flow::env APR_DESIGN_PROCESS 55] + setAnalysisMode -cppr both -analysisType onChipVariation +} + +# Placement modes from legacy setting/place_setting.tcl (effective lines 1-11). +proc apply_place_settings {} { + setPlaceMode -timingDriven true + setPlaceMode -place_global_clock_gate_aware true + setPlaceMode -place_global_place_io_pins false + setPlaceMode -honorSoftBlockage true + setPlaceMode -place_detail_honor_inst_pad true + setPlaceMode -place_global_uniform_density true + setPlaceMode -place_global_clock_power_driven true + setPlaceMode -place_global_cong_effort auto + setPlaceMode -place_detail_legalization_inst_gap 1 +} + +# Routing modes from legacy setting/route_setting.tcl (effective lines 1-16). +# Layer bounds stay name-based through APR_SIGNAL_MIN_LAYER/MAX_LAYER; the +# legacy numeric earlyGlobal window (lines 5-6) applies only when the optional +# APR_EARLY_GLOBAL_MIN_LAYER / APR_EARLY_GLOBAL_MAX_LAYER indexes are set. +proc apply_route_settings {} { + setNanoRouteMode -routeBottomRoutingLayer [flow::env APR_SIGNAL_MIN_LAYER] + setNanoRouteMode -routeTopRoutingLayer [flow::env APR_SIGNAL_MAX_LAYER] + setNanoRouteMode -drouteEndIteration 20 + setNanoRouteMode -routeWithSiDriven true + setNanoRouteMode -routeWithTimingDriven true + setNanoRouteMode -drouteNoTaperOnoutputPin false + setTrialRouteMode -skipTracks {} + setTrialRouteMode -ignoreAbutted2TermNet true + setOptMode -maxLength 500 + set early_min [string trim [flow::env APR_EARLY_GLOBAL_MIN_LAYER ""]] + if {$early_min ne ""} { + setRouteMode -earlyGlobalMinRouteLayer $early_min + } + set early_max [string trim [flow::env APR_EARLY_GLOBAL_MAX_LAYER ""]] + if {$early_max ne ""} { + setRouteMode -earlyGlobalMaxRouteLayer $early_max + } +} + +# Per-stage timing tightening: derate plus setup uncertainty from legacy +# CL1/set_derate_uncertainty.tcl, and the design-rule limits from legacy +# CL1/update_sdc.tcl:3-10 (max_fanout 32, max_transition 0.08 ns, +# max_capacitance 0.15 pF). MMMC constraint modes are read-only until enabled +# interactively (legacy place.tcl:19-25). +proc apply_stage_constraints {stage} { + set_interactive_constraint_modes [all_constraint_modes] + flow::apply_apr_derate + flow::apply_apr_stage_uncertainty $stage + set_max_fanout [flow::env APR_MAX_FANOUT 32] [current_design] + set_max_transition [flow::env APR_MAX_TRANSITION_NS 0.08] [current_design] + set_max_capacitance [flow::env APR_MAX_CAPACITANCE_PF 0.15] [current_design] + set_interactive_constraint_mode {} +} + +# 2x-width 2x-spacing clock NDR across the configured clock routing layers +# (legacy setting/cts_setting.tcl:21-23: CLKNDR on MET3:MET5 with +# width/spacing 0.2/0.2 um). +proc create_clock_ndr {} { + set name retrosoc_clock_ndr + if {[dbGet head.rules.name $name] eq "0x0"} { + set layers [flow::env APR_CLOCK_ROUTING_LAYERS] + set range "[lindex $layers 0]:[lindex $layers end]" + add_ndr -name $name \ + -width [list $range [flow::env APR_CTS_NDR_WIDTH_UM 0.2]] \ + -spacing [list $range [flow::env APR_CTS_NDR_SPACING_UM 0.2]] + } + return $name +} + +# Legacy generate_ccopt_spec.tcl:17-27 binds every clock route type to the +# NDR and shields the top route type with the ground net (VSS, line 22). The +# legacy trunk and top types share the same layer window, so the single +# retrosoc trunk route type carries both the NDR and the ground shield. +proc create_clock_trunk_route_type {ndr} { + set name retrosoc_clock_trunk + if {[dbGet head.routeTypes.name $name] eq "0x0"} { + create_route_type -name $name \ + -top_preferred_layer [lindex [flow::env APR_CLOCK_ROUTING_LAYERS] end] \ + -bottom_preferred_layer [lindex [flow::env APR_CLOCK_ROUTING_LAYERS] 0] \ + -non_default_rule $ndr \ + -shield_net [flow::env APR_GROUND_NET] + } + set_ccopt_property -net_type trunk route_type $name +} + +# ccopt targets from legacy setting/cts_setting.tcl:1-12 and +# generate_ccopt_spec.tcl:29-36: skew 0.08 ns, leaf/trunk transition 0.78 ns +# (max_sink_tran/max_buf_tran), insertion delay 0.05 ns (cts_max_delay), +# fanout 4. +proc configure_ccopt_targets {} { + set_ccopt_property target_skew [flow::env APR_CTS_TARGET_SKEW_NS 0.08] + set_ccopt_property target_max_trans -net_type leaf \ + [flow::env APR_CTS_TARGET_MAX_TRANS_LEAF_NS 0.78] + set_ccopt_property target_max_trans -net_type trunk \ + [flow::env APR_CTS_TARGET_MAX_TRANS_TRUNK_NS 0.78] + set_ccopt_property target_insertion_delay \ + [flow::env APR_CTS_TARGET_INSERTION_DELAY_NS 0.05] + set_ccopt_property max_fanout [flow::env APR_CTS_MAX_FANOUT 4] +} + proc write_apr_reports {report_dir stage} { redirect [file join $report_dir ${stage}.summary.rpt] { summaryReport } redirect [file join $report_dir ${stage}.timing.rpt] { @@ -165,12 +318,25 @@ proc verify_route {report_dir} { proc write_data_out {run_root top stage output_dir report_dir} { set prefix [file join $output_dir ${top}.${stage}] defOut -floorplan -netlist -routing ${prefix}.def - saveNetlist ${prefix}.v - saveNetlist -includePowerGround ${prefix}.pg.v + # Legacy dataOut.tcl:15,19 drop pad and seal-ring instances from the + # netlists; APR_NETLIST_EXCLUDE_CELLS carries the same exclusion list. + set exclude [string trim [flow::env APR_NETLIST_EXCLUDE_CELLS ""]] + if {$exclude eq ""} { + saveNetlist ${prefix}.v + saveNetlist -includePowerGround ${prefix}.pg.v + } else { + saveNetlist ${prefix}.v -excludeCellInst $exclude + saveNetlist -includePowerGround ${prefix}.pg.v -excludeCellInst $exclude + } write_sdc ${prefix}.sdc write_sdf -version 3.0 ${prefix}.sdf + # Legacy dataOut.tcl:26-35 stream-out contract. + setStreamOutMode -virtualConnection false streamOut ${prefix}.gds -mapFile [flow::env STREAM_MAP] \ - -structureName $top -mode ALL + -structureName $top -mode ALL \ + -stripes 1 -units 1000 \ + -attachInstanceName 2 -attachNetName 2 \ + -dieAreaAsBoundary verify_route $report_dir } @@ -208,6 +374,11 @@ proc run_apr {} { switch -- $stage { initialize { + # MMMC is ready once init_design completes; apply the engine modes + # (legacy setting/common_setting.tcl) and derate every delay + # corner (legacy CL1/set_derate_uncertainty.tcl:1-3). + apply_engine_settings + flow::apply_apr_derate checkDesign -all } floorplan { @@ -215,11 +386,39 @@ proc run_apr {} { [flow::env DIE_WIDTH] [flow::env DIE_HEIGHT] \ [flow::env CORE_MARGIN_LEFT] [flow::env CORE_MARGIN_BOTTOM] \ [flow::env CORE_MARGIN_RIGHT] [flow::env CORE_MARGIN_TOP] - set io_file [file join $work_dir ${top}.io] - write_io_ring $io_file + # Legacy floorplan.tcl:6 loads a reviewed IO order file; the + # generated round-robin ring stays the default fallback. + set io_order_file [string trim [flow::env APR_IO_ORDER_FILE ""]] + if {$io_order_file ne ""} { + if {![file isfile $io_order_file] || \ + ![file readable $io_order_file]} { + flow::fail "APR_IO_ORDER_FILE is not readable: $io_order_file" + } + set io_file [file normalize $io_order_file] + set io_order file + } else { + set io_file [file join $work_dir ${top}.io] + write_io_ring $io_file + set io_order round_robin + } loadIoFile $io_file fixAllIos + puts "APR floorplan io_order=$io_order io_file=$io_file" + flow::write_text [file join $output_dir floorplan.io.json] \ + "{\n \"stage\": \"floorplan\",\n \"io_order\": \"$io_order\",\n \"io_file\": \"$io_file\"\n}\n" addIoFiller -cell [flow::env APR_IO_FILLERS] -prefix IOFILL + # Optional macro pre-placement (legacy floorplan.tcl:11 sources + # CL1/_macro_loc.tcl; the file carries + # placeInstance/place_macro/createPlaceBlockage commands) plus a + # uniform macro placement halo (legacy prePlace.tcl:9 uses + # "addHaloToBlock -allMacro 2 2 2 2"; CL1/add_mem_blk.tcl instead + # extends every SRAM by 1.5 um as a pgnet route blockage). + set macro_loc [string trim [flow::env APR_MACRO_LOC_FILE ""]] + if {$macro_loc ne ""} { + flow::source_hook APR_MACRO_LOC_FILE + set halo [flow::env APR_MACRO_HALO_UM 5] + addHaloToBlock -allMacro $halo $halo $halo $halo + } flow::source_hook APR_FLOORPLAN_HOOK connect_power_nets set ring_layers [flow::env APR_RING_LAYERS] @@ -251,20 +450,33 @@ proc run_apr {} { checkFPlan -reportUtil } place { + # Legacy place.tcl:7-23 re-sources every setting file plus the + # per-stage constraint updates after restoreDesign. + apply_engine_settings + apply_place_settings + apply_route_settings + apply_stage_constraints place + # Legacy place_setting.tcl:1 fixes density at 0.7; the retroSoC + # product policy keeps it driven by CORE_UTILIZATION. setPlaceMode -place_global_max_density [flow::env CORE_UTILIZATION] placeDesign -concurrent_macros optDesign -preCTS flow::source_hook APR_PLACE_HOOK } cts { + # Legacy CTS.tcl:7-25 applies the same settings and constraints. + apply_engine_settings + apply_place_settings + apply_route_settings + apply_stage_constraints cts set_ccopt_property buffer_cells [flow::env APR_CTS_BUFFER_CELLS] set_ccopt_property inverter_cells [flow::env APR_CTS_INVERTER_CELLS] - set_ccopt_property route_type -net_type trunk \ - -route_type_name retrosoc_clock_trunk - create_route_type -name retrosoc_clock_trunk \ - -top_preferred_layer [lindex [flow::env APR_CLOCK_ROUTING_LAYERS] end] \ - -bottom_preferred_layer [lindex [flow::env APR_CLOCK_ROUTING_LAYERS] 0] + # Legacy generate_ccopt_spec.tcl: snapshot the tree spec, then + # bind the NDR route type and targets before ccopt_design. create_ccopt_clock_tree_spec -file [file join $work_dir ccopt.spec] + set clock_ndr [create_clock_ndr] + create_clock_trunk_route_type $clock_ndr + configure_ccopt_targets source [file join $work_dir ccopt.spec] ccopt_design optDesign -postCTS @@ -272,8 +484,14 @@ proc run_apr {} { flow::source_hook APR_CTS_HOOK } route { - setNanoRouteMode -routeBottomRoutingLayer [flow::env APR_SIGNAL_MIN_LAYER] - setNanoRouteMode -routeTopRoutingLayer [flow::env APR_SIGNAL_MAX_LAYER] + # Legacy route.tcl:6-25 applies the same settings and constraints. + apply_engine_settings + apply_place_settings + apply_route_settings + apply_stage_constraints route + # Legacy setting/common_setting.tcl:29-33 enables coupled + # postRoute extraction once the route step starts. + setExtractRCMode -engine postRoute -effortLevel medium -couples true routeDesign -globalDetail optDesign -postRoute optDesign -postRoute -hold diff --git a/physical/commercial/tcl/common/clocks.tcl b/physical/commercial/tcl/common/clocks.tcl index 4b718877..e53670ba 100644 --- a/physical/commercial/tcl/common/clocks.tcl +++ b/physical/commercial/tcl/common/clocks.tcl @@ -9,6 +9,8 @@ namespace eval flow { variable canonical_clock_domains {} variable canonical_reset_ports {} variable commercial_clock_names {} + variable commercial_clock_groups + array set commercial_clock_groups {} } proc flow::required_ports {label names} { @@ -53,6 +55,13 @@ proc flow::domain_object {domain} { return [flow::required_pins "clock $domain" $observation] } +proc flow::register_clock {name async_group} { + variable commercial_clock_names + variable commercial_clock_groups + lappend commercial_clock_names $name + lappend commercial_clock_groups($async_group) $name +} + proc flow::load_canonical_timing_contract {} { variable canonical_clock_domains variable canonical_reset_ports @@ -62,69 +71,58 @@ proc flow::load_canonical_timing_contract {} { flow::fail "canonical commercial timing contract is missing: $contract" } source $contract - set expected {external system audio jtag dvp usb2_ulpi} + set expected [product::expected_clock_domains] if {[lsort [dict keys $canonical_clock_domains]] ne [lsort $expected]} { - flow::fail "canonical commercial clock-domain set is invalid" + flow::fail "canonical commercial clock-domain set is invalid for $flow::soc" } if {[llength $canonical_reset_ports] == 0} { flow::fail "canonical reset-port list is empty" } } -proc flow::create_canonical_master_clock {domain} { - variable canonical_clock_domains - set values [dict get $canonical_clock_domains $domain] - set name clk_$domain - create_clock -name $name -period [dict get $values period_ns] \ - [flow::domain_object $domain] - return $name -} - proc flow::apply_clock_constraints {} { variable canonical_clock_domains variable canonical_reset_ports variable commercial_clock_names + variable commercial_clock_groups set commercial_clock_names {} - foreach domain {external audio jtag dvp usb2_ulpi} { - lappend commercial_clock_names [flow::create_canonical_master_clock $domain] + array set commercial_clock_groups {} + set custom [product::custom_clock_domains] + + array set objects {} + foreach domain [dict keys $canonical_clock_domains] { + set objects($domain) [flow::domain_object $domain] } + foreach domain [dict keys $canonical_clock_domains] { + if {$domain in $custom} { + continue + } + set values [dict get $canonical_clock_domains $domain] + set name clk_$domain + set source_domain [dict get $values source_domain] + if {$source_domain eq ""} { + create_clock -name $name -period [dict get $values period_ns] \ + $objects($domain) + } else { + if {$source_domain in $custom} { + flow::fail "domain $domain derives from the product-managed \ + domain $source_domain" + } + create_generated_clock -name $name -source $objects($source_domain) \ + -divide_by 1 $objects($domain) + } + flow::register_clock $name [dict get $values async_group] + } + product::apply_clock_overlays - set xtal_pin [flow::required_pins crystal_clock u_rcu/u_xtal_buf/clk_o] - create_clock -name clk_xtal -period [flow::env XTAL_CLK_PERIOD_NS] $xtal_pin - lappend commercial_clock_names clk_xtal - - set pll_pin [flow::required_pins pll_clock u_rcu/u_tc_pll/u_PLL_TOP/CKOUT1] - create_clock -name clk_pll -period [flow::env PLL_OUTPUT_PERIOD_NS] $pll_pin - lappend commercial_clock_names clk_pll - - set ext_pin [flow::domain_object external] - set sys_pin [flow::domain_object system] - create_generated_clock -name clk_system_ext -master_clock clk_external \ - -source $ext_pin -divide_by 1 $sys_pin - create_generated_clock -name clk_system_pll -master_clock clk_pll \ - -source $pll_pin -divide_by 1 -add $sys_pin - lappend commercial_clock_names clk_system_ext clk_system_pll - - set ext_group [get_clocks {clk_external clk_system_ext}] - set pll_group [get_clocks {clk_pll clk_system_pll}] - set_clock_groups -name retrosoc_system_sources -physically_exclusive \ - -group $ext_group -group $pll_group - - set system_group [get_clocks \ - {clk_external clk_system_ext clk_pll clk_system_pll}] - set async_groups [list \ - $system_group \ - [get_clocks clk_audio] \ - [get_clocks clk_xtal] \ - [get_clocks clk_jtag] \ - [get_clocks clk_dvp] \ - [get_clocks clk_usb2_ulpi]] - set command [list set_clock_groups -name retrosoc_async -asynchronous] - foreach group $async_groups { - lappend command -group $group + if {[array size commercial_clock_groups] > 1} { + set command [list set_clock_groups -name retrosoc_async -asynchronous] + foreach group [array names commercial_clock_groups] { + lappend command -group [get_clocks $commercial_clock_groups($group)] + } + eval $command } - eval $command set clocks [get_clocks $commercial_clock_names] if {[sizeof_collection $clocks] != [llength $commercial_clock_names]} { diff --git a/physical/commercial/tcl/common/common.tcl b/physical/commercial/tcl/common/common.tcl index 8fe7458a..ae1ddcfa 100644 --- a/physical/commercial/tcl/common/common.tcl +++ b/physical/commercial/tcl/common/common.tcl @@ -9,7 +9,9 @@ # See the Mulan PSL v2 for more details. namespace eval flow { - variable top [expr {[info exists ::env(TOP)] ? $::env(TOP) : "retrosoc_asic"}] + variable top [expr {[info exists ::env(TOP)] ? $::env(TOP) : ""}] + variable soc [expr {[info exists ::env(SOC)] ? [string toupper $::env(SOC)] : "MINI"}] + variable technology [expr {[info exists ::env(TECHNOLOGY)] ? [string toupper $::env(TECHNOLOGY)] : "ICS55"}] variable root [file normalize [file join [file dirname [info script]] ../..]] variable run_root [expr {[info exists ::env(RUN_ROOT)] ? $::env(RUN_ROOT) : ""}] } @@ -209,49 +211,6 @@ proc flow::report_has_failure {path patterns} { if {![file isfile $path]} { flow::fail "required report was not written: $path" } - - proc flow::count_report_matches {path pattern} { - if {![file isfile $path]} { - flow::fail "required report was not written: $path" - } - set handle [open $path r] - set text [read $handle] - close $handle - return [regexp -all -nocase -- $pattern $text] - } - - proc flow::require_qualified_synthesis {} { - variable run_root - set summary [file join $run_root syn output synthesis.summary.tsv] - if {![file isfile $summary]} { - flow::fail "qualified synthesis summary is missing: $summary" - } - array set values {} - set handle [open $summary r] - while {[gets $handle line] >= 0} { - if {[regexp {^([^\t]+)\t([^\t]+)$} $line unused name value]} { - set values($name) $value - } - } - - proc flow::require_commercial_clock_inventory {} { - set expected { - clk_external clk_audio clk_jtag clk_dvp clk_usb2_ulpi - clk_xtal clk_pll clk_system_ext clk_system_pll - } - foreach name $expected { - if {[sizeof_collection [get_clocks -quiet $name]] != 1} { - flow::fail "commercial clock is missing or ambiguous: $name" - } - } - } - close $handle - foreach name {flow_pass constraints_complete io_qualified} { - if {![info exists values($name)] || $values($name) ne "yes"} { - flow::fail "synthesis is not qualified for implementation: $name" - } - } - } set handle [open $path r] set text [read $handle] close $handle @@ -262,3 +221,51 @@ proc flow::report_has_failure {path patterns} { } return 0 } + +proc flow::count_report_matches {path pattern} { + if {![file isfile $path]} { + flow::fail "required report was not written: $path" + } + set handle [open $path r] + set text [read $handle] + close $handle + return [regexp -all -nocase -- $pattern $text] +} + +proc flow::require_commercial_clock_inventory {} { + foreach name [product::clock_names] { + if {[sizeof_collection [get_clocks -quiet $name]] != 1} { + flow::fail "commercial clock is missing or ambiguous: $name" + } + } +} + +proc flow::require_qualified_synthesis {} { + variable run_root + set summary [file join $run_root syn output synthesis.summary.tsv] + if {![file isfile $summary]} { + flow::fail "qualified synthesis summary is missing: $summary" + } + array set values {} + set handle [open $summary r] + while {[gets $handle line] >= 0} { + if {[regexp {^([^\t]+)\t([^\t]+)$} $line unused name value]} { + set values($name) $value + } + } + close $handle + foreach name {flow_pass constraints_complete io_qualified} { + if {![info exists values($name)] || $values($name) ne "yes"} { + flow::fail "synthesis is not qualified for implementation: $name" + } + } +} + +set flow_product_tcl [file join $flow::root tcl common products \ + [string tolower $flow::soc].tcl] +if {![file isfile $flow_product_tcl]} { + puts stderr \ + "COMMERCIAL_FLOW_ERROR: unsupported SOC=$flow::soc (missing $flow_product_tcl)" + exit 2 +} +source $flow_product_tcl diff --git a/physical/commercial/tcl/common/corners.tcl b/physical/commercial/tcl/common/corners.tcl index 631d0374..47c3d92f 100644 --- a/physical/commercial/tcl/common/corners.tcl +++ b/physical/commercial/tcl/common/corners.tcl @@ -52,3 +52,76 @@ proc flow::rc_cap_table {corner} { proc flow::spef_name {top corner} { return "${top}.${corner}.spef.gz" } + +# CX55 OCV policy for PrimeTime signoff (legacy bes_data/common/ +# signoff_table.tcl:364-390, expanded by bes_data/sta/scr/flow_com/ +# transform_signoff_table.tcl:10-25; command forms verified in legacy PT logs +# bes_data/sta/log/asic_top_V2026_1_CTS_MIN_CWORST.log and +# asic_top_CTS_TYP_TYP.log:2672-2673): +# MAX/WCL: clock-path early derate 0.95 (cell and net), all other arcs unity +# MIN/ML: clock-path early derate 0.90 (cell and net), all other arcs unity +# TYP: unqualified "set_timing_derate -early 0.95" with unity late (the +# legacy "default default" entry) +# The legacy DC synthesis run linked the TYP library and applied no timing +# derate at all (bes_data/syn/scr/flow_com/syn_common_flow.tcl never sources +# signoff_table.tcl; bes_data/syn/log/asic_top.log contains no +# set_timing_derate), so the synthesis flow does not call this helper. +proc flow::apply_signoff_derate {pvt} { + switch -glob -- $pvt { + MAX - WCL { + set cell_early [flow::env TIMING_DERATE_SETUP_CELL_CLOCK_EARLY 0.95] + set net_early [flow::env TIMING_DERATE_SETUP_NET_CLOCK_EARLY 0.95] + } + MIN - ML { + set cell_early [flow::env TIMING_DERATE_HOLD_CELL_CLOCK_EARLY 0.90] + set net_early [flow::env TIMING_DERATE_HOLD_NET_CLOCK_EARLY 0.90] + } + default { + set early [flow::env TIMING_DERATE_DEFAULT_EARLY 0.95] + if {$early != 1.0} { + set_timing_derate -late 1.0 + set_timing_derate -early $early + } + return + } + } + set_timing_derate -cell_delay -clock -early $cell_early + set_timing_derate -cell_delay -clock -late 1.0 + set_timing_derate -cell_delay -data -early 1.0 + set_timing_derate -cell_delay -data -late 1.0 + set_timing_derate -net_delay -clock -early $net_early + set_timing_derate -net_delay -clock -late 1.0 + set_timing_derate -net_delay -data -early 1.0 + set_timing_derate -net_delay -data -late 1.0 +} + +# Innovus derate and per-stage setup uncertainty (legacy +# pd_data/pr/scr/CL1/set_derate_uncertainty.tcl). +proc flow::apply_apr_derate {} { + set clock_early [flow::env APR_DERATE_CLOCK_EARLY 1.0] + set clock_late [flow::env APR_DERATE_CLOCK_LATE 1.0] + set data_late [flow::env APR_DERATE_DATA_LATE 1.0] + if {$clock_early != 1.0} { + set_timing_derate -early $clock_early -clock \ + -delay_corner [all_delay_corners] + } + if {$clock_late != 1.0} { + set_timing_derate -late $clock_late -clock \ + -delay_corner [all_delay_corners] + } + if {$data_late != 1.0} { + set_timing_derate -late $data_late -data -delay_corner [all_delay_corners] + } +} + +proc flow::apply_apr_stage_uncertainty {stage} { + switch -- $stage { + place { set value [flow::env APR_SETUP_UNCERTAINTY_PLACE_NS ""] } + cts { set value [flow::env APR_SETUP_UNCERTAINTY_CTS_NS ""] } + route { set value [flow::env APR_SETUP_UNCERTAINTY_ROUTE_NS ""] } + default { set value "" } + } + if {$value ne ""} { + set_clock_uncertainty -setup $value [all_clocks] + } +} diff --git a/physical/commercial/tcl/common/io_constraints.tcl b/physical/commercial/tcl/common/io_constraints.tcl index 15a5d5b8..75591543 100644 --- a/physical/commercial/tcl/common/io_constraints.tcl +++ b/physical/commercial/tcl/common/io_constraints.tcl @@ -1,7 +1,7 @@ # Copyright (c) 2023-2026 Yuchi Miao # retroSoC is licensed under Mulan PSL v2. # You can use this software according to the terms and conditions of the Mulan PSL v2. -# You may obtain a copy of Mulan PSL v2 at: +# You may obtain a copy of MulanPSL2 at: # http://license.coscl.org.cn/MulanPSL2 # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND. @@ -41,68 +41,6 @@ proc flow::numbered_ports {prefix first last suffix} { return $names } -proc flow::apply_qualified_io_constraints {} { - set jtag_in [flow::interface_ports jtag {jtag_tms_i_pad jtag_tdi_i_pad}] - set jtag_out [flow::interface_ports jtag {jtag_tdo_o_pad}] - flow::apply_input_budget JTAG clk_jtag $jtag_in - flow::apply_output_budget JTAG clk_jtag $jtag_out - - set dvp_names [flow::numbered_ports gpio_ 11 20 _io_pad] - set dvp_in [flow::interface_ports dvp $dvp_names] - flow::apply_input_budget DVP clk_dvp $dvp_in - - set ulpi_data [flow::numbered_ports usb2_ulpi_data 0 7 _io_pad] - set ulpi_in [flow::interface_ports ulpi \ - [concat {usb2_ulpi_dir_i_pad usb2_ulpi_nxt_i_pad} $ulpi_data]] - set ulpi_out [flow::interface_ports ulpi \ - [concat $ulpi_data {usb2_ulpi_stp_o_pad}]] - flow::apply_input_budget ULPI clk_usb2_ulpi $ulpi_in - flow::apply_output_budget ULPI clk_usb2_ulpi $ulpi_out - - set sdram_data [flow::numbered_ports sdram_dq 0 15 _io_pad] - set sdram_out_names { - sdram_clk_o_pad sdram_cke_o_pad sdram_cs_n_o_pad - sdram_ras_n_o_pad sdram_cas_n_o_pad sdram_we_n_o_pad - sdram_ba0_o_pad sdram_ba1_o_pad sdram_dqm0_o_pad sdram_dqm1_o_pad - } - set sdram_out_names [concat $sdram_out_names \ - [flow::numbered_ports sdram_addr 0 12 _o_pad] $sdram_data] - set sdram_clock [flow::create_virtual_interface_clock SDRAM] - flow::apply_input_budget SDRAM $sdram_clock \ - [flow::interface_ports sdram $sdram_data] - flow::apply_output_budget SDRAM $sdram_clock \ - [flow::interface_ports sdram $sdram_out_names] - - set sdio_data { - sdio1_cmd_io_pad sdio1_dat0_io_pad sdio1_dat1_io_pad - sdio1_dat2_io_pad sdio1_dat3_io_pad - } - set sdio_clock [flow::create_virtual_interface_clock SDIO] - flow::apply_input_budget SDIO $sdio_clock [flow::interface_ports sdio $sdio_data] - flow::apply_output_budget SDIO $sdio_clock \ - [flow::interface_ports sdio [concat {sdio1_clk_o_pad} $sdio_data]] - - set xpi_data [flow::numbered_ports xpi_dat 0 3 _io_pad] - set xpi_clock [flow::create_virtual_interface_clock XPI] - flow::apply_input_budget XPI $xpi_clock [flow::interface_ports xpi $xpi_data] - flow::apply_output_budget XPI $xpi_clock [flow::interface_ports xpi \ - [concat {xpi_sck_o_pad xpi_nss0_o_pad xpi_nss1_o_pad - xpi_nss2_o_pad xpi_nss3_o_pad} $xpi_data]] - - set gpio_names [concat \ - [flow::numbered_ports gpio_ 0 9 _io_pad] \ - [flow::numbered_ports gpio_ 21 31 _io_pad]] - set async_clock [flow::create_virtual_interface_clock ASYNC] - flow::apply_input_budget ASYNC $async_clock \ - [flow::interface_ports asynchronous [concat {uart0_rx_i_pad} $gpio_names]] - flow::apply_output_budget ASYNC $async_clock \ - [flow::interface_ports asynchronous [concat {uart0_tx_o_pad} $gpio_names]] - - set_false_path -to [flow::interface_ports asynchronous_control \ - {xo_o_pad usb2_ulpi_reset_n_o_pad}] - flow::source_hook TIMING_IO_MODE_HOOK -} - proc flow::apply_io_constraints {} { variable io_qualified variable unqualified_interfaces @@ -110,10 +48,10 @@ proc flow::apply_io_constraints {} { if {$mode eq "YES"} { set io_qualified 1 set unqualified_interfaces {} - flow::apply_qualified_io_constraints + product::apply_io_constraints } elseif {$mode eq "NO"} { set io_qualified 0 - set unqualified_interfaces {JTAG DVP ULPI SDRAM SDIO XPI ASYNC} + set unqualified_interfaces [product::io_interface_groups] # Internal-QoR mode closes all sequential domains while explicitly # excluding board and package paths that do not yet have a budget. set_false_path -from [all_inputs] diff --git a/physical/commercial/tcl/common/products/mini.tcl b/physical/commercial/tcl/common/products/mini.tcl new file mode 100644 index 00000000..5e3be422 --- /dev/null +++ b/physical/commercial/tcl/common/products/mini.tcl @@ -0,0 +1,131 @@ +# Copyright (c) 2023-2026 Yuchi Miao +# retroSoC is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. + +# retroSoC Mini (retrosoc_asic) product overlay. +# +# Clock intent: the canonical contract (rtl/mini/integration/ +# clock_reset_domains.json) owns nine domains. This overlay takes over the +# lp/pclk domains because the LP root mux selects between the 24 MHz reference +# (bypass) and the PLL (performance) source; both modes are modeled as +# physically exclusive generated clocks, preserving the legacy commercial +# system-clock mux semantics on the current hierarchy. + +namespace eval product {} + +proc product::expected_clock_domains {} { + return {aon lp hp pclk memory audio jtag dvp usb2_ulpi} +} + +proc product::custom_clock_domains {} { + return {lp pclk} +} + +proc product::clock_names {} { + return { + clk_aon clk_hp clk_memory clk_audio clk_jtag clk_dvp clk_usb2_ulpi + clk_pll clk_lp_ext clk_lp_pll clk_pclk_ext clk_pclk_pll + } +} + +proc product::io_interface_groups {} { + return {JTAG DVP ULPI SDRAM SDIO XPI ASYNC} +} + +proc product::apply_clock_overlays {} { + set pll_pin [flow::required_pins pll_clock \ + u_rcu/u_clock_reset_subsystem/u_tc_pll/u_PLL_TOP/CKOUT1] + create_clock -name clk_pll -period [flow::env PLL_OUTPUT_PERIOD_NS] $pll_pin + flow::register_clock clk_pll lp + + set aon_object [flow::domain_object aon] + set lp_object [flow::domain_object lp] + create_generated_clock -name clk_lp_ext -master_clock clk_aon \ + -source $aon_object -divide_by 1 $lp_object + flow::register_clock clk_lp_ext lp + create_generated_clock -name clk_lp_pll -master_clock clk_pll \ + -source $pll_pin -divide_by 1 -add $lp_object + flow::register_clock clk_lp_pll lp + + set pclk_object [flow::domain_object pclk] + create_generated_clock -name clk_pclk_ext -master_clock clk_lp_ext \ + -source $lp_object -divide_by 1 $pclk_object + flow::register_clock clk_pclk_ext lp + create_generated_clock -name clk_pclk_pll -master_clock clk_lp_pll \ + -source $lp_object -divide_by 1 -add $pclk_object + flow::register_clock clk_pclk_pll lp + + set_clock_groups -name retrosoc_system_sources -physically_exclusive \ + -group [get_clocks {clk_aon clk_lp_ext clk_pclk_ext}] \ + -group [get_clocks {clk_pll clk_lp_pll clk_pclk_pll}] +} + +proc product::apply_io_constraints {} { + set jtag_in [flow::interface_ports jtag {jtag_tms_i_pad jtag_tdi_i_pad}] + set jtag_out [flow::interface_ports jtag {jtag_tdo_o_pad}] + flow::apply_input_budget JTAG clk_jtag $jtag_in + flow::apply_output_budget JTAG clk_jtag $jtag_out + + set dvp_names [flow::numbered_ports gpio_ 11 20 _io_pad] + set dvp_in [flow::interface_ports dvp $dvp_names] + flow::apply_input_budget DVP clk_dvp $dvp_in + + set ulpi_data [flow::numbered_ports usb2_ulpi_data 0 7 _io_pad] + set ulpi_in [flow::interface_ports ulpi \ + [concat {usb2_ulpi_dir_i_pad usb2_ulpi_nxt_i_pad} $ulpi_data]] + set ulpi_out [flow::interface_ports ulpi \ + [concat $ulpi_data {usb2_ulpi_stp_o_pad}]] + flow::apply_input_budget ULPI clk_usb2_ulpi $ulpi_in + flow::apply_output_budget ULPI clk_usb2_ulpi $ulpi_out + + set sdram_data [flow::numbered_ports sdram_dq 0 15 _io_pad] + set sdram_out_names { + sdram_clk_o_pad sdram_cke_o_pad sdram_cs_n_o_pad + sdram_ras_n_o_pad sdram_cas_n_o_pad sdram_we_n_o_pad + sdram_ba0_o_pad sdram_ba1_o_pad sdram_dqm0_o_pad sdram_dqm1_o_pad + } + set sdram_out_names [concat $sdram_out_names \ + [flow::numbered_ports sdram_addr 0 12 _o_pad] $sdram_data] + set sdram_clock [flow::create_virtual_interface_clock SDRAM] + flow::apply_input_budget SDRAM $sdram_clock \ + [flow::interface_ports sdram $sdram_data] + flow::apply_output_budget SDRAM $sdram_clock \ + [flow::interface_ports sdram $sdram_out_names] + + set sdio_data { + sdio1_cmd_io_pad sdio1_dat0_io_pad sdio1_dat1_io_pad + sdio1_dat2_io_pad sdio1_dat3_io_pad + } + set sdio_clock [flow::create_virtual_interface_clock SDIO] + flow::apply_input_budget SDIO $sdio_clock [flow::interface_ports sdio $sdio_data] + flow::apply_output_budget SDIO $sdio_clock \ + [flow::interface_ports sdio [concat {sdio1_clk_o_pad} $sdio_data]] + + set xpi_data [flow::numbered_ports xpi_dat 0 3 _io_pad] + set xpi_clock [flow::create_virtual_interface_clock XPI] + flow::apply_input_budget XPI $xpi_clock [flow::interface_ports xpi $xpi_data] + flow::apply_output_budget XPI $xpi_clock [flow::interface_ports xpi \ + [concat {xpi_sck_o_pad xpi_nss0_o_pad xpi_nss1_o_pad + xpi_nss2_o_pad xpi_nss3_o_pad} $xpi_data]] + + set gpio_names [concat \ + [flow::numbered_ports gpio_ 0 9 _io_pad] \ + [flow::numbered_ports gpio_ 21 31 _io_pad]] + set async_clock [flow::create_virtual_interface_clock ASYNC] + flow::apply_input_budget ASYNC $async_clock \ + [flow::interface_ports asynchronous \ + [concat {uart0_rx_i_pad uart1_rx_i_pad} $gpio_names]] + flow::apply_output_budget ASYNC $async_clock \ + [flow::interface_ports asynchronous \ + [concat {uart0_tx_o_pad uart1_tx_o_pad} $gpio_names]] + + set_false_path -to [flow::interface_ports asynchronous_control \ + {usb2_ulpi_reset_n_o_pad}] + flow::source_hook TIMING_IO_MODE_HOOK +} diff --git a/physical/commercial/tcl/common/products/tiny.tcl b/physical/commercial/tcl/common/products/tiny.tcl new file mode 100644 index 00000000..247caba5 --- /dev/null +++ b/physical/commercial/tcl/common/products/tiny.tcl @@ -0,0 +1,61 @@ +# Copyright (c) 2023-2026 Yuchi Miao +# retroSoC is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. + +# retroSoC Tiny (retrosoc_tiny_asic) product overlay. +# +# Clock intent: the canonical contract (rtl/tiny/integration/ +# clock_reset_domains.json) owns two domains, system (SAFE24 external clock) +# and jtag. The ICS55 PLL_TOP macro is present but parked off (EN=0) and is +# not a clock source; it carries no characterized timing arcs yet, so no PLL +# overlay clock is created here. See docs/ip/tiny-soc.md (TINY-055). + +namespace eval product {} + +proc product::expected_clock_domains {} { + return {system jtag} +} + +proc product::custom_clock_domains {} { + return {} +} + +proc product::clock_names {} { + return {clk_system clk_jtag} +} + +proc product::io_interface_groups {} { + return {JTAG XPI ASYNC} +} + +proc product::apply_clock_overlays {} { +} + +proc product::apply_io_constraints {} { + set jtag_in [flow::interface_ports jtag {jtag_tms_i_pad jtag_tdi_i_pad}] + set jtag_out [flow::interface_ports jtag {jtag_tdo_o_pad}] + flow::apply_input_budget JTAG clk_jtag $jtag_in + flow::apply_output_budget JTAG clk_jtag $jtag_out + + set xpi_data [flow::numbered_ports xpi_dat 0 3 _io_pad] + set xpi_clock [flow::create_virtual_interface_clock XPI] + flow::apply_input_budget XPI $xpi_clock [flow::interface_ports xpi $xpi_data] + flow::apply_output_budget XPI $xpi_clock [flow::interface_ports xpi \ + [concat {xpi_sck_o_pad xpi_nss0_o_pad xpi_nss1_o_pad + xpi_nss2_o_pad xpi_nss3_o_pad} $xpi_data]] + + set gpio_names [flow::numbered_ports gpio_ 0 31 _io_pad] + set async_clock [flow::create_virtual_interface_clock ASYNC] + flow::apply_input_budget ASYNC $async_clock \ + [flow::interface_ports asynchronous \ + [concat {uart0_rx_i_pad uart1_rx_i_pad} $gpio_names]] + flow::apply_output_budget ASYNC $async_clock \ + [flow::interface_ports asynchronous \ + [concat {uart0_tx_o_pad uart1_tx_o_pad} $gpio_names]] +} diff --git a/physical/commercial/tcl/eco/main.tcl b/physical/commercial/tcl/eco/main.tcl index ff31deae..feee5f11 100644 --- a/physical/commercial/tcl/eco/main.tcl +++ b/physical/commercial/tcl/eco/main.tcl @@ -12,6 +12,22 @@ set flow_root [file normalize [file join [file dirname [info script]] ../..]] source [file join $flow_root tcl common common.tcl] source [file join $flow_root tcl common corners.tcl] +proc eco_flag {name} { + set value [string toupper [flow::env $name NO]] + if {$value ni {YES NO}} { + flow::fail "$name must be YES or NO" + } + return [expr {$value eq "YES"}] +} + +proc eco_pba_mode {} { + set mode [string tolower [flow::env ECO_PBA_MODE none]] + if {$mode ni {none path exhaustive}} { + flow::fail "ECO_PBA_MODE must be none, path, or exhaustive" + } + return $mode +} + proc run_eco {} { set run_root [flow::env RUN_ROOT] set top [flow::env TOP] @@ -68,6 +84,34 @@ proc run_eco {} { -hold_margin [flow::env ECO_HOLD_MARGIN_NS] \ -physical_mode [flow::env ECO_PHYSICAL_MODE] -verbose } + # Optional power-recovery subflows (legacy pd_data/pteco/scr/flow/swap.tcl + # and size_down.tcl); both default off and run after the setup fix. + if {[eco_flag ECO_ENABLE_VT_SWAP]} { + define_user_attribute -type string -class lib_cell cell_footprint -import + set_app_var eco_alternative_cell_attribute_restrictions \ + "area cell_footprint" + set_app_var eco_alternative_area_ratio_threshold 2 + remote_execute { + define_user_attribute -type string -class lib_cell cell_footprint -import + set_app_var eco_alternative_cell_attribute_restrictions \ + "area cell_footprint" + set_app_var eco_alternative_area_ratio_threshold 2 + update_timing -full + } + redirect [file join $report_dir vt_swap.rpt] { + fix_eco_power -methods size_cell \ + -pattern_priority [flow::env ECO_VT_PATTERN_PRIORITY] \ + -setup_margin [flow::env ECO_SETUP_MARGIN_NS] \ + -pba_mode [eco_pba_mode] -verbose + } + } + if {[eco_flag ECO_ENABLE_SIZE_DOWN]} { + redirect [file join $report_dir size_down.rpt] { + fix_eco_power -methods size_cell \ + -setup_margin [flow::env ECO_SETUP_MARGIN_NS] \ + -pba_mode [eco_pba_mode] -verbose + } + } redirect [file join $report_dir hold.rpt] { fix_eco_timing -type hold -methods insert_buffer \ -buffer_list [flow::env ECO_HOLD_BUFFERS] \ @@ -75,6 +119,17 @@ proc run_eco {} { -hold_margin [flow::env ECO_HOLD_MARGIN_NS] \ -physical_mode [flow::env ECO_PHYSICAL_MODE] -verbose } + # Optional redundant-buffer removal (legacy + # pd_data/pteco/scr/flow/remove_buffer.tcl); defaults off and runs after + # the hold fix, matching the legacy flow order. + if {[eco_flag ECO_ENABLE_REMOVE_BUFFER]} { + redirect [file join $report_dir remove_buffer.rpt] { + fix_eco_power -methods remove_buffer \ + -setup_margin [flow::env ECO_SETUP_MARGIN_NS] \ + -hold_margin [flow::env ECO_HOLD_MARGIN_NS] \ + -pba_mode [eco_pba_mode] -verbose + } + } redirect [file join $report_dir drc.rpt] { foreach type {max_transition max_capacitance} { fix_eco_drc -type $type -methods size_cell \ @@ -89,15 +144,25 @@ proc run_eco {} { } } - set raw [file join $work_dir func_TYP_TYP_25.icc2.tcl] - set eco_raw $raw - set_distributed_variables {eco_raw} - current_session func_TYP_TYP_25 - remote_execute { write_changes -format icc2tcl -output $eco_raw } + # Every scenario records its own change list in DMSA (legacy + # pd_data/pteco/scr/app/output.tcl writes changes from every worker); + # exporting only one scenario drops scenario-specific fixes. Iterate over + # every scenario with a current session so each write_changes lands in a + # dedicated file, then let translate_eco.py merge and deduplicate them. + set raw_changes {} + dict for {scenario unused} $::flow::scenarios { + set eco_raw [file join $work_dir ${scenario}.icc2.tcl] + set_distributed_variables {eco_raw} + current_session $scenario + remote_execute { write_changes -format icc2tcl -output $eco_raw } + lappend raw_changes $eco_raw + } set command [list [flow::env FLOW_PYTHON python] \ [file join $::flow::root scripts translate_eco.py] \ --output [file join $output_dir eco.tcl] --allow-empty] - lappend command --input $raw + foreach raw $raw_changes { + lappend command --input $raw + } if {[catch {exec {*}$command} message]} { flow::fail "PrimeTime ECO translation failed: $message" } diff --git a/physical/commercial/tcl/pv/generate.tcl b/physical/commercial/tcl/pv/generate.tcl index e378a5ad..11d98784 100644 --- a/physical/commercial/tcl/pv/generate.tcl +++ b/physical/commercial/tcl/pv/generate.tcl @@ -15,13 +15,29 @@ proc quote_svrf {value} { return "\"[string map {\" \\\"} $value]\"" } +proc optional_files {name} { + set result {} + foreach item [flow::env $name ""] { + if {[regexp {[*?\[]} $item]} { + flow::fail "$name contains a wildcard: $item" + } + if {![file isfile $item] || ![file readable $item]} { + flow::fail "$name is not readable: $item" + } + lappend result [file normalize $item] + } + return $result +} + proc write_merge {run_root top} { set base [flow::stage_dirs pv merge] set input [file join $run_root apr eco output ${top}.eco.gds] set output [file join $base output ${top}.merged.gds] set verdict [file join $base output verdict.pass] + # OTHER_GDS carries optional extra layouts such as seal rings (legacy + # pd_data/pv/scr/CL1/core.merge merges project_config(physical,gds,other)). set gds [concat [flow::env_list STD_GDS] [flow::env_list IO_GDS] \ - [flow::env_list MACRO_GDS]] + [flow::env_list MACRO_GDS] [optional_files OTHER_GDS]] set handle [open [file join $base work merge.tcl] w] puts $handle [list set input_gds $input] puts $handle [list set output_gds $output] @@ -124,6 +140,7 @@ proc write_lvs {run_root top} { puts $handle [list set runset $runset] puts $handle [list set report [file join $base reports ${top}.lvs.rpt]] puts $handle [list set verdict $verdict] + puts $handle [list set lvs_args [flow::env CALIBRE_LVS_ARGS ""]] puts $handle {if {[file exists $verdict]} { file delete -force $verdict } @@ -133,7 +150,7 @@ if {[catch {exec {*}$convert >@ stdout 2>@ stderr} message]} { puts stderr $message exit 2 } -set verify [concat $calibre [list -64 -lvs -hier -hcell $hcells $runset]] +set verify [concat $calibre [list -64 -lvs] $lvs_args [list -hier -hcell $hcells $runset]] if {[catch {exec {*}$verify >@ stdout 2>@ stderr} message]} { puts stderr $message exit 2 @@ -167,6 +184,101 @@ close $marker} close $handle } +# Macro-level LVS/ERC (legacy pd_data/pv/scr/CL1/_calibre_PLL_lvs.rul_ and +# PLL.lvs.cmd run a standalone hierarchical Calibre LVS+ERC per hard macro and +# keep reports such as PLL_TOP_lvs.full.rpt). MACRO_LVS_CELLS lists the macro +# primary cells; an empty list is a harmless skip with a passing verdict. +proc write_macro_lvs {run_root top} { + set base [flow::stage_dirs pv macro_lvs] + set verdict [file join $base output verdict.pass] + set driver [file join $base work macro_lvs_driver.tcl] + set cells {} + foreach cell [flow::env MACRO_LVS_CELLS ""] { + if {![regexp {^[A-Za-z0-9_]+$} $cell]} { + flow::fail "MACRO_LVS_CELLS contains an invalid cell name: $cell" + } + lappend cells $cell + } + if {[llength $cells] == 0} { + flow::write_text $driver {puts "MACRO_LVS_CELLS is empty; macro-level LVS is skipped" +} + flow::write_pass $verdict + puts "MACRO_LVS_CELLS is empty; macro-level LVS is skipped" + return + } + set deck [lindex [flow::env_list CALIBRE_LVS_DECK] 0] + set layouts {} + foreach path [flow::env_list MACRO_GDS] { + lappend layouts [quote_svrf $path] + } + set sources {} + foreach path [flow::env_list MACRO_CDL] { + lappend sources [quote_svrf $path] + } + set jobs {} + foreach cell $cells { + set runset [file join $base work ${cell}.lvs.runset] + set report [file join $base reports ${cell}.lvs.rpt] + set handle [open $runset w] + puts $handle "LAYOUT PATH [join $layouts { }]" + puts $handle "LAYOUT PRIMARY [quote_svrf $cell]" + puts $handle "LAYOUT SYSTEM GDSII" + puts $handle "SOURCE PATH [join $sources { }]" + puts $handle "SOURCE PRIMARY [quote_svrf $cell]" + puts $handle "SOURCE SYSTEM SPICE" + puts $handle "LVS REPORT [quote_svrf $report]" + puts $handle "LVS REPORT MAXIMUM ALL" + puts $handle "MASK SVDB DIRECTORY [quote_svrf [file join $base work svdb_${cell}]] QUERY" + puts $handle "LVS SPICE CULL PRIMITIVE SUBCIRCUITS YES" + puts $handle "LVS SPICE OVERRIDE GLOBALS YES" + puts $handle "LVS RECOGNIZE GATES NONE" + puts $handle "LVS EXECUTE ERC YES" + puts $handle "ERC RESULTS DATABASE [quote_svrf [file join $base work ${cell}.erc.db]]" + puts $handle "ERC SUMMARY REPORT [quote_svrf [file join $base reports ${cell}.erc.rpt]]" + puts $handle "ERC MAXIMUM RESULTS ALL" + puts $handle "INCLUDE [quote_svrf $deck]" + close $handle + lappend jobs $cell $runset $report + } + set handle [open $driver w] + puts $handle [list set calibre [flow::env CALIBRE]] + puts $handle [list set lvs_args [flow::env CALIBRE_LVS_ARGS ""]] + puts $handle [list set checker [list [flow::env FLOW_PYTHON python] \ + [file join $::flow::root scripts check_outputs.py]]] + puts $handle [list set reports [file join $base reports]] + puts $handle [list set verdict $verdict] + puts $handle [list set jobs $jobs] + puts $handle {if {[file exists $verdict]} { + file delete -force $verdict +} +set started [clock seconds] +foreach {cell runset report} $jobs { + if {[file exists $report]} { + file delete -force $report + } + set command [concat $calibre [list -64 -lvs] $lvs_args [list -hier $runset]] + if {[catch {exec {*}$command >@ stdout 2>@ stderr} message]} { + puts stderr $message + exit 2 + } + if {![file isfile $report]} { + puts stderr "missing macro LVS report: $report" + exit 2 + } + if {[file mtime $report] < ($started - 2)} { + puts stderr "macro LVS report was not refreshed: $report" + exit 2 + } +} +set check [concat $checker [list --kind calibre-lvs --root $reports \ + --verdict $verdict]] +if {[catch {exec {*}$check >@ stdout 2>@ stderr} message]} { + puts stderr $message + exit 2 +}} + close $handle +} + proc generate_pv {} { set run_root [flow::env RUN_ROOT] set top [flow::env TOP] @@ -175,6 +287,7 @@ proc generate_pv {} { merge { write_merge $run_root $top } drc - antenna { write_drc $run_root $top $mode } lvs { write_lvs $run_root $top } + macro_lvs { write_macro_lvs $run_root $top } default { flow::fail "unsupported PV mode: $mode" } } } diff --git a/physical/commercial/tcl/sta/load.tcl b/physical/commercial/tcl/sta/load.tcl index 5bf10729..7d258b5a 100644 --- a/physical/commercial/tcl/sta/load.tcl +++ b/physical/commercial/tcl/sta/load.tcl @@ -8,10 +8,49 @@ # MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. # See the Mulan PSL v2 for more details. +# PrimeTime analysis settings adopted from the CX55 legacy signoff flow +# (Flow_CX55_SoC_CL_2026 bes_data/sta/scr/flow_com/pt_common_flow.tcl:78-105; +# only the legacy lines that are actually uncommented are enabled here). The +# legacy script assigns these with plain `set`; current PrimeTime releases +# expose them as application variables, so set_app_var is used instead. +proc flow::apply_pt_analysis_settings {} { + # Clock reconvergence pessimism reduction (legacy lines 78 and 85). + set_app_var timing_remove_clock_reconvergence_pessimism true + set_app_var timing_crpr_threshold_ps 1 + # SMIC-recommended timing behavior (legacy lines 81-84). + set_app_var timing_save_pin_arrival_and_slack true + set_app_var timing_early_launch_at_borrowing_latches false + set_app_var timing_clock_gating_propagate_enable true + set_app_var timing_report_use_worst_parallel_cell_arc true + # Path-based analysis behavior (legacy lines 87 and 99). + set_app_var pba_exhaustive_endpoint_path_limit infinity + set_app_var pba_enable_path_based_physical_exclusivity true + # Constraint, parasitic, and linking behavior (legacy lines 90-92). + set_app_var timing_enable_max_capacitance_set_case_analysis true + set_app_var read_parasitics_load_locations true + set_app_var link_create_black_boxes false + # Signal-integrity and waveform-accurate delay calculation (legacy + # lines 94-102; the commented-out si_xtalk_composite_aggr_* candidates + # stay disabled). Coupling capacitors come from read_parasitics + # -keep_capacitive_coupling in flow::load_pt_scenario. + set_app_var si_enable_analysis true + set_app_var si_xtalk_double_switching_mode full_design + set_app_var si_xtalk_composite_aggr_mode statistical + set_app_var si_noise_composite_aggr_mode statistical + set_app_var si_xtalk_delay_analysis_mode all_path_edges + set_app_var delay_calc_waveform_analysis_mode full_design + set_app_var delay_calc_waveform_analysis_constraint_arcs_compatibility false + # Automatic mux clock exclusivity (legacy line 105). The site-specific + # legacy parasitics_log_file path (line 103) is not adopted; annotation + # evidence is covered by report_annotated_parasitics in main.tcl. + set_app_var timing_enable_auto_mux_clock_exclusivity true +} + proc flow::load_pt_scenario {tag scenario} { variable scenarios set top [flow::env TOP] set run_root [flow::env RUN_ROOT] + flow::apply_pt_analysis_settings set netlist [file join $run_root apr $tag output ${top}.${tag}.v] set sdc [file join $run_root apr $tag output ${top}.${tag}.sdc] set spef_root [file join $run_root extract $tag output] @@ -39,6 +78,9 @@ proc flow::load_pt_scenario {tag scenario} { } set_operating_conditions -analysis_type on_chip_variation read_sdc $sdc + # OCV clock derate per the scenario PVT corner (CX55 legacy + # bes_data/common/signoff_table.tcl policy). + flow::apply_signoff_derate $pvt set spef [file join $spef_root [flow::spef_name $top $rc]] if {![file isfile $spef]} { flow::fail "SPEF is missing for scenario $scenario: $spef" diff --git a/physical/commercial/tcl/sta/main.tcl b/physical/commercial/tcl/sta/main.tcl index a63c46ff..f4821d12 100644 --- a/physical/commercial/tcl/sta/main.tcl +++ b/physical/commercial/tcl/sta/main.tcl @@ -31,6 +31,10 @@ proc run_sta {} { if {![dict exists $::flow::scenarios $scenario]} { flow::fail "unsupported STA scenario: $scenario" } + set sdf_scenario [flow::env STA_SDF_SCENARIO func_TYP_TYP_25] + if {![dict exists $::flow::scenarios $sdf_scenario]} { + flow::fail "unsupported STA_SDF_SCENARIO: $sdf_scenario" + } set base [flow::stage_dirs sta $tag] set report_dir [file join $base reports] set output_dir [file join $base output] @@ -39,7 +43,36 @@ proc run_sta {} { flow::require_commercial_clock_inventory update_timing -full - if {![check_timing -include {no_clock unconstrained_endpoints}]} { + # Full CX55 legacy check_timing coverage (pt_common_flow.tcl:350); the + # redirect keeps the per-scenario report and preserves the pass/fail + # return value used as the verdict gate. + set check_timing_report [file join $report_dir ${scenario}.check_timing.rpt] + if {![redirect $check_timing_report { + check_timing -verbose -include { + clock_crossing + data_check_multiple_clock + data_check_no_clock + generated_clocks + generic + ideal_clocks + latch_fanout + latency_override + loops + ms_separation + no_clock + no_driving_cell + no_input_delay + partial_input_delay + pll_configuration + retain + signal_level + supply_net_voltage + unconstrained_endpoints + unexpandable_clocks + pulse_clock_non_pulse_clock_merge + pulse_clock_no_pulse_generator + } + }]} { flow::fail "check_timing failed in scenario $scenario" } @@ -61,6 +94,22 @@ proc run_sta {} { -max_paths 100000 -path_type full_clock_expanded \ -input_pins -nets -transition_time -capacitance -derate } + # Exhaustive path-based recheck next to the graph-based reports above + # (legacy pt_common_flow.tcl:381-392 emits the PBA violation variants at + # signoff; there report_constraint -pba exhaustive is used, here + # report_timing -pba_mode exhaustive keeps the full path detail). + redirect [file join $report_dir ${scenario}.setup.pba.rpt] { + report_timing -delay_type max -slack_lesser_than 0.0 \ + -max_paths 100000 -path_type full_clock_expanded \ + -input_pins -nets -transition_time -capacitance -derate \ + -pba_mode exhaustive + } + redirect [file join $report_dir ${scenario}.hold.pba.rpt] { + report_timing -delay_type min -slack_lesser_than 0.0 \ + -max_paths 100000 -path_type full_clock_expanded \ + -input_pins -nets -transition_time -capacitance -derate \ + -pba_mode exhaustive + } redirect $constraint_report { report_constraint -all_violators } redirect [file join $report_dir ${scenario}.coverage.rpt] { report_analysis_coverage -status_details untested @@ -79,6 +128,21 @@ proc run_sta {} { puts [get_object_name $library] } } + # Optional SAIF-annotated power analysis (legacy pt_common_flow.tcl:283-314 + # activity annotation and :325-331 report_power). An empty STA_SAIF_FILE + # keeps the previous timing-only behavior. + set saif_file [string trim [flow::env STA_SAIF_FILE ""]] + if {$saif_file eq ""} { + puts "STA_SAIF_FILE is not set; skipping SAIF power analysis" + } else { + if {![file isfile $saif_file] || ![file readable $saif_file]} { + flow::fail "STA_SAIF_FILE is not readable: $saif_file" + } + read_saif $saif_file + redirect [file join $report_dir ${scenario}.power.rpt] { + report_power -nosplit + } + } set drv_count [count_violations $constraint_report] flow::write_text [file join $output_dir ${scenario}.summary.tsv] \ "$scenario\t$setup_count\t$hold_count\t$drv_count\n" @@ -89,7 +153,7 @@ proc run_sta {} { flow::fail "incomplete parasitic annotation in scenario $scenario" } - if {$scenario eq "func_TYP_TYP_25"} { + if {$scenario eq $sdf_scenario} { write_sdf -version 3.0 -context verilog \ [file join $output_dir ${top}.${tag}.sdf] } diff --git a/physical/commercial/tcl/syn/main.tcl b/physical/commercial/tcl/syn/main.tcl index 662e0ce6..2c59c782 100644 --- a/physical/commercial/tcl/syn/main.tcl +++ b/physical/commercial/tcl/syn/main.tcl @@ -11,6 +11,7 @@ set flow_root [file normalize [file join [file dirname [info script]] ../..]] source [file join $flow_root tcl common common.tcl] source [file join $flow_root tcl common constraints.tcl] +source [file join $flow_root tcl common corners.tcl] source [file join $flow_root tcl syn reporting.tcl] proc run_synthesis {} { @@ -38,6 +39,31 @@ proc run_synthesis {} { set_app_var verilogout_no_tri true set_app_var compile_clock_gating_through_hierarchy true + # Tool setup migrated from the CX55 legacy flow + # (bes_data/syn/scr/flow_com/syn_common_flow.tcl:62-121); only the + # settings that were active (not commented out) there are kept. + set_app_var hdlin_vhdl_std 1987 + set_app_var hdlin_vrlg_std 2001 + set_app_var hdlin_sverilog_std 2012 + set_app_var alib_library_analysis_path $work_dir + set_host_options -max_cores [flow::env SYN_COMPILE_CORES 16] + suppress_message { + VER-936 VER-129 VER-173 VHD-4 VER-314 VER-318 ELAB-924 ELAB-130 + ELAB-909 VHD-7 ELAB-931 PWR-429 PWR-415 PWR-416 PWR-12 PWR-414 + PWR-428 UID-401 VER-281 ELAB-910 TIM-250 PSYN-1002 DEFR-103 + PSYN-950 DEFR-35 DCT-197 PSYN-123 ELAB-311 ELAB-388 TIM-134 + OPT-806 OPT-805 DCT-055 DDB-74 UCN-1 + } + set_app_var verilogout_show_unconnected_pins true + set_app_var hdlin_check_no_latch true + set_app_var timing_enable_multiple_clocks_per_reg true + set_app_var compile_seqmap_identify_shift_registers false + set_app_var verilogout_higher_designs_first true + set_app_var hdlin_shorten_long_module_name true + set_app_var hdlin_analyze_verbose_mode 2 + history keep 200 + set_app_var report_default_significant_digits 4 + define_design_lib WORK -path [file join $work_dir WORK] set analyze_command [list analyze -format sverilog -work WORK] if {[llength [dict get $rtl defines]] > 0} { @@ -63,7 +89,49 @@ proc run_synthesis {} { } } + # Legacy syn_common_flow.tcl:247-250: macro/IO cells that are already + # mapped must survive optimization untouched. + set mapped [get_cells -quiet -hierarchical \ + -filter "is_mapped == true && is_hierarchical == false && ref_name !~ *logic_*"] + if {[sizeof_collection $mapped] > 0} { + set_dont_touch $mapped + } + + # Legacy syn_common_flow.tcl:273. + set_max_area 0 + + # Legacy syn_common_flow.tcl:281-292: latch-based clock-gating style and + # exclusion of the input synchronizer registers. + set_clock_gating_style -sequential_cell latch \ + -control_point before -control_signal scan_enable \ + -minimum_bitwidth 4 -observation_point false -max_fanout 32 + set sync_patterns [flow::env SYN_CLOCK_GATING_EXCLUDE_PATTERNS \ + "{*/synch_toggle} {*/synch_preset} {*/synch_enable} {*/synch_clear} {*/next_state}"] + set sync_terms {} + foreach pattern $sync_patterns { + lappend sync_terms "full_name =~ $pattern" + } + set sync_endpoints "" + foreach_in_collection input [all_inputs] { + set fanout [all_fanout -from $input -flat -endpoints_only] + append_to_collection -unique sync_endpoints \ + [filter_collection $fanout [join $sync_terms " || "]] + } + set sync_cells [get_cells -quiet -of_objects $sync_endpoints] + if {[sizeof_collection $sync_cells] > 0} { + set_clock_gating_objects -exclude $sync_cells + } + + # Legacy syn_common_flow.tcl:300. + set_dynamic_optimization true + flow::apply_constraints + # Synthesis links the TYP library set (flow::synthesis_library_files). + # The legacy CX55 DC run at TYP applied no timing derate at all + # (bes_data/syn/scr/flow_com/syn_common_flow.tcl never sources + # signoff_table.tcl; bes_data/syn/log/asic_top.log has no + # set_timing_derate), so no derate is applied here either; the OCV derate + # policy lives in PrimeTime signoff (flow::apply_signoff_derate). redirect [file join $report_dir check_design.pre.rpt] { check_design } redirect [file join $report_dir check_timing.pre.rpt] { check_timing @@ -77,10 +145,25 @@ proc run_synthesis {} { set_fix_multiple_port_nets -all -buffer_constants set_boundary_optimization [current_design] true + # Legacy syn_common_flow.tcl:500-502: separate I/O path groups (the + # legacy script names the -to [all_outputs] group "out2reg"). + group_path -weight 0.1 -name in2reg -from [all_inputs] + group_path -weight 0.1 -name out2reg -to [all_outputs] + group_path -weight 0.1 -name in2out -from [all_inputs] -to [all_outputs] + # Legacy syn_common_flow.tcl:388: the legacy DC run was not in + # topographical mode, and neither is this one (DC_SHELL := dc_shell). + set_wire_load_mode top + # Legacy syn_common_flow.tcl:517 with critical_range default 0.2 (:24). + set_critical_range [flow::env SYN_CRITICAL_RANGE_NS 0.2] [current_design] + set svf [file join $output_dir ${top}.svf] set_svf $svf - compile_ultra -no_autoungroup -gate_clock - compile_ultra -incremental -no_autoungroup -gate_clock + # Legacy syn_common_flow.tcl:31-32,533: high-effort TNS optimization and + # compile_ultra without sequential output inversion. + set_app_var compile_timing_high_effort_tns true + compile_ultra -no_autoungroup -no_seq_output_inversion -gate_clock + compile_ultra -incremental -no_autoungroup -no_seq_output_inversion \ + -gate_clock set_svf -off change_names -rules verilog -hierarchy diff --git a/physical/commercial/tcl/syn/reporting.tcl b/physical/commercial/tcl/syn/reporting.tcl index a4b04602..140a0789 100644 --- a/physical/commercial/tcl/syn/reporting.tcl +++ b/physical/commercial/tcl/syn/reporting.tcl @@ -52,6 +52,15 @@ proc flow::vt_cells {suffix} { -filter "is_hierarchical == false && ref_name =~ *$suffix"] } +proc flow::vt_suffix_map {} { + set configured [flow::env SYN_VT_SUFFIX_MAP "HVT H7H LVT H7L SVT H7R"] + if {[llength $configured] == 0 || [llength $configured] % 2 != 0} { + flow::fail "SYN_VT_SUFFIX_MAP must be a flat group/suffix list" + } + array set map $configured + return [array get map] +} + proc flow::write_synthesis_summary {path drv_count} { variable io_qualified set setup [flow::timing_metrics "" max] @@ -69,10 +78,10 @@ proc flow::write_synthesis_summary {path drv_count} { set memories [get_cells -quiet -hierarchical \ -filter "is_hierarchical == false && is_memory_cell == true"] - array set vt_suffix {HVT H7H LVT H7L SVT H7R} + array set vt_suffix [flow::vt_suffix_map] set std_area 0.0 set std_count 0 - foreach group {HVT LVT SVT} { + foreach group [lsort [array names vt_suffix]] { set cells [flow::vt_cells $vt_suffix($group)] set vt_count($group) [sizeof_collection $cells] set vt_area($group) [flow::collection_area $cells] @@ -80,6 +89,18 @@ proc flow::write_synthesis_summary {path drv_count} { set std_area [expr {$std_area + $vt_area($group)}] } + # Area split adapted from the legacy syn_statistics.tcl:29-45 table. The + # new netlists contain only standard cells and macros, so the legacy + # mem/ipio/sub_harden buckets collapse into a single macro residual. + set total_leaf_area [flow::collection_area $leaf] + set macro_area [expr {$total_leaf_area - $std_area}] + set std_percent 0.0 + set macro_percent 0.0 + if {$total_leaf_area > 0.0} { + set std_percent [expr {100.0 * $std_area / $total_leaf_area}] + set macro_percent [expr {100.0 * $macro_area / $total_leaf_area}] + } + set handle [open $path w] puts $handle "metric\tvalue" puts $handle "flow_pass\tyes" @@ -100,8 +121,11 @@ proc flow::write_synthesis_summary {path drv_count} { puts $handle "memory_macros\t[sizeof_collection $memories]" puts $handle "standard_cells\t$std_count" puts $handle "standard_cell_area_um2\t[format %.4f $std_area]" - puts $handle "total_leaf_area_um2\t[format %.4f [flow::collection_area $leaf]]" - foreach group {HVT LVT SVT} { + puts $handle "macro_area_um2\t[format %.4f $macro_area]" + puts $handle "total_leaf_area_um2\t[format %.4f $total_leaf_area]" + puts $handle "standard_cell_area_percent\t[format %.2f $std_percent]" + puts $handle "macro_area_percent\t[format %.2f $macro_percent]" + foreach group [lsort [array names vt_suffix]] { set percent 0.0 if {$std_area > 0.0} { set percent [expr {100.0 * $vt_area($group) / $std_area}] @@ -113,11 +137,22 @@ proc flow::write_synthesis_summary {path drv_count} { close $handle } +proc flow::vt_library_map {} { + set configured [flow::env SYN_VT_LIBRARY_MAP "HVT *H7CH* LVT *H7CL* SVT *H7CR*"] + if {[llength $configured] == 0 || [llength $configured] % 2 != 0} { + flow::fail "SYN_VT_LIBRARY_MAP must be a flat group/pattern list" + } + array set map $configured + return [array get map] +} + proc flow::configure_synthesis_libraries {} { - foreach {group pattern} {HVT *H7CH* LVT *H7CL* SVT *H7CR*} { + array set vt_library [flow::vt_library_map] + foreach group [array names vt_library] { + set pattern $vt_library($group) set libraries [get_libs -quiet $pattern] if {[sizeof_collection $libraries] == 0} { - flow::fail "missing $group H7C library in the TYP link set" + flow::fail "missing $group standard-cell library in the TYP link set" } set_attribute $libraries default_threshold_voltage_group $group -type string } diff --git a/scripts/package.py b/scripts/package.py index 82f0b6c9..a400a8c4 100644 --- a/scripts/package.py +++ b/scripts/package.py @@ -102,9 +102,43 @@ def main() -> int: ) as temp: staging = Path(temp) / "package" staging.mkdir() - timing_contract = Path(temp) / ("tiny_core.sdc" if soc == "TINY" else "commercial_timing_contract.tcl") - timing_generator = (root / "physical/smoke/sta/opensta/generate_sdc.py" if soc == "TINY" - else root / "physical/commercial/scripts/generate_timing_contract.py") + commercial_contract = Path(temp) / "commercial_timing_contract.tcl" + subprocess.run( + [ + "python3", + str(root / "physical/commercial/scripts/generate_timing_contract.py"), + "--soc", + soc, + "--domains", + str(product_root / "integration/clock_reset_domains.json"), + "--pin-map", + str(product_root / "pin_map/pin_map.json"), + "--output", + str(commercial_contract), + ], + cwd=root, + check=True, + ) + metadata_files = [ + f"contracts/{commercial_contract.name}={commercial_contract}", + ] + if soc == "TINY": + tiny_sdc = Path(temp) / "tiny_core.sdc" + subprocess.run( + [ + "python3", + str(root / "physical/smoke/sta/opensta/generate_sdc.py"), + "--domains", + str(product_root / "integration/clock_reset_domains.json"), + "--pin-map", + str(product_root / "pin_map/pin_map.json"), + "--output", + str(tiny_sdc), + ], + cwd=root, + check=True, + ) + metadata_files.append(f"contracts/{tiny_sdc.name}={tiny_sdc}") if config.get("MINI_MODE", "PRODUCT") == "MPW": dynamic_core_filelist = ( args.variant_root @@ -125,20 +159,6 @@ def main() -> int: dynamic_ip_filelist = ( args.variant_root / "generated/user_extensions/legacy_ip.fl" ) - subprocess.run( - [ - "python3", - str(timing_generator), - "--domains", - str(product_root / "integration/clock_reset_domains.json"), - "--pin-map", - str(product_root / "pin_map/pin_map.json"), - "--output", - str(timing_contract), - ], - cwd=root, - check=True, - ) command = [ "python3", str(root / "physical/smoke/syn/tools/export_soc_sources.py"), @@ -171,9 +191,9 @@ def main() -> int: str(args.variant_root / "generated/pin_map/pin_map.fl"), "--archinfo-incdir", str(args.variant_root / "generated/archinfo"), - "--metadata-file", - f"contracts/{timing_contract.name}={timing_contract}", ] + for metadata in metadata_files: + command.extend(("--metadata-file", metadata)) if soc == "TINY": if config.get("SYNTH") == "YOSYS": recipe = config.get("SYNTH_RECIPE", "balanced") diff --git a/tests/test_commercial_flow.py b/tests/test_commercial_flow.py index 185d1602..d001e7be 100644 --- a/tests/test_commercial_flow.py +++ b/tests/test_commercial_flow.py @@ -57,13 +57,60 @@ def test_translate_eco_accepts_only_reviewed_operations(tmp_path: Path) -> None: translator.translate([str(unsupported)]) +def test_translate_eco_merges_scenarios_and_drops_duplicates(tmp_path: Path) -> None: + translator = load_script("translate_eco") + first = tmp_path / "func_TYP_TYP_25.icc2.tcl" + first.write_text( + "current_instance {u_core}\n" + "size_cell {u_reg} {DFFQX2H7R}\n" + "insert_buffer [get_pins {u_src/Q}] BUFX2H7R " + "-new_net_names {eco_net} -new_cell_names {eco_buf}\n", + encoding="utf-8", + ) + second = tmp_path / "func_MIN_Cworst_m40.icc2.tcl" + second.write_text( + "current_instance {u_core}\n" + "size_cell {u_reg} {DFFQX2H7R}\n" + "size_cell {u_hold} {DFFQX1H7R}\n", + encoding="utf-8", + ) + assert translator.translate([str(first), str(second)]) == [ + "ecoChangeCell -inst {u_core/u_reg} -cell {DFFQX2H7R}", + "ecoAddRepeater -term {u_core/u_src/Q} -cell {BUFX2H7R} " + "-newNetName {eco_net} -name {eco_buf}", + "ecoChangeCell -inst {u_core/u_hold} -cell {DFFQX1H7R}", + ] + + +def test_translate_eco_supports_removal_and_placed_insert(tmp_path: Path) -> None: + translator = load_script("translate_eco") + source = tmp_path / "changes.icc2.tcl" + source.write_text( + "current_instance {u_core}\n" + "remove_buffer [get_cells {u_buf}]\n" + "insert_buffer [get_pins {u_src/Q}] BUFX2H7R -new_net_names {eco_net} " + "-new_cell_names {eco_buf} -location {10.5 20.25}\n", + encoding="utf-8", + ) + assert translator.translate([str(source)]) == [ + "ecoDeleteRepeater -inst {u_core/u_buf}", + "ecoAddRepeater -term {u_core/u_src/Q} -cell {BUFX2H7R} " + "-newNetName {eco_net} -name {eco_buf} -loc {10.5 20.25}", + ] + + ambiguous = tmp_path / "ambiguous.icc2.tcl" + ambiguous.write_text("remove_buffer [get_cells {u_a u_b}]\n", encoding="utf-8") + with pytest.raises(ValueError, match="unsupported PrimeTime ECO command"): + translator.translate([str(ambiguous)]) + + def test_spef_checker_requires_every_named_corner(tmp_path: Path) -> None: checker = load_script("check_outputs") - for corner in checker.SPEF_CORNERS: + for corner in checker.spef_corners(): (tmp_path / f"retrosoc_asic.{corner}.spef.gz").write_bytes(b"spef") assert checker.apply_check("spef", str(tmp_path), 0, "retrosoc_asic") is None (tmp_path / "retrosoc_asic.TYP_25.spef.gz").unlink() - assert "missing ICS55 SPEF corners" in checker.apply_check( + assert "missing SPEF corners" in checker.apply_check( "spef", str(tmp_path), 0, "retrosoc_asic" ) @@ -116,11 +163,16 @@ def test_prepare_input_rejects_archive_escape(tmp_path: Path) -> None: def test_prepare_input_refreshes_archived_filelist(tmp_path: Path) -> None: archive_path = tmp_path / "rtl.tar" payload = b"rtl/top.sv\n" + contract = b"# contract\n" with tarfile.open(archive_path, "w") as archive: member = tarfile.TarInfo("rtl/filelist.fl") member.size = len(payload) member.mtime = 1 archive.addfile(member, io.BytesIO(payload)) + member = tarfile.TarInfo("rtl/contracts/commercial_timing_contract.tcl") + member.size = len(contract) + member.mtime = 1 + archive.addfile(member, io.BytesIO(contract)) output = tmp_path / "output" manifest = tmp_path / "manifest.json" started = archive_path.stat().st_mtime @@ -303,6 +355,8 @@ def test_commercial_timing_contract_covers_canonical_domains(tmp_path: Path) -> [ sys.executable, str(FLOW / "scripts/generate_timing_contract.py"), + "--soc", + "MINI", "--domains", str(ROOT / "rtl/mini/integration/clock_reset_domains.json"), "--pin-map", @@ -336,6 +390,67 @@ def test_commercial_timing_contract_covers_canonical_domains(tmp_path: Path) -> assert "observation {s_sys_clk}" in output.read_text(encoding="utf-8") +def test_commercial_timing_contract_supports_tiny(tmp_path: Path) -> None: + output = tmp_path / "commercial_timing_contract.tcl" + subprocess.run( + [ + sys.executable, + str(FLOW / "scripts/generate_timing_contract.py"), + "--soc", + "TINY", + "--domains", + str(ROOT / "rtl/tiny/integration/clock_reset_domains.json"), + "--pin-map", + str(ROOT / "rtl/tiny/pin_map/pin_map.json"), + "--output", + str(output), + ], + check=True, + ) + script = ( + "namespace eval flow {}\n" + f"source {{{output}}}\n" + "puts [join [lsort [dict keys $flow::canonical_clock_domains]] ,]\n" + "puts [join $flow::canonical_reset_ports ,]\n" + ) + result = subprocess.run( + ["tclsh"], + input=script, + text=True, + capture_output=True, + check=True, + ) + assert result.stdout.splitlines() == [ + "jtag,system", + "ext_rst_n_i_pad,jtag_trst_n_i_pad", + ] + assert "observation {u_clock_buffer/clk_o}" in output.read_text( + encoding="utf-8" + ) + + +def test_commercial_timing_contract_rejects_domain_drift(tmp_path: Path) -> None: + result = subprocess.run( + [ + sys.executable, + str(FLOW / "scripts/generate_timing_contract.py"), + "--soc", + "TINY", + "--domains", + str(ROOT / "rtl/mini/integration/clock_reset_domains.json"), + "--pin-map", + str(ROOT / "rtl/mini/pin_map/pin_map.json"), + "--output", + str(tmp_path / "drift.tcl"), + ], + text=True, + capture_output=True, + check=False, + ) + assert result.returncode != 0 + assert "differ from the canonical set" in result.stderr + + def test_commercial_constraints_do_not_apply_global_io_delays() -> None: constraints = "\n".join( path.read_text(encoding="utf-8") @@ -352,7 +467,7 @@ def test_commercial_constraints_do_not_apply_global_io_delays() -> None: def test_dc_uses_typ_link_set_and_svt_lvt_target_subset() -> None: synthesis = (FLOW / "tcl/syn/main.tcl").read_text(encoding="utf-8") common = (FLOW / "tcl/common/common.tcl").read_text(encoding="utf-8") - example = (FLOW / "config/ics55.example.mk").read_text(encoding="utf-8") + example = (FLOW / "config/ics55-mini.example.mk").read_text(encoding="utf-8") assert "flow::synthesis_library_files" in synthesis assert "flow::all_library_files" not in synthesis assert "return [flow::library_files TYP]" in common @@ -396,8 +511,10 @@ def test_internal_qor_doctor_does_not_require_backend_collateral( archive = touch("rtl.tar.gz") environment = { "PATH": os.environ["PATH"], + "SOC": "MINI", "TOP": "retrosoc_asic", "TECHNOLOGY": "ICS55", + "PRODUCT_PLL_MODE": "qualified", "RTL_ARCHIVE": archive, "LSF_MODE": "batch", "LSF_SYN_ARGS": "-q synth", @@ -468,10 +585,158 @@ def test_ics55_pll_wrapper(tmp_path: Path) -> None: def test_local_production_configuration_is_ignored() -> None: - local = FLOW / "local/ics55-production.mk" - result = subprocess.run( - ["git", "check-ignore", str(local.relative_to(ROOT))], - cwd=ROOT, - check=False, + for name in ("ics55-production.mk", "ics55-mini.mk", "ics55-tiny.mk"): + local = FLOW / "local" / name + result = subprocess.run( + ["git", "check-ignore", str(local.relative_to(ROOT))], + cwd=ROOT, + check=False, + ) + assert result.returncode == 0 + + +CONSTRAINT_SMOKE_TCL = r""" +set soc [lindex $argv 0] +set ::env(SOC) $soc +set ::env(TOP) [expr {$soc eq "TINY" ? "retrosoc_tiny_asic" : "retrosoc_asic"}] +set ::env(RUN_ROOT) [lindex $argv 1] +set ::env(CLOCK_SETUP_UNCERTAINTY_NS) 0.2 +set ::env(CLOCK_HOLD_UNCERTAINTY_NS) 0.1 +set ::env(CLOCK_TRANSITION_NS) 0.1 +set ::env(IO_TIMING_QUALIFIED) NO +set ::env(MAX_TRANSITION_NS) 0.5 +set ::env(MAX_FANOUT) 32 +if {$soc eq "MINI"} { set ::env(PLL_OUTPUT_PERIOD_NS) 13.888888889 } + +set ::created_clocks_named {} +set ::generated_clocks {} +set ::clock_groups {} +set ::false_paths 0 + +proc get_ports {args} { return [lindex $args end] } +proc get_pins {args} { return [list pin_[string map {/ _} [lindex $args end]]] } +proc get_nets {args} { return [list net_[lindex $args end]] } +proc filter_collection {args} { return [list driver] } +proc sizeof_collection {c} { return [llength $c] } +proc get_clocks {args} { return [lindex $args end] } +proc create_clock {args} { + lappend ::created_clocks_named [lindex $args [expr {[lsearch $args -name] + 1}]] +} +proc create_generated_clock {args} { + lappend ::generated_clocks [lindex $args [expr {[lsearch $args -name] + 1}]] +} +proc set_clock_groups {args} { lappend ::clock_groups $args } +proc set_clock_uncertainty {args} {} +proc set_clock_transition {args} {} +proc set_false_path {args} { incr ::false_paths } +proc all_inputs {} { return {} } +proc all_outputs {} { return {} } +proc current_design {args} { return {} } +proc set_max_transition {args} {} +proc set_max_fanout {args} {} + +source [file join $::env(FLOW_ROOT) tcl common common.tcl] +source [file join $::env(FLOW_ROOT) tcl common constraints.tcl] +flow::apply_constraints +puts "masters=$::created_clocks_named" +puts "generated=$::generated_clocks" +puts "groups=[llength $::clock_groups]" +puts "false_paths=$::false_paths" +flow::require_commercial_clock_inventory +puts "inventory-ok" +""" + +CONSTRAINT_SMOKE_CASES = { + "MINI": { + "domains": "rtl/mini/integration/clock_reset_domains.json", + "pin_map": "rtl/mini/pin_map/pin_map.json", + "masters": ( + "clk_aon clk_hp clk_memory clk_audio clk_jtag clk_dvp " + "clk_usb2_ulpi clk_pll" + ), + "generated": "clk_lp_ext clk_lp_pll clk_pclk_ext clk_pclk_pll", + "groups": "2", + }, + "TINY": { + "domains": "rtl/tiny/integration/clock_reset_domains.json", + "pin_map": "rtl/tiny/pin_map/pin_map.json", + "masters": "clk_system clk_jtag", + "generated": "", + "groups": "1", + }, +} + + +@pytest.mark.parametrize("soc", sorted(CONSTRAINT_SMOKE_CASES)) +def test_constraint_stack_models_product_clocks(tmp_path: Path, soc: str) -> None: + case = CONSTRAINT_SMOKE_CASES[soc] + run_root = tmp_path / soc.lower() + contract = run_root / "input/rtl/contracts/commercial_timing_contract.tcl" + contract.parent.mkdir(parents=True) + subprocess.run( + [ + sys.executable, + str(FLOW / "scripts/generate_timing_contract.py"), + "--soc", + soc, + "--domains", + str(ROOT / case["domains"]), + "--pin-map", + str(ROOT / case["pin_map"]), + "--output", + str(contract), + ], + check=True, ) - assert result.returncode == 0 + environment = dict(os.environ) + environment["FLOW_ROOT"] = str(FLOW) + script = CONSTRAINT_SMOKE_TCL.replace( + "set soc [lindex $argv 0]", f"set soc {soc}" + ).replace( + "set ::env(RUN_ROOT) [lindex $argv 1]", f"set ::env(RUN_ROOT) {run_root}" + ) + result = subprocess.run( + ["tclsh"], + input=script, + text=True, + capture_output=True, + check=False, + env=environment, + ) + assert result.returncode == 0, result.stderr + lines = dict( + line.split("=", 1) + for line in result.stdout.strip().splitlines() + if "=" in line + ) + assert lines["masters"] == case["masters"] + assert lines["generated"].strip("-") == case["generated"] + assert lines["groups"] == case["groups"] + assert "inventory-ok" in result.stdout + + +def test_commercial_make_graph_covers_both_products(tmp_path: Path) -> None: + stub = tmp_path / "local.mk" + stub.write_text("", encoding="utf-8") + for soc in ("MINI", "TINY"): + result = subprocess.run( + [ + "make", + "-C", + str(FLOW), + "-n", + "signoff", + f"SOC={soc}", + f"LOCAL_CONFIG={stub}", + "RUN_ID=pytest", + ], + text=True, + capture_output=True, + check=False, + ) + assert result.returncode == 0, result.stderr + assert f"build/commercial/ics55/{soc.lower()}/pytest" in result.stdout + for stage in ("syn", "apr-route", "extract", "sta", "pv-lvs", "pv-macro-lvs"): + assert f"--stage {stage} " in result.stdout or f"--stage {stage}\n" in ( + result.stdout + "\n" + ) diff --git a/tests/test_script_tools.py b/tests/test_script_tools.py index 2083d148..98884b6f 100644 --- a/tests/test_script_tools.py +++ b/tests/test_script_tools.py @@ -282,6 +282,8 @@ def test_package_forwards_manifest_jtag_idcode() -> None: assert f'"{option}"' in package_source assert "generate_timing_contract.py" in package_source assert "commercial_timing_contract.tcl" in package_source + assert '"--soc"' in package_source + assert "tiny_core.sdc" in package_source def test_source_export_writes_tar_without_staging_rtl_tree(tmp_path: Path) -> None: