diff --git a/AGENTS.md b/AGENTS.md index 46406858..713ade4f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -78,10 +78,11 @@ README that states its ownership, source of truth, and validation expectations. `app/apps//app.mk`. The `debug` application is an RTL debug-transport acceptance image, not a user-facing firmware profile. - Tiny is a distinct wired MCU product under `rtl/tiny` with AXI4/APB4 integration. - Its 2026-10-07 specification default is ICS55 with `HAVE_PLL=YES` and SAFE24 - boot; TINY-ICS55-P1 executable integration is pending. The current committed - Tiny profile remains IHP130/no PLL. Do not confuse target defaults with - implemented configuration or change Mini defaults. + Its default profile is `configs/ci/ics55-tiny.mk`: ICS55, `HAVE_PLL=YES`, + SAFE24 boot with the PLL macro parked off. IHP130/no PLL remains explicit + compatibility. PLL presence does not enable SYS switching; Tiny RCU remains + R2-P7. Keep implementation, observed timing and physical qualification distinct, + and do not change Mini defaults. - Do not add new dependencies on `crt/inc`, retired SDK `tiny` names, or legacy `rs_*.h`/`tiny*.h` include paths. - Treat `app/coremark/coremark-main`, `app/fatfs/ff16`, `app/lvgl/lvgl-main`, diff --git a/Makefile b/Makefile index 2cf602fb..351c064c 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,12 @@ ROOT_PATH ?= $(abspath $(dir $(firstword $(MAKEFILE_LIST)))) CONFIG ?= LOCK_FILE ?= $(ROOT_PATH)/dependencies/dependencies.lock.json +ifeq ($(strip $(CONFIG)),) +ifeq ($(SOC),TINY) +CONFIG := configs/ci/$(if $(filter IHP130,$(PDK)),ihp130,ics55)-tiny.mk +endif +endif + ifneq ($(strip $(CONFIG)),) CONFIG_PATH := $(if $(filter /%,$(CONFIG)),$(CONFIG),$(ROOT_PATH)/$(CONFIG)) ifeq ($(wildcard $(CONFIG_PATH)),) @@ -357,9 +363,11 @@ endif ifeq ($(STA),OPENSTA) ifeq ($(HAVE_PLL),YES) +ifneq ($(SOC):$(PDK):$(TINY_SAFE24_PLL_OFF),TINY:ICS55:YES) $(error STA=OPENSTA requires a qualified PDK PLL timing profile; HAVE_PLL=YES is unsupported) endif endif +endif DEF_LIST ?= +define+PDK_$(PDK) DEF_LIST += +define+SIMU_$(SIMU) @@ -636,7 +644,7 @@ config: doctor: @python3 $(ROOT_PATH)/scripts/doctor.py \ --root $(ROOT_PATH) --simu $(SIMU) --synth $(SYNTH) --sta $(STA) \ - --pdk $(PDK) --soc $(SOC) --have-sram-macro $(HAVE_SRAM_MACRO) \ + --pdk $(PDK) --soc $(SOC) --have-sram-macro $(HAVE_SRAM_MACRO) --have-pll $(HAVE_PLL) \ --formal $(FORMAL) --lock $(LOCK_FILE) benchmark-report: firmware @@ -661,7 +669,7 @@ hp-performance-check: ifeq ($(SOC),TINY) setup: setup-tiny setup-tiny: - python3 $(ROOT_PATH)/scripts/setup_tiny.py + python3 $(ROOT_PATH)/scripts/setup_tiny.py --pdk $(PDK) else setup: setup-mpw setup-vexiiriscv setup-clusterip setup-ip setup-pdk setup-app endif @@ -1181,13 +1189,13 @@ commercial-package: $(MPW_VARIANT_DEP) $(FILELIST_STAMP) manifest --variant-root $(VARIANT_ROOT) --output-dir $(VARIANT_ROOT)/commercial/input regress-smoke: - python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite smoke --pdk IHP130 $(if $(filter TINY,$(SOC)),--soc TINY) + python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite smoke --pdk $(if $(filter TINY,$(SOC)),$(PDK),IHP130) $(if $(filter TINY,$(SOC)),--soc TINY) regress-rtl: - python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite rtl --pdk IHP130 $(if $(filter TINY,$(SOC)),--soc TINY) + python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite rtl --pdk $(if $(filter TINY,$(SOC)),$(PDK),IHP130) $(if $(filter TINY,$(SOC)),--soc TINY) regress-pr: - python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite pr --pdk IHP130 $(if $(filter TINY,$(SOC)),--soc TINY) $(if $(filter YES,$(REGRESS_NETSIM_BOOT_ONLY)),--netsim-boot-only) + python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite pr --pdk $(if $(filter TINY,$(SOC)),$(PDK),IHP130) $(if $(filter TINY,$(SOC)),--soc TINY) $(if $(filter YES,$(REGRESS_NETSIM_BOOT_ONLY)),--netsim-boot-only) ifneq ($(SOC),TINY) python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite pr --pdk GF180 $(if $(filter YES,$(REGRESS_NETSIM_BOOT_ONLY)),--netsim-boot-only) python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite pr --pdk SKY130 $(if $(filter YES,$(REGRESS_NETSIM_BOOT_ONLY)),--netsim-boot-only) @@ -1196,7 +1204,7 @@ ifneq ($(SOC),TINY) endif regress-nightly: - python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite nightly $(if $(filter TINY,$(SOC)),--soc TINY) + python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite nightly $(if $(filter TINY,$(SOC)),--soc TINY --pdk $(PDK)) sim-asm: asm - $(MAKE) SIM_FIRMWARE_NAME=$(ASM_FIRMWARE_NAME) sim \ No newline at end of file + $(MAKE) SIM_FIRMWARE_NAME=$(ASM_FIRMWARE_NAME) sim diff --git a/README.md b/README.md index bfa3f3a4..9edacec2 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,8 @@ An open-source RISC-V SoC platform, from SystemVerilog RTL and firmware to repro low-power Hazard3 management hart and a VexiiRiscv application hart; its separate MPW profile retains selectable-core compatibility. [Tiny](docs/ip/tiny-soc.md) is a wired MCU with one Hazard3 RV32IMC hart, 128 KiB SRAM, AXI4/APB4 and no -wireless IP. Tiny first targets IHP130; [Std and Pro](docs/soc-family-positioning.md) +wireless IP. Tiny defaults to ICS55 SAFE24 with a parked PLL and retains +IHP130 compatibility; [Std and Pro](docs/soc-family-positioning.md) remain roadmap products. Qualification is determined by each profile's retained verification and physical reports. @@ -122,7 +123,8 @@ points, not the complete profile or application inventory. | Profile | Purpose | | --- | --- | -| [IHP130 Tiny](configs/ci/ihp130-tiny.mk) | Single-hart wired MCU, 128 KiB SRAM, pin-level Flash boot and automated acceptance. | +| [ICS55 Tiny](configs/ci/ics55-tiny.mk) | Default Tiny platform: 128 KiB SRAM, SAFE24 boot, PLL present but held off. | +| [IHP130 Tiny](configs/ci/ihp130-tiny.mk) | Explicit Tiny compatibility, 128 KiB SRAM, 24 MHz/no PLL. | | [IHP130 Mini](configs/ci/ihp130.mk) | Manual bring-up with 32 KiB macro-backed SRAM. | | [GF180](configs/ci/gf180.mk) / [SKY130](configs/ci/sky130.mk) | Alternative PDK profiles with 32 KiB macro-backed SRAM. | | [ICS55 Mini](configs/ci/ics55.mk) | Regression-compatible Mini profile with locked SRAM and PLL disabled. | @@ -135,9 +137,10 @@ points, not the complete profile or application inventory. | [Mini MPW](configs/cluster/mini-mpw.mk) | Legacy MPW C0-C3/user-IP compatibility, separate from the product ABI. | Tiny's [2026-10-07 target refreeze](docs/ip/tiny-soc.md#ics55-default-platform-and-final-timing-gate-2026-10-07) -selects ICS55 with `HAVE_PLL=YES` and SAFE24 startup as the future default. -Its new profile and RTL binding await TINY-ICS55-P1; the IHP130 Tiny entry above -remains the executable compatibility baseline. Locked PLL views can be acquired +selects ICS55 with `HAVE_PLL=YES` and SAFE24 startup as the default. +The profile instantiates a parked PLL; dynamic SYS switching remains R2-P7 and +physical qualification is pending. IHP130 remains explicit compatibility. +Locked PLL views can be acquired with `python3 physical/pdk/setup.py --pdk ICS55 --component pll` independently of platform implementation or timing qualification. diff --git a/app/apps/ci_smoke/tiny.c b/app/apps/ci_smoke/tiny.c index 6a1ac7c0..213975fb 100644 --- a/app/apps/ci_smoke/tiny.c +++ b/app/apps/ci_smoke/tiny.c @@ -159,9 +159,12 @@ static bool rs_mcu_io(void) { if ((rs_gpio_get_capabilities(&capabilities) != RS_OK) || ((capabilities.pad_features & (RS_GPIO_PAD_CAP_PULL_UP | RS_GPIO_PAD_CAP_PULL_DOWN)) != - 0U) || - (rs_gpio_configure(15U, &pull_up) != RS_ENOTSUP) || - (rs_gpio_configure(15U, &pull_down) != RS_ENOTSUP) || + ((RS_SOC_GPIO_HAS_PULLS != 0U) ? (RS_GPIO_PAD_CAP_PULL_UP | RS_GPIO_PAD_CAP_PULL_DOWN) + : 0U)) || + (rs_gpio_configure(15U, &pull_up) != + ((RS_SOC_GPIO_HAS_PULLS != 0U) ? RS_OK : RS_ENOTSUP)) || + (rs_gpio_configure(15U, &pull_down) != + ((RS_SOC_GPIO_HAS_PULLS != 0U) ? RS_OK : RS_ENOTSUP)) || (rs_gpio_configure(15U, &output) != RS_OK) || (rs_gpio_write(15U, true) != RS_OK) || (rs_gpio_read(15U, &high) != RS_OK) || !high || (rs_gpio_write(15U, false) != RS_OK) || (rs_gpio_read(15U, &high) != RS_OK) || high) { @@ -256,6 +259,8 @@ int main(void) { } if ((rs_archinfo_read(&info) != RS_OK) || (rs_archinfo_validate_build(&info) != RS_OK) || (info.soc_id != RS_SOC_ID) || (info.topology != UINT32_C(0x20200001)) || + (info.technology != RS_SOC_TECHNOLOGY_ID) || + ((info.features0 & UINT32_C(1)) != RS_SOC_PLL_PRESENT) || (rs_onchip_sram_probe(&memory) != RS_OK) || (memory.memory_bytes != UINT32_C(131072)) || (memory.data_bytes != 4U) || (memory.bank_count != 32U)) { rs_mcu_finish(false, 3U); diff --git a/configs/README.md b/configs/README.md index 2b538dd7..17fa9335 100644 --- a/configs/README.md +++ b/configs/README.md @@ -67,10 +67,12 @@ For supported profiles and commands, see the root [README](../README.md) and ## Tiny MCU The [2026-10-07 Tiny refreeze](../docs/ip/tiny-soc.md#ics55-default-platform-and-final-timing-gate-2026-10-07) -sets the **target** default to ICS55 with `HAVE_PLL=YES` and SAFE24 boot. -`ci/ics55-tiny.mk` is planned for TINY-ICS55-P1 and is not yet an executable -profile. IHP130 remains explicit compatibility; do not substitute the Mini -`ci/ics55.mk` profile for Tiny or bypass current configuration guards. +sets the default to ICS55 with `HAVE_PLL=YES` and SAFE24 boot. +`ci/ics55-tiny.mk` implements that entrypoint with the PLL held off; standalone +PLL backend tests do not enable RCU/SYS switching. `make SOC=TINY` selects this +committed profile, and `make SOC=TINY PDK=IHP130` selects explicit compatibility. +Do not substitute Mini's `ci/ics55.mk` for Tiny. Acceptance evidence belongs in +the Tiny ledger, and physical qualification remains pending. `ci/ihp130-tiny.mk` selects `SOC=TINY`, one RV32IMC Hazard3 (A disabled), 128 KiB SRAM, AXI32/APB4, four DMA channels and the wired peripheral subset. diff --git a/configs/ci/ics55-tiny.mk b/configs/ci/ics55-tiny.mk new file mode 100644 index 00000000..dadd7079 --- /dev/null +++ b/configs/ci/ics55-tiny.mk @@ -0,0 +1,18 @@ +SOC := TINY +MINI_MODE := NONE +PDK := ICS55 +SIMU := VERILATOR +HAVE_PLL := YES +HAVE_SRAM_IF := YES +HAVE_SRAM_MACRO := YES +SRAM_SIZE_KIB := 128 +HAVE_HP := NO +HAVE_SVA := NO +EXT_CLK_HZ := 24000000 +AUD_CLK_HZ := 24000000 +CLINT_TIMEBASE_HZ := 1000000 +ISA := RV32IM +HAVE_CSR := YES +APP := bringup +LINK_TYPE := ld2_all_sram +RTL_TOP := retrosoc_tiny_tb diff --git a/docs/README.md b/docs/README.md index 0aa3ac01..599ffaec 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,11 +6,16 @@ the root README and subsystem guides. - [Tiny Gen1 QFN64 R2](ip/tiny-soc.md) freezes dual CPU I/D paths, four independent 32 KiB main-SRAM groups on the CPU's SYS clock, per-target AXI32 concurrency, eight-channel DMA scheduling and the existing shared-IP/camera - target without changing the package budget. The active roadmap is sequential - `TINY-R2-P0` through `TINY-R2-P11`; legacy `TINY-P0` through `TINY-P12` + target without changing the package budget. After `TINY-ICS55-P1`, the active + roadmap completes remaining Tiny foundation, SPI, PIO-lite and PPALite, then + jointly qualifies the complete product. `TINY-R2-P0..P11` and legacy + `TINY-P0` through `TINY-P12` remain in the linked contract history. CPU/main-SRAM rates up to 240 MHz require qualification. These targets remain separate from the existing 24 MHz/four-channel implementation and physical evidence. +- [Tiny ICS55 platform](ip/tiny-ics55-platform.md) defines the default SAFE24 + profile, parked PLL, locked 32-macro SRAM mapping, validation commands and + source-bound platform evidence; PLL-driven SYS remains later work. - [PIO-lite](ip/piolite.md) freezes the separately approved future Tiny standard programmable-I/O block: two state machines, shared 32x16 program store, existing GPIO USER ownership, PCLK execution and central DMA V2.1 diff --git a/docs/engineering.md b/docs/engineering.md index e4fb0e1a..9c7bb55c 100644 --- a/docs/engineering.md +++ b/docs/engineering.md @@ -3,8 +3,9 @@ ## Supported Profiles Tiny's [2026-10-07 specification default](ip/tiny-soc.md#ics55-default-platform-and-final-timing-gate-2026-10-07) -is ICS55/PLL with SAFE24 boot; platform enablement and its new profile remain -pending. The table describes executable profiles, not planned support. +is ICS55/PLL with SAFE24 boot. The executable default keeps the PLL parked off; +PLL-driven SYS and physical qualification remain later work. The table +describes executable profiles, not qualified operating rates. For Tiny and its SPI/PIO-lite/PPALite extensions only, pre-final post-synthesis timing closure is observational. Retain STA attempts/failures and all required functional, synthesis/mapping and netlist-function evidence. The combined @@ -13,6 +14,7 @@ This does not change warning/metrics policy or another product's gates. | Tier | Profile | Automated coverage | | --- | --- | --- | +| Pull request | `configs/ci/ics55-tiny.mk` | Default Tiny SAFE24/parked PLL; firmware, Verilator/Icarus and JTAG, lint; local Yosys, netlist and OpenSTA | | Pull request | `configs/ci/ihp130-tiny.mk` | Tiny firmware, Verilator/Icarus, lint; local Yosys, netlist and OpenSTA | | Smoke | `configs/ci/ihp130.mk` | strict Verilator RTL lint, firmware, Verilator SVA compilation, Icarus assembly self-test | | Pull request | `configs/ci/ihp130.mk` | strict Verilator RTL lint, firmware, Verilator, Icarus | diff --git a/docs/ip/piolite-verification.md b/docs/ip/piolite-verification.md index 76fe6ef4..bbe0b420 100644 --- a/docs/ip/piolite-verification.md +++ b/docs/ip/piolite-verification.md @@ -11,8 +11,9 @@ automatically cover the new feature. - Current normative default: TINY/ICS55, `HAVE_PLL=YES`, SAFE24 boot under the [2026-10-07 Tiny policy](tiny-soc.md#ics55-default-platform-and-final-timing-gate-2026-10-07). - TINY-ICS55-P1 and `configs/ci/ics55-tiny.mk` are pending. The profile below - remains executable IHP130 compatibility, not an ICS55 test result. + `configs/ci/ics55-tiny.mk` now exists with a parked PLL; platform acceptance + belongs to the Tiny ledger. The profile below remains IHP130 compatibility, + not an ICS55 or PIO-lite test result. - All pre-final post-synthesis timing is observational. Retain failed/unrun measurements and functional/protocol, synthesis/mapping and netlist-function gates. PIOLITE-P5 joins R2-P11/SPI-P5/PPALITE-P5 only after all functional diff --git a/docs/ip/piolite.md b/docs/ip/piolite.md index 5163a3bc..ad1a4eb6 100644 --- a/docs/ip/piolite.md +++ b/docs/ip/piolite.md @@ -22,8 +22,9 @@ firmware, configuration, dependency, warning baseline or quality-policy change. The [2026-10-07 Tiny refreeze](tiny-soc.md#ics55-default-platform-and-final-timing-gate-2026-10-07) selects default TINY/ICS55, `HAVE_PLL=YES`, SAFE24 boot, retaining explicit -IHP130 compatibility. `configs/ci/ics55-tiny.mk` remains planned until -TINY-ICS55-P1; commands below are existing IHP130 references. Pre-final +IHP130 compatibility. `configs/ci/ics55-tiny.mk` now exists with its PLL parked +off; platform acceptance is tracked in the Tiny ledger. Commands below remain +IHP130 references and do not validate PIO-lite. Pre-final post-synthesis timing is observational, while functionality, protocol, synthesis/mapping and netlist-function checks remain required. PIOLITE-P5 joins R2-P11, SPI-P5 and PPALITE-P5 after all functional stages on the same @@ -34,7 +35,7 @@ PIO implementation and unrelated dependency changes remain outside this refreeze | Item | Frozen boundary | | --- | --- | | Target | TINY, future standard QFN64 product feature | -| Default target / planned profile | ICS55, `HAVE_PLL=YES`, SAFE24 boot; `configs/ci/ics55-tiny.mk` pending TINY-ICS55-P1 | +| Default target / platform profile | ICS55, `HAVE_PLL=YES`, SAFE24 boot; `configs/ci/ics55-tiny.mk`, PLL parked off; no PIO-lite integration | | Starting profile / PDK | `configs/ci/ihp130-tiny.mk` / IHP130; existing 24 MHz, no-PLL baseline | | Product integration | `rtl/tiny/top/retrosoc_tiny.sv`, `rtl/tiny/integration/soc_topology.json`, `rtl/tiny/address_map/memory_map.json`, `rtl/tiny/pin_map/pin_map.json`, `rtl/tiny/integration/clock_reset_domains.json` | | Filelists | `rtl/tiny/filelist/ip.fl`, `inc.fl`, `top.fl`, `tb.fl` and their existing build flow | @@ -748,7 +749,7 @@ authorized or marked complete by approval of this document. ```text Use $retrosoc-feature-implementation. Stage: preflight. Feature slug: piolite. Target SoCs: TINY. Phase: PIOLITE-P1 - ISA Reference Model and Assembler. -Read docs/ip/piolite.md and docs/ip/piolite-verification.md. Default target: ICS55/HAVE_PLL=YES with SAFE24 boot and planned configs/ci/ics55-tiny.mk; require accepted TINY-ICS55-P1 before integrated default-platform work. Current executable reference: configs/ci/ihp130-tiny.mk, IHP130, 24 MHz/no PLL, no PIO. Apply the 2026-10-07 observational pre-final timing policy and mandatory complete-product final campaign; do not invent an executable profile or relabel historical results. +Read docs/ip/piolite.md and docs/ip/piolite-verification.md. Default platform: configs/ci/ics55-tiny.mk, ICS55/HAVE_PLL=YES with external SAFE24 and a parked PLL; verify TINY-ICS55-P1 acceptance in the Tiny ledger before integrated default-platform work. Compatibility: configs/ci/ihp130-tiny.mk, IHP130, 24 MHz/no PLL. Neither platform implements PIO-lite. Apply the 2026-10-07 observational pre-final timing policy and mandatory complete-product final campaign; do not relabel historical results. Plan only the frozen ISA reference model, deterministic assembler, program metadata/relocation and focused tests, including actual 32-word application and dual-SM footprints. Preserve the two-SM architecture, PCLK tick/packing semantics, GPIO/DMA lifecycle contracts and all R2 phase identifiers. Do not implement RTL, HAL integration, DMA V2.1, GPIO/RCU wiring, new profiles or later phases. Do not alter the ISA/ABI, expand memory or package scope, download new dependencies, change quality policy, commit or push. Report SPEC_CONFLICT if a frozen requirement cannot be met; do not weaken it. Return the scoped preflight and validation plan for explicit approval before implementation. ``` diff --git a/docs/ip/ppalite-verification.md b/docs/ip/ppalite-verification.md index c68f987f..fe9f786a 100644 --- a/docs/ip/ppalite-verification.md +++ b/docs/ip/ppalite-verification.md @@ -10,8 +10,9 @@ evidence under its original scope. - Current normative default: TINY/ICS55, `HAVE_PLL=YES`, SAFE24 boot under the [2026-10-07 Tiny policy](tiny-soc.md#ics55-default-platform-and-final-timing-gate-2026-10-07). - TINY-ICS55-P1 and `configs/ci/ics55-tiny.mk` are pending; existing IHP130 - commands and historical records are compatibility evidence only. + `configs/ci/ics55-tiny.mk` now exists with a parked PLL; platform acceptance + belongs to the Tiny ledger. Existing IHP130 commands and historical records + are compatibility evidence only; no PPALite integration is delivered. - Pre-final post-synthesis timing is observational; failed/missing timing does not become a PASS or a hardware-frequency claim. Source correctness, functionality, synthesis/mapping and netlist-function requirements remain. diff --git a/docs/ip/ppalite.md b/docs/ip/ppalite.md index 1db58c18..e26b9cf3 100644 --- a/docs/ip/ppalite.md +++ b/docs/ip/ppalite.md @@ -17,15 +17,17 @@ AXI64/two-dimensional DMA architecture is not imported. This freeze changes documentation only. It does not change RTL, firmware, configuration, dependencies, warning baselines, metrics policy or readiness. -The executable Tiny baseline remains 24 MHz/no PLL, four DMA channels and no -PPALite. Source qualification gaps below remain explicit prerequisites. +The IHP130 reference remains 24 MHz/no PLL, four DMA channels and no PPALite; +the ICS55 SAFE24 platform likewise has no PPALite integration. Source +qualification gaps below remain explicit prerequisites. ## Target SoCs and Integration Scope The [2026-10-07 Tiny refreeze](tiny-soc.md#ics55-default-platform-and-final-timing-gate-2026-10-07) selects default TINY/ICS55, `HAVE_PLL=YES`, SAFE24 boot, retaining IHP130 -compatibility. `configs/ci/ics55-tiny.mk` is planned, not executable until -TINY-ICS55-P1. Existing commands below retain their IHP130 scope. Pre-final +compatibility. `configs/ci/ics55-tiny.mk` now exists with its PLL parked off; +platform acceptance is tracked in the Tiny ledger. Existing commands below +retain their IHP130 scope and do not validate PPALite. Pre-final post-synthesis timing is observational; source integrity, functionality, protocol, synthesis/mapping and netlist-function gates are unchanged. PPALITE-P5 joins R2-P11, SPI-P5 and PIOLITE-P5 after all functional stages on @@ -36,7 +38,7 @@ by this platform/timing refreeze. | Item | Frozen boundary | | --- | --- | | Target / deferred products | TINY future standard product; MINI/STD/PRO integration deferred | -| Default target / planned profile | ICS55, `HAVE_PLL=YES`, SAFE24 boot; `configs/ci/ics55-tiny.mk` pending TINY-ICS55-P1 | +| Default target / platform profile | ICS55, `HAVE_PLL=YES`, SAFE24 boot; `configs/ci/ics55-tiny.mk`, PLL parked off; no PPALite integration | | Starting profile / PDK | `configs/ci/ihp130-tiny.mk`, IHP130, existing 24 MHz/no-PLL baseline | | Product ownership | `rtl/tiny/top`, `rtl/tiny/integration/soc_topology.json`, `rtl/tiny/integration/clock_reset_domains.json`, `rtl/tiny/address_map/memory_map.json` and existing product filelists | | Shared source | `rtl/ip/multimedia/axi4s_dvp.sv`, `dvp_core.sv`, `dvp_reg.sv`; DVP V2 APB/stream contract and existing 512 B payload CDC FIFO | @@ -713,7 +715,7 @@ PPALite there. IDs and titles are stable and do not replace earlier roadmaps. ```text Use $retrosoc-feature-implementation. Stage: preflight. Feature slug: ppalite. Target SoCs: TINY. Phase: PPALITE-P1 - Stream Pixel Core and Reference Model. -Read docs/ip/ppalite.md and docs/ip/ppalite-verification.md. Default target: ICS55/HAVE_PLL=YES with SAFE24 boot and planned configs/ci/ics55-tiny.mk; require accepted TINY-ICS55-P1 before integrated default-platform work. Current executable reference: configs/ci/ihp130-tiny.mk, IHP130, 24 MHz/no PLL, no PPALite. Apply the 2026-10-07 observational pre-final timing policy and mandatory complete-product final campaign; do not invent an executable profile or relabel historical results. +Read docs/ip/ppalite.md and docs/ip/ppalite-verification.md. Default platform: configs/ci/ics55-tiny.mk, ICS55/HAVE_PLL=YES with external SAFE24 and a parked PLL; verify TINY-ICS55-P1 acceptance in the Tiny ledger before integrated default-platform work. Compatibility: configs/ci/ihp130-tiny.mk, IHP130, 24 MHz/no PLL. Neither platform implements PPALite. Apply the 2026-10-07 observational pre-final timing policy and mandatory complete-product final campaign; do not relabel historical results. Plan only the frozen standalone pixel/stream core, input validation, 1/2/4 sampling, exact format/row packing, small buffers, APB1.0 and handwritten SVH/C parity, scalar reference, independent stream BFM and focused tests. Preserve Y-only grayscale, RAW contract, resource limits and all earlier phase IDs. Do not implement DVP source repairs, Tiny routing/DMA/RCU integration, the complete SDK/display application or later phases. Do not add memory replay, RGB-to-gray arithmetic, interpolation, row/frame SRAM, DMA requests/masters, dependencies, profiles or quality-policy changes. Return SPEC_CONFLICT rather than weakening an unmet contract, and provide the scoped preflight/validation plan for explicit approval. Do not commit or push. ``` diff --git a/docs/ip/spi-verification.md b/docs/ip/spi-verification.md index 88322295..a13be4ea 100644 --- a/docs/ip/spi-verification.md +++ b/docs/ip/spi-verification.md @@ -10,8 +10,9 @@ Tiny, SPI-SD, XPI, DMA and PIO evidence does not automatically cover this IP. - Current normative default: TINY/ICS55, `HAVE_PLL=YES`, SAFE24 boot under the [2026-10-07 Tiny policy](tiny-soc.md#ics55-default-platform-and-final-timing-gate-2026-10-07). - TINY-ICS55-P1 and its planned `configs/ci/ics55-tiny.mk` are pending; - the starting profile below remains executable IHP130 compatibility only. + `configs/ci/ics55-tiny.mk` now exists with a parked PLL; platform acceptance + belongs to the Tiny ledger. The profile below remains IHP130 compatibility; + neither platform implements SPI. - Before final qualification, post-synthesis timing is observational and all failures/NOT_RUN cases remain attributed. Functional/source/protocol and synthesis/mapping/netlist-function gates remain mandatory. SPI-P5 joins diff --git a/docs/ip/spi.md b/docs/ip/spi.md index 6faa1ed7..bb1877ef 100644 --- a/docs/ip/spi.md +++ b/docs/ip/spi.md @@ -16,7 +16,8 @@ PSRAM still use XPI and contend there; a separate screen clock/data bus does not eliminate framebuffer-memory cost. The first application is sequential capture, verify, display and SD save, not simultaneous double-buffer video. -The committed profile remains 24 MHz/no PLL, four-channel DMA and no SPI. +The IHP130 reference remains 24 MHz/no PLL, four-channel DMA and no SPI; +the ICS55 SAFE24 platform likewise has no SPI integration. Eight DMA channels, the Gen1 pin map, faster PCLK and new IP are target contracts. This freeze does not change RTL, firmware, profiles, dependencies, warning baselines, metrics policy or readiness. It does not implement a new @@ -26,9 +27,10 @@ GUI framework or replace the legacy XPI LCD transport. The [2026-10-07 Tiny refreeze](tiny-soc.md#ics55-default-platform-and-final-timing-gate-2026-10-07) supersedes the original platform and timing-gate scope: default TINY/ICS55, -`HAVE_PLL=YES`, SAFE24 boot; IHP130 remains explicit compatibility. The planned -`configs/ci/ics55-tiny.mk` awaits TINY-ICS55-P1, so the existing commands below -remain IHP130-only executable references. All pre-final post-synthesis timing +`HAVE_PLL=YES`, SAFE24 boot; IHP130 remains explicit compatibility. The +`configs/ci/ics55-tiny.mk` platform now exists with its PLL parked off; its +acceptance is tracked in the Tiny ledger. Existing commands below retain +their IHP130 scope and do not validate SPI. All pre-final post-synthesis timing is observational; functional/protocol, synthesis/mapping and netlist-function requirements remain. SPI-P5 joins R2-P11, PIOLITE-P5 and PPALITE-P5 only after all functional stages, on the same complete-product netlist. Preserve all @@ -39,7 +41,7 @@ upgrade beyond the separately approved Tiny PLL acquisition. | Item | Frozen contract | | --- | --- | | Target / status | TINY future standard product; MINI/STD/PRO integration deferred | -| Default target / planned profile | ICS55, `HAVE_PLL=YES`, SAFE24 boot; `configs/ci/ics55-tiny.mk` pending TINY-ICS55-P1 | +| Default target / platform profile | ICS55, `HAVE_PLL=YES`, SAFE24 boot; `configs/ci/ics55-tiny.mk`, PLL parked off; no SPI integration | | Starting profile / PDK | `configs/ci/ihp130-tiny.mk` / IHP130, existing 24 MHz/no-PLL baseline | | Product sources | `rtl/tiny/top`, `rtl/tiny/integration/soc_topology.json`, `clock_reset_domains.json`, `rtl/tiny/address_map/memory_map.json`, `rtl/tiny/pin_map/pin_map.json` | | Filelists | Existing `rtl/tiny/filelist` ownership, especially `ip.fl`, `inc.fl`, `top.fl` and `tb.fl` | @@ -697,7 +699,7 @@ Stable IDs/titles below do not rename, reuse or advance R2/PIO phases. ```text Use $retrosoc-feature-implementation. Stage: preflight. Feature slug: spi. Target SoCs: TINY. Phase: SPI-P1 - SPI Core and Register Implementation. -Read docs/ip/spi.md and docs/ip/spi-verification.md. Default target: ICS55/HAVE_PLL=YES with SAFE24 boot and planned configs/ci/ics55-tiny.mk; require accepted TINY-ICS55-P1 before integrated default-platform work. Current executable reference: configs/ci/ihp130-tiny.mk, IHP130, 24 MHz/no PLL, no SPI. Apply the 2026-10-07 observational pre-final timing policy and mandatory complete-product final campaign; do not invent an executable profile or relabel historical results. +Read docs/ip/spi.md and docs/ip/spi-verification.md. Default platform: configs/ci/ics55-tiny.mk, ICS55/HAVE_PLL=YES with external SAFE24 and a parked PLL; verify TINY-ICS55-P1 acceptance in the Tiny ledger before integrated default-platform work. Compatibility: configs/ci/ihp130-tiny.mk, IHP130, 24 MHz/no PLL. Neither platform implements SPI. Apply the 2026-10-07 observational pre-final timing policy and mandatory complete-product final campaign; do not relabel historical results. Plan only the frozen standalone master/APB1.0 core, packing/phase/lifecycle and source-qualified interface contracts, handwritten SVH/C parity, reference behavior, independent pin BFM and focused tests. Preserve all modes/8-16-bit packing, FIFO sizes, exact quotas, CS/D-C boundaries and bounded close/recovery semantics. Do not implement DMA V2.2, Tiny routing/RCU/fabric integration, the SDK/display application, new profiles or later phases. Do not change the frozen ABI, increase storage/pads, alter the legacy XPI LCD path, upgrade dependencies, change quality policy, commit or push. Return SPEC_CONFLICT rather than weakening an unmet contract, and provide the scoped preflight and validation plan for explicit approval before implementation. ``` diff --git a/docs/ip/tiny-ics55-platform.md b/docs/ip/tiny-ics55-platform.md new file mode 100644 index 00000000..dd95a53a --- /dev/null +++ b/docs/ip/tiny-ics55-platform.md @@ -0,0 +1,147 @@ +# Tiny ICS55 SAFE24 platform + +This runbook implements the boundary of +[TINY-ICS55-P1](tiny-soc.md#tiny-ics55-p1---default-pdk-and-pll-platform-enablement). +Acceptance and exact artifact roots belong in the [ledger](tiny-soc-verification.md). +The implementation starts from `ecac3559b0ba67fa2630003ad718657969c78af4`. + +## Platform and ownership + +`configs/ci/ics55-tiny.mk` selects TINY/ICS55, HAVE_PLL=YES, 128 KiB macro SRAM +and external 24 MHz SAFE24. `make SOC=TINY` selects this profile; explicit +`PDK=IHP130` selects the existing compatibility profile. Mini defaults remain +unchanged. This phase does not add a crystal input, change the frozen package +or pin map, add a clock mux, or implement the RCU register bank. + +The ASIC wrapper selects `UseIcs55EcosPll=1` and `Ics55Parked=1`. The retained +PLL macro has constant EN=0 and N=32; it cannot drive SYS. The digital monitor +and control cone are unused in this configuration. SAFE24 CPU release never +waits for a PLL or an external peripheral clock. P2's five-edge reset chains, +CPU-last startup, watchdog POR boundary and payload retention remain intact. +Full clock switching and system clock-fault recovery remain R2-P7. + +Shared `onchip_ram` defaults still use the legacy ICS55 16 KiB geometry for +Mini. Tiny explicitly selects `Ics55SmallBanks=1`, restricted to the 32-bit +interface, to bind 32 OpenECOS 1024x32 macros. This changes technology selection, +not arbitration or the future four logical bank services. IHP130 remains on +its original 32 4 KiB macros. Memory aperture, byte masks, response semantics +and software bank discovery remain unchanged for Tiny. + +Tiny's generated GPIO pad instances explicitly select `ReadWhileDriving=1`. +ICS55's IE and OE are independent; enabling IE while driving preserves Tiny's +pad-input readback contract. The shared wrapper default is still zero for +Mini. Dedicated tests check both settings against native Icarus and functional +Verilator pad views. Firmware tests real technology pull capabilities instead +of assuming the IHP130 lack of pulls. ARCHINFO reports technology 0x02040037 +and PLL presence for ICS55; the IHP130 reference remains 0x02010082/no PLL. +Presence does not advertise a working RCU control API; unsupported writes and +HAL operations remain rejected. No address/IRQ/DMA/register ABI is added. + +The source lists select native Icarus IO, functional Verilator IO, or synthesis +library inputs explicitly. The unused shared 4096x32 wrapper has only its +official interface stub in Tiny simulation; the netlist audit rejects any +actual instance. Managed sources and dependency versions are unchanged. + +## PLL backend verification boundary + +The locked OpenECOS PLL revision is +`6ebb1a8f7f4ccbccdb7f587664fdfe63cd39e61b`. Standalone tests select the unparked +backend and use its actual behavioral view, not a pass-through stub. Selectors +5/7 configure N=32/40, SELECT=0 and OD=2 for nominal 192/240 MHz from REF24. +Other selectors disable the backend. Held APPLY is consumed once; each new +request deasserts EN for two reference cycles before restart. + +Clock qualification waits 512 reference cycles, discards the first 128-cycle +window and requires four good windows. A registered 16-bit Gray count crosses +through Common's two-stage synchronizer. Expected counts are 1024/1280 with +16/20-edge tolerance. A bad window removes lock; 4096 unqualified reference +cycles exhaust the acquisition budget and require a new request. Source-domain +reset release takes five edges. The interface's lock output is a digital +activity/rate observation, not analog lock or jitter qualification. + +The macro has no LOCK output; its Liberty has no timing arcs and its license +is unresolved. Ideal simulation supplies are not a physical ground/rail merge +decision. Reference loss cannot be diagnosed by a stopped reference clock; +the frozen external-reset recovery boundary remains. None of the standalone +tests qualifies CPU/main SRAM at a high rate. + +## Validation sequence + +Activate the existing locked development environment and use one timestamp +across commands. Run focused tests, RTL/C policy and host checks before the +full Pytest suite or product regressions. Tests requiring a model/simulator +must execute; a skip is not that gate's acceptance. + +```sh +source .cache/retrosoc/development/tiny-r2-p1/activate.sh +export BUILD_TIMESTAMP=$(date +%Y-%m-%d-%H-%M) +python3 -m pytest -q tests/test_tiny_ics55_platform.py tests/test_tiny.py \ + tests/test_tiny_r2_baseline.py tests/test_tiny_r2_p2.py \ + tests/test_clock_reset_domains.py tests/test_mgmt_debug.py \ + tests/test_publication_tiny.py tests/test_commercial_flow.py::test_ics55_pll_wrapper +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 \ + rtl-format-check rtl-style-check rtl-readiness-check \ + sw-format-check sw-policy-check sw-host-test +ruff check . +python3 -m pytest -q +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 APP=ci_smoke firmware +tiny_ics_root=$(make -s CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 APP=ci_smoke config | awk '$1 == "VARIANT_ROOT" {print $2}') +python3 scripts/tiny_ics55_platform.py --variant-root "$tiny_ics_root" capture +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55 --dry-run +python3 scripts/regress.py --root . --suite nightly --soc TINY --pdk ICS55 --dry-run +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 --dry-run +python3 scripts/regress.py --root . --suite pr --soc MINI --pdk ICS55 --dry-run +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55 +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 +python3 scripts/tiny_ics55_platform.py --variant-root "$tiny_ics_root" report +``` + +Tiny PR RTL simulation enables `+tiny_jtag_smoke`: IDCODE/DTMCS, DMI halt and +resume must finish before the testbench can accept firmware TEST_STATUS. +Normal workload replay leaves JTAG untouched. Tiny always requires command +success, valid TEST_STATUS/SIM_TEST_PASS and no forbidden errors; UART or the +Mini boot-only marker is insufficient. Tiny's filtered nightly matrix matches +PR, so verify that equivalence rather than repeating identical long runs. + +Replay the retained original P1 HEX using `tiny-r2-baseline-sim` under the new +profile with APP=bringup and explicit TINY_BASELINE_HEX. Each simulator executes +three cold starts; Icarus uses SOC_SIM_TIME=14400 and JOBS=3 and is polled every +30 minutes. If host timeout is exhausted, retain the failed attempt and do not +count an incomplete sample. An identical-model retry may increase only the +host budget, retaining successful cold starts and recording each run's budget; +the actual campaign recovery and its executable driver are in the ledger. +Keep the original image manifest/source/phase and binaries intact. +The P1 label identifies the measurement protocol; the new RTL/config/PDK and +outer phase record identify ICS55-P1. Historical IHP130 samples remain labeled +references, not current-source cross-PDK performance qualification. + +## Netlist, STA and evidence + +Yosys imports the PLL's library blackbox; it never synthesizes behavioral clock +delays. Its final Verilog and JSON are bound by hashes to a structural report: +32 4 KiB SRAM macros, one constant-disabled PLL, and CPU/SRAM clock connectivity. +The 16 KiB stub must have no instance. A modified audit script or consumed +artifact invalidates the report. Netlist configuration must match SOC/PDK, +capacity, PLL presence, 41667 ps synthesis target and the actual config digest. + +OpenSTA accepts only this verified parked Tiny/ICS55 exception to the existing +PLL guard. Active PLL configurations remain unsupported. SYS remains the +41.666666667 ns primary clock at the existing buffer output, with JTAG as the +separate asynchronous domain. No active PLL generated clock is invented. +The original core-only IO/reset exceptions remain unchanged. Reports include +coverage, clocks, SYS setup/hold, SRAM input/return paths and electrical, +recovery/removal, pulse-width and minimum-period observations. + +`TINY_ICS55_STA_AUDIT_PASS` means the audit ran, not timing closure. Negative +WNS/TNS and other timing violations remain failures in the report but do not +block this functional phase. Missing arcs, full-chip IO, extracted PVT, package, +power and analog qualification remain final-campaign gaps. The legacy pad +wrapper and Mini padless ECC runs do not qualify the frozen QFN64 product. + +Evidence lives under `build/--/meta/tiny-ics55-p1/`. +Capture snapshots before final synthesis/STA; a later capture cannot relabel an +older run. Archive source/generated input hashes, consumed models/libraries, +configuration, tool identities, commands/results and original binary identity. +The native report covers synthesis/STA only; functional and matched-workload +acceptance must be joined explicitly in the phase delivery index. Warning +baselines and global observe-mode metrics are not promoted or regenerated here. diff --git a/docs/ip/tiny-soc-verification.md b/docs/ip/tiny-soc-verification.md index 87bc2677..c3e77f00 100644 --- a/docs/ip/tiny-soc-verification.md +++ b/docs/ip/tiny-soc-verification.md @@ -1,11 +1,165 @@ # Tiny MCU Verification Record +## TINY-ICS55-P1 implementation (2026-10-07–08) + +Implementation is based on committed source +`ecac3559b0ba67fa2630003ad718657969c78af4` plus the retained working-tree diff. +The worktree was clean at entry. The preflight specification/ledger Git blobs +were `713f3d5a0d04a7b2fc4c66838e9ceaf6c7551263` and +`87bc267759d863d98f200e2740abcaa0dcba28a5`. This record is separate from the +earlier documentation/dependency refreeze and IHP130 R2 measurements. +Implementation and validation are complete for review; human phase acceptance +is still pending by this entry. + +The [platform runbook](tiny-ics55-platform.md) defines ownership and commands. +The default `configs/ci/ics55-tiny.mk` selects TINY/ICS55, HAVE_PLL=YES, +128 KiB main SRAM and external 24 MHz SYS. The PLL is physically represented +but disabled; standalone PLL192/240 tests do not enable those SoC rates. +IHP130 remains an explicit no-PLL profile, and Mini defaults are unchanged. + +Changes are limited to platform/profile selection, generated identity and +filelists, technology adapters, the standalone PLL digital backend, native +pad readback, JTAG smoke, netlist/STA evidence and matching publication-source +parsing. Tiny explicitly selects 32 locked 1024x32 SRAM macros through a +default-off shared parameter; this is not R2-P4 logical SRAM banking. Tiny's +GPIO input remains enabled while driving, with the legacy Mini default intact. +No register/API, address, IRQ, DMA channel/request, package assignment or +private Crypto storage change is introduced. No new MISRA deviation applies. +Dependency versions, warning signatures and metrics policy are unchanged. + +Final validation uses `BUILD_TIMESTAMP=2026-10-07-23-22`: + +- Primary CI/evidence variant: `build/ics55-tiny-2026-10-07-23-22-42d5beccb726/`. + Commands, logs and results are in `meta/tiny-ics55-p1/validation/`; the + captured source/tools/views are selected by `meta/tiny-ics55-p1/latest-inputs.json`. +- Matched workload variant: `build/ics55-tiny-2026-10-07-23-22-b4aa59f3e5d9/`, + APP=bringup. Each simulator runs three cold starts of the original retained + P1 HEX, SHA-256 `ba24c2dfd727a905f39b644df254e47262c39ab4960ff2614be44402020758b2`. + Its original image/source manifest remains under + `build/ihp130-tiny-2026-10-05-12-30-fa1c4ce1e303/meta/tiny-r2-p1/binaries/image-ozwo38_b/`. + The seven fixed workloads are empty-window calibration, CPU, memory, DMA + burst 1, DMA burst 16, CPU/DMA contention and descriptor-chain DMA. There + is no recompilation or layout experiment in this comparison. + +| Gate | Current result / retained evidence | +| --- | --- | +| Focused checks after the JTAG correction | PASS; 178 platform/provenance/baseline/build tests; earlier memory/debug/clock and publication checks also passed | +| Final policy and formatting | PASS; `validation/policy.json` and `rtl-format-retry.json`; C format/policy/host tests, Ruff, locked dependency schema, RTL style/readiness and changed-file formatting | +| Regression definitions | PASS; `validation/dry-runs.json`; Tiny ICS55 PR/nightly are identical, plus IHP130 Tiny and ICS55 Mini PR expansion | +| Tiny ICS55 PR | PASS; `validation/tiny-ics55-pr.json`; firmware, Verilator SVA/JTAG, native Icarus/JTAG, Yosys, strict Tiny netlist boot and OpenSTA execution | +| Tiny IHP130 PR | PASS; `validation/tiny-ihp130-pr.json`; separate compatibility artifacts, not relabeled ICS55 evidence | +| Shared Mini compatibility | PASS for firmware, memory/pin/topology/clock-domain checks and Verilator SVA lint; `validation/mini-compatibility.json`, `mini-rtl-lint-retry.json`; full Mini PR not run | +| Source / macro audit | PASS; `meta/tiny-ics55-p1/report.json`, `syn/yosys/tiny-ics55-netlist.json`; 32 SRAM macros, 2177 CPU clocked cells on direct SYS, one PLL with constant EN=0 | +| Matched Verilator workload | PASS, three identical samples, same original HEX; all seven workload samples also equal the historical IHP130 reference | +| Matched Icarus workload | PASS after timeout recovery; runs 1/2 at 13862.428/14242.835 s and recovered run 3 at 15379.18 s. All three samples match Verilator and historical IHP130; original exit-124 attempt is retained, with per-run budgets in `matched-report.json` | +| Full Pytest | PASS; 1568 passed / 1 skipped in 3769.75 s on final source; `validation/pytest.json`; the optional publication retrieval test lacks `pypdf` | +| Warning comparison | Non-blocking FAIL observations retained; no signatures regenerated or waived | +| Metrics | Collected on the actual APP=ci_smoke variants; `validation/ics55-ci-metrics.json`, `ihp130-ci-metrics.json`; policy remains observe | + +In this table `validation/` is relative to the primary variant's +`meta/tiny-ics55-p1/`. Verilator SVA CI variants use suffixes `2eb98c0e1fae` +(ICS55) and `39ea34c082ac` (IHP130); IHP130's ordinary CI variant uses +`bff91e3529ba`, all with the same fixed timestamp. Tiny netlist `netsim-boot` +uses a retained 220-byte assembly image and TEST_STATUS/SIM_TEST_PASS; it is +not the Mini UART-only boot target or full C netlist qualification. + +The native source/input digest is +`9566b3f2c06b21b3e06a4513d0163cc9ffcb63e6d68dd665a31f61be6c1e9def`. +The report rechecks consumed Verilog/JSON/configuration and audit-script hashes, +source snapshots, locked tools and post-capture execution timestamps. The +seven locked Tiny tools and PDK/model inputs retain their existing versions. +Mini's first lint attempt failed at SBT's local Unix socket in the sandbox; +the same source/configuration command passed with that environment restriction +removed. The initial multi-file formatter invocation was unsupported; individual +read-only verification passed without changing source. + +The original Icarus attempt is retained as failed at +`meta/tiny-r2-p1/workloads/iverilog/attempt-_fqxpoam/attempt.json` below the +matched variant. Runs 1/2 each completed all seven cases and strict Tiny +TEST_STATUS/SIM_TEST_PASS, with samples identical to Verilator. Run 3 printed +part of its final TCD record before host timeout; the missing completion marker +prevents acceptance. The generated recovery driver +`meta/tiny-ics55-p1/retry_icarus_run3.py` below the primary variant checks original +source/generated-input/model/tool/image hashes, preserves the two completed +run records, and launches a new cold start for repetition 3 only. It retains +the failed attempt and individual per-run timeout values. The recovered run 3 +passed in 15379.18 seconds with the same seven samples and strict markers. HEX, +simulator model, +workload parameters and the 100000000-cycle limit are unchanged; this is an +execution-budget recovery, not a compiler, layout or architecture experiment. +The first recovery launch stopped during provenance validation before starting +simulation: the stock runtime validator requires a common timeout for all runs. +The generated recovery adapter now invokes that unchanged validator separately +with each original command's declared budget. The final phase join uses +`collect_matched_report.py` and writes `meta/tiny-ics55-p1/matched-report.json` +below the primary variant, explicitly preserving per-run budgets and the failed +attempt. It does not claim that the ordinary homogeneous-budget baseline report +command accepts a mixed-budget record. Project implementation/validator sources +and all original run records remain unchanged by this operational recovery. + +Final SAFE24 core STA observations, in ns: + +| PDK | WNS max | TNS max | WNS min / TNS min | Verdict | +| --- | ---: | ---: | --- | --- | +| ICS55 | -41.14 | -508669.53 | 0.00 / 0.00 | Timing FAIL; observational in this phase | +| IHP130 | -94.28 | -1594880.00 | 0.00 / 0.00 | Timing FAIL; compatibility observation | + +ICS55 `sta/opensta/sys-setup.rpt` starts at the XPI reset leaf, fanout 4707, +and ends at RX FIFO storage; its SYS hold report's worst reported slack is ++0.08 ns. `coverage.rpt` records 43 inputs without input delays, 52 outputs +without output delays and 93 unconstrained endpoints. These are the retained +core-only analysis boundary, not full-chip IO closure. The clock report has +SYS 41.666666667 ns and JTAG 100 ns; no active PLL generated clock is claimed. +The electrical/recovery/removal/pulse-width/minimum-period and SRAM input/return +reports remain under the same `sta/opensta/` directory. + +Direct warning checks on the CI variants report 21 new Yosys signatures for +ICS55, and 14 Yosys plus one Icarus signature for IHP130, relative to the +stored baselines. Lint comparisons report 459/403 signatures respectively. +Neither Tiny profile has a stored RTL-lint baseline, so every normalized +lint signature is classified as new. The reports include unused parameters, +empty ports, vendor model limitations and ABC's Liberty-cache fallback; they are +not counts of defects introduced by this phase. The regression preserves +their failed observation status while its functional commands pass. + +The earlier development variant +`build/ics55-tiny-2026-10-07-21-00-42d5beccb726/` retains failed attempts: +missing unused SRAM interface, wrong 16 KiB macro selection, GPIO readback +failure and insufficient constant-disabled PLL proof. Corrected development +Verilator/JTAG, mapped-netlist audit and SAFE24 STA subsequently ran, but those +results are not substituted for the fresh final campaign. NFS clock-skew +warnings are retained; final variants and forced workload compilation avoid +reuse of those earlier simulator outputs. + +The intermediate `2026-10-07-23-06` campaign exposed an IHP130 Icarus JTAG +testbench reset-initialization failure. Four-state waveform inspection showed +internal TRST remaining X until release because TCK started after reset. The +opt-in test now clocks TCK during asserted external reset before its TAP/DMI +sequence; no reset RTL changed. A bounded Icarus probe then completed +halt/resume. Ongoing simulations in that campaign were explicitly interrupted +before final source recapture; partial workload passes are not final evidence. +The initial full Pytest was also interrupted and restarted on final source; +its interrupted record stays in that earlier directory. After the correction, +178 focused platform, provenance, baseline and build tests passed. + +The required acceptance remains command success, valid sticky TEST_STATUS, +SIM_TEST_PASS and no forbidden error marker. UART alone is insufficient. +Boot-only netlist checks are narrower than full C netlist acceptance. Timing +violations remain failures under the observational pre-final policy; no +behavioral result qualifies physical timing, analog PLL lock, PVT, power, +package or silicon. The PLL has no LOCK output, no characterized timing arcs +and an unresolved upstream license. Its ideal digital supply ties do not +define physical rail integration. Full RCU/source switching remains R2-P7. + +The next action is read-only feature review followed by explicit human phase +acceptance. No commit, push, PR or next-phase advancement is authorized here. + ## ICS55 default and final timing policy refreeze (2026-10-07) The [2026-10-07 normative policy](tiny-soc.md#ics55-default-platform-and-final-timing-gate-2026-10-07) selects TINY/ICS55 with `HAVE_PLL=YES` and SAFE24 startup as the default target. -`configs/ci/ics55-tiny.mk` and TINY-ICS55-P1 platform implementation are pending; -`configs/ci/ihp130-tiny.mk` remains the current executable compatibility baseline. +At refreeze, `configs/ci/ics55-tiny.mk` and TINY-ICS55-P1 platform implementation +were pending; `configs/ci/ihp130-tiny.mk` was the executable compatibility baseline. The existing 614fa623 commit adds shared ICS55 SRAM inputs, not Tiny enablement. All pre-final post-synthesis timing results are observations rather than phase diff --git a/docs/ip/tiny-soc.md b/docs/ip/tiny-soc.md index 713f3d5a..7e19de49 100644 --- a/docs/ip/tiny-soc.md +++ b/docs/ip/tiny-soc.md @@ -7,8 +7,9 @@ The 2026-10-07 refreeze selects **ICS55 as the default target PDK with complete-product final qualification campaign. The [platform and timing policy](#ics55-default-platform-and-final-timing-gate-2026-10-07) below supersedes earlier target-PDK and intermediate timing-gate wording. -It does not relabel historical IHP130 results or make the planned ICS55 profile -executable. This refreeze includes locked PLL acquisition, not RTL integration. +It does not relabel historical IHP130 results. The refreeze itself included +locked PLL acquisition only; the subsequent TINY-ICS55-P1 implementation is +described in the [platform runbook](tiny-ics55-platform.md) and verification ledger. The R2 portion of this contract freezes the performance-only design approved on 2026-10-04 for Target SoCs: `TINY`, feature slug `tiny-soc`. It preserves the QFN64 @@ -54,12 +55,12 @@ This is a specification freeze, not evidence of implemented or qualified additional product rollout; Std/Pro integration is outside this change. Tiny owns its integration in `rtl/tiny`; Mini remains a separate product. The -committed `configs/ci/ihp130-tiny.mk` profile still describes the initial -2026-09-25 implementation: 24 MHz external clock, no PLL, two UARTs and two I2C -controllers. Its RTL, canonical maps, SDK, configuration, generated datasheet -and verification evidence remain the executable baseline until separate -integration work updates them. The baseline sections below retain that -contract and must not be read as the new Gen1 integration or clock qualification. +`configs/ci/ics55-tiny.mk` default implements SAFE24 with a present, explicitly +disabled PLL; `configs/ci/ihp130-tiny.mk` preserves the 24 MHz/no-PLL compatibility +profile. Both retain two UARTs, two I2C controllers and the legacy executable +integration. The original generated datasheet and historical evidence remain +IHP130-specific. The baseline sections below must not be read as completed +Gen1 integration or clock qualification. Product RTL, address/pin/topology inputs and filelists remain under `rtl/tiny`; the SDK and application composition retain their existing `crt/` and `app/` @@ -68,8 +69,9 @@ the current product execution order is defined by the 2026-10-07 policy below. Legacy `TINY-P0` through `TINY-P12` keep their original headings and evidence in the archive; their former non-monotonic schedule is not the current execution plan. -The current profile still selects four DMA channels and forbids PLL/non-24-MHz -Tiny configurations. New configurations require explicit platform enablement. +Current profiles still select four DMA channels and require 24 MHz external +SYS. ICS55 PLL presence does not enable PLL-driven SYS; new operating points +require later clock/reset integration and qualification. PIO-lite integration in `PIOLITE-P3` requires the applicable accepted R2-P6 DMA/shared-integration and R2-P7 RCU functionality. `PIOLITE-P5` may share a physical run with R2-P11 only on the same PIO-inclusive source revision and @@ -91,9 +93,9 @@ integration contracts. It adds ICS55 platform enablement to the former performance-only scope; unrelated SoCs and PDK rollouts remain excluded. - TINY-052: the default **target** is TINY/ICS55, `HAVE_PLL=YES`, 128 KiB main - SRAM and SAFE24 boot. The planned committed entrypoint is - `configs/ci/ics55-tiny.mk`; it does not exist yet. The current executable - entrypoint remains `configs/ci/ihp130-tiny.mk`, IHP130, 24 MHz/no PLL. + SRAM and SAFE24 boot. The default entrypoint is `configs/ci/ics55-tiny.mk`, + with its PLL explicitly parked off in P1. The compatibility entrypoint is + `configs/ci/ihp130-tiny.mk`, IHP130, 24 MHz/no PLL. IHP130 remains an explicit compatibility option, not the future default. Mini profiles and defaults are unchanged. - TINY-053: before final complete-product qualification, post-synthesis timing @@ -182,7 +184,7 @@ lock/jitter proof nor final frequency qualification. ### TINY-ICS55-P1 - Default PDK and PLL Platform Enablement -This is a new, pending implementation prerequisite before remaining R2 work, +This is the platform implementation prerequisite before remaining R2 work, not a renaming of R2-P1/P2. Introduce the default ICS55 Tiny profile and explicit PDK selection, reuse the locked SRAM and PLL inputs, adapt filelists, technology bindings and observation endpoints, and establish SAFE24 PLL-present boot, @@ -196,7 +198,9 @@ Acceptance requires an executable source-bound platform, actual macro binding, both simulators and strict Tiny verdicts, dependency/model identity, relevant synthesis/netlist checks and observational timing with explicit gaps. Physical timing closure is deferred to the combined final campaign. This refreeze -implements only documentation and dependency acquisition, not this phase. +implemented only documentation and dependency acquisition. The subsequent +implementation and its source-bound acceptance status are recorded separately +in the ledger; neither enables R2-P7 clock switching or final physical qualification. ### Commercial references and reuse boundary @@ -2195,8 +2199,9 @@ evidence class. Record exact directed/formal commands when those cases are introduced; do not assume a nonexistent target, a skipped RTL test or another product's result satisfies Tiny acceptance. Before the combined final campaign, post-synthesis timing is observational under TINY-053. The commands above -remain the existing IHP130 executable baseline; the proposed ICS55 Tiny profile -and its commands must be introduced and verified by TINY-ICS55-P1. +retain the historical IHP130 compatibility entrypoint. The default ICS55 +platform's explicit commands and evidence boundaries are documented in +[the platform runbook](tiny-ics55-platform.md). ## Archived P0-P12 development order and acceptance @@ -2591,19 +2596,19 @@ concurrency and final physical product qualification are different claims. ## Implementation handoff -The next platform step after the 2026-10-07 refreeze is TINY-ICS55-P1 preflight. -Historical R2-P1/P2 evidence remains IHP130-only. After platform enablement, +The current handoff is review of TINY-ICS55-P1 implementation and its retained +evidence. Historical R2-P1/P2 evidence remains IHP130-only. After acceptance, resume the remaining Tiny foundation, then SPI, PIO-lite and PPALite functional phases, followed by the combined final qualification campaign. This handoff authorizes no implementation or automatic phase advancement. ```text -Use $retrosoc-feature-implementation in preflight mode for feature tiny-soc. +Use $retrosoc-feature-review in read-only review mode for feature tiny-soc. Target SoCs: TINY. Phase: TINY-ICS55-P1 - Default PDK and PLL Platform Enablement. Specification: docs/ip/tiny-soc.md; evidence: docs/ip/tiny-soc-verification.md. -Current executable reference: configs/ci/ihp130-tiny.mk, IHP130, 24 MHz/no PLL. Planned default: configs/ci/ics55-tiny.mk, ICS55, HAVE_PLL=YES, SAFE24 boot; do not pretend this profile already exists. Map the current source/worktree, locked OpenECOS SRAM and pdk_ics55_pll inputs, models, technology bindings and tools. PLL pin: 6ebb1a8f7f4ccbccdb7f587664fdfe63cd39e61b. Its Liberty has no timing arcs and its interface has no LOCK output; retain those qualification gaps. -Plan the smallest platform/profile/filelist/PLL-backend integration and source-bound functional baseline. Preserve QFN64 and IO/power assignments, 128 KiB CPU-rate main SRAM, private Crypto storage, boot/debug, original workload binaries and IHP130/shared-Mini compatibility. No half-rate SRAM fallback. Preserve selector 5/7 mappings for PLL192/240 and reject the old N=2/four-cycle lock assumption. +Default platform: configs/ci/ics55-tiny.mk, ICS55, HAVE_PLL=YES, external SAFE24 with the PLL explicitly parked off. Compatibility: configs/ci/ihp130-tiny.mk, IHP130, 24 MHz/no PLL. Read docs/ip/tiny-ics55-platform.md and the implementation ledger; distinguish the worktree based on ecac3559b0ba67fa2630003ad718657969c78af4 from historical results. Audit actual source, locked macro/model inputs and retained command verdicts. PLL pin: 6ebb1a8f7f4ccbccdb7f587664fdfe63cd39e61b; Liberty has no timing arcs and no LOCK output. +Review default selection, truthful ARCHINFO, 32 x 4 KiB SRAM mapping at CPU SYS, constant-disabled PLL binding, selector 5/7 backend qualification, native and functional pad readback, JTAG halt/resume, strict dual-simulator firmware/netlist results and original matched workload HEX. Preserve QFN64, private Crypto storage, boot/debug and IHP130/shared-Mini compatibility. No half-rate SRAM fallback. Check source/input/tool hashes and distinguish digital PLL rate observation from analog lock. Full Tiny RCU transitions and dynamic rate reporting remain R2-P7; do not implement later CPU/banking/fabric/DMA/peripheral phases. Preserve dependency versions, warning baselines and metrics policy. Retain required synthesis/mapping/netlist-functional checks and observational STA, with failures and missing inputs explicit. No intermediate post-synthesis timing closure gate; final timing and physical qualification occur only on the complete SPI/PIO-lite/PPALite-inclusive product. -Return a single-phase preflight with exact proposed changes, ownership, validation commands, blockers and acceptance boundaries. Read-only preflight only: no edits, dependency restoration, builds, simulations or STA. Do not commit, push, create a PR or advance a phase. Stop for explicit implementation approval. +Return prioritized findings with file/line evidence and phase readiness; identify failed, unrun or historical gates. Read-only review only: no edits, restoration, builds, simulations or STA. Do not commit, push, create a PR or advance a phase. Stop for human acceptance and separate approval before any next-phase implementation. ``` diff --git a/docs/soc-family-positioning.md b/docs/soc-family-positioning.md index 53841549..34e3832b 100644 --- a/docs/soc-family-positioning.md +++ b/docs/soc-family-positioning.md @@ -6,8 +6,9 @@ This document defines the intended Tiny, Mini, Std, and Pro product ladder. Tiny and Mini have executable build profiles; Std and Pro remain roadmap targets. Committed product profiles and retained validation evidence define implemented support. Tiny's 2026-10-07 specification selects ICS55/PLL as its -default wired-MCU target; current executable Tiny remains IHP130/no PLL until -TINY-ICS55-P1. IHP130 remains explicit compatibility, and Mini defaults are unchanged. +default wired-MCU target. Its executable profile keeps SYS on external SAFE24 +and the PLL parked off. IHP130 remains explicit compatibility, and Mini +defaults are unchanged. Mini is the family anchor. It establishes the common product model: an open RISC-V SoC in which a small, always-available Hazard3 management core owns @@ -44,7 +45,8 @@ verification requirements differ materially. The [2026-10-07 refreeze](ip/tiny-soc.md#ics55-default-platform-and-final-timing-gate-2026-10-07) requires ICS55 with `HAVE_PLL=YES`, SAFE24 startup and unchanged CPU-rate -128 KiB SRAM. The planned `configs/ci/ics55-tiny.mk` is not executable yet. +128 KiB SRAM. `configs/ci/ics55-tiny.mk` implements that SAFE24 platform; +PLL192/240 backend tests do not qualify those frequencies as SoC operating points. After platform enablement, the order is remaining Tiny foundation, SPI, PIO-lite, PPALite, then one complete-product final physical campaign. Earlier post-synthesis timing is observational; functionality and source-bound evidence diff --git a/physical/pdk/README.md b/physical/pdk/README.md index 1193fb56..e56910b5 100644 --- a/physical/pdk/README.md +++ b/physical/pdk/README.md @@ -51,8 +51,10 @@ Liberty views and prints their hashes. No profile or RTL binding is changed. The views have no characterized timing arcs or LOCK output; licensing is undetermined. Functional modeling, actual lock/loss detection, supply binding and final timing characterization remain separate integration/release work. -Tiny's new default ICS55/PLL specification still requires TINY-ICS55-P1; -current executable Tiny configurations remain IHP130/no PLL. +Tiny's default `configs/ci/ics55-tiny.mk` instantiates one disabled PLL and +32 locked 4 KiB SRAM macros at external SAFE24. Its digital backend tests and +core STA observations do not qualify analog PLL behavior or PLL-driven SYS. +IHP130/no PLL remains explicit Tiny compatibility. The IHP130 dependency is also the technology source for the independent `physical/librelane/{mini,tiny}/` core-hardening and full-chip pad-ring flows. diff --git a/physical/smoke/sta/opensta/opensta.mk b/physical/smoke/sta/opensta/opensta.mk index 2073f969..901adca7 100644 --- a/physical/smoke/sta/opensta/opensta.mk +++ b/physical/smoke/sta/opensta/opensta.mk @@ -20,6 +20,11 @@ $(OPENSTA_SDC): $(OPENSTA_SDC_GEN) $(OPENSTA_DOMAIN_MAP) $(OPENSTA_PIN_MAP) | ma sta: $(OPENSTA_SDC) | manifest @mkdir -p $(STA_BUILD_ROOT) +ifeq ($(TINY_SAFE24_PLL_OFF),YES) + python3 $(ROOT_PATH)/scripts/tiny_ics55_platform.py --variant-root $(VARIANT_ROOT) verify-netlist \ + --netlist $(OPENSTA_NETLIST) --config $(OPENSTA_CONFIG) \ + --output $(SYN_BUILD_ROOT)/tiny-ics55-netlist.json +endif @for input in $(OPENSTA_NETLIST) $(OPENSTA_LIBERTY) $(OPENSTA_LINK_LIBS) $(OPENSTA_SRAM_LIBS) $(OPENSTA_SDC) $(OPENSTA_CONFIG); do \ test -f "$$input" || { echo "OpenSTA input missing: $$input" >&2; exit 1; }; \ done @@ -40,7 +45,7 @@ sta: $(OPENSTA_SDC) | manifest --env OPENSTA_TOP=$(OPENSTA_TOP) --env OPENSTA_NETLIST=$(OPENSTA_NETLIST) --env OPENSTA_LIBERTY=$(OPENSTA_LIBERTY) \ --env 'OPENSTA_LINK_LIBS=$(OPENSTA_LINK_LIBS)' --env 'OPENSTA_SRAM_LIBS=$(OPENSTA_SRAM_LIBS)' \ --env OPENSTA_SDC=$(OPENSTA_SDC) --env OPENSTA_REPORT=$(OPENSTA_REPORT) \ - --env OPENSTA_METRICS=$(OPENSTA_METRICS) -- \ + --env OPENSTA_METRICS=$(OPENSTA_METRICS) --env OPENSTA_TINY_ICS55=$(TINY_SAFE24_PLL_OFF) -- \ $(OPENSTA) -no_init -exit -threads $(OPENSTA_THREADS) $(ROOT_PATH)/physical/smoke/sta/opensta/opensta.tcl -.PHONY: sta \ No newline at end of file +.PHONY: sta diff --git a/physical/smoke/sta/opensta/opensta.tcl b/physical/smoke/sta/opensta/opensta.tcl index d852b3d7..966a9e50 100644 --- a/physical/smoke/sta/opensta/opensta.tcl +++ b/physical/smoke/sta/opensta/opensta.tcl @@ -20,6 +20,9 @@ if {[info exists ::env(OPENSTA_TOP)] && $::env(OPENSTA_TOP) ne ""} { } link_design $top_design read_sdc $::env(OPENSTA_SDC) +if {[info exists ::env(OPENSTA_TINY_ICS55)] && $::env(OPENSTA_TINY_ICS55) eq "YES"} { + source [file join [file dirname [info script]] tiny_ics55_checks.tcl] +} report_checks -path_delay min_max -sort_by_slack -slack_max 0.0 -group_path_count 1000 -endpoint_path_count 1000 > $::env(OPENSTA_REPORT) diff --git a/physical/smoke/sta/opensta/pdk_timing.mk b/physical/smoke/sta/opensta/pdk_timing.mk index 9597e066..05d8a0ed 100644 --- a/physical/smoke/sta/opensta/pdk_timing.mk +++ b/physical/smoke/sta/opensta/pdk_timing.mk @@ -26,6 +26,9 @@ endif else ifeq ($(PDK),ICS55) OPENSTA_LIBERTY := $(ROOT_PATH)/.cache/retrosoc/pdk/ics55/ics55_h7cr_ss.lib OPENSTA_LINK_LIBS := $(ROOT_PATH)/physical/pdk/icsprout55-pdk/IP/IO/ICsprout_55LLULP1233_IO_251013/liberty/ICSIOA_N55_3P3_ss_1p08_2p97_125c.lib +ifeq ($(SOC),TINY) +OPENSTA_LINK_LIBS += $(ROOT_PATH)/.cache/retrosoc/sources/ics55_ecos_pll/lib/PLL_TOP_max.lib +endif ifeq ($(HAVE_SRAM_MACRO),YES) OPENSTA_SRAM_LIBS := \ $(ROOT_PATH)/.cache/retrosoc/pdk/ics55/sram/ics55_ecos_sram_1024x32_m8/lib/ics55_ecos_sram_1024x32_m8_ss1p08v125ccmax.lib \ @@ -35,4 +38,4 @@ else $(error OpenSTA core-STA does not support PDK=$(PDK)) endif -OPENSTA_SRAM_LIBS ?= \ No newline at end of file +OPENSTA_SRAM_LIBS ?= diff --git a/physical/smoke/sta/opensta/tiny_ics55_checks.tcl b/physical/smoke/sta/opensta/tiny_ics55_checks.tcl new file mode 100644 index 00000000..58642eb7 --- /dev/null +++ b/physical/smoke/sta/opensta/tiny_ics55_checks.tcl @@ -0,0 +1,22 @@ +# The Python precondition binds the consumed netlist to its structural audit: +# one disabled PLL, 32 SYS SRAM macros and common CPU/SRAM clock connectivity. +# This script adds observations, not PLL timing arcs or new timing exceptions. +set audit_dir [file dirname $::env(OPENSTA_REPORT)] +set pll [get_cells -hierarchical -filter {ref_name == PLL_TOP}] +set ram [get_cells -hierarchical -filter {ref_name == ics55_ecos_sram_1024x32_m8}] +if {[llength $pll] != 1 || [llength $ram] != 32} { error "Tiny ICS55 macro binding mismatch" } +if {[llength [get_clocks clk_system]] != 1} { error "Tiny SYS clock missing" } +report_clock_properties > $audit_dir/clocks.rpt +check_setup -verbose > $audit_dir/coverage.rpt +report_check_types -recovery -removal -min_pulse_width -min_period -max_slew -max_capacitance -max_fanout -violators -verbose > $audit_dir/constraint-violations.rpt +report_checks -path_group clk_system -path_delay max -group_path_count 20 -fields {capacitance slew fanout} > $audit_dir/sys-setup.rpt +report_checks -path_group clk_system -path_delay min -group_path_count 20 -fields {capacitance slew fanout} > $audit_dir/sys-hold.rpt +set ram_inputs {} +set ram_outputs {} +foreach pin [get_pins -of_objects $ram] { + if {[get_property $pin direction] eq "input"} { lappend ram_inputs $pin } + if {[get_property $pin direction] eq "output"} { lappend ram_outputs $pin } +} +report_checks -to $ram_inputs -path_delay min_max -group_path_count 20 > $audit_dir/sram-input-paths.rpt +report_checks -from $ram_outputs -path_delay min_max -group_path_count 20 > $audit_dir/sram-return-paths.rpt +puts "TINY_ICS55_STA_AUDIT_PASS: SAFE24 only; PLL held off; no PLL timing qualification" diff --git a/physical/smoke/syn/yosys/script/init_tech.tcl b/physical/smoke/syn/yosys/script/init_tech.tcl index dbbae188..99653a69 100644 --- a/physical/smoke/syn/yosys/script/init_tech.tcl +++ b/physical/smoke/syn/yosys/script/init_tech.tcl @@ -48,6 +48,9 @@ if {$pdk == "IHP130"} { set pdk_io_lib "${pdk_dir}/IP/IO/ICsprout_55LLULP1233_IO_251013/liberty" set tech_cells [list "$pdk_cells_lib/ics55_h7cr_tt.lib"] set tech_macros [list "$pdk_io_lib/ICSIOA_N55_3P3_tt_1p2_3p3_25c.lib"] + if {$soc == "TINY"} { + lappend tech_macros "$root_dir/.cache/retrosoc/sources/ics55_ecos_pll/lib/PLL_TOP_typ.lib" + } if {$have_sram_macro} { lappend tech_macros "$pdk_cells_lib/sram/ics55_ecos_sram_1024x32_m8/lib/ics55_ecos_sram_1024x32_m8_tt1p2v25cctyp.lib" lappend tech_macros "$pdk_cells_lib/sram/ics55_ecos_sram_4096x32_m8/lib/ics55_ecos_sram_4096x32_m8_tt1p2v25cctyp.lib" diff --git a/physical/smoke/syn/yosys/script/synth.tcl b/physical/smoke/syn/yosys/script/synth.tcl index a2709b24..568ca6d9 100644 --- a/physical/smoke/syn/yosys/script/synth.tcl +++ b/physical/smoke/syn/yosys/script/synth.tcl @@ -165,6 +165,9 @@ yosys tee -q -o "${report_dir}/${proj_name}_area_logic.rpt" stat -top $top_desig # final netlist yosys write_verilog -noattr -noexpr -nohex -nodec $netlist +if {$soc == "TINY" && $pdk == "ICS55"} { + yosys write_json ${netlist}.json +} # Record the configuration only after the netlist has been written successfully. set config_tmp "${config}.tmp" @@ -176,5 +179,7 @@ puts $config_file "SYNTH_RECIPE=$synth_recipe" puts $config_file "PERIOD_PS=$period_ps" puts $config_file "HAVE_SRAM_MACRO=$::env(HAVE_SRAM_MACRO)" puts $config_file "SRAM_SIZE_KIB=$::env(SRAM_SIZE_KIB)" +if {[info exists ::env(HAVE_PLL)]} { puts $config_file "HAVE_PLL=$::env(HAVE_PLL)" } +if {[info exists ::env(CONFIG_DIGEST)]} { puts $config_file "CONFIG_DIGEST=$::env(CONFIG_DIGEST)" } close $config_file file rename -force $config_tmp $config diff --git a/physical/smoke/syn/yosys/yosys.mk b/physical/smoke/syn/yosys/yosys.mk index 38802fce..4ac51392 100644 --- a/physical/smoke/syn/yosys/yosys.mk +++ b/physical/smoke/syn/yosys/yosys.mk @@ -67,12 +67,17 @@ $(NETLIST): $(SV_FLIST) $(YOSYS_SCRIPTS) --log $(YOSYS_BUILD)/$(RTL_NAME).log --result $(YOSYS_BUILD)/result-synth.json \ --env PDK=$(PDK) --env SOC=$(SOC) --env SYNTH_RECIPE=$(SYNTH_RECIPE) \ --env HAVE_SRAM_MACRO=$(HAVE_SRAM_MACRO) --env SRAM_SIZE_KIB=$(SRAM_SIZE_KIB) \ + --env HAVE_PLL=$(HAVE_PLL) --env CONFIG_DIGEST=$(CONFIG_DIGEST) \ --env YOSYS_TARGET_PERIOD_PS=$(YOSYS_TARGET_PERIOD_PS) \ --env SV_FLIST=$(SV_FLIST) --env TOP_DESIGN=$(TOP_DESIGN) --env CONFIG=$(NETLIST_CONFIG) \ --env PROJ_NAME=$(RTL_NAME) --env WORK=$(YOSYS_TMP) --env BUILD=$(YOSYS_OUT) \ --env REPORTS=$(YOSYS_RPT) --env NETLIST=$(NETLIST) -- \ timeout --foreground --kill-after=5s $(YOSYS_TIMEOUT)s \ yosys -t --perffile $(YOSYS_BUILD)/yosys-perf.json -c $(YOSYS_DIR)/script/synth.tcl +ifeq ($(SOC)-$(PDK),TINY-ICS55) + python3 $(ROOT_PATH)/scripts/tiny_ics55_platform.py --variant-root $(VARIANT_ROOT) audit-netlist \ + --netlist $(NETLIST) --config $(NETLIST_CONFIG) --output $(YOSYS_BUILD)/tiny-ics55-netlist.json +endif $(NETLIST_DEBUG): $(NETLIST) @test -f $@ @@ -80,4 +85,4 @@ $(NETLIST_DEBUG): $(NETLIST) synth_clean: python3 $(ROOT_PATH)/scripts/clean.py --root $(ROOT_PATH) --path $(YOSYS_BUILD) -.PHONY: gen_synth_filelist synth_clean synth \ No newline at end of file +.PHONY: gen_synth_filelist synth_clean synth diff --git a/publications/tiny_reference.py b/publications/tiny_reference.py index b645f3c8..42c4a022 100644 --- a/publications/tiny_reference.py +++ b/publications/tiny_reference.py @@ -68,8 +68,11 @@ def facts(root: Path = ROOT) -> dict: ram = instance_parameters(top, "onchip_ram", "u_sram") dma = instance_parameters(top, "apb4_dma", "u_dma") cpu = instance_parameters(top, "mgmt_core_wrapper", "u_cpu") - if ram != {"CapacityKiB": 128, "DataWidth": 32, "IdWidth": 1}: + if ram != {"CapacityKiB": 128, "DataWidth": 32, "IdWidth": 1, "Ics55SmallBanks": 1}: raise ValueError("Tiny SRAM parameters changed") + # The publication remains the reviewed IHP130 compatibility profile; + # selecting ICS55's 4 KiB geometry does not alter its reported SRAM facts. + ram = {key: value for key, value in ram.items() if key != "Ics55SmallBanks"} if dma != {"NumChannels": 4, "MaxBurstBeats": 16, "FifoDepth": 32, "RequestMask": 0x7F9, "EnableStreams": 0}: raise ValueError("Tiny DMA parameters changed") if cpu != {"ExternalIrqCount": 30, "EnableAtomics": 0, "TwoCycleBusErrors": 1, "ResetSyncStages": 5}: @@ -82,7 +85,15 @@ def facts(root: Path = ROOT) -> dict: require_snippets(root, SYSCTRL, ["(apb4.paddr[1:0] != 2'd0)", "(apb4.pstrb != 4'hf)", "if (!s_test_q[31] && apb4.pwdata[31]) s_test_d = apb4.pwdata & 32'h8000_ff01;", "if (!s_fault_stat_d[0])", "if (rtc_wake_i) s_wake_d = 1'b1;"]) - arch = instance_parameters((root / ARCHINFO).read_text(encoding="utf-8").split(".BUILD_ID")[0] + ") u_archinfo (", "apb4_archinfo", "u_archinfo") + from scripts.generate_tiny import platform_values + platform = platform_values(profile["PDK"], profile["HAVE_PLL"] == "YES") + arch_text = (root / ARCHINFO).read_text(encoding="utf-8") + require_snippets(root, ARCHINFO, ['`include "tiny_platform.svh"', + ".FEATURES0(`RETROSOC_TINY__FEATURES0)", + ".TECHNOLOGY(`RETROSOC_TINY__TECHNOLOGY)"]) + arch_text = arch_text.replace("`RETROSOC_TINY__FEATURES0", f"32'h{platform['features0']:08x}") + arch_text = arch_text.replace("`RETROSOC_TINY__TECHNOLOGY", f"32'h{platform['technology']:08x}") + arch = instance_parameters(arch_text.split(".BUILD_ID")[0] + ") u_archinfo (", "apb4_archinfo", "u_archinfo") if arch != {"REFERENCE_CLOCK_HZ": 24000000, "SRAM_BYTES": 131072, "TOPOLOGY": 0x20200001, "FEATURES0": 0x7FFE, "TECHNOLOGY": 0x02010082}: raise ValueError("Tiny discovery parameters changed") @@ -227,7 +238,7 @@ def collect(config: dict, *, check_snapshot: bool = True) -> dict: evidence = read(ROOT / BOOK / "evidence.json") validate_evidence(evidence) lock = read(ROOT / "dependencies/dependencies.lock.json") - paths = {MAP, TOPOLOGY, PINS, CLOCKS, TOP, RESET_TREE, SYSCTRL, ARCHINFO, config["profile"], + paths = {MAP, TOPOLOGY, PINS, CLOCKS, TOP, RESET_TREE, SYSCTRL, ARCHINFO, "scripts/generate_tiny.py", config["profile"], "docs/ip/tiny-soc.md", "docs/ip/tiny-soc-verification.md", "dependencies/dependencies.lock.json", "LICENSE"} paths.update(contract["sources"]) paths.update(b["file"] for b in contract["bindings"]) diff --git a/rtl/ip/memory/onchip_ram.sv b/rtl/ip/memory/onchip_ram.sv index 4adec926..f5b8f66a 100644 --- a/rtl/ip/memory/onchip_ram.sv +++ b/rtl/ip/memory/onchip_ram.sv @@ -12,10 +12,11 @@ `include "mmap_define.svh" module onchip_ram #( - parameter bit Present = 1'b1, - parameter int unsigned CapacityKiB = 128, - parameter int unsigned DataWidth = 32, - parameter int unsigned IdWidth = 1 + parameter bit Present = 1'b1, + parameter int unsigned CapacityKiB = 128, + parameter int unsigned DataWidth = 32, + parameter int unsigned IdWidth = 1, + parameter bit Ics55SmallBanks = 1'b0 ) ( // verilog_format: off -- preserve the AXI, APB, and performance grouping input logic clk_i, @@ -27,8 +28,10 @@ module onchip_ram #( // verilog_format: on ); `ifdef PDK_ICS55 - localparam int unsigned TechnologyBankKiB = 16; - localparam int unsigned TechnologyAddrWidth = 12; + // Mini retains its existing 16 KiB macro geometry. Tiny selects 4 KiB + // physical macros without adding logical services or changing arbitration. + localparam int unsigned TechnologyBankKiB = (Ics55SmallBanks && DataWidth == 32) ? 4 : 16; + localparam int unsigned TechnologyAddrWidth = (Ics55SmallBanks && DataWidth == 32) ? 10 : 12; `else localparam int unsigned TechnologyBankKiB = 4; localparam int unsigned TechnologyAddrWidth = 10; @@ -382,15 +385,27 @@ module onchip_ram #( assign s_bank_cs[bank] = (s_mem_read || s_mem_write) && (s_mem_word_addr[14:TechnologyAddrWidth] == (15 - TechnologyAddrWidth)'(bank)); `ifdef PDK_ICS55 - tc_sram_4096x32 u_ram ( - .clk_i (clk_i), - .cs_i (s_bank_cs[bank]), - .addr_i(s_mem_word_addr[11:0]), - .data_i(s_mem_wdata), - .mask_i(s_mem_wstrb), - .wren_i(s_mem_write), - .data_o(s_bank_rdata[bank]) - ); + if (Ics55SmallBanks) begin : gen_small + tc_sram_1024x32 u_ram ( + .clk_i (clk_i), + .cs_i (s_bank_cs[bank]), + .addr_i(s_mem_word_addr[9:0]), + .data_i(s_mem_wdata), + .mask_i(s_mem_wstrb), + .wren_i(s_mem_write), + .data_o(s_bank_rdata[bank]) + ); + end else begin : gen_large + tc_sram_4096x32 u_ram ( + .clk_i (clk_i), + .cs_i (s_bank_cs[bank]), + .addr_i(s_mem_word_addr[11:0]), + .data_i(s_mem_wdata), + .mask_i(s_mem_wstrb), + .wren_i(s_mem_write), + .data_o(s_bank_rdata[bank]) + ); + end `else tc_sram_1024x32 u_ram ( .clk_i (clk_i), @@ -695,6 +710,7 @@ module onchip_ram #( `ifndef SYNTHESIS initial begin if (((DataWidth != 32) && (DataWidth != 64)) || (IdWidth < 1) || + (Ics55SmallBanks && (DataWidth != 32)) || ((DataWidth % 8) != 0) || ((CapacityKiB != 4) && (CapacityKiB != 16) && (CapacityKiB != 32) && (CapacityKiB != 64) && (CapacityKiB != 128))) begin diff --git a/rtl/mini/filelist/tech.fl b/rtl/mini/filelist/tech.fl index f3e25a2d..b1a12a91 100644 --- a/rtl/mini/filelist/tech.fl +++ b/rtl/mini/filelist/tech.fl @@ -4,5 +4,6 @@ /tech/tc_clk.sv /tech/tc_opipsram_delay.sv /tech/tc_pll.sv +/tech/tc_pll_ics55_ecos.sv /tech/tc_sram.sv /tech/tc_usb2_packet_ram.sv diff --git a/rtl/tech/tc_io.sv b/rtl/tech/tc_io.sv index e5500fce..4ec3e4cd 100644 --- a/rtl/tech/tc_io.sv +++ b/rtl/tech/tc_io.sv @@ -520,7 +520,9 @@ module tc_io_schmitt_in_pad ( `endif endmodule -module tc_io_tri_full_pad ( +module tc_io_tri_full_pad #( + parameter bit ReadWhileDriving = 1'b0 +) ( `ifdef PDK_IHP130 `ifdef USE_POWER_PINS inout wire iovdd, @@ -599,15 +601,15 @@ module tc_io_tri_full_pad ( .C (p2c), .A (), .PAD(pad), - .IE (~c2p_en), - .CS (cs), // 1: CMOS 0: SCHMI + .IE (ReadWhileDriving || !c2p_en), + .CS (cs), // 1: CMOS 0: SCHMI .I (c2p), .OE (c2p_en), .OD (1'b0), - .PU (pu), // active high - .PD (pd), // active high + .PU (pu), // active high + .PD (pd), // active high .DS0(1'b0), - .DS1(1'b1) // 8mA + .DS1(1'b1) // 8mA ); `endif diff --git a/rtl/tech/tc_pll.sv b/rtl/tech/tc_pll.sv index e4a6e305..434178e4 100644 --- a/rtl/tech/tc_pll.sv +++ b/rtl/tech/tc_pll.sv @@ -8,7 +8,10 @@ // MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. -module tc_pll ( +module tc_pll #( + parameter bit UseIcs55EcosPll = 1'b0, + parameter bit Ics55Parked = 1'b0 +) ( input logic fref_i, input logic rst_n_i, input logic [2:0] cfg_sel_i, @@ -117,6 +120,27 @@ module tc_pll ( ); `elsif PDK_ICS55 `ifdef HAVE_PLL +`ifdef RETROSOC_SOC__TINY + if (UseIcs55EcosPll) begin : gen_ecos + tc_pll_ics55_ecos #( + .Parked(Ics55Parked) + ) u_backend ( + .fref_i (fref_i), + .rst_n_i (rst_n_i), + .cfg_sel_i (cfg_sel_i), + .cfg_apply_i (cfg_apply_i), + .pll_capable_o(pll_capable_o), + .pll_lock_o (pll_lock_o), + .pll_clk_o (pll_clk_o) + ); + end else begin : gen_unsupported + assign pll_capable_o = 1'b0; + assign pll_lock_o = 1'b0; + assign pll_clk_o = 1'b0; + end +`else + // Retain the legacy Mini experimental binding and instance path. It is not + // the locked OpenECOS backend and does not qualify that macro. localparam logic [2:0] ICS55_SUPPORTED_SEL = 3'd0; localparam logic [7:0] ICS55_N = 8'd2; localparam logic [1:0] ICS55_OD = 2'd2; @@ -178,6 +202,7 @@ module tc_pll ( .OD (ICS55_OD), .N (ICS55_N) ); +`endif `else assign pll_capable_o = 1'b0; assign pll_lock_o = 1'b0; diff --git a/rtl/tech/tc_pll_ics55_ecos.sv b/rtl/tech/tc_pll_ics55_ecos.sv new file mode 100644 index 00000000..f18344af --- /dev/null +++ b/rtl/tech/tc_pll_ics55_ecos.sv @@ -0,0 +1,167 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 +// Digital output-clock qualification only; the macro has no analog LOCK pin. +// REFCLK must remain live. System source switching belongs to the Tiny RCU. +module tc_pll_ics55_ecos #( + parameter bit Parked = 1'b0 +) ( + input logic fref_i, + input logic rst_n_i, + input logic [2:0] cfg_sel_i, + input logic cfg_apply_i, + output logic pll_capable_o, + output logic pll_lock_o, + output logic pll_clk_o +); + logic s_ref_rst_n, s_monitor_rst_n, s_pll_rst_n; + logic s_apply_seen_q, s_apply, s_valid_q, s_enable; + logic [ 2:0] s_selection_q; + logic [ 1:0] s_restart_q; + logic [ 9:0] s_settle_q; + logic [12:0] s_budget_q; + logic [ 6:0] s_window_q; + logic s_discard_q, s_lock_q; + logic [2:0] s_good_q; + logic [15:0] s_count_q, s_count_next, s_gray_q, s_gray_next; + logic [15:0] s_gray_ref, s_count_ref, s_last_q, s_delta; + logic [15:0] s_expected, s_tolerance; + logic [7:0] s_n; + // Ideal functional supply connections, not a physical rail/ground merge. + wire s_vdd = 1'b1; + wire s_vss = 1'b0; + + rst_sync #( + .STAGE(5) + ) u_ref_reset ( + .clk_i (fref_i), + .rst_n_i(rst_n_i), + .rst_n_o(s_ref_rst_n) + ); + assign s_apply = cfg_apply_i && !s_apply_seen_q; + assign s_enable = !Parked && rst_n_i && s_ref_rst_n && s_valid_q && (s_restart_q == 0) && !s_apply; + assign s_n = (!Parked && s_selection_q == 3'd7) ? 8'd40 : 8'd32; + assign pll_capable_o = 1'b1; + assign pll_lock_o = s_lock_q && s_enable; + (* keep = 1, dont_touch = "true" *) + PLL_TOP u_PLL_TOP ( + .AVDD (s_vdd), + .AVSS (s_vss), + .DVDD (s_vdd), + .DVSS (s_vss), + .DVDD_DRV(s_vdd), + .DVSS_DRV(s_vss), + .EN (s_enable), + .BP (1'b0), + .N (s_n), + .SELECT (1'b0), + .OD (2'd2), + .REFCLK (fref_i), + .CKOUT1 (pll_clk_o), + .CKOUT2 (), + .CKTST () // No extra functional domain or package pin. + ); + + assign s_monitor_rst_n = s_enable && (s_settle_q == 10'd512); + rst_sync #( + .STAGE(5) + ) u_pll_reset ( + .clk_i (pll_clk_o), + .rst_n_i(s_monitor_rst_n), + .rst_n_o(s_pll_rst_n) + ); + assign s_count_next = s_count_q + 16'd1; + bin2gray #( + .DATA_WIDTH(16) + ) u_encode ( + .bin_i (s_count_next), + .gray_o(s_gray_next) + ); + // Register Gray in the source domain: combinational binary-to-Gray glitches + // must not cross into REFCLK. All bits belong to one Gray-coded counter. + always_ff @(posedge pll_clk_o or negedge s_pll_rst_n) begin + if (!s_pll_rst_n) begin + s_count_q <= '0; + s_gray_q <= '0; + end else begin + s_count_q <= s_count_next; + s_gray_q <= s_gray_next; + end + end + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(16) + ) u_count_sync ( + .clk_i (fref_i), + .rst_n_i(s_monitor_rst_n), + .dat_i (s_gray_q), + .dat_o (s_gray_ref) + ); + gray2bin #( + .DATA_WIDTH(16) + ) u_decode ( + .gray_i(s_gray_ref), + .bin_o (s_count_ref) + ); + assign s_delta = s_count_ref - s_last_q; + assign s_expected = (s_selection_q == 3'd7) ? 16'd1280 : 16'd1024; + assign s_tolerance = (s_selection_q == 3'd7) ? 16'd20 : 16'd16; + + always_ff @(posedge fref_i or negedge s_ref_rst_n) begin + if (!s_ref_rst_n) begin + s_apply_seen_q <= 1'b0; + s_selection_q <= '0; + s_valid_q <= 1'b0; + s_restart_q <= '0; + s_settle_q <= '0; + s_budget_q <= '0; + s_window_q <= '0; + s_discard_q <= 1'b1; + s_good_q <= '0; + s_lock_q <= 1'b0; + s_last_q <= '0; + end else begin + s_apply_seen_q <= cfg_apply_i; + if (s_apply) begin + s_selection_q <= cfg_sel_i; + s_valid_q <= (cfg_sel_i == 3'd5) || (cfg_sel_i == 3'd7); + s_restart_q <= 2'd2; + s_settle_q <= '0; + s_budget_q <= '0; + s_window_q <= '0; + s_discard_q <= 1'b1; + s_good_q <= '0; + s_lock_q <= 1'b0; + s_last_q <= '0; + end else if (s_valid_q) begin + if (s_restart_q != 0) begin + s_restart_q <= s_restart_q - 2'd1; + end else begin + if (!s_lock_q) s_budget_q <= s_budget_q + 13'd1; + else s_budget_q <= '0; + if (!s_lock_q && s_budget_q == 13'd4095) begin + s_valid_q <= 1'b0; + s_lock_q <= 1'b0; + end else if (s_settle_q != 10'd512) begin + s_settle_q <= s_settle_q + 10'd1; + end else if (s_window_q == 7'd127) begin + s_window_q <= '0; + s_last_q <= s_count_ref; + s_discard_q <= 1'b0; + if (s_discard_q) begin + s_good_q <= '0; + end else if ((s_delta >= s_expected - s_tolerance) && + (s_delta <= s_expected + s_tolerance)) begin + if (s_good_q < 3'd4) s_good_q <= s_good_q + 3'd1; + if (s_good_q >= 3'd3) s_lock_q <= 1'b1; + end else begin + s_good_q <= '0; + s_lock_q <= 1'b0; + end + end else begin + s_window_q <= s_window_q + 7'd1; + end + end + end + end + end +endmodule diff --git a/rtl/tiny/Makefile b/rtl/tiny/Makefile index 5eb17870..d39deecf 100644 --- a/rtl/tiny/Makefile +++ b/rtl/tiny/Makefile @@ -31,7 +31,8 @@ $(ARCHINFO_METADATA_STAMP): $(LOCK_FILE) $(wildcard $(ARCHINFO_METADATA_SCRIPT)) $(FILELIST_STAMP): $(ROOT_PATH)/Makefile $(RTL_PATH)/Makefile $(TINY_MAPS) $(TINY_TEMPLATES) $(TINY_TOOLS) $(ARCHINFO_METADATA_STAMP) $(PYTHON) $(ROOT_PATH)/scripts/generate_tiny.py --output $(TINY_GENERATED_DIR) \ - --filelists $(GENERATED_FL_DIR) $(foreach define,$(DEF_LIST),--define $(define)) + --filelists $(GENERATED_FL_DIR) --pdk $(PDK) --simulator $(SIMU) --synthesis $(SYNTH) \ + $(foreach define,$(DEF_LIST),--define $(define)) @touch $@ $(SOC_IRQ_METADATA): $(FILELIST_STAMP) @@ -50,7 +51,7 @@ check-clock-reset-domains: RTL_FILELISTS := $(addprefix $(GENERATED_FL_DIR)/,def.fl inc.fl commonip.fl clusterip.fl ip.fl tech.fl core_hazard3.fl top.fl) RTL_FLIST := $(foreach filelist,$(RTL_FILELISTS),-f $(filelist)) -PDK_FILELIST := $(GENERATED_FL_DIR)/pdk_ihp130.fl +PDK_FILELIST := $(GENERATED_FL_DIR)/pdk_selected.fl TINY_TB_FLIST := -f $(GENERATED_FL_DIR)/tb.fl TOP_DESIGN := retrosoc_tiny_asic RTL_NAME := retrosoc_tiny_asic @@ -62,6 +63,7 @@ TINY_DEPFILE := $(SIM_BUILD_ROOT)/sources.d TINY_LINT_LOG := $(VARIANT_ROOT)/lint/verilator/lint.log SOC_SIM_TIME ?= 1800 TINY_SIM_TIMEOUT ?= 20000000 +TINY_SIM_ARGS ?= VERILATOR ?= verilator IVERILOG ?= iverilog VVP ?= vvp @@ -110,7 +112,7 @@ check-rtl-lint: rtl-lint sim: comp firmware | manifest $(PYTHON) $(ROOT_PATH)/scripts/run_flow.py --tool $(SIM_TOOL_NAME)-sim \ --log $(SIM_BUILD_ROOT)/sim.log --result $(SIM_BUILD_ROOT)/result-sim.json -- \ - $(TINY_RUN) +firmware=$(SW_BUILD_DIR)/$(SIM_FIRMWARE_NAME).hex +max_cycles=$(TINY_SIM_TIMEOUT) + $(TINY_RUN) +firmware=$(SW_BUILD_DIR)/$(SIM_FIRMWARE_NAME).hex +max_cycles=$(TINY_SIM_TIMEOUT) $(TINY_SIM_ARGS) $(PYTHON) $(ROOT_PATH)/scripts/check_simulation.py --log $(SIM_BUILD_ROOT)/sim.log \ --result $(SIM_BUILD_ROOT)/result-sim-check.json --require '$(SIM_SUCCESS_MARKER)' @@ -172,4 +174,4 @@ tiny-r2-baseline-sim: | manifest tiny-r2-baseline-report: $(PYTHON) $(TINY_BASELINE_TOOL) --root $(ROOT_PATH) --variant-root $(VARIANT_ROOT) report -.PHONY: tiny-r2-baseline-image tiny-r2-baseline-sim tiny-r2-baseline-report \ No newline at end of file +.PHONY: tiny-r2-baseline-image tiny-r2-baseline-sim tiny-r2-baseline-report diff --git a/rtl/tiny/README.md b/rtl/tiny/README.md index 7dd1460a..e172e693 100644 --- a/rtl/tiny/README.md +++ b/rtl/tiny/README.md @@ -2,11 +2,12 @@ Tiny is the independent single-Hazard3 wired MCU product. Its frozen target contract is [Tiny Gen1 QFN64 R2](../../docs/ip/tiny-soc.md). The 2026-10-07 -specification default is ICS55, `HAVE_PLL=YES`, SAFE24 boot; TINY-ICS55-P1 -platform integration and `configs/ci/ics55-tiny.mk` are pending. IHP130 remains -explicit compatibility. For the current executable baseline, use -`make CONFIG=configs/ci/ihp130-tiny.mk setup` followed by -`make CONFIG=configs/ci/ihp130-tiny.mk firmware sim`. +default is ICS55, `HAVE_PLL=YES`, SAFE24 boot, with the PLL macro parked off. +Use `make CONFIG=configs/ci/ics55-tiny.mk setup` followed by +`make CONFIG=configs/ci/ics55-tiny.mk firmware sim`. IHP130/no PLL remains +explicit compatibility through `configs/ci/ihp130-tiny.mk`. See the +[platform runbook](../../docs/ip/tiny-ics55-platform.md) for source-bound +validation; backend frequency tests are not SYS switching or timing qualification. `top` owns product integration, `address_map`, `pin_map` and `integration` own canonical address, pad, IRQ and clock/reset inputs, `filelist` selects sources, diff --git a/rtl/tiny/dv/retrosoc_tiny_tb.sv b/rtl/tiny/dv/retrosoc_tiny_tb.sv index 2242b6bb..08172052 100644 --- a/rtl/tiny/dv/retrosoc_tiny_tb.sv +++ b/rtl/tiny/dv/retrosoc_tiny_tb.sv @@ -10,9 +10,12 @@ module retrosoc_tiny_tb; wire s_uart0_tx, s_uart1_tx; wire s_uart0_rx = 1'b1; wire s_uart1_rx = 1'b1; - wire s_jtag_tck = 1'b0; - wire s_jtag_tms = 1'b1; - wire s_jtag_tdi = 1'b0; + logic s_jtag_tck_driver = 1'b0; + logic s_jtag_tms_driver = 1'b1; + logic s_jtag_tdi_driver = 1'b0; + wire s_jtag_tck = s_jtag_tck_driver; + wire s_jtag_tms = s_jtag_tms_driver; + wire s_jtag_tdi = s_jtag_tdi_driver; wire s_jtag_trst_n = s_rst_n; wire s_jtag_tdo; tri1 [31:0] s_gpio; @@ -23,6 +26,7 @@ module retrosoc_tiny_tb; integer s_max_cycles = 20000000; integer s_done_cycles = 0; logic [ 7:0] s_uart_byte; + `include "tiny_jtag_smoke.svh" `ifdef RETROSOC_SOC__TINY_NETLIST wire [7:0] s_test_code = { @@ -51,7 +55,7 @@ module retrosoc_tiny_tb; if (u_dut.s_test_done) begin if (!u_dut.s_test_pass) $fatal(1, "SIM_TEST_FAIL code=%0d", s_test_code); s_done_cycles = s_done_cycles + 1; - if (s_done_cycles == 128) begin + if (s_done_cycles >= 128 && s_jtag_checks_done) begin $display("SIM_TEST_PASS Tiny cycles=%0d", s_cycles); $finish; end diff --git a/rtl/tiny/dv/tiny_jtag_smoke.svh b/rtl/tiny/dv/tiny_jtag_smoke.svh new file mode 100644 index 00000000..8b32a8bb --- /dev/null +++ b/rtl/tiny/dv/tiny_jtag_smoke.svh @@ -0,0 +1,98 @@ +// Pin-level JTAG/DTM/DMI acceptance, opt-in and independent of UART output. +logic s_jtag_checks_done = 1'b0; + +task automatic jtag_tick(input bit tms, input bit tdi, output logic tdo); + s_jtag_tms_driver = tms; + s_jtag_tdi_driver = tdi; + #50; + tdo = s_jtag_tdo; + s_jtag_tck_driver = 1'b1; + #50; + s_jtag_tck_driver = 1'b0; +endtask + +task automatic jtag_idle(input integer count); + bit ignored; + repeat (count) jtag_tick(0, 0, ignored); +endtask + +task automatic jtag_ir(input logic [4:0] instruction); + bit ignored; + jtag_tick(1, 0, ignored); + jtag_tick(1, 0, ignored); + jtag_tick(0, 0, ignored); + jtag_tick(0, 0, ignored); + for (int i = 0; i < 5; i++) jtag_tick(i == 4, instruction[i], ignored); + jtag_tick(1, 0, ignored); + jtag_tick(0, 0, ignored); +endtask + +task automatic jtag_dr(input integer width, input logic [40:0] data, output logic [40:0] result); + logic sampled; + result = '0; + jtag_tick(1, 0, sampled); + jtag_tick(0, 0, sampled); + jtag_tick(0, 0, sampled); + for (int i = 0; i < width; i++) begin + jtag_tick(i == width - 1, data[i], sampled); + if (sampled !== 1'b0 && sampled !== 1'b1) $fatal(1, "SIM_TEST_FAIL unknown shifted JTAG TDO"); + result[i] = sampled; + end + jtag_tick(1, 0, sampled); + jtag_tick(0, 0, sampled); +endtask + +task automatic jtag_dmi(input bit write_access, input logic [6:0] address, input logic [31:0] data, + output logic [31:0] result); + logic [40:0] response; + jtag_dr(41, {address, data, (write_access ? 2'd2 : 2'd1)}, response); + jtag_idle(16); + jtag_dr(41, 41'd0, response); + if (response[1:0] != 0) $fatal(1, "SIM_TEST_FAIL DMI status %0d", response[1:0]); + result = response[33:2]; +endtask + +initial begin : jtag_smoke + bit ignored; + logic [40:0] response; + logic [31:0] status; + integer attempts; + if ($test$plusargs("tiny_jtag_smoke")) begin + // Exercise reset with a live TCK before releasing the external reset. + // Four-state models must observe an asserted reset, not X -> 1. + repeat (6) jtag_tick(1, 0, ignored); + wait (s_rst_n); + // Five-edge JTAG reset release plus TAP reset; SYS remains independent. + repeat (12) jtag_tick(1, 0, ignored); + jtag_idle(1); + jtag_ir(5'h01); + jtag_dr(32, 41'd0, response); + if (response[31:0] != 32'(`SOC_JTAG_IDCODE)) + $fatal(1, "SIM_TEST_FAIL JTAG IDCODE %h", response[31:0]); + jtag_ir(5'h10); + jtag_dr(32, 41'd0, response); + if (response[3:0] != 1 || response[9:4] != 7) + $fatal(1, "SIM_TEST_FAIL DTMCS %h", response[31:0]); + jtag_ir(5'h11); + jtag_dmi(1, 7'h10, 32'h00000001, status); + jtag_dmi(1, 7'h10, 32'h80000001, status); + attempts = 0; + status = 0; + while (!status[9] && attempts < 256) begin + jtag_dmi(0, 7'h11, 32'd0, status); + attempts++; + end + if (!status[9]) $fatal(1, "SIM_TEST_FAIL JTAG halt timeout"); + jtag_dmi(1, 7'h10, 32'h40000001, status); + attempts = 0; + status = 0; + while (!status[11] && attempts < 256) begin + jtag_dmi(0, 7'h11, 32'd0, status); + attempts++; + end + if (!status[11]) $fatal(1, "SIM_TEST_FAIL JTAG resume timeout"); + jtag_dmi(1, 7'h10, 32'h00000001, status); + $display("SIM_TEST_PASS Tiny JTAG transport halt/resume"); + end + s_jtag_checks_done = 1'b1; +end diff --git a/rtl/tiny/filelist/tech.fl b/rtl/tiny/filelist/tech.fl index e82b3a91..c93e2107 100644 --- a/rtl/tiny/filelist/tech.fl +++ b/rtl/tiny/filelist/tech.fl @@ -1,3 +1,5 @@ /tech/tc_io.sv /tech/tc_clk.sv +/tech/tc_pll.sv +/tech/tc_pll_ics55_ecos.sv /tech/tc_sram.sv diff --git a/rtl/tiny/integration/clock_reset_domains.json b/rtl/tiny/integration/clock_reset_domains.json index f2842d78..e9bb824d 100644 --- a/rtl/tiny/integration/clock_reset_domains.json +++ b/rtl/tiny/integration/clock_reset_domains.json @@ -1,5 +1,13 @@ { "schema_version": 2, + "inactive_macros": { + "ics55_pll": { + "cell": "PLL_TOP", + "scope": "TINY-ICS55-P1: present, EN=0, no SYS connection", + "active_clock_domain": false, + "qualification": "no characterized timing arcs; full RCU remains R2-P7" + } + }, "reset_ports": [ "ext_rst_n_i_pad", "jtag_trst_n_i_pad" diff --git a/rtl/tiny/mk/config.mk b/rtl/tiny/mk/config.mk index 59e65aef..1833a157 100644 --- a/rtl/tiny/mk/config.mk +++ b/rtl/tiny/mk/config.mk @@ -1,15 +1,26 @@ -# Tiny's first qualified integration is deliberately a fixed MCU profile. +# Both Tiny integrations remain SAFE24; PLL presence is not SYS selection. ifneq ($(MINI_MODE),NONE) $(error SOC=TINY requires MINI_MODE=NONE) endif -ifneq ($(PDK),IHP130) -$(error SOC=TINY currently supports only PDK=IHP130) +ifeq ($(filter $(PDK),IHP130 ICS55),) +$(error SOC=TINY supports only PDK=IHP130 or ICS55) endif ifneq ($(HAVE_HP),NO) $(error SOC=TINY requires HAVE_HP=NO) endif +ifeq ($(PDK),ICS55) +ifneq ($(HAVE_PLL),YES) +$(error SOC=TINY PDK=ICS55 requires HAVE_PLL=YES with the PLL held off) +endif +ifeq ($(PDK_BEHAV),YES) +$(error SOC=TINY PDK=ICS55 requires the locked technology models, PDK_BEHAV=NO) +endif +override TINY_SAFE24_PLL_OFF := YES +else ifneq ($(HAVE_PLL),NO) -$(error SOC=TINY requires HAVE_PLL=NO) +$(error SOC=TINY PDK=IHP130 requires HAVE_PLL=NO) +endif +override TINY_SAFE24_PLL_OFF := NO endif ifneq ($(HAVE_SRAM_IF),YES) $(error SOC=TINY requires HAVE_SRAM_IF=YES) @@ -46,4 +57,4 @@ $(error SOC=TINY firmware uses ISA=RV32IM) endif ifneq ($(filter YES,$(APU_ENABLE_P7) $(NPU_P5_ACCEPTANCE) $(NPU_P6_ACCEPTANCE)),) $(error SOC=TINY does not include multimedia accelerators) -endif \ No newline at end of file +endif diff --git a/rtl/tiny/top/retrosoc_tiny.sv b/rtl/tiny/top/retrosoc_tiny.sv index 867c8e61..7278291b 100644 --- a/rtl/tiny/top/retrosoc_tiny.sv +++ b/rtl/tiny/top/retrosoc_tiny.sv @@ -168,9 +168,10 @@ uart_if u_uart0_if (); .dma_wait_o (s_dma_wait) ); onchip_ram #( - .CapacityKiB(128), - .DataWidth (32), - .IdWidth (1) + .CapacityKiB (128), + .DataWidth (32), + .IdWidth (1), + .Ics55SmallBanks(1'b1) ) u_sram ( .clk_i (clk_i), .rst_n_i (s_sram_rst_n), diff --git a/rtl/tiny/top/retrosoc_tiny_asic.sv b/rtl/tiny/top/retrosoc_tiny_asic.sv index 0c89657b..ffa3ad18 100644 --- a/rtl/tiny/top/retrosoc_tiny_asic.sv +++ b/rtl/tiny/top/retrosoc_tiny_asic.sv @@ -14,6 +14,22 @@ module retrosoc_tiny_asic ( /* verilator lint_on UNUSEDSIGNAL */ gpio_if u_gpio_if (); xpi_if u_xpi_if (); +`ifdef HAVE_PLL + // P1 preserves the macro but supplies no request or reset release. SYS is + // always the external SAFE24 path; RCU source switching belongs to R2-P7. + tc_pll #( + .UseIcs55EcosPll(1'b1), + .Ics55Parked (1'b1) + ) u_pll ( + .fref_i (s_ext_clk), + .rst_n_i (1'b0), + .cfg_sel_i (3'd5), + .cfg_apply_i (1'b0), + .pll_capable_o(), + .pll_lock_o (), + .pll_clk_o () + ); +`endif `include "retrosoc_asic_pad_bindings.svh" tc_clk_buf u_clock_buffer ( .clk_i(s_ext_clk), diff --git a/rtl/tiny/top/tiny_archinfo.sv b/rtl/tiny/top/tiny_archinfo.sv index 40f0ad62..b156334b 100644 --- a/rtl/tiny/top/tiny_archinfo.sv +++ b/rtl/tiny/top/tiny_archinfo.sv @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MulanPSL-2.0 `include "archinfo_integration_metadata.svh" `include "archinfo_define.svh" +`include "tiny_platform.svh" // The locked ArchInfo V2 has a fixed Mini SOC_ID. This owned adapter changes // only that read-only identity, retaining the managed ABI and build metadata. @@ -29,8 +30,8 @@ module tiny_archinfo ( .REFERENCE_CLOCK_HZ(24_000_000), .SRAM_BYTES (131072), .TOPOLOGY (32'h2020_0001), - .FEATURES0 (32'h0000_7ffe), - .TECHNOLOGY (32'h0201_0082), + .FEATURES0 (`RETROSOC_TINY__FEATURES0), + .TECHNOLOGY (`RETROSOC_TINY__TECHNOLOGY), .BUILD_ID (`ARCHINFO_INTEGRATION_BUILD_ID), .CONFIG_ID (`ARCHINFO_INTEGRATION_CONFIG_ID), .BUILD_STATUS (`ARCHINFO_INTEGRATION_BUILD_STATUS) diff --git a/scripts/doctor.py b/scripts/doctor.py index 8e0c6961..9a574a15 100644 --- a/scripts/doctor.py +++ b/scripts/doctor.py @@ -25,6 +25,7 @@ def parse_args() -> argparse.Namespace: parser.add_argument("--pdk", required=True) parser.add_argument("--soc", choices=("MINI", "TINY"), default="MINI") parser.add_argument("--have-sram-macro", choices=("YES", "NO"), default="NO") + parser.add_argument("--have-pll", choices=("YES", "NO"), default="NO") parser.add_argument("--formal", choices=("YES", "NO"), default="NO") parser.add_argument("--require-debug-tools", action="store_true") parser.add_argument("--lock", type=Path, default=DEFAULT_LOCK) @@ -85,7 +86,23 @@ def main() -> int: } if args.pdk in pdk_names: source_names.append(pdk_names[args.pdk]) + if args.pdk == "ICS55" and args.soc == "TINY" and args.have_pll == "YES": + source_names.append("pdk_ics55_pll") paths = {name: root / lock["sources"][name]["destination"] for name in source_names} + if args.pdk == "ICS55": + for corner in ("tt", "ss"): + paths[f"ics55_cells_{corner}"] = root / f".cache/retrosoc/pdk/ics55/ics55_h7cr_{corner}.lib" + if args.have_sram_macro == "YES": + for size in ("1024", "4096"): + name = f"ics55_ecos_sram_{size}x32_m8" + for view in (f"verilog/{name}_core.v", f"verilog/{name}_stub.v", + f"lib/{name}_tt1p2v25cctyp.lib", f"lib/{name}_ss1p08v125ccmax.lib"): + paths[f"{name}/{view}"] = root / ".cache/retrosoc/pdk/ics55/sram" / name / view + if args.soc == "TINY" and args.have_pll == "YES": + from physical.pdk.setup import PLL_VIEWS + pll = root / lock["sources"]["pdk_ics55_pll"]["destination"] + for view in PLL_VIEWS: + paths[f"pll/{view}"] = pll / view if args.pdk == "SKY130" and args.have_sram_macro == "YES": paths["sky130_openram_sram"] = root / ".cache/retrosoc/pdk/sky130/openram" tool_results = { @@ -97,6 +114,17 @@ def main() -> int: } failures = sum(value["path"] is None for value in tool_results.values()) failures += sum(not value["exists"] for value in path_results.values()) + if "pdk_ics55_pll" in paths and paths["pdk_ics55_pll"].exists(): + try: + revision = subprocess.check_output(["git", "-C", str(paths["pdk_ics55_pll"]), + "rev-parse", "HEAD"], text=True).strip() + dirty = subprocess.check_output(["git", "-C", str(paths["pdk_ics55_pll"]), + "status", "--porcelain"], text=True).strip() + matching = revision == lock["sources"]["pdk_ics55_pll"]["revision"] and not dirty + except subprocess.CalledProcessError: + matching = False + path_results["pdk_ics55_pll"]["locked_clean_checkout"] = bool(matching) + failures += int(not matching) report = { "schema_version": 1, "ok": failures == 0, diff --git a/scripts/generate_tiny.py b/scripts/generate_tiny.py index 1b59cd38..8c359363 100644 --- a/scripts/generate_tiny.py +++ b/scripts/generate_tiny.py @@ -53,7 +53,17 @@ def read_topology(path: Path, memory_map: Path) -> dict: return document -def generate_bindings(document: dict, output: Path) -> None: +def platform_values(pdk: str, have_pll: bool = False) -> dict[str, int]: + if (pdk, have_pll) not in (("IHP130", False), ("ICS55", True)): + raise ValueError("unsupported Tiny PDK/PLL combination") + return {"technology": 0x02040037 if pdk == "ICS55" else 0x02010082, + "features0": 0x7FFE | int(have_pll), "pll_present": int(have_pll), + "gpio_pulls": int(pdk == "ICS55")} + + +def generate_bindings(document: dict, output: Path, pdk: str = "IHP130", + have_pll: bool = False) -> None: + platform = platform_values(pdk, have_pll) comment = "// Generated by scripts/generate_tiny.py; do not edit.\n" irq = [comment, "always_comb begin", " s_irq = '0;"] header = ["#ifndef RETROSOC_GENERATED_IRQ_METADATA_H", @@ -80,7 +90,13 @@ def generate_bindings(document: dict, output: Path) -> None: "#ifndef RETROSOC_GENERATED_SOC_FEATURES_H\n" "#define RETROSOC_GENERATED_SOC_FEATURES_H\n" "#define RS_SOC_TINY 1\n#define RS_SOC_HAS_PSRAM 0U\n" - "#define RS_SOC_DMA_CHANNELS 4U\n#define RS_SOC_ID 0x54494E59U\n#endif\n") + "#define RS_SOC_DMA_CHANNELS 4U\n#define RS_SOC_ID 0x54494E59U\n" + f"#define RS_SOC_TECHNOLOGY_ID 0x{platform['technology']:08X}U\n" + f"#define RS_SOC_PLL_PRESENT {platform['pll_present']}U\n" + f"#define RS_SOC_GPIO_HAS_PULLS {platform['gpio_pulls']}U\n#endif\n") + atomic_write(output / "rtl/tiny_platform.svh", comment + + f"`define RETROSOC_TINY__TECHNOLOGY 32'h{platform['technology']:08x}\n" + f"`define RETROSOC_TINY__FEATURES0 32'h{platform['features0']:08x}\n") gpio = [comment] for function in document["gpio_alt_functions"]: pin = function["pin"] @@ -119,18 +135,33 @@ def generate_bindings(document: dict, output: Path) -> None: atomic_write(output / "rtl/tiny_apb_wiring.svh", "\n".join(wiring) + "\n") -def generate_filelists(output: Path, generated: Path, defines: list[str]) -> None: +def generate_filelists(output: Path, generated: Path, defines: list[str], + pdk: str = "IHP130", simulator: str = "VERILATOR", + synthesis: str = "NONE") -> None: prefixes = {"top": TINY / "top", "dv": TINY / "dv", "ip": ROOT / "rtl/ip", "tech": ROOT / "rtl/tech", "clusterip": ROOT / "rtl/managed/clusterip", - "hazard3": ROOT / "rtl/managed/hazard3/hdl", "physical": ROOT / "physical"} + "hazard3": ROOT / "rtl/managed/hazard3/hdl", "physical": ROOT / "physical", + "cache": ROOT / ".cache"} + suffix = "_yosys" if synthesis == "YOSYS" else "_verilator" if simulator == "VERILATOR" else "" + pdk_name = "pdk_ihp130.fl" if pdk == "IHP130" else f"pdk_ics55{suffix}.fl" templates = [*sorted((TINY / "filelist").glob("*.fl")), - ROOT / "rtl/filelist/pdk_ihp130.fl"] + ROOT / "rtl/filelist" / pdk_name] for template in templates: content = template.read_text() for prefix, directory in prefixes.items(): content = re.sub(rf"(? int: parser.add_argument("--filelists", type=Path) parser.add_argument("--define", action="append", default=[]) parser.add_argument("--check", action="store_true") + parser.add_argument("--pdk", choices=("IHP130", "ICS55"), default="IHP130") + parser.add_argument("--simulator", choices=("IVERILOG", "VERILATOR"), default="VERILATOR") + parser.add_argument("--synthesis", choices=("NONE", "YOSYS"), default="NONE") args = parser.parse_args() document = read_topology(TINY / "integration/soc_topology.json", TINY / "address_map/memory_map.json") @@ -153,9 +187,10 @@ def main() -> int: parser.error("--define requires a +define+ token") output = args.output.resolve() generate_memory_map(TINY / "address_map/memory_map.json", output / "memory_map", "YES", 128) - generate_pin_map(TINY / "pin_map/pin_map.json", output / "pin_map") - generate_bindings(document, output) - generate_filelists(args.filelists.resolve(), output, args.define) + generate_pin_map(TINY / "pin_map/pin_map.json", output / "pin_map", read_while_driving=True) + generate_bindings(document, output, args.pdk, "+define+HAVE_PLL" in args.define) + generate_filelists(args.filelists.resolve(), output, args.define, + args.pdk, args.simulator, args.synthesis) return 0 diff --git a/scripts/regress.py b/scripts/regress.py index caa742f4..51f4f581 100644 --- a/scripts/regress.py +++ b/scripts/regress.py @@ -79,6 +79,14 @@ TINY_PR_COMMANDS = ( ("configs/ci/ihp130-tiny.mk", ("APP=ci_smoke", "STA=OPENSTA", "sta")), ) RTL_COMMANDS = (*PR_COMMANDS[:6], *TINY_PR_COMMANDS[:3]) +TINY_PROFILES = {"IHP130": "configs/ci/ihp130-tiny.mk", "ICS55": "configs/ci/ics55-tiny.mk"} + + +def tiny_commands(pdk: str) -> tuple: + return tuple((TINY_PROFILES[pdk], (*values, "TINY_SIM_ARGS=+tiny_jtag_smoke") + if "sim" in values else values) for _, values in TINY_PR_COMMANDS) + + PR_COMMANDS = (*PR_COMMANDS, *TINY_PR_COMMANDS) SMOKE_COMMANDS = ( ("configs/ci/ihp130.mk", RTL_LINT_VALUES), @@ -179,6 +187,8 @@ def select_regression( if pdk == "IHP130": return PR_COMMANDS, PR_PROFILES profile = PDK_PR_PROFILES[pdk] + if pdk == "ICS55": + return (*pdk_pr_commands(profile), *tiny_commands(pdk)), (profile, TINY_PROFILES[pdk]) return pdk_pr_commands(profile), (profile,) if suite == "pr": return PR_COMMANDS, PR_PROFILES @@ -282,18 +292,24 @@ def main() -> int: ) args = parser.parse_args() try: - commands, profiles = select_regression(args.suite, args.pdk) + if args.soc == "TINY": + pdk = args.pdk or "ICS55" + if pdk not in TINY_PROFILES or args.suite == "nightly-extra": + raise ValueError("Tiny supports IHP130/ICS55 smoke, rtl, pr and nightly suites") + commands = tiny_commands(pdk) + if args.suite == "smoke": + commands = commands[:2] + elif args.suite == "rtl": + commands = commands[:3] + profiles = (TINY_PROFILES[pdk],) + else: + commands, profiles = select_regression(args.suite, args.pdk) except ValueError as error: parser.error(str(error)) - if args.soc is not None: - tiny_profile = "configs/ci/ihp130-tiny.mk" - if args.soc == "TINY" and args.suite == "smoke": - commands, profiles = TINY_PR_COMMANDS[:2], (tiny_profile,) - else: - commands = tuple((profile, values) for profile, values in commands - if (profile == tiny_profile) == (args.soc == "TINY")) - profiles = tuple(profile for profile in profiles - if (profile == tiny_profile) == (args.soc == "TINY")) + if args.soc == "MINI": + commands = tuple((profile, values) for profile, values in commands + if profile not in TINY_PROFILES.values()) + profiles = tuple(profile for profile in profiles if profile not in TINY_PROFILES.values()) if not commands: parser.error(f"suite {args.suite} has no {args.soc} configuration for this PDK") if args.netsim_boot_only: diff --git a/scripts/rtl/generate_pin_map.py b/scripts/rtl/generate_pin_map.py index 57deee25..2ea95679 100644 --- a/scripts/rtl/generate_pin_map.py +++ b/scripts/rtl/generate_pin_map.py @@ -271,7 +271,7 @@ def render_core_ports(pads: list[Pad]) -> str: return "\n".join(lines) + "\n" -def render_pad_instance(pad: Pad) -> str | None: +def render_pad_instance(pad: Pad, read_while_driving: bool = False) -> str | None: instance = f"u_{pad.name}" if pad.kind == "tri_input": return ( @@ -297,8 +297,9 @@ def render_pad_instance(pad: Pad) -> str | None: f".c2p_en({pad.output_enable_signal}), .p2c({pad.input_signal}));" ) if pad.kind == "full": + parameters = " #(.ReadWhileDriving(1'b1))" if read_while_driving else "" return ( - f" tc_io_tri_full_pad {instance} ({POWER_CONNECTIONS}.pad({pad.name}), " + f" tc_io_tri_full_pad{parameters} {instance} ({POWER_CONNECTIONS}.pad({pad.name}), " f".c2p({pad.interface}.do_o[{pad.index}]), " f".c2p_en({pad.interface}.oe_o[{pad.index}]), .p2c({pad.interface}.di_i[{pad.index}]), " f".cs({pad.interface}.cs_o[{pad.index}]), .pu({pad.interface}.pu_o[{pad.index}]), " @@ -325,7 +326,7 @@ def render_pad_instance(pad: Pad) -> str | None: def render_pad_bindings( - pads: list[Pad], power_counts: dict[str, int] | None = None + pads: list[Pad], power_counts: dict[str, int] | None = None, read_while_driving: bool = False ) -> str: counts = IHP130_POWER_PAD_COUNTS if power_counts is None else power_counts lines = [ @@ -358,7 +359,7 @@ def render_pad_bindings( ] ) for pad in pads: - line = render_pad_instance(pad) + line = render_pad_instance(pad, read_while_driving) if line is not None: lines.extend(wrap_feature(pad, line)) lines.append("`undef RETROSOC_PAD_POWER_CONNECTIONS") @@ -461,13 +462,13 @@ def render_profile_bindings(pads: list[Pad], bindings: dict[str, str | None]) -> return "\n".join(lines) + "\n" -def generate(map_path: Path, output_dir: Path) -> None: +def generate(map_path: Path, output_dir: Path, *, read_while_driving: bool = False) -> None: pads, profiles = read_map(map_path) power_counts = read_power_pad_counts(map_path) rtl_dir = output_dir / "rtl" atomic_write(rtl_dir / "retrosoc_asic_ports.svh", render_ports(pads)) atomic_write( - rtl_dir / "retrosoc_asic_pad_bindings.svh", render_pad_bindings(pads, power_counts) + rtl_dir / "retrosoc_asic_pad_bindings.svh", render_pad_bindings(pads, power_counts, read_while_driving) ) atomic_write(rtl_dir / "retrosoc_core_ports.svh", render_core_ports(pads)) atomic_write(rtl_dir / "retrosoc_core_bindings.svh", render_core_bindings(pads)) diff --git a/scripts/setup_tiny.py b/scripts/setup_tiny.py index 57235911..826c9697 100644 --- a/scripts/setup_tiny.py +++ b/scripts/setup_tiny.py @@ -4,6 +4,7 @@ from __future__ import annotations import argparse +import subprocess import sys from pathlib import Path @@ -21,13 +22,16 @@ TINY_SOURCES = ( def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--update", action="store_true") + parser.add_argument("--pdk", choices=("IHP130", "ICS55"), default="ICS55") args = parser.parse_args() - for name in TINY_SOURCES: + for name in (name for name in TINY_SOURCES if not name.startswith("pdk_")): dependency = source(name) ensure_git_repo( dependency["url"], ROOT / dependency["destination"], dependency["revision"], recursive=dependency.get("recursive", False), update=args.update, ) + subprocess.run([sys.executable, str(ROOT / "physical/pdk/setup.py"), "--pdk", args.pdk, + *(["--update"] if args.update else [])], check=True) return 0 diff --git a/scripts/tiny_ics55_platform.py b/scripts/tiny_ics55_platform.py new file mode 100644 index 00000000..cd85c9fc --- /dev/null +++ b/scripts/tiny_ics55_platform.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python3 +"""Source-bound Tiny ICS55 evidence and a fail-closed SAFE24 netlist audit.""" +from __future__ import annotations + +import argparse +from datetime import datetime, timezone +import hashlib +from pathlib import Path +import re +import sys +import tarfile + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT)) +from scripts import tiny_r2_baseline as b # noqa: E402 +from scripts.config_key import variant_id # noqa: E402 +from scripts.tiny_r2_feasibility import timing_metrics, path_slack # noqa: E402 + +PHASE = "TINY-ICS55-P1" +RAM = "ics55_ecos_sram_1024x32_m8" + + +def verify_locked_views(root: Path) -> dict: + """Check derived cache bytes against the unchanged locked archives.""" + lock = b.read_json(root / "dependencies/dependencies.lock.json") + verified = {} + for key in ("pdk_ics55_h7cr_liberty", "pdk_ics55_sram_1024x32_m8", "pdk_ics55_sram_4096x32_m8"): + spec = lock["archives"][key] + path = root / spec["destination"] + if b.sha256(path) != spec["sha256"]: + raise ValueError(f"locked archive changed: {key}") + package = key.replace("pdk_ics55_sram_", "ics55_ecos_sram_") + seen = set() + with tarfile.open(path) as archive: + for member in archive: + if not member.isfile(): + continue + relative = Path(member.name) + if relative.is_absolute() or ".." in relative.parts: + raise ValueError("unsafe archive view name") + target = None + if key == "pdk_ics55_h7cr_liberty": + for corner, tokens in (("tt", ("tt", "1p2", "25")), ("ss", ("ss", "1p08", "125"))): + if member.name.endswith(".lib") and all(t in member.name.lower() for t in tokens): + target = root / f".cache/retrosoc/pdk/ics55/ics55_h7cr_{corner}.lib" + else: + if relative.parts[0] == package: + relative = Path(*relative.parts[1:]) + if relative.parts and relative.parts[0] in ("lib", "verilog"): + target = root / ".cache/retrosoc/pdk/ics55/sram" / package / relative + if target is not None: + if not target.is_file() or hashlib.sha256(archive.extractfile(member).read()).hexdigest() != b.sha256(target): + raise ValueError(f"cached view differs from locked archive: {target}") + seen.add(str(target)) + if len(seen) != (2 if key == "pdk_ics55_h7cr_liberty" else 11): + raise ValueError(f"incomplete locked view set: {key}") + verified[key] = {"archive": b.artifact(path), "views": sorted(seen)} + from physical.pdk.prepare_ics55_sim_model import patch_muxi2_models, VERILATOR_LINT_OFF, VERILATOR_LINT_ON + pdk = root / lock["sources"]["pdk_ics55"]["destination"] + original = pdk / "IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CR/verilog/ics55_LLSC_H7CR.v" + patched, _ = patch_muxi2_models(original.read_text()) + expected = VERILATOR_LINT_OFF + patched.rstrip() + "\n" + VERILATOR_LINT_ON + model = root / ".cache/retrosoc/pdk/ics55/ics55_h7cr_functional.v" + if model.read_text() != expected: + raise ValueError("standard-cell functional model differs from the owned locked-source transform") + verified["functional_cells"] = b.artifact(model) + return verified + + +def inspect_netlist(document: dict) -> dict: + modules = document["modules"] + top = "retrosoc_tiny_asic" + leaves = [] + aliases = {} + + def canonical(bit): + if bit in aliases: + aliases[bit] = canonical(aliases[bit]) + return aliases[bit] + return bit + + def alias(a, other): + a, other = canonical(a), canonical(other) + if a == other: + return + if a in ("0", "1", "x", "z"): + a, other = other, a + if a in ("0", "1", "x", "z"): + raise ValueError("conflicting constants at a hierarchy boundary") + aliases[a] = other + + def visit(module_name, scope, bound): + module = modules[module_name] + mapping = {} + for port, bits in bound.items(): + declaration = module["ports"][port] + for local, external in zip(declaration["bits"], bits): + if isinstance(local, str): + if declaration["direction"] == "output": + alias(external, local) + elif local in mapping: + alias(external, mapping[local]) + else: + mapping[local] = external + + def net(bit): + return bit if isinstance(bit, str) else mapping.get(bit, f"{scope}:{bit}") + + for name, cell in module.get("cells", {}).items(): + ports = {port: [net(bit) for bit in bits] for port, bits in cell["connections"].items()} + kind = cell["type"] + path = f"{scope}/{name}" + attrs = modules.get(kind, {}).get("attributes", {}) + if kind in modules and not int(str(attrs.get("blackbox", "0")), 2): + visit(kind, path, ports) + else: + leaves.append({"path": path, "type": kind, "ports": ports}) + + visit(top, top, {}) + for c in leaves: + for port, bits in c["ports"].items(): + c["ports"][port] = [canonical(bit) for bit in bits] + clock = modules[top]["cells"]["u_clock_buffer"]["connections"]["clk_o"] + if len(clock) != 1 or not isinstance(clock[0], int): + raise ValueError("missing live SYS clock") + system = canonical(f"{top}:{clock[0]}") + if system in ("0", "1", "x", "z"): + raise ValueError("SYS is not a live clock net") + ram = [c for c in leaves if c["type"] == RAM] + pll = [c for c in leaves if c["type"] == "PLL_TOP"] + if any(c["type"] == "ics55_ecos_sram_4096x32_m8" for c in leaves): + raise ValueError("Tiny instantiated the unsupported 16 KiB SRAM interface stub") + if len(ram) != 32 or len(pll) != 1: + raise ValueError("expected 32 main SRAM macros and one PLL") + if any(c["ports"].get("CLK") != [system] for c in ram): + raise ValueError("main SRAM does not share direct SYS") + constants = {"0": 0, "1": 1} + for c in leaves: + if c["type"] in ("TIELOH7R", "TIEHIH7R"): + for bit in c["ports"]["Z"]: + constants[bit] = int(c["type"] == "TIEHIH7R") + enable = pll[0]["ports"].get("EN", []) + if len(enable) != 1 or constants.get(enable[0]) != 0: + raise ValueError("PLL enable is not proven constant zero") + cpu = [c for c in leaves if "u_hazard3_cpu_1port" in c["path"] + and ("CK" in c["ports"] or "CKN" in c["ports"])] + if not cpu or any(c["ports"].get("CK", c["ports"].get("CKN")) != [system] for c in cpu): + raise ValueError("CPU sequential clocks do not share direct SYS") + pll_outputs = {bit for name in ("CKOUT1", "CKOUT2", "CKTST") for bit in pll[0]["ports"].get(name, [])} + if system in pll_outputs: + raise ValueError("PLL drives SYS") + return {"system_net": system, "cpu_clocked_cells": len(cpu), + "sram": [{"path": c["path"], "clock": c["ports"]["CLK"]} for c in ram], + "pll": pll[0], "pll_enable": 0, "qualification": "SAFE24 digital connectivity only"} + + +def check_configuration(variant: Path, path: Path) -> None: + manifest = b.configuration(variant) + if manifest["configuration"]["PDK"] != "ICS55": + raise ValueError("ICS55 platform evidence cannot consume another PDK") + config = dict(line.split("=", 1) for line in path.read_text().splitlines() if "=" in line) + for key, value in {"PDK": "ICS55", "SOC": "TINY", "HAVE_PLL": "YES", + "HAVE_SRAM_MACRO": "YES", "SRAM_SIZE_KIB": "128", + "TOP_DESIGN": "retrosoc_tiny_asic", "PERIOD_PS": "41667"}.items(): + if config.get(key) != value: + raise ValueError(f"consumed netlist configuration mismatch: {key}") + values = [f"{k}={v}" for k, v in manifest["configuration"].items() + if k not in ("SIMU", "SYNTH", "SYNTH_RECIPE", "STA")] + expected = variant_id(manifest["profile"], values, ROOT / "dependencies/dependencies.lock.json", + "2000-01-01-00-00").rsplit("-", 1)[1] + if config.get("CONFIG_DIGEST") != expected or variant.name.rsplit("-", 1)[1] != expected: + raise ValueError("consumed configuration digest mismatch") + + +def verify_netlist(variant: Path, netlist: Path, config: Path, output: Path) -> dict: + check_configuration(variant, config) + report = b.read_json(output) + if report.get("status") != "passed" or report.get("phase") != PHASE: + raise ValueError("missing successful structural audit") + if report.get("audit_script") != b.artifact(Path(__file__)): + raise ValueError("structural audit was produced by another script") + expected = [netlist, Path(str(netlist) + ".json"), config] + if report.get("inputs") != [b.artifact(path) for path in expected]: + raise ValueError("missing or changed consumed netlist artifacts") + return report + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--variant-root", type=Path, required=True) + parser.add_argument("action", choices=("capture", "audit-netlist", "verify-netlist", "report")) + parser.add_argument("--netlist", type=Path) + parser.add_argument("--config", type=Path) + parser.add_argument("--output", type=Path) + args = parser.parse_args() + variant = args.variant_root.resolve() + parent = variant / "meta/tiny-ics55-p1" + try: + if args.action == "capture": + directory = b.new_attempt(parent, "inputs-") + b.dump(parent / "latest-inputs.json", {"path": str(directory / "identity.json")}) + b.dump(directory / "identity.json", {"phase": PHASE, "status": "capturing"}) + inputs = b.source_inputs(ROOT, "ICS55") + for path in (ROOT / "tests").rglob("*"): + if path.is_file() and path.suffix in (".py", ".sv", ".svh"): + inputs[str(path.relative_to(ROOT))] = b.sha256(path) + views = verify_locked_views(ROOT) + identity = {"phase": PHASE, "status": "captured", "created_at": datetime.now(timezone.utc).isoformat(), "views": views, + "source": b.snapshot(ROOT, directory, inputs), "tools": b.tools_identity(ROOT)} + if any(b.sha256(ROOT / name) != digest for name, digest in inputs.items()): + raise ValueError("source changed while capturing inputs") + b.dump(directory / "identity.json", identity) + b.dump(parent / "latest-inputs.json", {"path": str(directory / "identity.json")}) + elif args.action in ("audit-netlist", "verify-netlist"): + if not all((args.netlist, args.config, args.output)): + parser.error("netlist actions require --netlist, --config and --output") + check_configuration(variant, args.config) + if args.action == "audit-netlist": + net_json = Path(str(args.netlist) + ".json") + report = {"phase": PHASE, "status": "passed", "audit_script": b.artifact(Path(__file__)), + "binding": inspect_netlist(b.read_json(net_json)), + "inputs": [b.artifact(p) for p in (args.netlist, net_json, args.config)]} + b.dump(args.output, report) + else: + verify_netlist(variant, args.netlist, args.config, args.output) + else: + b.dump(parent / "report.json", {"phase": PHASE, "status": "incomplete"}) + identity = b.read_json(Path(b.read_json(parent / "latest-inputs.json")["path"])) + if identity.get("status") != "captured": + raise ValueError("missing successful source capture") + inputs = b.read_json(b.check_artifact(identity["source"]["inputs"])) + if any(b.sha256(ROOT / name) != digest for name, digest in inputs.items()): + raise ValueError("captured source changed") + flows = {} + for key in ("snapshot", "patch"): + b.check_artifact(identity["source"][key]) + for tool in identity["tools"].values(): + b.check_artifact(tool["executable"]) + b.check_artifact(tool["archive"]) + for name in ("syn/yosys/result-synth.json", "sta/opensta/result-sta.json"): + path = variant / name + result = b.read_json(path) + if result.get("status") != "passed" or result.get("exit_code") != 0: + raise ValueError(f"incomplete flow: {name}") + if datetime.fromisoformat(result["started_at"]) < datetime.fromisoformat(identity["created_at"]): + raise ValueError(f"flow predates captured source: {name}") + flows[name] = b.artifact(path) + sta = variant / "sta/opensta" + mapped = variant / "syn/yosys/out/retrosoc_tiny_asic_yosys.v" + verify_netlist(variant, mapped, mapped.with_suffix(".config"), + variant / "syn/yosys/tiny-ics55-netlist.json") + log = (sta / "opensta.log").read_text() + if "TINY_ICS55_STA_AUDIT_PASS:" not in log or re.search(r"(?im)^Error:|\bFATAL\b|%Error", log): + raise ValueError("incomplete or erroneous STA audit") + metrics = timing_metrics(sta / "timing_metrics.rpt") + report = {"phase": PHASE, "status": "completed", "source": identity, + "flows": flows, "qualification": "SAFE24 synthesis/STA observation only; functional acceptance is separate", + "timing": metrics, + "timing_status": "failed" if any(v < 0 for v in metrics.values()) else "nonnegative_reported_slack", + "sys_setup_slack_ns": path_slack(sta / "sys-setup.rpt"), + "sys_hold_slack_ns": path_slack(sta / "sys-hold.rpt"), + "sta_artifacts": {p.name: b.artifact(p) for p in sta.iterdir() if p.is_file()}, + "macro_audit": b.artifact(variant / "syn/yosys/tiny-ics55-netlist.json")} + b.dump(parent / "report.json", report) + print(f"{PHASE} {args.action}: PASS (not physical qualification)") + return 0 + except (OSError, ValueError, KeyError) as error: + if args.action == "capture" and "directory" in locals(): + b.dump(directory / "identity.json", {"phase": PHASE, "status": "failed", "error": str(error)}) + print(f"{PHASE}: {error}", file=sys.stderr) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/tiny_r2_baseline.py b/scripts/tiny_r2_baseline.py index f8e4959e..31144d77 100644 --- a/scripts/tiny_r2_baseline.py +++ b/scripts/tiny_r2_baseline.py @@ -38,7 +38,7 @@ TOOL_PROGRAMS = {"verilator": "verilator", "verible": "verible-verilog-format", SOURCE_PATHS = ("Makefile", "configs/ci/ihp130-tiny.mk", "dependencies/dependencies.lock.json", "requirements", "rtl/tiny", "rtl/ip", "rtl/tech", "rtl/mk/software.mk", "crt", "app/apps/ci_smoke", "app/apps/bringup", "scripts", - "physical/smoke", "physical/librelane/tiny") + "physical/smoke", "physical/librelane/tiny", "physical/pdk") CASE_FIELDS = {"id", "name", "cycles", "instructions", "kernel_cycles", "kernel_calls", "payload_bytes", "checksum", "cpu_wait", "dma_wait", "sram_reads", "sram_writes", "sram_rbeats", "sram_wbeats", "sram_stalls", "sram_errors"} @@ -75,7 +75,7 @@ def digest_map(value: dict) -> str: return hashlib.sha256(json.dumps(value, sort_keys=True).encode()).hexdigest() -def source_inputs(root: Path) -> dict[str, str]: +def source_inputs(root: Path, pdk: str = "IHP130") -> dict[str, str]: names = subprocess.check_output( ["git", "ls-files", "--cached", "--others", "--exclude-standard", "-z", "--", *SOURCE_PATHS], cwd=root, @@ -83,7 +83,14 @@ def source_inputs(root: Path) -> dict[str, str]: paths = {root / name for name in names if name and not name.endswith( (".md", ".png", ".jpg", ".svg", ".pdf"))} lock = read_json(root / "dependencies/dependencies.lock.json") - for name in TINY_SOURCES: + selected_sources = list(TINY_SOURCES) + if pdk == "ICS55": + selected_sources = [name if name != "pdk_ihp130" else "pdk_ics55" for name in selected_sources] + selected_sources.append("pdk_ics55_pll") + paths.add(root / "configs/ci/ics55-tiny.mk") + paths.update(p for p in (root / ".cache/retrosoc/pdk/ics55").rglob("*") + if p.is_file() and p.suffix in (".v", ".lib")) + for name in selected_sources: spec = lock["sources"][name] checkout = root / spec["destination"] revision = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=checkout, @@ -100,7 +107,7 @@ def source_inputs(root: Path) -> dict[str, str]: paths.update((base / "sg13g2_stdcell/lib").glob("*slow_1p08V_125C.lib")) paths.update((base / "sg13g2_io/lib").glob("*slow_1p08V_3p0V_125C.lib")) else: - for suffix in ("*.v", "*.sv", "*.vh", "*.svh", "*.c", "*.h"): + for suffix in ("*.v", "*.sv", "*.vh", "*.svh", "*.c", "*.h", "*.lib", "*.lef"): paths.update(checkout.rglob(suffix)) return {str(path.relative_to(root)): sha256(path) for path in sorted(paths) if path.is_file()} @@ -126,13 +133,22 @@ def runtime_inputs(root: Path, variant: Path, simulator: str) -> dict[str, str]: def configuration(variant: Path) -> dict: manifest = read_json(variant / "meta/manifest.json") config = manifest["configuration"] - if manifest["profile"] != "ihp130-tiny" or any( - config.get(key) != expected for key, expected in COMPATIBILITY.items() + expected = dict(COMPATIBILITY) + if manifest["profile"] == "ics55-tiny": + expected.update(PDK="ICS55", HAVE_PLL="YES") + if manifest["profile"] not in ("ihp130-tiny", "ics55-tiny") or any( + config.get(key) != value for key, value in expected.items() ): - raise ValueError("baseline requires the committed Tiny/IHP130 24 MHz macro profile") + raise ValueError("baseline requires an explicit Tiny 24 MHz macro profile") return manifest +def variant_source_inputs(root: Path, variant: Path) -> dict: + if configuration(variant)["configuration"]["PDK"] == "ICS55": + return source_inputs(root, "ICS55") + return source_inputs(root) + + def tools_identity(root: Path) -> dict: cache_value = os.environ.get("RETROSOC_DEVELOPMENT_CACHE") if not cache_value: @@ -325,7 +341,7 @@ def build_image(args: argparse.Namespace) -> int: try: manifest = configuration(variant) tools = tools_identity(root) - before = source_inputs(root) + before = variant_source_inputs(root, variant) # Archive generated C headers and the preprocessed flat linker input too. generated = {str(path.relative_to(root)): sha256(path) for path in (variant / "generated/tiny").rglob("*") if path.is_file()} @@ -355,7 +371,7 @@ def build_image(args: argparse.Namespace) -> int: ebss = re.search(r"^([0-9a-f]+)(?: [0-9a-f]+)? [A-Za-z] _ebss$", symbols, re.MULTILINE) if ebss is None or not 0x30000000 <= int(ebss[1], 16) <= 0x3001F000: raise ValueError("image does not preserve at least 4 KiB of stack headroom") - if before != source_inputs(root): + if before != variant_source_inputs(root, variant): raise ValueError("source changed while compiling the retained image") if any(not (root / name).is_file() or sha256(root / name) != digest for name, digest in generated.items()): @@ -421,12 +437,12 @@ def simulate(args: argparse.Namespace) -> int: firmware_source=image["source"], manifest=configuration(variant), tools=tools_identity(root)) if args.simulator == "iverilog": record["runtime_tool"] = vvp_identity(args.vvp, record["tools"]["iverilog"]) - before = source_inputs(root) + before = variant_source_inputs(root, variant) command = args.command[1:] if args.command[:1] == ["--"] else args.command if not command: raise ValueError("a fresh simulator compilation command is required") execute(root, directory, "compile-model", command, quiet=True) - if before != source_inputs(root): + if before != variant_source_inputs(root, variant): raise ValueError("source changed while compiling the simulator") runtime = runtime_inputs(root, variant, args.simulator) record["rtl_source"] = snapshot(root, directory / "inputs", before | runtime) @@ -472,7 +488,7 @@ def simulate(args: argparse.Namespace) -> int: dump(output, record) if errors: raise ValueError("; ".join(errors)) - if before != source_inputs(root) or runtime != runtime_inputs(root, variant, args.simulator): + if before != variant_source_inputs(root, variant) or runtime != runtime_inputs(root, variant, args.simulator): raise ValueError("source or generated inputs changed during simulation") validate_runs(record["runs"]) validate_runtime_tool(root, record) @@ -511,7 +527,7 @@ def report(args: argparse.Namespace) -> int: check_artifact(attempt["model"]) source = attempt["rtl_source"] inputs = read_json(check_artifact(source["inputs"])) - if inputs != source_inputs(root) | runtime_inputs(root, variant, simulator): + if inputs != variant_source_inputs(root, variant) | runtime_inputs(root, variant, simulator): raise ValueError(f"stale {simulator} source inputs") check_artifact(source["snapshot"]) validate_runs(attempt["runs"]) diff --git a/scripts/tiny_r2_feasibility.py b/scripts/tiny_r2_feasibility.py index 877a1679..c98e7927 100644 --- a/scripts/tiny_r2_feasibility.py +++ b/scripts/tiny_r2_feasibility.py @@ -44,7 +44,7 @@ def require_variant(root: Path, variant: Path) -> dict: raise ValueError("evidence must remain below repository build variants") manifest = baseline.configuration(variant) config = manifest["configuration"] - if config["APP"] != "ci_smoke" or config["MGMT_CPU_CLK_HZ"] != "24000000": + if config["PDK"] != "IHP130" or config["APP"] != "ci_smoke" or config["MGMT_CPU_CLK_HZ"] != "24000000": raise ValueError("feasibility requires the 24 MHz Tiny ci_smoke variant") return manifest diff --git a/tests/rtl/onchip_ram_tb.sv b/tests/rtl/onchip_ram_tb.sv index 0a3bd93e..29617fcd 100644 --- a/tests/rtl/onchip_ram_tb.sv +++ b/tests/rtl/onchip_ram_tb.sv @@ -5,7 +5,8 @@ `include "onchip_ram_define.svh" module onchip_ram_tb #( - parameter int unsigned CapacityKiB = 128 + parameter bit Ics55SmallBanks = 1'b0, + parameter int unsigned CapacityKiB = 128 ); localparam logic [31:0] SramBase = `SOC_ADDR_SRAM_BASE; localparam logic [31:0] SramEnd = `SOC_ADDR_SRAM_END; @@ -34,8 +35,9 @@ module onchip_ram_tb #( always #5 clk_i = ~clk_i; onchip_ram #( - .Present (1'b1), - .CapacityKiB(CapacityKiB) + .Ics55SmallBanks(Ics55SmallBanks), + .Present (1'b1), + .CapacityKiB (CapacityKiB) ) u_dut ( .clk_i (clk_i), .rst_n_i (rst_n_i), diff --git a/tests/rtl/tc_ics55_gpio_readback_tb.sv b/tests/rtl/tc_ics55_gpio_readback_tb.sv new file mode 100644 index 00000000..e704b3ff --- /dev/null +++ b/tests/rtl/tc_ics55_gpio_readback_tb.sv @@ -0,0 +1,45 @@ +`timescale 1ns / 1ps +module tc_ics55_gpio_readback_tb; + logic drive = 0, value = 0, external_value = 0; + wire tiny_read, legacy_read; + tri tiny_pad, legacy_pad; + assign tiny_pad = drive ? 1'bz : external_value; + assign legacy_pad = drive ? 1'bz : external_value; + tc_io_tri_full_pad #( + .ReadWhileDriving(1'b1) + ) u_tiny ( + .pad (tiny_pad), + .c2p (value), + .c2p_en(drive), + .p2c (tiny_read), + .cs (1'b1), + .pu (1'b0), + .pd (1'b0) + ); + tc_io_tri_full_pad u_legacy ( + .pad (legacy_pad), + .c2p (value), + .c2p_en(drive), + .p2c (legacy_read), + .cs (1'b1), + .pu (1'b0), + .pd (1'b0) + ); + initial begin + #20; + if ({tiny_read, legacy_read} !== 2'b00) $fatal(1, "input low"); + external_value = 1; + #20; + if ({tiny_read, legacy_read} !== 2'b11) $fatal(1, "input high"); + drive = 1; + value = 1; + #20; + if (tiny_pad !== 1'b1 || legacy_pad !== 1'b1 || tiny_read !== 1'b1 || legacy_read !== 1'b0) + $fatal(1, "output readback or legacy behavior"); + value = 0; + #20; + if ({tiny_pad, legacy_pad, tiny_read, legacy_read} !== 4'b0000) $fatal(1, "output low"); + $display("SIM_TEST_PASS ICS55 GPIO readback and legacy compatibility"); + $finish; + end +endmodule diff --git a/tests/rtl/tc_pll_ics55_ecos_tb.sv b/tests/rtl/tc_pll_ics55_ecos_tb.sv new file mode 100644 index 00000000..cceb9ed0 --- /dev/null +++ b/tests/rtl/tc_pll_ics55_ecos_tb.sv @@ -0,0 +1,114 @@ +`timescale 1ns / 1ps +module tc_pll_ics55_ecos_tb; + logic s_ref = 0; + logic s_reset_n = 0; + logic [2:0] s_selection = 0; + logic s_apply = 0; + wire s_capable, s_locked, s_clock; + integer s_edges = 0; + tc_pll #( + .UseIcs55EcosPll(1'b1) + ) u_dut ( + .fref_i (s_ref), + .rst_n_i (s_reset_n), + .cfg_sel_i (s_selection), + .cfg_apply_i (s_apply), + .pll_capable_o(s_capable), + .pll_lock_o (s_locked), + .pll_clk_o (s_clock) + ); + always #20.833333 s_ref = ~s_ref; + always @(posedge s_clock) s_edges++; + + task automatic cycle(input integer count); + repeat (count) @(negedge s_ref); + endtask + task automatic configure(input logic [2:0] selection); + @(negedge s_ref); + s_selection = selection; + s_apply = 1; + cycle(1); + s_apply = 0; + endtask + task automatic await_lock; + integer waited; + waited = 0; + while (!s_locked && waited < 2000) begin + cycle(1); + waited++; + end + if (!s_locked || !s_capable) $fatal(1, "PLL did not qualify"); + endtask + task automatic measure(input integer multiplier); + integer before_edges; + before_edges = s_edges; + cycle(128); + if (s_edges-before_edges < multiplier*128-2 || + s_edges-before_edges > multiplier*128+2) + $fatal(1, "wrong measured PLL rate"); + endtask + initial begin + cycle(2); + s_reset_n = 1; + cycle(8); + if (s_locked || s_clock) $fatal(1, "unrequested PLL started"); + configure(5); + cycle(510); + if (s_locked) $fatal(1, "early lock without clock observations"); + await_lock(); + measure(8); + configure(7); + cycle(1); + if (s_locked) $fatal(1, "reconfiguration retained stale lock"); + await_lock(); + measure(10); + // A held APPLY is one command, not an endless restart. + @(negedge s_ref); + s_selection = 5; + s_apply = 1; + cycle(1); + await_lock(); + measure(8); + s_apply = 0; + force u_dut.gen_ecos.u_backend.u_PLL_TOP.N = 8'd40; + cycle(136); + if (s_locked) $fatal(1, "wrong-frequency output retained lock"); + release u_dut.gen_ecos.u_backend.u_PLL_TOP.N; + await_lock(); + measure(8); + // Stop both polarities. No internal model-ready signal is consulted. + force u_dut.gen_ecos.u_backend.pll_clk_o = 1'b0; + cycle(136); + if (s_locked) $fatal(1, "stopped-low output retained lock"); + release u_dut.gen_ecos.u_backend.pll_clk_o; + await_lock(); + force u_dut.gen_ecos.u_backend.pll_clk_o = 1'b1; + cycle(136); + if (s_locked) $fatal(1, "stopped-high output retained lock"); + cycle(4100); + if (u_dut.gen_ecos.u_backend.u_PLL_TOP.EN) $fatal(1, "acquisition timeout left PLL enabled"); + release u_dut.gen_ecos.u_backend.pll_clk_o; + configure(7); + await_lock(); + measure(10); + configure(0); + cycle(1500); + if (s_locked || u_dut.gen_ecos.u_backend.u_PLL_TOP.EN) $fatal(1, "invalid selection accepted"); + configure(5); + await_lock(); + @(negedge s_ref); + s_reset_n = 0; + #1; + if (s_locked) $fatal(1, "reset retained lock"); + cycle(10); + s_reset_n = 1; + cycle(20); + if (s_locked) $fatal(1, "reset replayed old configuration"); + $display("SIM_TEST_PASS ICS55 PLL backend"); + $finish; + end + initial begin + #1000000; + $fatal(1, "SIM_TEST_TIMEOUT PLL backend"); + end +endmodule diff --git a/tests/test_publication_tiny.py b/tests/test_publication_tiny.py index 3a4cb80a..01d42098 100644 --- a/tests/test_publication_tiny.py +++ b/tests/test_publication_tiny.py @@ -3,6 +3,7 @@ from __future__ import annotations import copy import json +import re from pathlib import Path import shutil @@ -81,6 +82,7 @@ def source_tree(tmp_path): @pytest.mark.parametrize("path,before,after", [ (tiny.TOP, ".CapacityKiB(128)", ".CapacityKiB(32)"), + (tiny.TOP, ".Ics55SmallBanks(1'b1)", ".Ics55SmallBanks(1'b0)"), (tiny.TOP, ".DataWidth (32)", ".DataWidth (64)"), (tiny.TOP, ".NumChannels (4)", ".NumChannels (8)"), (tiny.TOP, ".EnableStreams(1'b0)", ".EnableStreams(1'b1)"), @@ -93,8 +95,11 @@ def source_tree(tmp_path): def test_changed_hardware_or_isa_cannot_retain_publication_claim(source_tree, path, before, after): file = source_tree / path text = file.read_text(encoding="utf-8") - assert before in text - file.write_text(text.replace(before, after, 1), encoding="utf-8") + # Parameter alignment is formatting, while the altered value must still + # fail the source-bound publication contract. + pattern = r"\s*".join(re.escape(char) for char in before if not char.isspace()) + assert re.search(pattern, text) + file.write_text(re.sub(pattern, lambda _: after, text, count=1), encoding="utf-8") with pytest.raises(ValueError): tiny.facts(source_tree) diff --git a/tests/test_tiny_ics55_platform.py b/tests/test_tiny_ics55_platform.py new file mode 100644 index 00000000..e9373e47 --- /dev/null +++ b/tests/test_tiny_ics55_platform.py @@ -0,0 +1,188 @@ +"""Tiny ICS55 SAFE24 platform and the real locked PLL behavioral view.""" +from __future__ import annotations + +import os +import json +from pathlib import Path +import shutil +import subprocess + +import pytest + +from scripts.check_simulation import DEFAULT_FAILURE +from scripts.generate_tiny import generate_filelists, platform_values +from scripts.rtl.generate_memory_map import generate as generate_memory_map +from scripts.tiny_ics55_platform import inspect_netlist, RAM +from scripts import tiny_ics55_platform as platform + +ROOT = Path(__file__).resolve().parents[1] +COMMON = ROOT / "rtl/managed/clusterip/common/rtl" +PLL = ROOT / ".cache/retrosoc/sources/ics55_ecos_pll/verilog/PLL_TOP.behavioral.v" + + +def run(command, **kwargs): + p = subprocess.run(command, text=True, capture_output=True, **kwargs) + assert p.returncode == 0, p.stdout + p.stderr + assert not DEFAULT_FAILURE.search(p.stdout + p.stderr), p.stdout + p.stderr + return p.stdout + + +@pytest.mark.parametrize("simulator", ["iverilog", "verilator"]) +def test_actual_pll_backend(simulator, tmp_path): + assert shutil.which(simulator) and PLL.is_file(), "required locked input unavailable" + sources = [COMMON / p for p in ["utils/register.sv", "clkrst/rst_sync.sv", "cdc/cdc_sync.sv", + "utils/bin2gray.sv", "utils/gray2bin.sv"]] + sources += [ROOT / "rtl/tech/tc_pll.sv", ROOT / "rtl/tech/tc_pll_ics55_ecos.sv", + PLL, ROOT / "tests/rtl/tc_pll_ics55_ecos_tb.sv"] + flags = [f"-I{COMMON}", "-DSV_ASSRT_DISABLE", "-DPDK_ICS55", "-DHAVE_PLL", + "-DRETROSOC_SOC__TINY"] + binary = tmp_path / "sim" + top = "tc_pll_ics55_ecos_tb" + if simulator == "iverilog": + command = [simulator, "-g2012", "-s", top, *flags, *map(str, sources), "-o", str(binary)] + launch = ["vvp", str(binary)] + else: + command = [simulator, "--binary", "--timing", "--assert", "-Wno-fatal", "--top-module", top, + *flags, *map(str, sources), "--Mdir", str(tmp_path / "obj"), "-o", str(binary)] + launch = [str(binary)] + run(command, timeout=180, env={**os.environ, "CCACHE_DISABLE": "1"}) + assert "SIM_TEST_PASS ICS55 PLL backend" in run(launch, timeout=60) + + +@pytest.mark.parametrize("pdk,pll,technology", [("IHP130", False, 0x02010082), ("ICS55", True, 0x02040037)]) +def test_identity(pdk, pll, technology): + values = platform_values(pdk, pll) + assert values["technology"] == technology + assert values["features0"] == 0x7FFE | pll + + +@pytest.mark.parametrize("simulator,synthesis", [("VERILATOR", "NONE"), ("IVERILOG", "NONE"), ("IVERILOG", "YOSYS")]) +def test_flow_specific_inputs(tmp_path, simulator, synthesis): + generate_filelists(tmp_path, tmp_path / "generated", ["+define+HAVE_PLL"], "ICS55", simulator, synthesis) + text = (tmp_path / "pdk_selected.fl").read_text() + assert "IHP-Open-PDK" not in text and "/cache/" not in text.replace("/.cache/", "") + assert ("PLL_TOP.behavioral.v" in text) == (synthesis == "NONE") + assert ("ics55_io_sim_cells.sv" in text) == (simulator == "VERILATOR" and synthesis == "NONE") + + +def test_default_and_compatibility_configurations(): + output = run(["make", "-s", "SOC=TINY", "config"], cwd=ROOT) + assert "ics55-tiny.mk" in output and "ICS55" in output + output = run(["make", "-s", "CONFIG=configs/ci/ihp130-tiny.mk", "config"], cwd=ROOT) + assert "IHP130" in output + result = subprocess.run(["make", "-s", "CONFIG=configs/ci/ics55.mk", "SOC=MINI", + "HAVE_PLL=YES", "TINY_SAFE24_PLL_OFF=YES", "STA=OPENSTA", "config"], + cwd=ROOT, capture_output=True, text=True) + assert result.returncode != 0 and "qualified PDK PLL timing profile" in result.stderr + + +@pytest.mark.parametrize("changed", [None, "netlist", "json", "config", "script", "missing"]) +def test_consumed_netlist_provenance(tmp_path, monkeypatch, changed): + monkeypatch.setattr(platform, "check_configuration", lambda *args: None) + paths = [tmp_path / name for name in ("net.v", "net.v.json", "net.config")] + for path in paths: + path.write_text("original") + report = {"phase": platform.PHASE, "status": "passed", + "audit_script": platform.b.artifact(Path(platform.__file__)), + "inputs": [platform.b.artifact(path) for path in paths]} + if changed in ("netlist", "json", "config"): + paths[("netlist", "json", "config").index(changed)].write_text("changed") + elif changed == "script": + report["audit_script"] = platform.b.artifact(paths[0]) + elif changed == "missing": + report["inputs"].pop() + output = tmp_path / "audit.json" + output.write_text(json.dumps(report)) + if changed: + with pytest.raises(ValueError): + platform.verify_netlist(tmp_path, paths[0], paths[2], output) + else: + assert platform.verify_netlist(tmp_path, paths[0], paths[2], output) == report + + +@pytest.fixture +def mapped_netlist(): + def cell(kind, **ports): + return {"type": kind, "connections": {k: [v] for k, v in ports.items()}} + cells = {"u_clock_buffer": cell("BUF", clk_o=2), + "u_soc.u_cpu.u_hazard3_cpu_1port.core.pc_reg": cell("DFF", CK=2), + "pll": cell("PLL_TOP", EN=3, CKOUT1=4, CKOUT2=5, CKTST=6), + "low": cell("TIELOH7R", Z=3)} + cells.update({f"bank{i}": cell(RAM, CLK=2) for i in range(32)}) + return {"modules": {"retrosoc_tiny_asic": {"ports": {}, "cells": cells}}} + + +def test_safe24_structural_binding(mapped_netlist): + result = inspect_netlist(mapped_netlist) + assert len(result["sram"]) == 32 and result["cpu_clocked_cells"] == 1 + assert result["pll_enable"] == 0 + + +def test_hierarchical_constant_output_is_resolved(mapped_netlist): + modules = mapped_netlist["modules"] + modules["constant_zero"] = {"ports": {"result": {"direction": "output", "bits": ["0"]}}, "cells": {}} + modules["retrosoc_tiny_asic"]["cells"]["low"] = { + "type": "constant_zero", "connections": {"result": [3]}} + assert inspect_netlist(mapped_netlist)["pll_enable"] == 0 + + +@pytest.mark.parametrize("mutation", ["missing_pll", "missing_ram", "live_pll", "unknown_enable", + "divided_ram", "wrong_cpu", "pll_sys"]) +def test_unsafe_or_incomplete_mapping_is_rejected(mapped_netlist, mutation): + cells = mapped_netlist["modules"]["retrosoc_tiny_asic"]["cells"] + if mutation == "missing_pll": + del cells["pll"] + elif mutation == "missing_ram": + del cells["bank0"] + elif mutation == "live_pll": + cells["low"]["type"] = "TIEHIH7R" + elif mutation == "unknown_enable": + cells["pll"]["connections"]["EN"] = [99] + elif mutation == "divided_ram": + cells["bank0"]["connections"]["CLK"] = [99] + elif mutation == "wrong_cpu": + cells["u_soc.u_cpu.u_hazard3_cpu_1port.core.pc_reg"]["connections"]["CK"] = [99] + else: + cells["pll"]["connections"]["CKOUT1"] = [2] + with pytest.raises(ValueError): + inspect_netlist(mapped_netlist) + + +def test_tiny_uses_32_actual_ics55_small_macros(tmp_path): + assert shutil.which("verilator"), "required simulator unavailable" + generated = tmp_path / "map" + generate_memory_map(ROOT / "rtl/tiny/address_map/memory_map.json", generated, "YES", 128) + sram = ROOT / ".cache/retrosoc/pdk/ics55/sram" + sources = [COMMON / p for p in ["interface/axi4_if.sv", "interface/apb4_if.sv", + "interface/axi4_addr_gen.sv", "utils/register.sv", "tech/ram.sv"]] + sources += [ROOT / p for p in ["rtl/tech/tc_sram.sv", "rtl/ip/memory/onchip_ram_reg.sv", + "rtl/ip/memory/onchip_ram.sv", "tests/rtl/onchip_ram_tb.sv"]] + sources += [sram / "ics55_ecos_sram_1024x32_m8/verilog/ics55_ecos_sram_1024x32_m8_core.v", + sram / "ics55_ecos_sram_4096x32_m8/verilog/ics55_ecos_sram_4096x32_m8_stub.v"] + binary = tmp_path / "sim" + run(["verilator", "--binary", "--timing", "--assert", "-Wno-fatal", "--top-module", "onchip_ram_tb", + "-GCapacityKiB=128", "-GIcs55SmallBanks=1", "-DPDK_ICS55", "-DHAVE_SRAM_MACRO", "-DSV_ASSRT_DISABLE", + *(f"-I{p}" for p in [generated / "rtl", ROOT / "rtl/ip/memory", COMMON, COMMON / "interface"]), + *map(str, sources), "--Mdir", str(tmp_path / "obj"), "-o", str(binary)], + timeout=180, env={**os.environ, "CCACHE_DISABLE": "1"}) + assert "on-chip SRAM AXI4 test passed capacity_kib=128" in run([str(binary)], timeout=60) + + +@pytest.mark.parametrize("simulator", ["iverilog", "verilator"]) +def test_gpio_readback_preserves_legacy_default(tmp_path, simulator): + assert shutil.which(simulator), "required simulator unavailable" + model = ROOT / ("rtl/tech/ics55_io_sim_cells.sv" if simulator == "verilator" else + "physical/pdk/icsprout55-pdk/IP/IO/ICsprout_55LLULP1233_IO_251013/verilog/icsIOA_N55_3P3.v") + sources = [str(ROOT / "rtl/tech/tc_io.sv"), str(model), str(ROOT / "tests/rtl/tc_ics55_gpio_readback_tb.sv")] + binary = tmp_path / "sim" + if simulator == "iverilog": + compile_command = [simulator, "-g2012", "-DPDK_ICS55", "-Dfunctional", "-s", + "tc_ics55_gpio_readback_tb", *sources, "-o", str(binary)] + launch = ["vvp", str(binary)] + else: + compile_command = [simulator, "--binary", "--timing", "-Wno-fatal", "-DPDK_ICS55", + "--top-module", "tc_ics55_gpio_readback_tb", *sources, + "--Mdir", str(tmp_path / "obj"), "-o", str(binary)] + launch = [str(binary)] + run(compile_command, timeout=120, env={**os.environ, "CCACHE_DISABLE": "1"}) + assert "SIM_TEST_PASS ICS55 GPIO" in run(launch, timeout=30) diff --git a/tests/test_tiny_r2_baseline.py b/tests/test_tiny_r2_baseline.py index 32dbf25d..0dbc664d 100644 --- a/tests/test_tiny_r2_baseline.py +++ b/tests/test_tiny_r2_baseline.py @@ -281,7 +281,7 @@ def test_invalid_runtime_fails_before_compilation_or_simulation(locked_icarus, m image_path.write_text("{}") image = {"source": {}, "artifacts": {"tiny_baseline.hex": {"sha256": "fixture"}}} monkeypatch.setattr(baseline, "retained_image", lambda *_: (image_path, image)) - monkeypatch.setattr(baseline, "configuration", lambda *_: {}) + monkeypatch.setattr(baseline, "configuration", lambda *_: {"configuration": {"PDK": "IHP130"}}) monkeypatch.setattr(baseline, "execute", lambda *_args, **_kwargs: pytest.fail("tool execution preceded rejection")) args = argparse.Namespace(root=root, variant_root=root / "build/variant", simulator="iverilog", hex=None, vvp=selected, command=["unexpected-build"]) @@ -322,7 +322,7 @@ def test_runtime_retention_and_actual_launch_are_rechecked(locked_icarus, mutati def test_image_retains_compiler_command_after_postprocessing(tmp_path: Path, monkeypatch) -> None: variant = tmp_path / "build/variant" - monkeypatch.setattr(baseline, "configuration", lambda *_: {}) + monkeypatch.setattr(baseline, "configuration", lambda *_: {"configuration": {"PDK": "IHP130"}}) monkeypatch.setattr(baseline, "tools_identity", lambda *_: {}) monkeypatch.setattr(baseline, "source_inputs", lambda *_: {}) monkeypatch.setattr(baseline, "snapshot", lambda *_: {})