diff --git a/Makefile b/Makefile index d6d019c1..a81e0287 100644 --- a/Makefile +++ b/Makefile @@ -102,13 +102,18 @@ VARIANT_ROOT := $(abspath $(BUILD_ROOT))/$(VARIANT_ID) SW_BUILD_DIR := $(VARIANT_ROOT)/sw SIM_TOOL_NAME := $(shell printf '%s' '$(SIMU)' | tr '[:upper:]' '[:lower:]') SIM_BUILD_ROOT := $(VARIANT_ROOT)/sim/$(SIM_TOOL_NAME) -ifeq ($(SYNTH_RECIPE),balanced) -SYN_BUILD_ROOT := $(VARIANT_ROOT)/syn/yosys -else -SYN_BUILD_ROOT := $(VARIANT_ROOT)/syn/yosys-$(SYNTH_RECIPE) -endif -STA_BUILD_ROOT := $(VARIANT_ROOT)/sta/opensta META_DIR := $(VARIANT_ROOT)/meta +ifeq ($(SYNTH_RECIPE),balanced) +SYN_BUILD_ROOT := $(VARIANT_ROOT)/syn/yosys +STA_BUILD_ROOT := $(VARIANT_ROOT)/sta/opensta +NETLIST_SIM_ROOT := $(SIM_BUILD_ROOT)/netl +METRICS_OUTPUT := $(META_DIR)/metrics.json +else +SYN_BUILD_ROOT := $(VARIANT_ROOT)/syn/yosys-$(SYNTH_RECIPE) +STA_BUILD_ROOT := $(VARIANT_ROOT)/sta/opensta-$(SYNTH_RECIPE) +NETLIST_SIM_ROOT := $(SIM_BUILD_ROOT)/netl-$(SYNTH_RECIPE) +METRICS_OUTPUT := $(META_DIR)/metrics-$(SYNTH_RECIPE).json +endif ifeq ($(SYNTH),YOSYS) FLOW_FILELIST_DIR := $(SYN_BUILD_ROOT)/filelists else @@ -407,10 +412,11 @@ check-warnings: metrics: python3 $(ROOT_PATH)/scripts/metrics.py collect --variant-root $(VARIANT_ROOT) \ - --output $(META_DIR)/metrics.json + --synth-root $(SYN_BUILD_ROOT) --sta-root $(STA_BUILD_ROOT) \ + --recipe $(SYNTH_RECIPE) --output $(METRICS_OUTPUT) check-metrics: metrics - python3 $(ROOT_PATH)/scripts/metrics.py check --metrics $(META_DIR)/metrics.json \ + python3 $(ROOT_PATH)/scripts/metrics.py check --metrics $(METRICS_OUTPUT) \ --policy $(ROOT_PATH)/quality/metrics/policy.json \ --baseline $(ROOT_PATH)/quality/metrics/baseline.json diff --git a/docs/engineering.md b/docs/engineering.md index 316ac0af..600c46e1 100644 --- a/docs/engineering.md +++ b/docs/engineering.md @@ -121,19 +121,42 @@ build// sw/ sim// formal// - syn/yosys/ - syn/yosys-area/ (optional SYNTH_RECIPE=area) - syn/yosys-speed/ (optional SYNTH_RECIPE=speed) - sta/opensta/ + syn/yosys/ (balanced) + syn/yosys-area/ (SYNTH_RECIPE=area) + syn/yosys-speed/ (SYNTH_RECIPE=speed) + sta/opensta/ (balanced) + sta/opensta-area/ (SYNTH_RECIPE=area) + sta/opensta-speed/ (SYNTH_RECIPE=speed) meta/manifest.json meta/warnings.json - meta/metrics.json + meta/metrics.json (balanced) + meta/metrics-area.json (SYNTH_RECIPE=area) + meta/metrics-speed.json (SYNTH_RECIPE=speed) ``` -The default Yosys recipe writes `syn/yosys/`. Optional `SYNTH_RECIPE=area` and -`SYNTH_RECIPE=speed` jobs write sibling directories; STA, netlist simulation, warning analysis, -and metrics read only `syn/yosys/`. `SYNTH_RECIPE` is a flow selector and does not change the -configuration hash. +`SYNTH_RECIPE` is a flow selector and does not change the configuration hash. +The selected recipe determines the synthesis netlist consumed by OpenSTA and +netlist simulation, and determines which metrics file is written. `balanced` +is the default PR flow. Nightly IHP130 runs `area` and `speed` through synth, +STA, and metrics; their netlist simulation remains an explicit, on-demand +validation because of its runtime. + +Use the same commit, PDK, `BUILD_TIMESTAMP`, and `YOSYS_TARGET_PERIOD_PS` for +all recipes in a QoR comparison: + +```sh +make CONFIG=configs/ci/ihp130.mk SYNTH=YOSYS SYNTH_RECIPE=balanced synth +make CONFIG=configs/ci/ihp130.mk SYNTH=YOSYS SYNTH_RECIPE=area synth +make CONFIG=configs/ci/ihp130.mk SYNTH=YOSYS SYNTH_RECIPE=speed synth +make CONFIG=configs/ci/ihp130.mk SYNTH_RECIPE=area STA=OPENSTA sta +make CONFIG=configs/ci/ihp130.mk SYNTH_RECIPE=area metrics +``` + +Recipe optimization is evaluated using top area, cell count, WNS/TNS, flow +duration, warning signatures, and netlist simulation verdict together. The +`area` recipe is area-first and the `speed` recipe is timing-first; a single +improved metric is not sufficient to replace `balanced`. Metrics policy remains +in `observe` mode while recipe baselines are collected and reviewed. Generated filelists and MPW output are flow-local. Make depfiles track expanded RTL sources and included headers. The shared MPW generator is protected by a file lock. Default tool parallelism is diff --git a/physical/smoke/sta/opensta/opensta.mk b/physical/smoke/sta/opensta/opensta.mk index ec40f830..df8cfd81 100644 --- a/physical/smoke/sta/opensta/opensta.mk +++ b/physical/smoke/sta/opensta/opensta.mk @@ -1,11 +1,11 @@ OPENSTA ?= sta OPENSTA_THREADS ?= $(JOBS) -OPENSTA_NETLIST ?= $(VARIANT_ROOT)/syn/yosys/out/retrosoc_asic_yosys.v +OPENSTA_NETLIST ?= $(SYN_BUILD_ROOT)/out/retrosoc_asic_yosys.v OPENSTA_REPORT ?= $(STA_BUILD_ROOT)/retrosoc_sta.log OPENSTA_LOG ?= $(STA_BUILD_ROOT)/opensta.log OPENSTA_METRICS ?= $(STA_BUILD_ROOT)/timing_metrics.rpt -OPENSTA_CONFIG ?= $(VARIANT_ROOT)/syn/yosys/out/retrosoc_asic_yosys.config +OPENSTA_CONFIG ?= $(SYN_BUILD_ROOT)/out/retrosoc_asic_yosys.config OPENSTA_SDC ?= $(STA_BUILD_ROOT)/retrosoc_core.sdc OPENSTA_SDC_GEN := $(ROOT_PATH)/physical/smoke/sta/opensta/generate_sdc.py OPENSTA_DOMAIN_MAP := $(ROOT_PATH)/rtl/mini/integration/clock_reset_domains.json diff --git a/physical/smoke/syn/README.md b/physical/smoke/syn/README.md index 46d18760..7db4ff57 100644 --- a/physical/smoke/syn/README.md +++ b/physical/smoke/syn/README.md @@ -10,25 +10,42 @@ Use a committed profile and run the supported flow: make CONFIG=configs/ci/ihp130.mk SYNTH=YOSYS synth ``` -`SYNTH_RECIPE` selects the ABC mapping recipe without changing the variant hash: +`SYNTH_RECIPE` selects the ABC mapping recipe without changing the variant hash. +The recipe also selects the synthesis-derived output roots so timing, netlist +simulation, and metrics cannot accidentally consume the `balanced` netlist: -- `balanced` (default) writes `build//syn/yosys/` -- `area` writes `build//syn/yosys-area/` -- `speed` writes `build//syn/yosys-speed/` +- `balanced` (default): `syn/yosys/`, `sta/opensta/`, `sim//netl/`, and `meta/metrics.json` +- `area`: `syn/yosys-area/`, `sta/opensta-area/`, `sim//netl-area/`, and `meta/metrics-area.json` +- `speed`: `syn/yosys-speed/`, `sta/opensta-speed/`, `sim//netl-speed/`, and `meta/metrics-speed.json` -STA, netlist simulation, warning analysis, and metrics consume the default -`syn/yosys/` slot. Nightly IHP130 regression also runs the `area` and `speed` -recipes as synth-only extra jobs. +`balanced` remains the PR and default CI recipe. Nightly IHP130 runs `area` +and `speed` through synthesis, OpenSTA, and metrics; their long netlist +simulation is available on demand but is not part of nightly-extra. ```sh make CONFIG=configs/ci/ihp130.mk SYNTH=YOSYS SYNTH_RECIPE=area synth make CONFIG=configs/ci/ihp130.mk SYNTH=YOSYS SYNTH_RECIPE=speed synth + +make CONFIG=configs/ci/ihp130.mk SYNTH_RECIPE=area STA=OPENSTA sta +make CONFIG=configs/ci/ihp130.mk SYNTH_RECIPE=area metrics +make CONFIG=configs/ci/ihp130.mk SYNTH_RECIPE=area SIMU=IVERILOG netsim ``` +Replace `area` with `speed` for the corresponding recipe. Run all three +recipes with the same commit, PDK, `BUILD_TIMESTAMP`, and +`YOSYS_TARGET_PERIOD_PS` when collecting a comparable QoR baseline. + +Compare top area, cell count, WNS/TNS, synthesis and STA duration, warning +signatures, and the netlist simulation verdict. Area is an area-first mapping +recipe and speed is a timing-first exploration recipe; neither is promoted to +the default solely from one area or timing result. Metrics remain in observe +mode until the documented baseline review is complete. + The default ABC target period is derived from the `external` STA period in `rtl/mini/integration/clock_reset_domains.json`. Override it with `YOSYS_TARGET_PERIOD_PS=` when needed. Synthesis outputs are generated below `build/` and must not be committed. Review warning and metric changes with `make check-warnings check-metrics` for -the affected profile. +the affected profile. `make metrics` writes the recipe-specific file listed +above; `make check-metrics` checks that same file. diff --git a/physical/smoke/syn/yosys/script/synth.tcl b/physical/smoke/syn/yosys/script/synth.tcl index 789c3ed1..ee4fa9d0 100644 --- a/physical/smoke/syn/yosys/script/synth.tcl +++ b/physical/smoke/syn/yosys/script/synth.tcl @@ -28,16 +28,6 @@ source $script_dir/init_tech.tcl yosys read_slang --top $top_design -F $sv_flist \ --keep-hierarchy --allow-use-before-declare --ignore-initial --ignore-timing -# # blackbox requested modules -# if { [info exists ::env(YOSYS_BLACKBOX_MODULES)] } { -# foreach sel $::env(YOSYS_BLACKBOX_MODULES) { -# puts "Blackboxing the module ${sel}" -# yosys select -list {*}$sel -# yosys blackbox {*}$sel -# yosys setattr -set keep_hierarchy 1 {*}$sel -# } -# } - # # preserve hierarchy of selected modules/instances if { [info exists ::env(YOSYS_KEEP_HIER_INST)] } { foreach sel $::env(YOSYS_KEEP_HIER_INST) { @@ -116,7 +106,6 @@ yosys select -write ${report_dir}/${proj_name}_registers.rpt t:*DFF* yosys clean -purge # print paths to important instances -yosys select -write ${report_dir}/${proj_name}_registers.rpt t:*DFF* set report [open ${report_dir}/${proj_name}_instances.rpt "w"] close $report if { [info exists ::env(YOSYS_REPORT_INSTS)] } { diff --git a/physical/smoke/syn/yosys/synth_config.mk b/physical/smoke/syn/yosys/synth_config.mk index 6ef7598a..e46218d2 100644 --- a/physical/smoke/syn/yosys/synth_config.mk +++ b/physical/smoke/syn/yosys/synth_config.mk @@ -5,9 +5,6 @@ # Authors: # - Philippe Sauter -# modules Yosys will treat as blackboxes -export YOSYS_BLACKBOX_MODULES := "*tc_sram_blackbox*" - # flatten hierarchy (except for below selections) export YOSYS_FLATTEN_HIER := 1 diff --git a/rtl/mini/mk/formal.mk b/rtl/mini/mk/formal.mk index 487ac7ed..3d5614a2 100644 --- a/rtl/mini/mk/formal.mk +++ b/rtl/mini/mk/formal.mk @@ -21,6 +21,7 @@ FORMAL_I2C_TIMEOUT ?= 300 FORMAL_OPIPSRAM_TIMEOUT ?= 300 FORMAL_DMA_TIMEOUT ?= 120 FORMAL_SDIO_TIMEOUT ?= 120 +FORMAL_COMPACT_COVER_TARGETS := i2c opipsram FORMAL_TARGETS := bus rib_adapter rib2apb sysctrl pll_rcu gpio ws2812 uart i2c timer clint dvp i2s psram opipsram dma sdio FORMAL_FILELIST_GENERATOR := $(RTL_PATH)/formal/generate_formal_filelist.py FORMAL_SBY_GENERATOR := $(RTL_PATH)/formal/generate_sby_config.py @@ -186,7 +187,7 @@ $(FORMAL_DIR)/%/cover.sby: $(FORMAL_DIR)/%/design.v $(RTL_PATH)/formal/%_formal_ python3 $(FORMAL_SBY_GENERATOR) --top $*_formal --input $< \ --properties $(RTL_PATH)/formal/$*_formal_props.sv --solver $(FORMAL_SOLVER) \ --mode cover --depth $(if $(filter dma,$*),$(FORMAL_DMA_COVER_DEPTH),$(if $(filter sdio,$*),$(FORMAL_SDIO_COVER_DEPTH),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_DEPTH),$(if $(filter ws2812,$*),$(FORMAL_WS2812_DEPTH),$(if $(filter i2c,$*),$(FORMAL_I2C_DEPTH),$(if $(filter clint,$*),$(FORMAL_CLINT_DEPTH),$(if $(filter psram,$*),$(FORMAL_PSRAM_DEPTH),$(FORMAL_DEPTH)))))))) \ - $(if $(filter i2c,$*),--no-vcd) --output $@ + $(if $(filter $(FORMAL_COMPACT_COVER_TARGETS),$*),--no-vcd) --output $@ @if [ "$*" = opipsram ]; then sed -i '/^async2sync/i clk2fflogic' $@; fi $(FORMAL_DIR)/opipsram/bmc.sby: $(FORMAL_DIR)/opipsram/design.v \ diff --git a/rtl/mini/mk/iverilog.mk b/rtl/mini/mk/iverilog.mk index 22d6f050..fc611a95 100644 --- a/rtl/mini/mk/iverilog.mk +++ b/rtl/mini/mk/iverilog.mk @@ -2,14 +2,14 @@ IVERILOG ?= iverilog VVP ?= vvp GTKWAVE ?= gtkwave -NETLIST_PATH ?= $(VARIANT_ROOT)/syn/yosys/out/retrosoc_asic_yosys.v +NETLIST_PATH ?= $(SYN_BUILD_ROOT)/out/retrosoc_asic_yosys.v POST_PATH ?= $(ROOT_PATH)/physical/sdf/retrosoc_asic.v SDF_PATH ?= $(ROOT_PATH)/physical/sdf/retrosoc_asic_CTS_MIN.sdf.gz SDF_SCOPE ?= $(RTL_TOP).u_retrosoc_asic IVERILOG_ROOT := $(SIM_BUILD_ROOT) IVERILOG_BEHV_DIR := $(IVERILOG_ROOT)/behv -IVERILOG_NETL_DIR := $(IVERILOG_ROOT)/netl +IVERILOG_NETL_DIR := $(NETLIST_SIM_ROOT) IVERILOG_POST_DIR := $(IVERILOG_ROOT)/post IVERILOG_BEHV_FLIST := $(IVERILOG_BEHV_DIR)/iverilog.fl IVERILOG_NETL_FLIST := $(IVERILOG_NETL_DIR)/iverilog.fl diff --git a/rtl/mini/mk/vcs.mk b/rtl/mini/mk/vcs.mk index 19e990e0..0d87b05a 100644 --- a/rtl/mini/mk/vcs.mk +++ b/rtl/mini/mk/vcs.mk @@ -10,7 +10,7 @@ VCS_FLOW_PYTHON := $(strip $(VCS_RUNNER) $(PYTHON)) COMP_LOG := -l compile.log SIM_LOG := -l sim.log # netlist file path -NETLIST_PATH := -v $(VARIANT_ROOT)/syn/yosys/out/retrosoc_asic_yosys.v +NETLIST_PATH := -v $(SYN_BUILD_ROOT)/out/retrosoc_asic_yosys.v POST_PATH := -v $(ROOT_PATH)/physical/sdf/retrosoc_asic.v SDF_PATH := "$(ROOT_PATH)/physical/sdf/retrosoc_asic_CTS_MIN.sdf.gz" # testbench filelist @@ -48,7 +48,7 @@ SIM_OPTS := +vcs+loopreport+1000 -suppress=ASLR_DETECTED_INFO \ +sim_timeout=$(RTL_SIM_TIMEOUT) +wave_$(WAVE) VCS_BEHV_DIR := $(SIM_BUILD_ROOT)/behv -VCS_NETL_DIR := $(SIM_BUILD_ROOT)/netl +VCS_NETL_DIR := $(NETLIST_SIM_ROOT) VCS_POST_DIR := $(SIM_BUILD_ROOT)/post VCS_BEHV_SIMV := $(VCS_BEHV_DIR)/simv VCS_NETL_SIMV := $(VCS_NETL_DIR)/simv @@ -69,7 +69,7 @@ $(VCS_NETL_SIMV): DIR := $(VCS_NETL_DIR) $(VCS_NETL_SIMV): FLIST := $(NETLIST_PATH) $(NET_FLIST) $(TB_FLIST) $(VCS_NETL_SIMV): OPTS := $(TIME_OPTION) $(VCS_NETL_SIMV): DEPFILE := $(VCS_NETL_DEPFILE) -$(VCS_NETL_SIMV): DEPS_ARGS := $(NET_FLIST) -f $(GENERATED_FL_DIR)/tb.fl --extra $(VARIANT_ROOT)/syn/yosys/out/retrosoc_asic_yosys.v +$(VCS_NETL_SIMV): DEPS_ARGS := $(NET_FLIST) -f $(GENERATED_FL_DIR)/tb.fl --extra $(SYN_BUILD_ROOT)/out/retrosoc_asic_yosys.v $(VCS_POST_SIMV): DIR := $(VCS_POST_DIR) $(VCS_POST_SIMV): FLIST := $(POST_PATH) $(NET_FLIST) $(TB_FLIST) @@ -82,7 +82,7 @@ netsim: DIR := $(VCS_NETL_DIR) postsim: DIR := $(VCS_POST_DIR) wave: DIR := $(SIM_BUILD_ROOT)/behv -netwave: DIR := $(SIM_BUILD_ROOT)/netl +netwave: DIR := $(NETLIST_SIM_ROOT) postwave: DIR := $(SIM_BUILD_ROOT)/post comp: $(VCS_BEHV_SIMV) @@ -101,7 +101,7 @@ $(VCS_BEHV_SIMV) $(VCS_NETL_SIMV) $(VCS_POST_SIMV): $(MPW_VARIANT_STAMP) $(FILEL $(SIM_TOOL) $(COMMON_OPTS) $(OPTS) $(FLIST) -top $(RTL_TOP) $(COMP_LOG) $(FLOW_PYTHON) $(RTL_PATH)/script/filelist_deps.py $(DEPS_ARGS) --target $@ --output $(DEPFILE) -$(VCS_NETL_SIMV): $(VARIANT_ROOT)/syn/yosys/out/retrosoc_asic_yosys.v +$(VCS_NETL_SIMV): $(SYN_BUILD_ROOT)/out/retrosoc_asic_yosys.v $(VCS_POST_SIMV): $(ROOT_PATH)/physical/sdf/retrosoc_asic.v $(ROOT_PATH)/physical/sdf/retrosoc_asic_CTS_MIN.sdf.gz sim netsim postsim: diff --git a/scripts/metrics.py b/scripts/metrics.py index ac2fc49e..cf9bf916 100644 --- a/scripts/metrics.py +++ b/scripts/metrics.py @@ -22,19 +22,21 @@ HIERARCHY_RE = re.compile(r"^\s*(\d+)\s+[0-9.eE+-]+\s+retrosoc_asic\s*$", re.MUL def collect(args: argparse.Namespace) -> int: root = args.variant_root.resolve() + synth_root = (args.synth_root or root / "syn/yosys").resolve() + sta_root = (args.sta_root or root / "sta/opensta").resolve() metrics: dict[str, Any] = { - "schema_version": 1, + "schema_version": 2, "policy": "observe", "firmware": {}, - "synthesis": {}, + "synthesis": {"recipe": args.recipe}, "timing": {}, "flows": {}, } for binary in sorted((root / "sw").glob("*.bin")): metrics["firmware"][binary.name] = {"bytes": binary.stat().st_size} - area_json = root / "syn/yosys/rpt/retrosoc_asic_area.json" - area_report = root / "syn/yosys/rpt/retrosoc_asic_area.rpt" + area_json = synth_root / "rpt/retrosoc_asic_area.json" + area_report = synth_root / "rpt/retrosoc_asic_area.rpt" if area_json.is_file(): design = json.loads(area_json.read_text(encoding="utf-8"))["design"] metrics["synthesis"]["top_area"] = float(design["area"]) @@ -48,7 +50,7 @@ def collect(args: argparse.Namespace) -> int: if hierarchy: metrics["synthesis"]["top_cells"] = int(hierarchy[-1]) - timing_report = root / "sta/opensta/timing_metrics.rpt" + timing_report = sta_root / "timing_metrics.rpt" if timing_report.is_file(): labels = ("wns_min", "wns_max", "tns_min", "tns_max") timing_text = timing_report.read_text(encoding="utf-8", errors="replace") @@ -73,6 +75,19 @@ def collect(args: argparse.Namespace) -> int: "status": data.get("status"), "duration_seconds": data.get("duration_seconds"), } + for label, result in ( + ("synthesis", synth_root / "result-synth.json"), + ("sta", sta_root / "result-sta.json"), + ): + if result.is_file(): + try: + data = json.loads(result.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError): + continue + metrics["flows"][label] = { + "status": data.get("status"), + "duration_seconds": data.get("duration_seconds"), + } atomic_write(args.output, json.dumps(metrics, indent=2, sort_keys=True) + "\n") print(f"metrics: {args.output.resolve()}") return 0 @@ -130,6 +145,9 @@ def parse_args() -> argparse.Namespace: subparsers = parser.add_subparsers(dest="command", required=True) collect_parser = subparsers.add_parser("collect") collect_parser.add_argument("--variant-root", type=Path, required=True) + collect_parser.add_argument("--synth-root", type=Path) + collect_parser.add_argument("--sta-root", type=Path) + collect_parser.add_argument("--recipe", choices=("balanced", "area", "speed"), default="balanced") collect_parser.add_argument("--output", type=Path, required=True) baseline_parser = subparsers.add_parser("baseline") baseline_parser.add_argument("--input", type=Path, action="append", required=True) diff --git a/scripts/regress.py b/scripts/regress.py index 9245d82c..7c2a43bd 100644 --- a/scripts/regress.py +++ b/scripts/regress.py @@ -65,7 +65,11 @@ NIGHTLY_EXTRA_COMMANDS = ( ("SIMU=VERILATOR", "HAVE_SVA=YES", "coremark-report"), ), ("configs/ci/ihp130.mk", ("SYNTH=YOSYS", "SYNTH_RECIPE=area", "synth")), + ("configs/ci/ihp130.mk", ("STA=OPENSTA", "SYNTH_RECIPE=area", "sta")), + ("configs/ci/ihp130.mk", ("SYNTH_RECIPE=area", "metrics")), ("configs/ci/ihp130.mk", ("SYNTH=YOSYS", "SYNTH_RECIPE=speed", "synth")), + ("configs/ci/ihp130.mk", ("STA=OPENSTA", "SYNTH_RECIPE=speed", "sta")), + ("configs/ci/ihp130.mk", ("SYNTH_RECIPE=speed", "metrics")), ) NIGHTLY_COMMANDS = ( *PR_COMMANDS, diff --git a/tests/test_script_tools.py b/tests/test_script_tools.py index cbdb4536..4d36a8ad 100644 --- a/tests/test_script_tools.py +++ b/tests/test_script_tools.py @@ -894,10 +894,26 @@ def test_nightly_regression_runs_optional_yosys_recipes() -> None: "configs/ci/ihp130.mk", ("SYNTH=YOSYS", "SYNTH_RECIPE=area", "synth"), ) in NIGHTLY_COMMANDS + assert ( + "configs/ci/ihp130.mk", + ("STA=OPENSTA", "SYNTH_RECIPE=area", "sta"), + ) in NIGHTLY_COMMANDS + assert ( + "configs/ci/ihp130.mk", + ("SYNTH_RECIPE=area", "metrics"), + ) in NIGHTLY_COMMANDS assert ( "configs/ci/ihp130.mk", ("SYNTH=YOSYS", "SYNTH_RECIPE=speed", "synth"), ) in NIGHTLY_COMMANDS + assert ( + "configs/ci/ihp130.mk", + ("STA=OPENSTA", "SYNTH_RECIPE=speed", "sta"), + ) in NIGHTLY_COMMANDS + assert ( + "configs/ci/ihp130.mk", + ("SYNTH_RECIPE=speed", "metrics"), + ) in NIGHTLY_COMMANDS assert not any( any(value.startswith("SYNTH_RECIPE=") for value in values) for _, values in PR_COMMANDS ) @@ -914,7 +930,11 @@ def test_nightly_regression_runs_optional_yosys_recipes() -> None: "--dry-run", ) assert "+ make CONFIG=configs/ci/ihp130.mk SYNTH=YOSYS SYNTH_RECIPE=area synth" in nightly.stdout + assert "+ make CONFIG=configs/ci/ihp130.mk STA=OPENSTA SYNTH_RECIPE=area sta" in nightly.stdout + assert "+ make CONFIG=configs/ci/ihp130.mk SYNTH_RECIPE=area metrics" in nightly.stdout assert "+ make CONFIG=configs/ci/ihp130.mk SYNTH=YOSYS SYNTH_RECIPE=speed synth" in nightly.stdout + assert "+ make CONFIG=configs/ci/ihp130.mk STA=OPENSTA SYNTH_RECIPE=speed sta" in nightly.stdout + assert "+ make CONFIG=configs/ci/ihp130.mk SYNTH_RECIPE=speed metrics" in nightly.stdout pr = run( sys.executable, @@ -953,7 +973,11 @@ def test_nightly_extra_regression_skips_pr_netsim() -> None: ) assert "+ make CONFIG=configs/benchmark/ihp130-hazard3-coremark.mk SIMU=VERILATOR HAVE_SVA=YES coremark-report" in extra.stdout assert "+ make CONFIG=configs/ci/ihp130.mk SYNTH=YOSYS SYNTH_RECIPE=area synth" in extra.stdout + assert "+ make CONFIG=configs/ci/ihp130.mk STA=OPENSTA SYNTH_RECIPE=area sta" in extra.stdout + assert "+ make CONFIG=configs/ci/ihp130.mk SYNTH_RECIPE=area metrics" in extra.stdout assert "+ make CONFIG=configs/ci/ihp130.mk SYNTH=YOSYS SYNTH_RECIPE=speed synth" in extra.stdout + assert "+ make CONFIG=configs/ci/ihp130.mk STA=OPENSTA SYNTH_RECIPE=speed sta" in extra.stdout + assert "+ make CONFIG=configs/ci/ihp130.mk SYNTH_RECIPE=speed metrics" in extra.stdout assert "netsim" not in extra.stdout invalid = subprocess.run( @@ -1522,7 +1546,7 @@ def test_metrics_collection_and_observe_policy(tmp_path: Path) -> None: ) data = json.loads(metrics.read_text(encoding="utf-8")) assert data["firmware"]["firmware.bin"]["bytes"] == 4 - assert data["synthesis"] == {"top_area": 124.0, "top_cells": 43} + assert data["synthesis"] == {"recipe": "balanced", "top_area": 124.0, "top_cells": 43} assert data["timing"]["wns_min"] == -1.0 policy = tmp_path / "policy.json" @@ -1538,6 +1562,38 @@ def test_metrics_collection_and_observe_policy(tmp_path: Path) -> None: ) +def test_metrics_collection_selects_recipe_roots(tmp_path: Path) -> None: + variant = tmp_path / "variant" + synth_root = variant / "syn/yosys-area" + sta_root = variant / "sta/opensta-area" + (synth_root / "rpt").mkdir(parents=True) + sta_root.mkdir(parents=True) + (synth_root / "rpt/retrosoc_asic_area.json").write_text( + json.dumps({"design": {"area": 88.0, "num_cells": 19}}), encoding="utf-8" + ) + (sta_root / "timing_metrics.rpt").write_text("wns_max=-0.25\n", encoding="utf-8") + metrics = tmp_path / "metrics-area.json" + run( + sys.executable, + str(ROOT / "scripts/metrics.py"), + "collect", + "--variant-root", + str(variant), + "--synth-root", + str(synth_root), + "--sta-root", + str(sta_root), + "--recipe", + "area", + "--output", + str(metrics), + ) + data = json.loads(metrics.read_text(encoding="utf-8")) + assert data["schema_version"] == 2 + assert data["synthesis"] == {"recipe": "area", "top_area": 88.0, "top_cells": 19} + assert data["timing"]["wns_max"] == -0.25 + + def test_safe_extract_rejects_parent_traversal(tmp_path: Path) -> None: archive = tmp_path / "unsafe.tar" with tarfile.open(archive, "w") as bundle: diff --git a/tests/test_yosys_synth_recipes.py b/tests/test_yosys_synth_recipes.py index 350e1e38..456c6779 100644 --- a/tests/test_yosys_synth_recipes.py +++ b/tests/test_yosys_synth_recipes.py @@ -48,6 +48,9 @@ def test_yosys_recipe_scripts_and_public_slot_are_stable() -> None: assert "SYNTH_RECIPE ?= balanced" in makefile assert "VALID_SYNTH_RECIPE := balanced area speed" in makefile assert "syn/yosys-$(SYNTH_RECIPE)" in makefile + assert "sta/opensta-$(SYNTH_RECIPE)" in makefile + assert "netl-$(SYNTH_RECIPE)" in makefile + assert "metrics-$(SYNTH_RECIPE).json" in makefile assert "CONFIG_KEY_VARS" in makefile assert "SYNTH_RECIPE" not in makefile.split("CONFIG_KEY_VARS", 1)[1].split("VARIANT_ID", 1)[0] assert "--env SYNTH_RECIPE=$(SYNTH_RECIPE)" in yosys_mk @@ -55,11 +58,12 @@ def test_yosys_recipe_scripts_and_public_slot_are_stable() -> None: assert "abc_${synth_recipe}.script" in synth_tcl assert "yosys abc {*}$tech_cells_args -D $period_ps -script $abc_script -constr $abc_constr" in synth_tcl assert "S110" not in synth_tcl - assert "$(VARIANT_ROOT)/syn/yosys/out/retrosoc_asic_yosys.v" in opensta - assert "$(VARIANT_ROOT)/syn/yosys/out/retrosoc_asic_yosys.v" in iverilog - assert "$(VARIANT_ROOT)/syn/yosys/out/retrosoc_asic_yosys.v" in vcs - assert "SYN_BUILD_ROOT" not in opensta - assert "SYN_BUILD_ROOT" not in iverilog.split("NETLIST_PATH", 1)[1][:200] + assert "$(SYN_BUILD_ROOT)/out/retrosoc_asic_yosys.v" in opensta + assert "$(SYN_BUILD_ROOT)/out/retrosoc_asic_yosys.v" in iverilog + assert "$(SYN_BUILD_ROOT)/out/retrosoc_asic_yosys.v" in vcs + assert "$(NETLIST_SIM_ROOT)" in iverilog + assert "$(NETLIST_SIM_ROOT)" in vcs + assert "$(VARIANT_ROOT)/syn/yosys/out/retrosoc_asic_yosys.v" not in vcs assert (YOSYS_SCRIPTS / "abc_balanced.script").read_text(encoding="utf-8").count("&nf {D}") == 1 assert "&nf {D}" not in (YOSYS_SCRIPTS / "abc_area.script").read_text(encoding="utf-8") assert (YOSYS_SCRIPTS / "abc_speed.script").read_text(encoding="utf-8").count("&nf {D}") == 6