From b5cbc89928920704b0560cec234df1cbd0034260 Mon Sep 17 00:00:00 2001 From: maksyuki Date: Tue, 6 Oct 2026 14:28:52 +0800 Subject: [PATCH] feat: add Tiny R2 reset distribution and feasibility analysis - Implemented a new TCL script for Tiny R2 feasibility checks, reporting clock properties, path delays, and reset loads. - Created a SystemVerilog module for the Tiny R2 reset tree, managing multiple reset signals for the system and its leaves. - Developed a Python script for capturing and analyzing feasibility metrics, ensuring compliance with reset-load and clock frequency requirements. - Added testbenches for the Tiny R2 reset tree and FIFO, validating reset behavior and timing contracts. - Introduced unit tests for feasibility analysis, ensuring correct handling of clock rates and reset load conditions. --- docs/ip/tiny-soc-r2-reset-feasibility.md | 156 ++++++++++++ docs/ip/tiny-soc-verification.md | 156 +++++++++++- .../smoke/sta/opensta/tiny_r2_feasibility.tcl | 88 +++++++ publications/tiny_reference.py | 6 +- rtl/ip/core/mgmt_core_wrapper.sv | 6 +- rtl/ip/core/mgmt_debug_reset.sv | 8 +- rtl/ip/core/mgmt_debug_wrapper.sv | 15 +- rtl/rtl_style_audit.json | 1 + rtl/tiny/README.md | 8 + rtl/tiny/filelist/top.fl | 1 + rtl/tiny/integration/clock_reset_domains.json | 25 +- rtl/tiny/top/retrosoc_tiny.sv | 91 ++++--- rtl/tiny/top/tiny_reset_tree.sv | 50 ++++ scripts/generate_tiny.py | 7 +- scripts/tiny_r2_feasibility.py | 241 ++++++++++++++++++ tests/rtl/mgmt_debug_reset_tb.sv | 14 +- tests/rtl/onchip_ram_tb.sv | 20 ++ tests/rtl/tiny_r2_fifo_reset_tb.sv | 115 +++++++++ tests/rtl/tiny_r2_reset_tb.sv | 107 ++++++++ tests/test_mgmt_debug.py | 5 +- tests/test_publication_tiny.py | 22 +- tests/test_script_tools.py | 4 +- tests/test_tiny.py | 5 + tests/test_tiny_r2_p2.py | 124 +++++++++ 24 files changed, 1209 insertions(+), 66 deletions(-) create mode 100644 docs/ip/tiny-soc-r2-reset-feasibility.md create mode 100644 physical/smoke/sta/opensta/tiny_r2_feasibility.tcl create mode 100644 rtl/tiny/top/tiny_reset_tree.sv create mode 100644 scripts/tiny_r2_feasibility.py create mode 100644 tests/rtl/tiny_r2_fifo_reset_tb.sv create mode 100644 tests/rtl/tiny_r2_reset_tb.sv create mode 100644 tests/test_tiny_r2_p2.py diff --git a/docs/ip/tiny-soc-r2-reset-feasibility.md b/docs/ip/tiny-soc-r2-reset-feasibility.md new file mode 100644 index 00000000..5399b5f2 --- /dev/null +++ b/docs/ip/tiny-soc-r2-reset-feasibility.md @@ -0,0 +1,156 @@ +# Tiny R2-P2 reset distribution and clock feasibility + +This is the implementation record boundary for +[TINY-R2-P2](tiny-soc.md#tiny-r2-p2---reset-distribution-and-cpusram-clock-feasibility). +Results and remaining gates belong in the [verification ledger](tiny-soc-verification.md). +The starting revision is `f06c039e790e38ccaf336ff21b68619010e7cc9e`. +Use `configs/ci/ihp130-tiny.mk`, TINY, IHP130, 24 MHz/no PLL. Higher rates +below are analysis constraints, not executable profiles or qualified clocks. + +## Reset ownership + +`tiny_reset_tree` reuses the locked Common `rst_sync`. Every chain has five +stages and asserts asynchronously. Cold release traverses POR, system and +the leaf chains. All 17 leaf resets must be released before the core/debug +wrapper starts its own five-edge hart release. The nominal cold sequence is +POR at edge 5, system at edge 10, leaves at edge 15 and hart at edge 20. +These counts are behavioral sequencing, not a metastability or physical-skew +bound. A stopped clock never completes a release. + +```mermaid +flowchart LR + ext[RESET_N] --> por[POR: five edges] + por --> system[System: five edges] + watchdog[Watchdog reset request] --> system + system --> leaves[17 SYS reset leaves: five edges] + leaves --> ready[All leaves released] + ready --> hart[Hart: five edges] + hart --> cpu[CPU and request frontend] + por --> timer[Watchdog functional timer] + trst[JTAG_TRST_N] --> jtag[JTAG: five TCK edges] +``` + +Leaf zero belongs to the fabric, APB bridge, error responders and common tick. +Leaves 1 through 16 follow the existing `soc_topology.json` APB target order: +GPIO, UART0, UART1, TIMER0, TIMER1, I2C0, I2C1, XPI, DMA, SYSCTRL, CLINT, +SRAM, ARCHINFO, PWM, RTC and WDG. Generated APB interfaces and each IP's +explicit reset input use the same leaf. AXI interface reset arrays retain +their existing owner/target order. All clocks still use SYS; reset partitioning +does not add a clock domain, divider, clock gate or SRAM CDC. + +The watchdog functional timer stays on POR reset; its APB state uses the WDG +leaf. A watchdog request therefore survives the system reset it triggers. +Hart-only debug reset keeps its existing accepted-transfer drain and leaves +other clients running. The shared core/debug/reset wrappers add only the +`ResetSyncStages` RTL parameter: default 3 preserves Mini behavior, while Tiny +selects 5 for hart, JTAG and DMI release. There is no new software ABI. + +Common FIFO payload storage, pointer/count/empty behavior, flush precedence +and warm-flush handshakes are unchanged. The FIFO's mixed asynchronous-control +process already omits payload clearing; P2 does not rewrite that managed +process or claim to have removed a payload clear. Local reset loads are +partitioned instead. Empty-read masking and stale-data isolation are exercised +with directed cases and an independent queue scoreboard. + +Main SRAM payload is not reset. The existing SRAM protocol fixture now also +holds a read response, resets the controller, checks that the stale response +is cancelled and verifies the previously written word after release. Crypto +is not integrated in the selected Tiny baseline; its six private-bank and +physical FIFO erasure contracts remain unchanged. CPU release does not wait +for Crypto READY or external AUDIO/PIXCLK clocks. + +Leaf synchronizer instances have narrow `keep`/`keep_hierarchy` attributes to +prevent synthesis from merging identical reset drivers. The feasibility audit +requires 17 distinct instances and driver identities. These attributes do not +create a timing exception or establish a routed reset tree. + +## Reproducible analysis + +Before changing RTL, the implementation captured clean source, tools and inputs +and ran fresh firmware, balanced Yosys synthesis and core STA. The baseline is +`build/ihp130-tiny-2026-10-06-09-33-19f1e9c571ee/`; its +`meta/tiny-r2-p2/baseline-artifacts.json` binds the netlist, config, SDC, +commands/results and archived source. The preceding P1 timing numbers remain +separately attributed even when numerically equal. + +Use a new timestamp/variant for a candidate. With `APP=ci_smoke`, build firmware +first, capture its inputs, then run fresh synthesis and STA: + +```sh +source .cache/retrosoc/development/tiny-r2-p1/activate.sh +export BUILD_TIMESTAMP=$(date +%Y-%m-%d-%H-%M) +make CONFIG=configs/ci/ihp130-tiny.mk SOC=TINY PDK=IHP130 APP=ci_smoke firmware +tiny_candidate_root=$(make -s CONFIG=configs/ci/ihp130-tiny.mk SOC=TINY PDK=IHP130 APP=ci_smoke config | awk '$1 == "VARIANT_ROOT" {print $2}') +python3 scripts/tiny_r2_feasibility.py --variant-root "$tiny_candidate_root" capture +make -j1 CONFIG=configs/ci/ihp130-tiny.mk SOC=TINY PDK=IHP130 APP=ci_smoke SYNTH=YOSYS STA=OPENSTA synth sta +python3 scripts/tiny_r2_feasibility.py --variant-root "$tiny_candidate_root" run \ + --baseline-root build/ihp130-tiny-2026-10-06-09-33-19f1e9c571ee +``` + +The runner creates unique attempts below `meta/tiny-r2-p2/`. It rejects missing +or stale captures, synthesis older than the capture, incomplete native flows, +changed artifacts, and missing audit markers. Every point retains the exact +command, libraries, analysis SDC, metrics, reset loads/endpoints, net capacitance, +CPU and SRAM input/return paths, clock binding, constraint violations and +coverage report. Direct load counts exclude the output driver itself; a +structural all-arcs endpoint count is a different measure. +Native max/min WNS includes asynchronous recovery/removal checks. Separate +`sys-setup.rpt` and `sys-hold.rpt` report the SYS data-path slacks; neither +relabels a recovery violation as a data setup result. + +Both actual netlists are analyzed at SYS24/96/192/240 MHz (periods +41.666666667/10.416666667/5.208333333/4.166666667 ns) in slow, typical and fast +views. The 24 MHz SDC is retained byte-for-byte; other points replace only its +single SYS clock period. Existing uncertainty, clock grouping and port/reset +exceptions are retained. CPU and all 32 main-SRAM A_CLK loads share the same +SYS driver. No profile, PLL or main-SRAM divider changes result from this sweep. +The current peripherals also share SYS. This sweep does not model the later +R2-P7 SYS/MEM/PCLK partition or the future four 32 KiB SRAM frontends; its +failures and path measurements belong to the actual current netlist. + +The fast SRAM view is 1.32 V/-55 C, paired with the actual standard-cell and +IO fast -40 C views. Typical uses 1.20 V/25 C and slow uses 1.08 V/125 C. +The locked SRAM libraries provide setup/hold, clock-to-output and pulse-width +arcs but no explicit minimum-period entry. Missing characterization, table +extrapolation and uncovered checks remain gaps; nonnegative reported slack +does not establish a qualified maximum frequency. These ideal-clock, +unextracted analyses exclude CTS, routing, package and board effects. + +## Functional and compatibility acceptance + +Run focused tests and policy before the wider regression. Required fixtures +must actually execute with the selected tools; a missing tool or skipped +fixture cannot satisfy a gate. + +```sh +python3 -m pytest -q tests/test_tiny_r2_p2.py tests/test_tiny.py \ + tests/test_mgmt_debug.py tests/test_i2c.py tests/test_clock_reset_domains.py \ + 'tests/test_onchip_ram.py::test_onchip_ram_axi4_capacity_protocol_and_performance[128]' +make CONFIG=configs/ci/ihp130-tiny.mk SOC=TINY PDK=IHP130 rtl-format-check rtl-style-check rtl-readiness-check +ruff check . +python3 -m pytest -q +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 --dry-run +python3 scripts/regress.py --root . --suite nightly --soc TINY --pdk IHP130 --dry-run +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 +make CONFIG=configs/ci/ihp130-debug.mk SOC=MINI PDK=IHP130 SIMU=VERILATOR debug-sim +``` + +The additional Mini command checks existing shared-wrapper compatibility; +it is not a Tiny external-JTAG qualification or a new product rollout. +Its locked OpenOCD is restored separately under +`.cache/retrosoc/development/tiny-r2-p2-debug`; select that executable explicitly +without changing the P1 environment. Dependency versions remain unchanged. + +For matched-binary workloads, follow the [P1 runbook](tiny-soc-r2-baseline.md) +with a new variant and explicitly select the original +`image-ozwo38_b/tiny_baseline.hex`. Both simulators perform three cold starts. +Compare the measured windows separately from the intentionally longer reset +startup. Icarus has a 14400-second per-run wall budget and is polled every +30 minutes. Command success, valid TEST_STATUS, `SIM_TEST_PASS Tiny`, complete +records and absence of forbidden errors remain mandatory; UART alone cannot pass. + +P2 acceptance requires functional evidence, mapped reset partitioning and +source-bound attempted frequency results with explicit gaps. Warning and +metrics policies are unchanged. Failed timing remains failed, and neither +this phase nor its safe-frequency functional branch qualifies a physical +operating point or authorizes R2-P3 and later work. diff --git a/docs/ip/tiny-soc-verification.md b/docs/ip/tiny-soc-verification.md index 634a0b48..69e4917d 100644 --- a/docs/ip/tiny-soc-verification.md +++ b/docs/ip/tiny-soc-verification.md @@ -8,8 +8,10 @@ or register ABI version. The selected executable baseline remains their physical acceptance do not exist merely because this plan names them. The R2 freeze itself changed documentation only. Separately authorized -R2-P1 implementation and validation are recorded below; the other R2 -implementation and qualification phases remain **pending**. +R2-P1 implementation and validation are recorded below. Its functional and +measurement baseline was accepted with the explicit R2-P2 implementation +approval on 2026-10-06; the recorded physical gaps remain open. R2-P2 work is +recorded separately below; later implementation and qualification remain pending. Historical `TINY-P0` through `TINY-P12` identifiers and evidence are preserved under their original scope; the mapping in the main contract transfers outstanding obligations without renaming old results or declaring them passed. @@ -171,8 +173,8 @@ not a prerequisite to the architectural improvements in R2-P3 through R2-P5. | Active phase | Required evidence and completion boundary | Status | | --- | --- | --- | | TINY-R2-P0 - Performance Contract and Roadmap Freeze | Reviewable requirements, exact old/new phase mapping, invariant package/IO counts, same-frequency CPU/main-SRAM ownership, explicit performance budgets, links and commands. Record actual documentation checks separately. | Documentation freeze only; no hardware result | -| TINY-R2-P1 - Reproducible Baseline, Constraints and Measurements | Reviewed source/profile/lock/tool identity; current 24 MHz functional and workload baseline; clocks, reset endpoints, constraints and timing-exception audit; counters and comparison methodology. Historical WNS is a risk reference, not a refreshed measurement. | Implementation and baseline evidence ready for review; timing and warning failures recorded; phase acceptance pending human review | -| TINY-R2-P2 - Reset Distribution and CPU/SRAM Clock Feasibility | Reset distribution and payload-reset semantics; locked IHP130 main-SRAM macro timing, candidate common CPU/SRAM periods and representative paths; early clock/reset feasibility with explicit gaps. No final routed/PVT pass is implied. | Pending | +| TINY-R2-P1 - Reproducible Baseline, Constraints and Measurements | Reviewed source/profile/lock/tool identity; current 24 MHz functional and workload baseline; clocks, reset endpoints, constraints and timing-exception audit; counters and comparison methodology. Historical WNS is a risk reference, not a refreshed measurement. | Functional/measurement baseline accepted by explicit P2 approval on 2026-10-06; timing, warning and physical gaps retained | +| TINY-R2-P2 - Reset Distribution and CPU/SRAM Clock Feasibility | Reset distribution and payload-reset semantics; locked IHP130 main-SRAM macro timing, candidate common CPU/SRAM periods and representative paths; early clock/reset feasibility with explicit gaps. No final routed/PVT pass is implied. | Implementation and evidence ready for review; all analyzed rates retain timing failures; phase acceptance pending human review | | TINY-R2-P3 - Software and DMA Scheduling | Compiler and placement A/B results, finite DMA ownership, burst/chunk scheduling, WS2812 refill budget and workload correctness on the available platform. | Pending | | TINY-R2-P4 - Dual-Port Hazard3 and Four-Bank Local SRAM | Actual separate I/D paths, four independent 32 KiB bank frontends, SYS-clocked physical main-SRAM macros, local latency/fairness, FENCE.I, debug/reset and early inventory/SDC/macro-binding updates. | Pending | | TINY-R2-P5 - Per-Target Concurrent Fabric | Cross-target concurrency, one combined transaction per target, central-DMA read/write overlap, AW/W binding, errors, simultaneous faults/counters and accepted-transfer drain. | Pending | @@ -537,6 +539,152 @@ versions, warning baselines and metrics policy are unchanged by this repair. The earlier failed timing qualification and all unrun physical/debug/PVT gates remain open. No phase acceptance or subsequent-phase authorization is implied. +## R2-P2 implementation evidence (2026-10-06) + +The approved starting revision is `f06c039e790e38ccaf336ff21b68619010e7cc9e` +on `dev`, clean before work. Target `TINY`, profile `configs/ci/ihp130-tiny.mk`, +PDK `IHP130`, 24 MHz/no PLL remain fixed. The +[reset/feasibility runbook](tiny-soc-r2-reset-feasibility.md) owns the commands, +reset-leaf mapping, measurement endpoints and qualification boundaries. + +Before editing RTL, fresh firmware, Yosys and STA completed under +`build/ihp130-tiny-2026-10-06-09-33-19f1e9c571ee/` (`APP=ci_smoke`). +`meta/tiny-r2-p2/baseline-inputs/identity.json` retains the clean source/tools; +`baseline-artifacts.json` additionally binds generated inputs, netlist/config, +SDC and native flow results. The source input digest is +`336aa85c3f213104d0d4b875ae18a427a403bb829c775904784311fd865cbfd0` and netlist +SHA-256 is `7caefc1d53aeec3d506efea6d90a7dc3845dd8177a6bba262e9fbd3814d34631`. +Fresh setup WNS/TNS are -455.18/-13049600 ns, hold WNS/TNS zero. These new +records do not change the attribution of the equal P1 timing values. + +The implementation partitions reset across the existing 16 APB targets and +one fabric leaf, with five-edge local releases and CPU-last startup. Shared +debug wrappers retain default three-stage behavior and Tiny selects five. +No FIFO/CDC managed source, Crypto erasure, memory capacity, clock operating +point, register/HAL ABI, dependency version or quality policy changes. + +Development checks passed both simulators' reset/FIFO fixtures, shared +three/five-stage hart reset, Tiny protocol/generator checks, I2C recovery, +clock metadata and 128 KiB SRAM protocol/reset readback. The first four-state +fixtures lacked a post-elaboration reset edge; their startup failures were +fixed in test initialization, not by changing the design. RTL format/style and +readiness passed. Common's FIFO and warm-flush fixtures passed in both +simulators with output redirected below the candidate build tree. + +The initial candidate under `build/ihp130-tiny-2026-10-06-10-05-19f1e9c571ee/` +completed synthesis/STA at aggregate max-delay WNS -94.28 ns and TNS +-1594880 ns; min-delay WNS/TNS zero. These native metrics include recovery +checks, rather than only data setup. Timing still fails. The worst path is the XPI leaf +reset driver's recovery path. The first multi-point audit stopped because +Yosys renamed a leaf wire to its APB interface alias. The audit was +corrected to resolve actual mapped leaf registers and consumed nets; this +partial campaign is not complete feasibility acceptance. A subsequent probe +also rejected an invalid Tcl name-escaping expression. The final audit resolves +the preserved leaf instances, their actual output-register pins and consumed +interface nets without relying on source-level aliases or unsafe name escaping. + +Final synthesis/analysis evidence uses +`build/ihp130-tiny-2026-10-06-10-25-19f1e9c571ee/meta/tiny-r2-p2/`. +`feasibility-rke9efn7/report.json` completed all 24 source-bound points: two +netlists, three corners, and four common SYS periods. Every point retains +the exact libraries/SDC, commands, metrics, reset loads/endpoints, CPU/SRAM +paths and constraint/coverage reports. No frequency point passed timing. + +| Corner | Baseline aggregate WNS at 24 MHz (ns) | Candidate aggregate WNS at 24 MHz (ns) | Candidate SYS data setup slack (ns) | Candidate SYS data hold slack (ns) | +| --- | ---: | ---: | ---: | ---: | +| Slow | -455.18 | -94.28 | -87.78 | 0.22 | +| Typical | -299.07 | -51.46 | -48.16 | 0.11 | +| Fast | -185.71 | -19.94 | -19.18 | 0.02 | + +| Candidate corner | SYS96 aggregate WNS (ns) | SYS192 aggregate WNS (ns) | SYS240 aggregate WNS (ns) | +| --- | ---: | ---: | ---: | +| Slow | -125.53 | -130.74 | -131.78 | +| Typical | -82.71 | -87.91 | -88.96 | +| Fast | -51.19 | -56.39 | -57.44 | + +The baseline root reset directly drives 17211 loads; the candidate root drives +85 leaf-synchronizer reset pins. All 17 leaf instances and output-register +drivers remain distinct. Largest leaf loads are XPI 4710, PWM 4193 and DMA +3212. These residual loads and their recovery/data-path violations remain +explicit early feasibility limits, not a routed reset-tree pass. Both netlists +retain exactly 32 main macros driven directly by SYS. The fast SRAM/standard-cell +temperature mismatch, missing explicit macro minimum period, and ideal-clock, +unextracted analysis limitations remain as documented in the runbook. + +The fresh baseline reports 207695 cells and 9107192.081398 square micrometers +of library area; the final candidate reports 208551 cells and 9109365.619198 +square micrometers. These are native balanced-synthesis observations, including +normal build-identity constants, not extracted die area or a power claim. +Final metric collection retains the unchanged observe policy. Synthetic +negative-test result JSONs were initially found by the generic collector below +the full-Pytest scratch directory. After tests completed, that directory was +moved to the separate `ihp130-tiny-2026-10-06-12-38-19f1e9c571ee` build variant, +with its original path preserved as a symlink. `test-fixture-relocation.json` +records the move and the original metric file; final native firmware, synthesis +and timing values are unchanged, and synthetic test flows are excluded. + +OpenOCD 0.12.0-1 and the SBT 2.0.5 launcher bundle are restored through the existing locked helper +in `.cache/retrosoc/development/tiny-r2-p2-debug`; P1's environment is unchanged. +The initial OpenOCD download's sandbox DNS failure and the initial Mini debug +attempt's SBT local-socket permission failure are retained separately. These +are environment failures, not hardware acceptance. The retry with locked tools +and local-socket permission passed `DEBUG_GDB_PASS` in 47.323 seconds under +`build/ihp130-debug-2026-10-06-10-15-b4a322720e40/sim/verilator/debug/`. +The locked VexiiRiscv project selected sbt 1.10.0 on Ubuntu Java 17.0.18, +as recorded in the generation log; its project tool selection was not changed. +This validates shared-wrapper default compatibility, not Tiny external JTAG. + +The final focused suite passed 45 cases; source format/style/readiness and +Ruff passed. The Tiny PR campaign completed with exit zero in 2185.548 seconds +under `BUILD_TIMESTAMP=2026-10-06-10-26`. Both ordinary RTL simulations passed +at 3744281 cycles; Icarus took 1603.291 seconds. Netlist boot passed at 29373 +cycles in 147.240 seconds. The PR synthesis and STA commands completed, while +warning observations and timing failures retain their existing policy boundary. +This is not a full-C netlist or physical pass. + +The first full Pytest run recorded 1500 passed, 2 failed, 10 fixture errors and +one skipped PDF test. Both root causes were static consumers of the approved +reset parameter change: the Tiny publication collector required the old exact +three-parameter CPU dictionary, and the debug-flow test required the old +unparameterized instance spelling. The collector now explicitly requires +`ResetSyncStages=5` and includes the reset tree in source tracking; its negative +tests reject a wrong stage count and preserve the reviewed-snapshot check. +The debug test checks actual parameter forwarding and unchanged default three +stages. These compatibility updates do not alter RTL or publication identity; +no PDF or publication snapshot is refreshed. The focused consumer suite passed +31 tests. Final full Pytest passed 1517 tests with one skipped PDF retrieval +test (`pypdf` unavailable) in 3624.06 seconds. The initial failed run remains +recorded separately; focused and full-suite counts overlap. + +`consumer-inputs-rv35_5et/identity.json` in the final analysis root retains the +post-flow consumer/test increment and verifies that every archived RTL, +generator, STA and workload source input is unchanged. Original timing inputs +and reports are preserved, rather than relabeled with these later test changes. + +P1-image Verilator replay passed all three runs under +`build/ihp130-tiny-2026-10-06-10-27-fa1c4ce1e303/meta/tiny-r2-p1/workloads/verilator/attempt-88f2f259/`. +All measured records match the original P1 image baseline; terminal cycles +increase from 18458948 to 18458959, separately recording the intentional +11-cycle reset-startup change. Icarus attempt +`workloads/iverilog/attempt-e147iu49` under the same workload root passed all +three runs at 18458959 cycles, in 11275.215, 11254.774 and 11341.696 wall +seconds. All three logs satisfy command success, TEST_STATUS/SIM_TEST_PASS +and forbidden-marker checks. The workload root's `meta/tiny-r2-p1/baseline-report.json` +passed cross-simulator aggregation with the unchanged original HEX and identical +measured records. This directory/record retains the P1 measurement protocol +identity; its separate RTL-source snapshot identifies the P2 candidate. + +Implementation and reproducible P2 evidence are ready for human review. +Timing qualification still fails at all analyzed rates, including 24 MHz; +the current executable branch is functionally validated, not physically qualified. +Full C netlist execution, routed/PVT/IO/package/silicon qualification, exhaustive +CDC/RDC and Tiny external-JTAG qualification remain unrun. No standalone formal +qualification is claimed; this phase used directed reset/protocol checks and +an independent FIFO scoreboard with unchanged Common implementations. +No Required-rule MISRA deviation was added. Publication PDF rebuilding and +snapshot advancement were not part of the consumer-compatibility repair. +No P2 acceptance or later-phase authorization is claimed. + ## Archived P6/P10 planning and documentation evidence The remainder of this section preserves the prior planning and P10 document diff --git a/physical/smoke/sta/opensta/tiny_r2_feasibility.tcl b/physical/smoke/sta/opensta/tiny_r2_feasibility.tcl new file mode 100644 index 00000000..8ad30866 --- /dev/null +++ b/physical/smoke/sta/opensta/tiny_r2_feasibility.tcl @@ -0,0 +1,88 @@ +# Source-bound Tiny R2 feasibility only. No timing exceptions are added here. +source [file join [file dirname [info script]] opensta.tcl] +set output $::env(TINY_R2_OUTPUT) +set candidate $::env(TINY_R2_CANDIDATE) +report_clock_properties > $output/clocks.rpt +report_checks -path_group clk_system -path_delay max -group_path_count 1 -fields {capacitance slew fanout} > $output/sys-setup.rpt +report_checks -path_group clk_system -path_delay min -group_path_count 1 -fields {capacitance slew fanout} > $output/sys-hold.rpt +report_check_types -recovery -removal -min_pulse_width -min_period -max_slew -max_capacitance -max_fanout -violators -verbose > $output/constraint-violations.rpt +check_setup -verbose > $output/coverage.rpt + +set counts [open $output/reset-loads.tsv w] +puts $counts "name\tdrivers\tdirect_loads\tstructural_endpoints" +proc reset_report {label nets {drivers {}}} { + global output counts + if {[llength $nets] != 1} { error "expected one reset net for $label" } + set net [lindex $nets 0] + if {[llength $drivers] == 0} { + set drivers [get_pins -of_objects $net -filter {direction == output}] + } + if {[llength $drivers] != 1} { error "expected one reset driver for $label" } + set direct {} + foreach pin [get_fanout -from $drivers -flat -pin_levels 1 -trace_arcs all] { + if {[get_property $pin direction] eq "input"} { lappend direct $pin } + } + set endpoints [get_fanout -from $drivers -flat -endpoints_only -trace_arcs all] + puts $counts "$label\t[get_full_name [lindex $drivers 0]]\t[llength $direct]\t[llength $endpoints]" + report_net [get_full_name $net] > $output/$label-net.rpt + report_checks -through $drivers -path_delay min_max -group_path_count 10 -fields {capacitance slew fanout} > $output/$label-paths.rpt + set file [open $output/$label-endpoints.rpt w] + foreach pin $endpoints { puts $file [get_full_name $pin] } + close $file +} +reset_report system [get_nets -quiet {u_soc.s_rst_n}] +set leaf_instances [get_cells -hierarchical -quiet {*u_leaf_rst_sync}] +if {$candidate} { + if {[llength $leaf_instances] != 17} { error "reset leaves merged or missing" } + set output_register_pins [get_pins -hierarchical -quiet {*rst_n_o_reg/Q}] + for {set index 0} {$index < 17} {incr index} { + set selected {} + foreach instance $leaf_instances { + set name [get_full_name $instance] + if {[regexp {gen_leaf\[([0-9]+)\][.]u_leaf_rst_sync$} $name -> leaf_index] && $leaf_index == $index} { + lappend selected $instance + } + } + if {[llength $selected] != 1} { error "leaf index missing or duplicated: $index" } + set name [get_full_name [lindex $selected 0]] + set driver {} + foreach pin $output_register_pins { + if {[get_full_name $pin] eq "$name/rst_n_o_reg/Q"} { lappend driver $pin } + } + if {[llength $driver] != 1} { error "mapped leaf output register missing: $name" } + # Yosys may rename an alias to its APB interface. Resolve the consumed + # net through the preserved leaf instance, not a source-level wire name. + set outputs [get_pins -of_objects $selected -filter {direction == output}] + reset_report leaf-$index [get_nets -of_objects $outputs] $driver + } +} elseif {[llength $leaf_instances] != 0} { + error "pre-change netlist already contains reset leaves" +} +close $counts + +set clock_source [get_pins -quiet {u_clock_buffer/clk_o}] +if {[llength $clock_source] != 1} { error "system clock source missing" } +set macro_clocks {} +foreach pin [get_fanout -from $clock_source -flat -pin_levels 1 -trace_arcs all] { + if {[string match {*/A_CLK} [get_full_name $pin]]} { lappend macro_clocks $pin } +} +if {[llength $macro_clocks] != 32} { error "expected 32 direct SYS main-SRAM clock loads" } +set macros [get_cells -hierarchical -filter {ref_name == RM_IHPSG13_1P_1024x32_c2_bm_bist}] +if {[llength $macros] != 32} { error "main-SRAM macro binding differs from 32" } +set file [open $output/sram-clock-pins.rpt w] +foreach pin $macro_clocks { puts $file [get_full_name $pin] } +close $file +set macro_inputs {} +set macro_outputs {} +foreach pin [get_pins -of_objects $macros] { + set name [get_full_name $pin] + if {[string match {*/A_DOUT*} $name]} { lappend macro_outputs $pin } + if {[regexp {/A_(ADDR|DIN|BM|WEN|REN|MEN)} $name]} { lappend macro_inputs $pin } +} +if {[llength $macro_inputs] == 0 || [llength $macro_outputs] == 0} { error "SRAM data timing pins missing" } +report_checks -to $macro_inputs -path_delay min_max -group_path_count 20 -fields {capacitance slew fanout} > $output/sram-input-paths.rpt +report_checks -from $macro_outputs -path_delay min_max -group_path_count 20 -fields {capacitance slew fanout} > $output/sram-return-paths.rpt +set cpu_pins [get_pins -hierarchical -quiet {*u_cpu*/Q}] +if {[llength $cpu_pins] == 0} { error "CPU sequential launch pins missing" } +report_checks -from $cpu_pins -path_delay min_max -group_path_count 20 -fields {capacitance slew fanout} > $output/cpu-paths.rpt +puts "TINY_R2_FEASIBILITY_PASS: source-bound reports; 32 SYS macros; [llength $leaf_instances] reset leaves" diff --git a/publications/tiny_reference.py b/publications/tiny_reference.py index 294f4514..b645f3c8 100644 --- a/publications/tiny_reference.py +++ b/publications/tiny_reference.py @@ -23,6 +23,7 @@ TOPOLOGY = "rtl/tiny/integration/soc_topology.json" PINS = "rtl/tiny/pin_map/pin_map.json" CLOCKS = "rtl/tiny/integration/clock_reset_domains.json" TOP = "rtl/tiny/top/retrosoc_tiny.sv" +RESET_TREE = "rtl/tiny/top/tiny_reset_tree.sv" SYSCTRL = "rtl/tiny/top/tiny_sysctrl.sv" ARCHINFO = "rtl/tiny/top/tiny_archinfo.sv" FAMILIES = {"xpi", "sram", "gpio", "dma", "timer", "pwm", "rtc", "wdg", "uart", "i2c", "clint", "archinfo"} @@ -71,8 +72,9 @@ def facts(root: Path = ROOT) -> dict: raise ValueError("Tiny SRAM parameters changed") 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}: + if cpu != {"ExternalIrqCount": 30, "EnableAtomics": 0, "TwoCycleBusErrors": 1, "ResetSyncStages": 5}: raise ValueError("Tiny CPU integration changed") + require_snippets(root, RESET_TREE, [".STAGE(5)", "assign cpu_ready_rst_n_o = &leaf_rst_n_o;"]) require_snippets(root, "rtl/ip/core/mgmt_core_wrapper.sv", [".EXTENSION_C(1)", ".EXTENSION_A(EnableAtomics)"]) require_snippets(root, TOP, ["u_masters_axi4_if[2]", ".axi4(u_masters_axi4_if[0])", ".axi4(u_masters_axi4_if[1])", ".cfg_apb4(u_sram_apb4_if)", @@ -225,7 +227,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, SYSCTRL, ARCHINFO, config["profile"], + paths = {MAP, TOPOLOGY, PINS, CLOCKS, TOP, RESET_TREE, SYSCTRL, ARCHINFO, 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/core/mgmt_core_wrapper.sv b/rtl/ip/core/mgmt_core_wrapper.sv index e0c7cfa2..6afa424c 100644 --- a/rtl/ip/core/mgmt_core_wrapper.sv +++ b/rtl/ip/core/mgmt_core_wrapper.sv @@ -13,7 +13,8 @@ module mgmt_core_wrapper #( parameter int ExternalIrqCount = 30, parameter bit EnableAtomics = 1'b1, - parameter bit TwoCycleBusErrors = 1'b0 + parameter bit TwoCycleBusErrors = 1'b0, + parameter int ResetSyncStages = 3 ) ( // verilog_format: off -- preserve reviewed column alignment input logic clk_i, @@ -68,7 +69,8 @@ module mgmt_core_wrapper #( // verilog_format: on mgmt_debug_wrapper #( - .JtagIdcode(`SOC_JTAG_IDCODE) + .JtagIdcode (`SOC_JTAG_IDCODE), + .ResetSyncStages(ResetSyncStages) ) u_mgmt_debug_wrapper ( .clk_i (clk_i), .rst_n_i (rst_n_i), diff --git a/rtl/ip/core/mgmt_debug_reset.sv b/rtl/ip/core/mgmt_debug_reset.sv index d0011653..9505eabc 100644 --- a/rtl/ip/core/mgmt_debug_reset.sv +++ b/rtl/ip/core/mgmt_debug_reset.sv @@ -11,7 +11,9 @@ // Converts Debug Module reset requests into a management-hart-only reset. // The request is retained until the AHB-Lite bridge has completed its current // AXI4 transfer, so a debugger cannot discard an accepted management access. -module mgmt_debug_reset ( +module mgmt_debug_reset #( + parameter int ResetSyncStages = 3 +) ( input logic clk_i, input logic rst_n_i, input logic reset_req_i, @@ -55,7 +57,9 @@ module mgmt_debug_reset ( .dat_i (s_reset_active_d), .dat_o (s_reset_active_q) ); - rst_sync u_core_rst_sync ( + rst_sync #( + .STAGE(ResetSyncStages) + ) u_core_rst_sync ( .clk_i (clk_i), .rst_n_i(rst_n_i && !s_reset_active_q), .rst_n_o(core_rst_n_o) diff --git a/rtl/ip/core/mgmt_debug_wrapper.sv b/rtl/ip/core/mgmt_debug_wrapper.sv index cc65fc45..5d6bad1c 100644 --- a/rtl/ip/core/mgmt_debug_wrapper.sv +++ b/rtl/ip/core/mgmt_debug_wrapper.sv @@ -11,7 +11,8 @@ // Hazard3 JTAG-DTM and Debug Module integration. The Debug Module is in the // management-core clock domain and exposes abstract commands only (no SBA). module mgmt_debug_wrapper #( - parameter logic [31:0] JtagIdcode = 32'hDEAD_BEEF + parameter logic [31:0] JtagIdcode = 32'hDEAD_BEEF, + parameter int ResetSyncStages = 3 ) ( input logic clk_i, input logic rst_n_i, @@ -67,7 +68,9 @@ module mgmt_debug_wrapper #( .clk_i(jtag_tck_i), .clk_o(s_jtag_tck_buf) ); - rst_sync u_jtag_rst_sync ( + rst_sync #( + .STAGE(ResetSyncStages) + ) u_jtag_rst_sync ( .clk_i (s_jtag_tck_buf), .rst_n_i(jtag_trst_n_i), .rst_n_o(s_jtag_trst_n_sync) @@ -78,7 +81,9 @@ module mgmt_debug_wrapper #( .dat_i (s_dmihardreset_req), .dat_o (s_dmihardreset_sync) ); - rst_sync u_dmi_rst_sync ( + rst_sync #( + .STAGE(ResetSyncStages) + ) u_dmi_rst_sync ( .clk_i (clk_i), .rst_n_i(rst_n_i && !s_dmihardreset_sync), .rst_n_o(s_dmi_rst_n) @@ -146,7 +151,9 @@ module mgmt_debug_wrapper #( .sbus_rdata (dbg_sbus_rdata_i) ); - mgmt_debug_reset u_mgmt_debug_reset ( + mgmt_debug_reset #( + .ResetSyncStages(ResetSyncStages) + ) u_mgmt_debug_reset ( .clk_i (clk_i), .rst_n_i (rst_n_i), .reset_req_i (s_sys_reset_req || s_hart_reset_req), diff --git a/rtl/rtl_style_audit.json b/rtl/rtl_style_audit.json index f8aa0658..ad5f310d 100644 --- a/rtl/rtl_style_audit.json +++ b/rtl/rtl_style_audit.json @@ -437,6 +437,7 @@ "tiny_archinfo.sv", "tiny_axi42apb4.sv", "tiny_axi4_fabric.sv", + "tiny_reset_tree.sv", "tiny_sysctrl.sv" ] }, diff --git a/rtl/tiny/README.md b/rtl/tiny/README.md index 69998509..938dc9ab 100644 --- a/rtl/tiny/README.md +++ b/rtl/tiny/README.md @@ -39,6 +39,14 @@ the contract history. The committed RTL/profile still implements the initial 24 MHz/no-PLL/four-channel design; no faster CPU/SRAM or PLL timing support follows from this freeze or the `HAVE_PLL` selector alone. +The R2-P2 candidate keeps that executable clock/memory configuration and adds +five-edge local reset release, separate reset leaves for the existing targets, +and CPU-last startup. Shared debug-wrapper defaults remain unchanged for Mini. +The [reset/feasibility runbook](../../docs/ip/tiny-soc-r2-reset-feasibility.md) +defines the source-bound audit and analysis-only frequency sweep. Results and +phase acceptance remain in the verification ledger; this is not routed timing +or high-frequency qualification. + The separately approved [PIO-lite extension](../../docs/ip/piolite.md) is part of the future standard Tiny product, with its own `PIOLITE-P0` through `PIOLITE-P5` phases. It adds two state machines, shared 32 x 16-bit program diff --git a/rtl/tiny/filelist/top.fl b/rtl/tiny/filelist/top.fl index fbea0eac..08b0c7ab 100644 --- a/rtl/tiny/filelist/top.fl +++ b/rtl/tiny/filelist/top.fl @@ -2,5 +2,6 @@ /top/tiny_axi42apb4.sv /top/tiny_sysctrl.sv /top/tiny_archinfo.sv +/top/tiny_reset_tree.sv /top/retrosoc_tiny.sv /top/retrosoc_tiny_asic.sv diff --git a/rtl/tiny/integration/clock_reset_domains.json b/rtl/tiny/integration/clock_reset_domains.json index 718581e9..f2842d78 100644 --- a/rtl/tiny/integration/clock_reset_domains.json +++ b/rtl/tiny/integration/clock_reset_domains.json @@ -8,10 +8,10 @@ { "name": "system", "clock": "clk_i", - "reset": "s_rst_n", - "reset_source": "rst_n_i", + "reset": "system_rst_n_o", + "reset_source": "s_system_req_n", "reset_primitive": "rst_sync", - "path": "rtl/tiny/top/retrosoc_tiny.sv", + "path": "rtl/tiny/top/tiny_reset_tree.sv", "instance": "u_system_rst_sync", "sta": { "source_port": "extclk_i_pad", @@ -36,6 +36,25 @@ } } ], + "reset_distribution": { + "release_edges": 5, + "por": "u_soc.u_reset_tree.u_por_rst_sync", + "system": "u_soc.u_reset_tree.u_system_rst_sync", + "system_source": "por_rst_n_o && !watchdog_reset_req_i", + "leaf_instance_pattern": "u_soc.u_reset_tree.gen_leaf[%d].u_leaf_rst_sync", + "leaf_names": [ + "fabric", "gpio", "uart0", "uart1", "timer0", "timer1", "i2c0", "i2c1", + "xpi", "dma", "sysctrl", "clint", "sram", "archinfo", "pwm", "rtc", "wdg" + ], + "cpu_prerequisite": "all leaf resets released", + "hart": "u_soc.u_cpu.u_mgmt_debug_wrapper.u_mgmt_debug_reset.u_core_rst_sync", + "jtag": "u_soc.u_cpu.u_mgmt_debug_wrapper.u_jtag_rst_sync", + "dmi": "u_soc.u_cpu.u_mgmt_debug_wrapper.u_dmi_rst_sync", + "watchdog_functional_reset": "POR only; APB uses wdg leaf", + "main_sram_payload_reset": "none", + "crypto": "not integrated; physical erasure contract unchanged", + "clock_domain": "system" + }, "crossings": [ { "name": "debug_dmi", diff --git a/rtl/tiny/top/retrosoc_tiny.sv b/rtl/tiny/top/retrosoc_tiny.sv index d9e1e846..867c8e61 100644 --- a/rtl/tiny/top/retrosoc_tiny.sv +++ b/rtl/tiny/top/retrosoc_tiny.sv @@ -21,6 +21,10 @@ module retrosoc_tiny ( output logic [7:0] test_code_o ); logic s_por_rst_n, s_rst_n; + logic [16:0] s_leaf_rst_n; + logic s_fabric_rst_n, s_cpu_ready_rst_n; + logic s_master_rst_n [2]; + logic s_target_rst_n [5]; logic s_debug_halted; logic [31:0] s_irq; logic s_fault_valid, s_fault_master, s_fault_write; @@ -34,7 +38,8 @@ module retrosoc_tiny ( logic [4:0] s_tick_count_d, s_tick_count_q; logic s_tick; logic s_unused_uart1_tx_stall, s_unused_uart1_rx_stall; - uart_if u_uart0_if (); + `include "tiny_apb_interfaces.svh" +uart_if u_uart0_if (); uart_if u_uart1_if (); i2c_if u_i2c0_if (); i2c_if u_i2c1_if (); @@ -45,7 +50,7 @@ module retrosoc_tiny ( dma_req_if u_dma_req_if (); rtc_if u_rtc_if ( .rtc_clk_i (clk_i), - .rtc_rst_n_i(s_rst_n) + .rtc_rst_n_i(s_rtc_rst_n) ); // The watchdog reset pulse must survive resetting the bus/CPU. Its timer // uses POR reset while its APB register domain uses the system reset. @@ -60,7 +65,7 @@ module retrosoc_tiny ( .USER_WIDTH(1) ) u_masters_axi4_if[2] ( .aclk (clk_i), - .aresetn(s_rst_n) + .aresetn(s_master_rst_n) ); axi4_if #( .ADDR_WIDTH(32), @@ -69,49 +74,56 @@ module retrosoc_tiny ( .USER_WIDTH(1) ) u_targets_axi4_if[5] ( .aclk (clk_i), - .aresetn(s_rst_n) + .aresetn(s_target_rst_n) ); axi4_stream_if #( .DATA_WIDTH(32) ) u_i2s_tx_axis_if ( .aclk (clk_i), - .aresetn(s_rst_n) + .aresetn(s_dma_rst_n) ); axi4_stream_if #( .DATA_WIDTH(32) ) u_i2s_rx_axis_if ( .aclk (clk_i), - .aresetn(s_rst_n) + .aresetn(s_dma_rst_n) ); axi4_stream_if #( .DATA_WIDTH(32) ) u_dvp_rx_axis_if ( .aclk (clk_i), - .aresetn(s_rst_n) + .aresetn(s_dma_rst_n) ); axi4_stream_if #( .DATA_WIDTH(32) ) u_crypto_tx_axis_if ( .aclk (clk_i), - .aresetn(s_rst_n) + .aresetn(s_dma_rst_n) ); axi4_stream_if #( .DATA_WIDTH(32) ) u_crypto_rx_axis_if ( .aclk (clk_i), - .aresetn(s_rst_n) + .aresetn(s_dma_rst_n) ); - `include "tiny_apb_interfaces.svh" + assign s_fabric_rst_n = s_leaf_rst_n[0]; + assign s_master_rst_n = '{s_cpu_ready_rst_n, s_dma_rst_n}; + assign s_target_rst_n = '{ + s_sram_rst_n, + s_xpi_rst_n, + s_fabric_rst_n, + s_fabric_rst_n, + s_fabric_rst_n + }; -rst_sync u_por_rst_sync ( - .clk_i (clk_i), - .rst_n_i(rst_n_i), - .rst_n_o(s_por_rst_n) - ); - rst_sync u_system_rst_sync ( - .clk_i (clk_i), - .rst_n_i(s_por_rst_n && !u_wdg_if.reset_req_o), - .rst_n_o(s_rst_n) + tiny_reset_tree u_reset_tree ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .watchdog_reset_req_i(u_wdg_if.reset_req_o), + .por_rst_n_o (s_por_rst_n), + .system_rst_n_o (s_rst_n), + .leaf_rst_n_o (s_leaf_rst_n), + .cpu_ready_rst_n_o (s_cpu_ready_rst_n) ); assign s_tick = s_tick_count_q == 5'd23; assign s_tick_count_d = s_tick ? 5'd0 : (s_tick_count_q + 5'd1); @@ -119,17 +131,18 @@ rst_sync u_por_rst_sync ( .DATA_WIDTH(5) ) u_tick_reg ( .clk_i (clk_i), - .rst_n_i(s_rst_n), + .rst_n_i(s_fabric_rst_n), .dat_i (s_tick_count_d), .dat_o (s_tick_count_q) ); mgmt_core_wrapper #( .ExternalIrqCount (30), .EnableAtomics (1'b0), - .TwoCycleBusErrors(1'b1) + .TwoCycleBusErrors(1'b1), + .ResetSyncStages (5) ) u_cpu ( .clk_i (clk_i), - .rst_n_i (s_rst_n), + .rst_n_i (s_cpu_ready_rst_n), .irq_i (s_irq), .jtag_tck_i (jtag_tck_i), .jtag_tms_i (jtag_tms_i), @@ -141,7 +154,7 @@ rst_sync u_por_rst_sync ( ); tiny_axi4_fabric u_fabric ( .clk_i (clk_i), - .rst_n_i (s_rst_n), + .rst_n_i (s_fabric_rst_n), .masters (u_masters_axi4_if), .targets (u_targets_axi4_if), .perf_enable_i (s_perf_enable), @@ -160,7 +173,7 @@ rst_sync u_por_rst_sync ( .IdWidth (1) ) u_sram ( .clk_i (clk_i), - .rst_n_i (s_rst_n), + .rst_n_i (s_sram_rst_n), .mem_axi4 (u_targets_axi4_if[0]), .cfg_apb4 (u_sram_apb4_if), .perf_enable_i(s_perf_enable), @@ -168,7 +181,7 @@ rst_sync u_por_rst_sync ( ); apb4_xpi u_xpi ( .clk_i (clk_i), - .rst_n_i (s_rst_n), + .rst_n_i (s_xpi_rst_n), .mem_axi4 (u_targets_axi4_if[1]), .apb4 (u_xpi_apb4_if), .xpi (xpi), @@ -179,7 +192,7 @@ rst_sync u_por_rst_sync ( ); tiny_axi42apb4 u_apb_bridge ( .clk_i (clk_i), - .rst_n_i(s_rst_n), + .rst_n_i(s_fabric_rst_n), .axi4 (u_targets_axi4_if[2]), `include "tiny_apb_connections.svh" ); @@ -187,19 +200,19 @@ rst_sync u_por_rst_sync ( .Response(2'b11) ) u_decode_error ( .clk_i (clk_i), - .rst_n_i(s_rst_n), + .rst_n_i(s_fabric_rst_n), .axi4 (u_targets_axi4_if[3]) ); axi4_error_slave #( .Response(2'b10) ) u_protocol_error ( .clk_i (clk_i), - .rst_n_i(s_rst_n), + .rst_n_i(s_fabric_rst_n), .axi4 (u_targets_axi4_if[4]) ); tiny_sysctrl u_sysctrl ( .clk_i (clk_i), - .rst_n_i (s_rst_n), + .rst_n_i (s_sysctrl_rst_n), .apb4 (u_sysctrl_apb4_if), .fault_valid_i (s_fault_valid), .fault_addr_i (s_fault_addr), @@ -217,7 +230,7 @@ rst_sync u_por_rst_sync ( ); tiny_archinfo u_archinfo ( .clk_i (clk_i), - .rst_n_i(s_rst_n), + .rst_n_i(s_archinfo_rst_n), .apb4 (u_archinfo_apb4_if) ); apb4_gpio #( @@ -226,7 +239,7 @@ rst_sync u_por_rst_sync ( .HasPullDown (gpio_pad_caps_pkg::HasPullDown) ) u_gpio ( .clk_i (clk_i), - .rst_n_i (s_rst_n), + .rst_n_i (s_gpio_rst_n), .apb4 (u_gpio_apb4_if), .gpio (u_gpio_if), .user_gpio(u_user_gpio_if) @@ -247,7 +260,7 @@ rst_sync u_por_rst_sync ( `include "tiny_irq.svh" apb4_uart u_uart0 ( .clk_i (clk_i), - .rst_n_i (s_rst_n), + .rst_n_i (s_uart0_rst_n), .apb4 (u_uart0_apb4_if), .uart (u_uart0_if), .dma_tx_stall_o(s_dma_stall[2]), @@ -255,7 +268,7 @@ apb4_uart u_uart0 ( ); apb4_i2c u_i2c0 ( .clk_i (clk_i), - .rst_n_i (s_rst_n), + .rst_n_i (s_i2c0_rst_n), .apb4 (u_i2c0_apb4_if), .i2c (u_i2c0_if), .dma_tx_stall_o(s_dma_stall[4]), @@ -263,14 +276,14 @@ apb4_uart u_uart0 ( ); apb4_timer u_timer0 ( .clk_i (clk_i), - .rst_n_i (s_rst_n), + .rst_n_i (s_timer0_rst_n), .debug_halted_i(s_debug_halted), .apb4 (u_timer0_apb4_if), .irq_o (s_timer0_irq) ); apb4_uart u_uart1 ( .clk_i (clk_i), - .rst_n_i (s_rst_n), + .rst_n_i (s_uart1_rst_n), .apb4 (u_uart1_apb4_if), .uart (u_uart1_if), .dma_tx_stall_o(s_unused_uart1_tx_stall), @@ -278,7 +291,7 @@ apb4_uart u_uart0 ( ); apb4_i2c u_i2c1 ( .clk_i (clk_i), - .rst_n_i (s_rst_n), + .rst_n_i (s_i2c1_rst_n), .apb4 (u_i2c1_apb4_if), .i2c (u_i2c1_if), .dma_tx_stall_o(s_dma_stall[6]), @@ -286,14 +299,14 @@ apb4_uart u_uart0 ( ); apb4_timer u_timer1 ( .clk_i (clk_i), - .rst_n_i (s_rst_n), + .rst_n_i (s_timer1_rst_n), .debug_halted_i(s_debug_halted), .apb4 (u_timer1_apb4_if), .irq_o (s_timer1_irq) ); apb4_clint u_clint ( .clk_i (clk_i), - .rst_n_i (s_rst_n), + .rst_n_i (s_clint_rst_n), .timebase_tick_i(s_tick), .apb4 (u_clint_apb4_if), .clint (u_clint_if) @@ -326,7 +339,7 @@ apb4_uart u_uart0 ( .EnableStreams(1'b0) ) u_dma ( .clk_i (clk_i), - .rst_n_i (s_rst_n), + .rst_n_i (s_dma_rst_n), .dma_xfer_done_o(s_dma_done), .irq_o (s_dma_irq), .hw_trg (u_dma_req_if), diff --git a/rtl/tiny/top/tiny_reset_tree.sv b/rtl/tiny/top/tiny_reset_tree.sv new file mode 100644 index 00000000..45c0dac3 --- /dev/null +++ b/rtl/tiny/top/tiny_reset_tree.sv @@ -0,0 +1,50 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// One SYS clock, separate reset loads. Leaf zero owns the fabric; the remaining +// leaves follow the canonical APB target order. This is not a clock-domain split. +module tiny_reset_tree #( + parameter int LeafCount = 17 +) ( + input logic clk_i, + input logic rst_n_i, + input logic watchdog_reset_req_i, + output logic por_rst_n_o, + output logic system_rst_n_o, + output logic [LeafCount-1:0] leaf_rst_n_o, + output logic cpu_ready_rst_n_o +); + logic s_system_req_n; + + assign s_system_req_n = por_rst_n_o && !watchdog_reset_req_i; + // The core/debug wrapper adds its five local release edges after every + // functional endpoint is ready. No external peripheral clock is a prerequisite. + assign cpu_ready_rst_n_o = &leaf_rst_n_o; + + rst_sync #( + .STAGE(5) + ) u_por_rst_sync ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .rst_n_o(por_rst_n_o) + ); + rst_sync #( + .STAGE(5) + ) u_system_rst_sync ( + .clk_i (clk_i), + .rst_n_i(s_system_req_n), + .rst_n_o(system_rst_n_o) + ); + for (genvar leaf = 0; leaf < LeafCount; leaf++) begin : gen_leaf + // Preserve independent reset drivers through the existing Yosys flow. + // P2 validates the mapped drivers and loads; this is not a timing exception. + (* keep = 1, keep_hierarchy = 1 *) + rst_sync #( + .STAGE(5) + ) u_leaf_rst_sync ( + .clk_i (clk_i), + .rst_n_i(system_rst_n_o), + .rst_n_o(leaf_rst_n_o[leaf]) + ); + end +endmodule diff --git a/scripts/generate_tiny.py b/scripts/generate_tiny.py index 6c908315..1b59cd38 100644 --- a/scripts/generate_tiny.py +++ b/scripts/generate_tiny.py @@ -91,15 +91,18 @@ def generate_bindings(document: dict, output: Path) -> None: f"assign u_gpio_if.{mode}_oe_i[{pin}] = {value['oe']};")) atomic_write(output / "rtl/tiny_gpio.svh", "\n".join(gpio) + "\n") ports, declarations, routes, decode = [], [comment], [comment], [comment] + reset_assignments = [] for index, target in enumerate(document["apb_targets"]): name = target["name"] ports.append(f"apb4_if.master {name}") - declarations.append(f"apb4_if u_{name}_apb4_if (.pclk(clk_i), .presetn(s_rst_n));") + declarations.append(f"logic s_{name}_rst_n;") + declarations.append(f"apb4_if u_{name}_apb4_if (.pclk(clk_i), .presetn(s_{name}_rst_n));") + reset_assignments.append(f"assign s_{name}_rst_n = s_leaf_rst_n[{index + 1}];") routes.append(f".{name}(u_{name}_apb4_if)") decode.append(f"if ({' || '.join(f'`SOC_ADDR_IS_{r}(s_addr_q)' for r in target['regions'])}) " f"s_select = {len(document['apb_targets'])}'b{1 << index:016b};") atomic_write(output / "rtl/tiny_apb_ports.svh", ",\n".join(ports) + "\n") - atomic_write(output / "rtl/tiny_apb_interfaces.svh", "\n".join(declarations) + "\n") + atomic_write(output / "rtl/tiny_apb_interfaces.svh", "\n".join(declarations + reset_assignments) + "\n") atomic_write(output / "rtl/tiny_apb_connections.svh", ",\n".join(routes[1:]) + "\n") atomic_write(output / "rtl/tiny_apb_decode.svh", "\n".join(decode) + "\n") wiring = [comment] diff --git a/scripts/tiny_r2_feasibility.py b/scripts/tiny_r2_feasibility.py new file mode 100644 index 00000000..877a1679 --- /dev/null +++ b/scripts/tiny_r2_feasibility.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python3 +"""Retain reset-load and same-frequency CPU/SRAM feasibility; never qualify a rate.""" + +from __future__ import annotations + +import argparse +from datetime import datetime, timezone +import math +from pathlib import Path +import re +import subprocess +import sys + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT)) +from scripts import tiny_r2_baseline as baseline # noqa: E402 + +PHASE = "TINY-R2-P2" +RATES = (24, 96, 192, 240) +CORNERS = { + "slow": ("slow_1p08V_125C", "slow_1p08V_3p0V_125C", "slow_1p08V_125C"), + "typ": ("typ_1p20V_25C", "typ_1p2V_3p3V_25C", "typ_1p20V_25C"), + "fast": ("fast_1p32V_m40C", "fast_1p32V_3p6V_m40C", "fast_1p32V_m55C"), +} +MACRO = "RM_IHPSG13_1P_1024x32_c2_bm_bist" +SDC_CLOCK = re.compile(r"^(create_clock -name clk_system -period )([0-9.]+)( \$clk_system_pin)$", re.MULTILINE) + + +def source_inputs(root: Path) -> dict: + inputs = baseline.source_inputs(root) + for path in (root / "tests").rglob("*"): + if path.is_file() and path.suffix in (".py", ".sv", ".svh"): + inputs[str(path.relative_to(root))] = baseline.sha256(path) + return inputs + + +def generated_inputs(root: Path, variant: Path) -> dict: + return {str(p.relative_to(root)): baseline.sha256(p) + for p in (variant / "generated/tiny").rglob("*") if p.is_file()} + + +def require_variant(root: Path, variant: Path) -> dict: + if not variant.is_relative_to(root / "build"): + 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": + raise ValueError("feasibility requires the 24 MHz Tiny ci_smoke variant") + return manifest + + +def candidate_sdc(original: str, mhz: int) -> str: + if mhz not in RATES or len(SDC_CLOCK.findall(original)) != 1: + raise ValueError("expected one baseline system clock and a frozen candidate rate") + if abs(float(SDC_CLOCK.search(original)[2]) - 1000 / 24) > 1e-6: + raise ValueError("source SDC is not the 24 MHz baseline") + if mhz == 24: + return original + return SDC_CLOCK.sub(lambda m: m[1] + f"{1000 / mhz:.12g}" + m[3], original) + + +def timing_metrics(path: Path) -> dict: + values = {} + for line in path.read_text().splitlines(): + key, _, value = line.partition("=") + values[key] = float(value.split()[-1]) + if (set(values) != {"wns_min", "wns_max", "tns_min", "tns_max"} + or not all(math.isfinite(value) for value in values.values())): + raise ValueError("incomplete STA measurements") + return values + + +def reset_loads(path: Path, candidate: bool) -> dict: + rows = {} + for line in path.read_text().splitlines()[1:]: + name, driver, direct, endpoints = line.split("\t") + if name in rows or min(int(direct), int(endpoints)) < 0: + raise ValueError("invalid reset-load accounting") + rows[name] = {"driver": driver, "direct_loads": int(direct), "structural_endpoints": int(endpoints)} + expected = {"system"} | ({f"leaf-{index}" for index in range(17)} if candidate else set()) + if set(rows) != expected or len({r["driver"] for r in rows.values()}) != len(rows): + raise ValueError("reset drivers merged or missing") + return rows + + +def path_slack(path: Path) -> float: + match = re.search(r"(?m)^\s*([-+]?\d+(?:\.\d+)?)\s+slack\b", path.read_text()) + if match is None: + raise ValueError(f"missing constrained SYS path: {path}") + return float(match[1]) + + +def capture(args: argparse.Namespace) -> int: + root, variant = args.root.resolve(), args.variant_root.resolve() + manifest = require_variant(root, variant) + parent = variant / "meta/tiny-r2-p2" + directory = baseline.new_attempt(parent, "inputs-") + inputs = source_inputs(root) | generated_inputs(root, variant) + record = {"phase": PHASE, "created_at": datetime.now(timezone.utc).isoformat(), + "manifest": manifest, "tools": baseline.tools_identity(root), + "source": baseline.snapshot(root, directory, inputs)} + baseline.dump(directory / "identity.json", record) + baseline.dump(parent / "latest-inputs.json", {"path": str(directory / "identity.json")}) + print(f"Tiny P2 captured candidate inputs: {directory}") + return 0 + + +def consumed_files(variant: Path) -> dict: + return {name: baseline.artifact(variant / name) for name in ( + "meta/manifest.json", "syn/yosys/result-synth.json", "sta/opensta/result-sta.json", + "syn/yosys/out/retrosoc_tiny_asic_yosys.v", "syn/yosys/out/retrosoc_tiny_asic_yosys.config", + "sta/opensta/retrosoc_core.sdc")} + + +def run(args: argparse.Namespace) -> int: + root, variant = args.root.resolve(), args.variant_root.resolve() + old_variant = args.baseline_root.resolve() + require_variant(root, variant) + require_variant(root, old_variant) + parent = variant / "meta/tiny-r2-p2" + directory = baseline.new_attempt(parent, "feasibility-") + output = directory / "report.json" + baseline.dump(parent / "latest-feasibility.json", {"path": str(output)}) + record = {"phase": PHASE, "status": "running", "points": [], + "qualification": "early unextracted feasibility only; no qualified operating point"} + try: + identity_path = Path(baseline.read_json(parent / "latest-inputs.json")["path"]) + identity = baseline.read_json(identity_path) + before = source_inputs(root) | generated_inputs(root, variant) + if before != baseline.read_json(baseline.check_artifact(identity["source"]["inputs"])): + raise ValueError("candidate inputs changed after capture") + for key in ("inputs", "snapshot", "patch"): + baseline.check_artifact(identity["source"][key]) + record.update(candidate_identity=baseline.artifact(identity_path), tools=baseline.tools_identity(root)) + original = baseline.read_json(old_variant / "meta/tiny-r2-p2/baseline-artifacts.json") + for key in ("inputs", "snapshot", "patch"): + baseline.check_artifact(original["source"][key]) + for artifact in original["artifacts"].values(): + baseline.check_artifact(artifact) + record["baseline_identity"] = baseline.artifact(old_variant / "meta/tiny-r2-p2/baseline-artifacts.json") + record["inputs"] = {"baseline": consumed_files(old_variant), "candidate": consumed_files(variant)} + for role, files in record["inputs"].items(): + for key in ("syn/yosys/result-synth.json", "sta/opensta/result-sta.json"): + result = baseline.read_json(Path(files[key]["path"])) + if result.get("status") != "passed" or result.get("exit_code") != 0: + raise ValueError(f"{role} lacks completed synthesis/STA") + if role == "candidate": + synth = baseline.read_json(Path(files["syn/yosys/result-synth.json"]["path"])) + if datetime.fromisoformat(synth["started_at"]) < datetime.fromisoformat(identity["created_at"]): + raise ValueError("candidate synthesis predates its captured inputs") + libraries = root / "physical/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref" + script = root / "physical/smoke/sta/opensta/tiny_r2_feasibility.tcl" + record["script"] = baseline.artifact(script) + record["macro_views"] = {} + for corner, (std, io, ram) in CORNERS.items(): + paths = [libraries / f"sg13g2_stdcell/lib/sg13g2_stdcell_{std}.lib", + libraries / f"sg13g2_io/lib/sg13g2_io_{io}.lib", + libraries / f"sg13g2_sram/lib/{MACRO}_{ram}.lib"] + text = paths[2].read_text() + record["macro_views"][corner] = { + "libraries": [baseline.artifact(p) for p in paths], + "explicit_minimum_period": bool(re.search(r"\b(?:minimum_period|min_period)\b", text)), + "setup": "setup_rising" in text, "hold": "hold_rising" in text, + "clock_to_output": "rising_edge" in text, "pulse_width": "min_pulse_width" in text, + "temperature_pairing": ram + "; stdcell " + std, + } + for role, files in record["inputs"].items(): + original_sdc = Path(files["sta/opensta/retrosoc_core.sdc"]["path"]).read_text() + for mhz in RATES: + point = directory / role / corner / str(mhz) + point.mkdir(parents=True) + sdc = point / "analysis.sdc" + baseline.atomic_write(sdc, candidate_sdc(original_sdc, mhz)) + environment = { + "OPENSTA_TOP": "retrosoc_tiny_asic", + "OPENSTA_NETLIST": files["syn/yosys/out/retrosoc_tiny_asic_yosys.v"]["path"], + "OPENSTA_LIBERTY": str(paths[0]), "OPENSTA_LINK_LIBS": str(paths[1]), + "OPENSTA_SRAM_LIBS": str(paths[2]), "OPENSTA_SDC": str(sdc), + "OPENSTA_REPORT": str(point / "timing.rpt"), + "OPENSTA_METRICS": str(point / "timing_metrics.rpt"), + "TINY_R2_OUTPUT": str(point), "TINY_R2_CANDIDATE": str(int(role == "candidate")), + } + command = [sys.executable, str(root / "scripts/run_flow.py"), "--tool", "opensta-p2", + "--log", str(point / "sta.log"), "--result", str(point / "result.json")] + for key, value in environment.items(): + command += ["--env", f"{key}={value}"] + command += ["--", record["tools"]["opensta"]["executable"]["path"], + "-no_init", "-exit", "-threads", "2", str(script)] + subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=1800) + log = (point / "sta.log").read_text() + if "TINY_R2_FEASIBILITY_PASS:" not in log or re.search(r"(?im)^Error:|\bFATAL\b", log): + raise ValueError(f"STA audit incomplete: {point}") + metrics = timing_metrics(point / "timing_metrics.rpt") + record["points"].append({"role": role, "corner": corner, "sys_mhz": mhz, + "metrics": metrics, + "sys_setup_path_slack_ns": path_slack(point / "sys-setup.rpt"), + "sys_hold_path_slack_ns": path_slack(point / "sys-hold.rpt"), + "reset_loads": reset_loads(point / "reset-loads.tsv", role == "candidate"), + "timing": "failed" if any(v < 0 for v in metrics.values()) else "nonnegative_reported_slack", + "artifacts": {p.name: baseline.artifact(p) for p in point.iterdir() if p.is_file()}}) + baseline.dump(output, record) + print(f"{role} {corner} SYS={mhz} MHz: WNS={metrics['wns_max']} ns; not qualification", flush=True) + if before != source_inputs(root) | generated_inputs(root, variant): + raise ValueError("candidate inputs changed during feasibility") + for files in record["inputs"].values(): + for artifact in files.values(): + baseline.check_artifact(artifact) + for views in record["macro_views"].values(): + for artifact in views["libraries"]: + baseline.check_artifact(artifact) + for key in ("candidate_identity", "baseline_identity", "script"): + baseline.check_artifact(record[key]) + for tool in record["tools"].values(): + baseline.check_artifact(tool["executable"]) + record["status"] = "completed" + except (OSError, ValueError, KeyError, subprocess.SubprocessError) as error: + record.update(status="failed", error=str(error)) + print(f"Tiny P2 feasibility: {error}", file=sys.stderr) + finally: + baseline.dump(output, record) + return int(record["status"] != "completed") + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--root", type=Path, default=ROOT) + parser.add_argument("--variant-root", type=Path, required=True) + subparsers = parser.add_subparsers(dest="action", required=True) + subparsers.add_parser("capture") + execute = subparsers.add_parser("run") + execute.add_argument("--baseline-root", type=Path, required=True) + args = parser.parse_args() + try: + return capture(args) if args.action == "capture" else run(args) + except (OSError, ValueError, KeyError, subprocess.SubprocessError) as error: + print(f"Tiny P2: {error}", file=sys.stderr) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/rtl/mgmt_debug_reset_tb.sv b/tests/rtl/mgmt_debug_reset_tb.sv index d5572d49..ba322d81 100644 --- a/tests/rtl/mgmt_debug_reset_tb.sv +++ b/tests/rtl/mgmt_debug_reset_tb.sv @@ -1,6 +1,8 @@ `timescale 1ns / 1ps -module mgmt_debug_reset_tb; +module mgmt_debug_reset_tb #( + parameter int ResetSyncStages = 3 +); logic s_clk; logic s_rst_n; logic s_reset_req; @@ -10,7 +12,9 @@ module mgmt_debug_reset_tb; always #5 s_clk = ~s_clk; - mgmt_debug_reset u_mgmt_debug_reset ( + mgmt_debug_reset #( + .ResetSyncStages(ResetSyncStages) + ) u_mgmt_debug_reset ( .clk_i (s_clk), .rst_n_i (s_rst_n), .reset_req_i (s_reset_req), @@ -27,7 +31,7 @@ module mgmt_debug_reset_tb; repeat (2) @(posedge s_clk); s_rst_n = 1'b1; - repeat (4) @(posedge s_clk); + repeat (ResetSyncStages + 1) @(posedge s_clk); #1; assert (s_core_rst_n && s_reset_done) else $fatal(1, "initial reset release failed"); @@ -46,7 +50,7 @@ module mgmt_debug_reset_tb; #1; assert (!s_core_rst_n && !s_reset_done) else $fatal(1, "core reset did not assert"); - repeat (4) @(posedge s_clk); + repeat (ResetSyncStages + 1) @(posedge s_clk); #1; assert (s_core_rst_n && s_reset_done) else $fatal(1, "core reset did not release"); @@ -62,7 +66,7 @@ module mgmt_debug_reset_tb; else $fatal(1, "held reset released too early"); s_reset_req = 1'b0; - repeat (4) @(posedge s_clk); + repeat (ResetSyncStages + 1) @(posedge s_clk); #1; assert (s_core_rst_n && s_reset_done) else $fatal(1, "held reset did not release"); diff --git a/tests/rtl/onchip_ram_tb.sv b/tests/rtl/onchip_ram_tb.sv index f941eb58..0a3bd93e 100644 --- a/tests/rtl/onchip_ram_tb.sv +++ b/tests/rtl/onchip_ram_tb.sv @@ -381,6 +381,26 @@ module onchip_ram_tb #( $fatal(1, "performance counters did not record the directed traffic"); end + // System reset cancels a held response but must preserve SRAM payload. + write_single(SramBase + 32'h300, `AXI4_BURST_SIZE_4BYTES, 32'h5A36_C9E7, 4'hf, `AXI4_RESP_OKAY); + @(negedge clk_i); + mem_axi4.araddr = SramBase + 32'h300; + mem_axi4.arvalid = 1'b1; + do @(posedge clk_i); while (!mem_axi4.arready); + @(negedge clk_i); + mem_axi4.arvalid = 1'b0; + mem_axi4.rready = 1'b0; + wait (mem_axi4.rvalid); + @(negedge clk_i); + rst_n_i = 1'b0; + #1; + if (mem_axi4.rvalid || mem_axi4.bvalid) $fatal(1, "reset retained a stale SRAM response"); + repeat (3) @(posedge clk_i); + @(negedge clk_i); + rst_n_i = 1'b1; + repeat (2) @(negedge clk_i); + read_single(SramBase + 32'h300, `AXI4_BURST_SIZE_4BYTES, 32'h5A36_C9E7, `AXI4_RESP_OKAY); + $display("on-chip SRAM AXI4 test passed capacity_kib=%0d", CapacityKiB); $finish; end diff --git a/tests/rtl/tiny_r2_fifo_reset_tb.sv b/tests/rtl/tiny_r2_fifo_reset_tb.sv new file mode 100644 index 00000000..40e2e02d --- /dev/null +++ b/tests/rtl/tiny_r2_fifo_reset_tb.sv @@ -0,0 +1,115 @@ +`timescale 1ns / 1ps +module tiny_r2_fifo_reset_tb; + logic s_clk = 0, s_reset_n = 1, s_watchdog = 0; + logic s_por_n, s_system_n, s_ready_n; + logic [16:0] s_leaf_n; + logic s_flush = 0, s_push = 0, s_pop = 0, s_full, s_empty; + logic [7:0] s_input = 0, s_output; + logic [2:0] s_count; + logic [7:0] s_model [4]; + int s_head = 0, s_tail = 0, s_used = 0; + logic [31:0] s_random = 32'h12345678; + tiny_reset_tree u_tree ( + .clk_i (s_clk), + .rst_n_i (s_reset_n), + .watchdog_reset_req_i(s_watchdog), + .por_rst_n_o (s_por_n), + .system_rst_n_o (s_system_n), + .leaf_rst_n_o (s_leaf_n), + .cpu_ready_rst_n_o (s_ready_n) + ); + fifo #( + .DATA_WIDTH (8), + .BUFFER_DEPTH(4) + ) u_fifo ( + .clk_i (s_clk), + .rst_n_i(s_leaf_n[6]), + .flush_i(s_flush), + .push_i (s_push), + .full_o (s_full), + .dat_i (s_input), + .pop_i (s_pop), + .empty_o(s_empty), + .dat_o (s_output), + .cnt_o (s_count) + ); + + task automatic check_visible; + if (s_count !== 3'(s_used) || s_empty !== (s_used == 0) || s_full !== (s_used == 4)) + $fatal(1, "FIFO control disagrees with independent queue"); + if (s_output !== ((s_used == 0) ? 8'd0 : s_model[s_head])) + $fatal(1, "stale or incorrect FIFO payload became visible"); + endtask + + task automatic cycle; + bit take_push, take_pop; + take_push = s_push && ((s_used < 4) || (s_pop && s_used != 0)); + take_pop = s_pop && s_used != 0; + if (!s_leaf_n[6] || s_flush) begin + s_head = 0; + s_tail = 0; + s_used = 0; + end else begin + if (take_pop) begin + s_head = (s_head + 1) % 4; + s_used--; + end + if (take_push) begin + s_model[s_tail] = s_input; + s_tail = (s_tail + 1) % 4; + s_used++; + end + end + #5 s_clk = 1; + #1; + check_visible(); + #4 s_clk = 0; + endtask + + initial begin + #1 s_reset_n = 0; + #1 s_reset_n = 1; + repeat (16) cycle(); + s_push = 1; + for (int value = 1; value <= 4; value++) begin + s_input = 8'(value); + cycle(); + end + s_pop = 1; + s_input = 8'h55; + cycle(); // Full simultaneous pop/push must accept replacement. + s_flush = 1; + cycle(); // Flush wins over both operations; data output becomes zero. + s_flush = 0; + s_pop = 0; + s_input = 8'ha5; + cycle(); + #2 s_watchdog = 1; + #1; + s_head = 0; + s_tail = 0; + s_used = 0; + check_visible(); + #50; // Reset and empty masking must not need a running clock. + check_visible(); + s_watchdog = 0; + s_push = 0; + repeat (11) cycle(); + for (int trial = 0; trial < 1024; trial++) begin + s_random ^= s_random << 13; + s_random ^= s_random >> 17; + s_random ^= s_random << 5; + s_push = s_random[0]; + s_pop = s_random[1]; + s_flush = (s_random[7:2] == 0); + s_input = s_random[15:8]; + cycle(); + end + $display("SIM_TEST_PASS Tiny R2 FIFO reset"); + $finish; + end + initial begin + #20000; + $fatal(1, "SIM_TEST_TIMEOUT Tiny R2 FIFO reset"); + end +endmodule diff --git a/tests/rtl/tiny_r2_reset_tb.sv b/tests/rtl/tiny_r2_reset_tb.sv new file mode 100644 index 00000000..e1995553 --- /dev/null +++ b/tests/rtl/tiny_r2_reset_tb.sv @@ -0,0 +1,107 @@ +`timescale 1ns / 1ps +module tiny_r2_reset_tb; + logic s_clk = 0; + logic s_reset_n = 1; + logic s_watchdog = 0; + logic s_por_n, s_system_n, s_ready_n; + logic [16:0] s_leaf_n; + logic s_hart_req = 0, s_idle = 1, s_core_n, s_done; + + tiny_reset_tree u_tree ( + .clk_i (s_clk), + .rst_n_i (s_reset_n), + .watchdog_reset_req_i(s_watchdog), + .por_rst_n_o (s_por_n), + .system_rst_n_o (s_system_n), + .leaf_rst_n_o (s_leaf_n), + .cpu_ready_rst_n_o (s_ready_n) + ); + mgmt_debug_reset #( + .ResetSyncStages(5) + ) u_hart ( + .clk_i (s_clk), + .rst_n_i (s_ready_n), + .reset_req_i (s_hart_req), + .bridge_idle_i(s_idle), + .core_rst_n_o (s_core_n), + .reset_done_o (s_done) + ); + + task automatic cycle; + #5 s_clk = 1; + #1; + if (s_core_n && s_leaf_n !== 17'h1ffff) $fatal(1, "CPU released before all leaves"); + #4 s_clk = 0; + endtask + + task automatic cold_release; + for (int edge_count = 1; edge_count <= 20; edge_count++) begin + cycle(); + if (s_por_n !== (edge_count >= 5)) $fatal(1, "POR release edge %0d", edge_count); + if (s_system_n !== (edge_count >= 10)) $fatal(1, "system release edge %0d", edge_count); + if (s_leaf_n !== ((edge_count >= 15) ? 17'h1ffff : 17'd0)) + $fatal(1, "leaf release edge %0d", edge_count); + if (s_core_n !== (edge_count >= 20)) $fatal(1, "hart release edge %0d", edge_count); + end + endtask + + initial begin + // Generate a real reset edge after elaboration in four-state simulation. + #1 s_reset_n = 0; + #1 s_reset_n = 1; + cold_release(); + #2 s_reset_n = 0; + #1; + if ({s_por_n, s_system_n, s_leaf_n, s_core_n} !== '0) + $fatal(1, "asynchronous stopped-clock assertion failed"); + #2 s_reset_n = 1; + #100; + if ({s_por_n, s_system_n, s_leaf_n, s_core_n} !== '0) + $fatal(1, "stopped clock allowed release"); + repeat (4) cycle(); + if (s_por_n) $fatal(1, "released before five edges"); + #2 s_reset_n = 0; + #1 s_reset_n = 1; + cold_release(); + + #2 s_watchdog = 1; + #1; + if (!s_por_n || s_system_n || s_leaf_n != 0 || s_core_n) + $fatal(1, "watchdog did not preserve POR and reset the system"); + repeat (8) cycle(); + s_watchdog = 0; + for (int edge_count = 1; edge_count <= 15; edge_count++) begin + cycle(); + if (!s_por_n || s_system_n !== (edge_count >= 5) || + s_leaf_n !== ((edge_count >= 10) ? 17'h1ffff : 17'd0) || + s_core_n !== (edge_count >= 15)) + $fatal(1, "watchdog release sequencing"); + end + + s_idle = 0; + s_hart_req = 1; + cycle(); + s_hart_req = 0; + repeat (4) begin + cycle(); + if (!s_core_n || s_done || s_leaf_n != 17'h1ffff) + $fatal(1, "hart request discarded accepted work or reset other clients"); + end + s_idle = 1; + cycle(); + if (s_core_n || s_done) $fatal(1, "idle hart did not assert reset"); + cycle(); // Retained request clears; five release edges follow. + for (int edge_count = 1; edge_count <= 5; edge_count++) begin + cycle(); + if (s_leaf_n != 17'h1ffff || s_core_n !== (edge_count == 5)) + $fatal(1, "hart-only release or peripheral preservation failed"); + end + if (!s_done) $fatal(1, "hart reset completion missing"); + $display("SIM_TEST_PASS Tiny R2 reset"); + $finish; + end + initial begin + #10000; + $fatal(1, "SIM_TEST_TIMEOUT Tiny R2 reset"); + end +endmodule diff --git a/tests/test_mgmt_debug.py b/tests/test_mgmt_debug.py index fbe9539d..1a7e36ef 100644 --- a/tests/test_mgmt_debug.py +++ b/tests/test_mgmt_debug.py @@ -6,11 +6,13 @@ import shutil import subprocess from pathlib import Path +import pytest ROOT = Path(__file__).resolve().parents[1] -def test_management_debug_reset_waits_for_the_ahbl_bridge(tmp_path: Path) -> None: +@pytest.mark.parametrize("stages", [3, 5]) +def test_management_debug_reset_waits_for_the_ahbl_bridge(tmp_path: Path, stages: int) -> None: iverilog = shutil.which("iverilog") vvp = shutil.which("vvp") if iverilog is None or vvp is None: @@ -31,6 +33,7 @@ def test_management_debug_reset_waits_for_the_ahbl_bridge(tmp_path: Path) -> Non f"-I{ROOT / 'rtl/managed/clusterip/common/rtl'}", "-s", "mgmt_debug_reset_tb", + f"-Pmgmt_debug_reset_tb.ResetSyncStages={stages}", "-o", str(simulation), *(str(source) for source in sources), diff --git a/tests/test_publication_tiny.py b/tests/test_publication_tiny.py index 81597ee3..3a4cb80a 100644 --- a/tests/test_publication_tiny.py +++ b/tests/test_publication_tiny.py @@ -25,6 +25,8 @@ def test_tiny_configuration_and_inventory_are_derived(data): assert data["facts"]["dma"]["NumChannels"] == 4 assert data["facts"]["dma"]["RequestMask"] == 0x7F9 assert data["facts"]["dma"]["EnableStreams"] == 0 + assert data["facts"]["cpu"]["ResetSyncStages"] == 5 + assert tiny.RESET_TREE in data["source_paths"] assert len(data["catalog"]) == 14 and len(data["regions"]) == 20 and len(data["pads"]) == 52 assert len(data["interrupts"]) == 14 and len(data["gpio"]) == 32 assert not any(p.startswith("rtl/mini/") for p in data["source_paths"]) @@ -49,9 +51,26 @@ def test_common_register_default_remains_mini_compatible(): assert "SOC_SYSCTRL_TEST_STATUS_OFFSET" in constants +def test_reset_update_does_not_bypass_reviewed_snapshot(monkeypatch): + original = tiny.subprocess.check_output + checked = [] + + def changed_snapshot(command, **kwargs): + if command[:3] == ["git", "diff", "--name-only"]: + assert tiny.RESET_TREE in command + checked.append(command) + return tiny.RESET_TREE + "\n" + return original(command, **kwargs) + + monkeypatch.setattr(tiny.subprocess, "check_output", changed_snapshot) + with pytest.raises(ValueError, match="technical sources differ from reviewed snapshot"): + tiny.collect(tiny.read(ROOT / tiny.BOOK / "tiny.json")) + assert len(checked) == 1 + + @pytest.fixture def source_tree(tmp_path): - sources = [tiny.TOP, tiny.ARCHINFO, tiny.SYSCTRL, "rtl/ip/core/mgmt_core_wrapper.sv", "configs/ci/ihp130-tiny.mk", + sources = [tiny.TOP, tiny.RESET_TREE, tiny.ARCHINFO, tiny.SYSCTRL, "rtl/ip/core/mgmt_core_wrapper.sv", "configs/ci/ihp130-tiny.mk", tiny.MAP, tiny.TOPOLOGY, (tiny.BOOK / "source-contract.json").as_posix()] for relative in sources: target = tmp_path / relative @@ -66,6 +85,7 @@ def source_tree(tmp_path): (tiny.TOP, ".NumChannels (4)", ".NumChannels (8)"), (tiny.TOP, ".EnableStreams(1'b0)", ".EnableStreams(1'b1)"), (tiny.TOP, ".EnableAtomics (1'b0)", ".EnableAtomics (1'b1)"), + (tiny.TOP, ".ResetSyncStages (5)", ".ResetSyncStages (3)"), ("configs/ci/ihp130-tiny.mk", "ISA := RV32IM", "ISA := RV32IMC"), (tiny.ARCHINFO, "32'h5449_4e59", "32'h4d49_4e49"), (tiny.SYSCTRL, "!s_test_q[31] && apb4.pwdata[31]", "s_test_q[31] && apb4.pwdata[31]"), diff --git a/tests/test_script_tools.py b/tests/test_script_tools.py index 1b16a75d..111ca7aa 100644 --- a/tests/test_script_tools.py +++ b/tests/test_script_tools.py @@ -1384,7 +1384,9 @@ def test_hazard3_debug_flow_is_locked_and_uses_remote_bitbang() -> None: assert ".BRANCH_PREDICTOR (1)" in wrapper assert ".BREAKPOINT_TRIGGERS(2)" in wrapper assert ".HAVE_SBA(0)" in debug_wrapper - assert "mgmt_debug_reset u_mgmt_debug_reset" in debug_wrapper + assert re.search(r"mgmt_debug_reset\s*#\(\s*\.ResetSyncStages\(ResetSyncStages\)\s*\)\s+u_mgmt_debug_reset", debug_wrapper) + assert re.search(r"parameter\s+int\s+ResetSyncStages\s*=\s*3", wrapper) + assert re.search(r"parameter\s+int\s+ResetSyncStages\s*=\s*3", debug_wrapper) assert "--timeout $(SOC_SIM_TIME)" in verilator_makefile assert "--require-debug-tools" in verilator_makefile assert "HAVE_DEBUG" not in verilator_makefile diff --git a/tests/test_tiny.py b/tests/test_tiny.py index 7c633fb7..a1116257 100644 --- a/tests/test_tiny.py +++ b/tests/test_tiny.py @@ -31,6 +31,11 @@ def test_tiny_sources_and_abi_are_independent(tmp_path: Path) -> None: document = read_topology(TINY / "integration/soc_topology.json", TINY / "address_map/memory_map.json") generate_bindings(document, output) + interfaces = (output / "rtl/tiny_apb_interfaces.svh").read_text() + for index, target in enumerate(document["apb_targets"], start=1): + name = target["name"] + assert f".presetn(s_{name}_rst_n)" in interfaces + assert f"assign s_{name}_rst_n = s_leaf_rst_n[{index}];" in interfaces generate_filelists(filelists, output, ["+define+RETROSOC_SOC__TINY"]) generate_memory_map(TINY / "address_map/memory_map.json", output / "memory_map", "YES", 128) resolved = parse_filelists(filelists / name for name in diff --git a/tests/test_tiny_r2_p2.py b/tests/test_tiny_r2_p2.py new file mode 100644 index 00000000..96760beb --- /dev/null +++ b/tests/test_tiny_r2_p2.py @@ -0,0 +1,124 @@ +"""Tiny reset distribution and early timing-feasibility contracts.""" + +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 import tiny_r2_feasibility as feasibility + +ROOT = Path(__file__).resolve().parents[1] +COMMON = ROOT / "rtl/managed/clusterip/common/rtl" + + +def run(command: list[str], log: Path, **kwargs) -> str: + result = subprocess.run(command, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kwargs) + log.write_text(result.stdout) + assert result.returncode == 0, result.stdout + assert not DEFAULT_FAILURE.search(result.stdout), result.stdout + return result.stdout + + +@pytest.mark.parametrize("simulator", ["iverilog", "verilator"]) +@pytest.mark.parametrize("fixture,marker", [ + ("tiny_r2_reset_tb", "SIM_TEST_PASS Tiny R2 reset"), + ("tiny_r2_fifo_reset_tb", "SIM_TEST_PASS Tiny R2 FIFO reset"), +]) +def test_reset_and_fifo_contracts(tmp_path: Path, simulator: str, fixture: str, marker: str) -> None: + assert shutil.which(simulator), f"required simulator unavailable: {simulator}" + sources = [COMMON / "utils/register.sv", COMMON / "clkrst/rst_sync.sv", + COMMON / "utils/fifo.sv", ROOT / "rtl/tiny/top/tiny_reset_tree.sv", + ROOT / "rtl/ip/core/mgmt_debug_reset.sv", ROOT / f"tests/rtl/{fixture}.sv"] + includes = [f"-I{COMMON}", "-DSV_ASSRT_DISABLE"] + output = tmp_path / "sim" + if simulator == "iverilog": + assert shutil.which("vvp"), "required Icarus runtime unavailable" + command = [simulator, "-g2012", "-s", fixture, *includes, + *(str(p) for p in sources), "-o", str(output)] + launch = ["vvp", str(output)] + else: + command = [simulator, "--binary", "--timing", "--assert", "-Wno-fatal", + "--top-module", fixture, *includes, *(str(p) for p in sources), + "--Mdir", str(tmp_path / "obj"), "-o", str(output)] + launch = [str(output)] + run(command, tmp_path / "compile.log", env={**os.environ, "CCACHE_DISABLE": "1"}, timeout=120) + content = run(launch, tmp_path / "sim.log", timeout=30) + assert marker in content + assert "SIM_TEST_FAIL" not in content and "SIM_TEST_TIMEOUT" not in content + + +def test_reset_inventory_has_one_clock_and_all_existing_targets() -> None: + topology = json.loads((ROOT / "rtl/tiny/integration/soc_topology.json").read_text()) + inventory = json.loads((ROOT / "rtl/tiny/integration/clock_reset_domains.json").read_text()) + reset = inventory["reset_distribution"] + assert reset["release_edges"] == 5 + assert reset["leaf_names"] == ["fabric", *(target["name"] for target in topology["apb_targets"])] + assert len(reset["leaf_names"]) == 17 + assert {domain["name"] for domain in inventory["domains"]} == {"system", "jtag"} + assert reset["clock_domain"] == "system" and reset["main_sram_payload_reset"] == "none" + + +SDC = ("create_clock -name clk_system -period 41.666666667 $clk_system_pin\n" + "create_clock -name clk_jtag -period 100 $clk_jtag_port\n" + "set_false_path -from [all_inputs]\nset_clock_uncertainty -setup 0.2 [all_clocks]\n") + + +@pytest.mark.parametrize("mhz", [24, 96, 192, 240]) +def test_analysis_only_changes_the_system_period(mhz: int) -> None: + output = feasibility.candidate_sdc(SDC, mhz) + assert output.splitlines()[1:] == SDC.splitlines()[1:] + assert float(output.splitlines()[0].split()[4]) == pytest.approx(1000 / mhz) + if mhz == 24: + assert output == SDC + + +@pytest.mark.parametrize("sdc,mhz", [(SDC, 48), (SDC + SDC, 96), + (SDC.replace("clk_system", "clk_other"), 96), + (SDC.replace("41.666666667", "5"), 96)]) +def test_feasibility_rejects_wrong_source_clock_or_rate(sdc: str, mhz: int) -> None: + with pytest.raises(ValueError): + feasibility.candidate_sdc(sdc, mhz) + + +@pytest.mark.parametrize("content", ["wns_max=wns max -1.0\n", "", "wns_min=0\nwns_max=nan\ntns_min=0\ntns_max=0\n"]) +def test_incomplete_or_invalid_metrics_cannot_pass(tmp_path: Path, content: str) -> None: + report = tmp_path / "metrics.rpt" + report.write_text(content) + with pytest.raises(ValueError): + feasibility.timing_metrics(report) + + +@pytest.mark.parametrize("mutation", ["none", "missing", "merged"]) +def test_reset_loads_require_seventeen_independent_leaves(tmp_path: Path, mutation: str) -> None: + report = tmp_path / "loads.tsv" + lines = ["name\tdrivers\tdirect_loads\tstructural_endpoints", "system\troot/Q\t85\t1000"] + lines += [f"leaf-{i}\tleaf{i}/Q\t10\t20" for i in range(17)] + if mutation == "missing": + lines.pop() + if mutation == "merged": + lines[-1] = "leaf-16\tleaf0/Q\t10\t20" + report.write_text("\n".join(lines)) + if mutation == "none": + assert len(feasibility.reset_loads(report, True)) == 18 + else: + with pytest.raises(ValueError): + feasibility.reset_loads(report, True) + + +@pytest.mark.parametrize("content,expected", [(" -1.25 slack (VIOLATED)\n", -1.25), + (" 0.50 slack (MET)\n", 0.5), + ("No paths found.\n", None)]) +def test_sys_data_slack_is_separate_from_aggregate_recovery(tmp_path, content, expected) -> None: + path = tmp_path / "sys-setup.rpt" + path.write_text(content) + if expected is None: + with pytest.raises(ValueError, match="missing constrained"): + feasibility.path_slack(path) + else: + assert feasibility.path_slack(path) == expected