From 4db27524a6282ca40d76b935415b89f043d6ea83 Mon Sep 17 00:00:00 2001 From: maksyuki Date: Sat, 10 Oct 2026 09:35:49 +0800 Subject: [PATCH 01/12] feat: implement DMA IRQ handling and WS2812 completion race tests --- crt/src/hal/dma.c | 20 +- crt/src/hal/ws2812.c | 6 +- docs/ip/tiny-soc-r2-p3-review-fixes.md | 284 +++++++++++++++++++++++++ docs/ip/tiny-soc-verification.md | 64 +++++- tests/c/test_runtime.c | 143 ++++++++++++- 5 files changed, 503 insertions(+), 14 deletions(-) diff --git a/crt/src/hal/dma.c b/crt/src/hal/dma.c index bd9c2a81..455a32ac 100644 --- a/crt/src/hal/dma.c +++ b/crt/src/hal/dma.c @@ -5,16 +5,25 @@ static const rs_dma_session_t *rs_dma_owners[RS_DMA_CHANNEL_COUNT]; +#ifdef RS_DMA_TEST_MMIO +uint32_t rs_dma_test_irq_save(void); +void rs_dma_test_irq_restore(uint32_t saved); +#endif + static uint32_t rs_dma_lock(void) { uint32_t saved = 0U; -#if defined(__riscv) && defined(CSR_ENABLE) +#ifdef RS_DMA_TEST_MMIO + saved = rs_dma_test_irq_save(); +#elif defined(__riscv) && defined(CSR_ENABLE) __asm__ volatile("csrrci %0, mstatus, 8" : "=r"(saved)::"memory"); #endif return saved; } static void rs_dma_unlock(uint32_t saved) { -#if defined(__riscv) && defined(CSR_ENABLE) +#ifdef RS_DMA_TEST_MMIO + rs_dma_test_irq_restore(saved); +#elif defined(__riscv) && defined(CSR_ENABLE) if ((saved & UINT32_C(8)) != 0U) { __asm__ volatile("csrsi mstatus, 8" ::: "memory"); } @@ -185,6 +194,7 @@ rs_status_t rs_dma_submit_tcd_chain(uint32_t channel, rs_dma_tcd_t *first, uint3 rs_status_t result; uint32_t control; uint32_t burst; + uint32_t saved; result = rs_dma_tcd_validate(channel, current); if (result != RS_OK) { @@ -208,14 +218,20 @@ rs_status_t rs_dma_submit_tcd_chain(uint32_t channel, rs_dma_tcd_t *first, uint3 config.crc_expected = current->crc_expected; config.priority = (uint8_t)((control >> RS_DMA_TCD_PRIORITY_SHIFT) & UINT32_C(0x3)); config.burst_beats = (uint8_t)burst; + /* Publish configuration, TCD and START before an ISR can lease the + * idle channel. Nested helpers preserve this outer interrupt mask. + * Restore interrupts before the bounded completion wait. */ + saved = rs_dma_lock(); result = rs_dma_configure(channel, &config); if (result != RS_OK) { + rs_dma_unlock(saved); return result; } RS_DMA_CH_REG(channel, RS_DMA_CH_REG_TCD_HEAD) = (uint32_t)(uintptr_t)current; RS_DMA_CH_REG(channel, RS_DMA_CH_REG_TCD_COUNT) = UINT32_C(1); rs_dma_fence(); result = rs_dma_start(channel); + rs_dma_unlock(saved); if (result != RS_OK) { return result; } diff --git a/crt/src/hal/ws2812.c b/crt/src/hal/ws2812.c index 47c5cf94..6fd811a5 100644 --- a/crt/src/hal/ws2812.c +++ b/crt/src/hal/ws2812.c @@ -419,7 +419,11 @@ rs_status_t rs_ws2812_dma_service(uint64_t now) { return rs_ws2812_dma_finish(); } if ((transmitter_flags & RS_WS2812_STATUS_BUSY) == 0U) { - return rs_ws2812_dma_fail(RS_EIO); + /* Completion may occur between the interrupt and status reads, and + * sticky DONE can lag the core's idle transition. Observe terminal + * events on the next bounded service call; never refill an idle core. + * Errors, aborts and the deadline remain checked on every call. */ + return RS_OK; } if (rs_ws2812_transfer.remaining == 0U) { rs_ws2812_write_register(RS_WS2812_INTR_STATE_OFFSET, RS_WS2812_INTR_FIFO_LOW); diff --git a/docs/ip/tiny-soc-r2-p3-review-fixes.md b/docs/ip/tiny-soc-r2-p3-review-fixes.md index cc06926b..2c0568af 100644 --- a/docs/ip/tiny-soc-r2-p3-review-fixes.md +++ b/docs/ip/tiny-soc-r2-p3-review-fixes.md @@ -3,6 +3,9 @@ This record covers the two P1 findings approved for follow-up on `bab01352f8f7c80e39546b04e3ed37a3d927f475` plus the existing P3 worktree. It does not approve Mini fabric enablement or advance TINY-R2-P4. +Earlier sections retain their original development status and attribution; +the source-bound campaign below records completed validation on `3e46cc8`. +The final section separately records the subsequent two HAL race fixes. ## Retained Tiny Icarus finding: closed @@ -182,6 +185,10 @@ STA observations qualify a new physical operating point. ## Compiler-matrix execution corrections (2026-10-09) +The intermediate snapshots below are historical. The final source-bound results +later in this record supersede their in-progress status without relabeling any +failed attempt or earlier artifact. + The initial flat-image collector could not read `_stack_point` from its symbol listing. Flat placement now falls back to the retained linker map's explicit `PROVIDE (_stack_point = .)` value; the exact SRAM endpoint and 4 KiB stack @@ -286,3 +293,280 @@ the selected target's successful simulation checks. This behavioral-only run provides no Mini synthesis, netlist, STA or physical evidence. Mini lint baseline differences remain non-blocking observations, separately retained in the `56f28cd9230c` variant's `meta/rtl-lint-warnings.json`. + +## Final source-bound P3 matrix (2026-10-10) + +After the external style commit and the publication/configuration corrections +settled, the final campaign was run against commit +`3e46cc80e2d2243233d284b7cd9583714b0773bb`, source digest +`a7fc5112cd6f20df956243c4d081a6a31ede6192cd270e3c13340557b0e58776`. +Its master record is +`build/ics55-tiny-performance-2026-10-09-17-10-53cc6674d532/meta/tiny-r2-p3/matrix/attempt-3jy5g5vo/record.json`; +the final report is in the same root at `meta/tiny-r2-p3/report.json`. + +All eight cases pass image collection, normal Tiny acceptance and three cold +Verilator runs. The selected native Icarus cases also pass three cold runs: + +- `compat-os-flat`: 7464.762, 7891.159 and 7428.430 seconds; +- `perf-o2-lto-flat`: 5841.742, 5890.497 and 6149.375 seconds; +- `perf-o2-plain-banked`: 5951.447, 6345.716 and 5964.039 seconds. + +The collector validates Tiny `TEST_STATUS`/`SIM_TEST_PASS`, forbidden markers, +matched retained image/model/tool artifacts, source digests, three repetitions, +and cross-simulator sample identity. `report.json` remains +`measurement_complete`; the ledger's human phase gate is still required. + +The earlier 09-25, 15-10 and 16-45 campaigns remain historical: source changes +during execution included the `352b200` style commit, the configuration/publication +repairs, and the performance profile's trailing-newline change. Their raw passing +samples and provenance failures are retained and are not relabeled as final +evidence. The final campaign uses the complete committed corrections in `3e46cc8`. + +### Current validation and commands + +The tested primary platform is TINY/ICS55, external SAFE24 with HAVE_PLL=YES +but the PLL parked off. CPU and the existing 128 KiB main SRAM remain at SYS24. +The IHP130 compatibility profile keeps no PLL. Dependencies, warning baselines, +metrics policy, hardware integration and the frozen specification blob +`7e19de49d49efaafacccfa85671e314d53dd15a4` are unchanged by this validation. + +After activating `.cache/retrosoc/development/tiny-r2-p1/activate.sh`, the matrix +and final report commands were: + +```sh +make CONFIG=configs/benchmark/ics55-tiny-performance.mk SOC=TINY PDK=ICS55 \ + BUILD_TIMESTAMP=2026-10-09-17-10 SOC_SIM_TIME=21600 JOBS=3 \ + TINY_P3_RESUME=NO tiny-r2-p3-matrix +make CONFIG=configs/benchmark/ics55-tiny-performance.mk SOC=TINY PDK=ICS55 \ + BUILD_TIMESTAMP=2026-10-09-17-10 tiny-r2-p3-report +``` + +The current full Pytest run reports **1629 passed, 1 skipped** in 4200.10 seconds. +The one skipped PDF inspection test requires `pypdf`, absent from this environment; +it is not a hardware or simulation pass. Its command, complete log and result are +in the matrix root under `meta/tiny-r2-p3/quality/pytest.{log,json}`. The previous +4 failures and 94 fixture errors remain in their old logs; they are resolved by +the configuration declaration and publication/source-fixture corrections in +`3e46cc8`. The focused correction check also passed 149 tests with the same one +PDF skip. No PDF or publication source revision was advanced by these checks. + +`ruff check .`, dependency-lock validation and `git diff --check` pass. The +current C format/policy/host and RTL format/style/readiness checks pass; their +exact Make command and outputs are retained under +`meta/tiny-r2-p3/quality/policy.{log,json}`. These are the repository's mechanical +quality checks, not complete MISRA certification; no new deviation is recorded. + +Regression dry-runs selected TINY explicitly for both PDKs and MINI with +`--behavioral-only`. Tiny nightly resolves to the same commands as PR, so it was +not executed a second time. The final executions were: + +```sh +BUILD_TIMESTAMP=2026-10-09-19-55 python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55 +BUILD_TIMESTAMP=2026-10-09-19-56 python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 +JAVA_TOOL_OPTIONS=-Dsbt.server.forcestart=true BUILD_TIMESTAMP=2026-10-09-19-57 \ + python3 scripts/regress.py --root . --suite pr --soc MINI --pdk ICS55 --behavioral-only +``` + +All three exit zero. Current evidence roots and results are: + +| Scope | Evidence root below `build/` | Result | +| --- | --- | --- | +| Tiny ICS55 PR | `ics55-tiny-2026-10-09-19-55-42d5beccb726/` | `meta/tiny-r2-p3/regress-ics55.{log,json}`; behavioral/JTAG, netlist boot, synthesis and STA commands pass | +| Tiny IHP130 PR | `ihp130-tiny-2026-10-09-19-56-bff91e3529ba/` | `meta/tiny-r2-p3/regress-ihp130.{log,json}`; same target-specific gates pass | +| Mini ICS55 behavioral-only | `ics55-2026-10-09-19-57-d1cacf6d3b62/` | `meta/tiny-r2-p3/regress-mini.{log,json}`; SDRAM Verilator and Icarus assembly boot pass | + +Each Tiny PR's HAVE_SVA=YES Verilator acceptance has its separate configuration +variant, identified in the retained command/log. Mini's SDRAM run uses +`dc24d22ad53d` and assembly boot uses `ec9e1f2e9c2c` at the same 19-57 timestamp. +Mini behavioral-only supplies no synthesis, netlist, STA or physical evidence. + +The additional IHP130 performance/banked normal-firmware check also passes: + +```sh +make CONFIG=configs/ci/ihp130-tiny.mk SOC=TINY PDK=IHP130 \ + BUILD_TIMESTAMP=2026-10-10-00-13 SW_ISA_PROFILE=TINY_PERF SW_OPT=O2 SW_LTO=NO \ + LINK_TYPE=ld2_tiny_banked SIMU=VERILATOR firmware sim +``` + +Its root is `build/ihp130-tiny-2026-10-10-00-13-8648aa4845d9/`, with the exact +command/result at `meta/tiny-r2-p3/performance-banked.{log,json}` and strict Tiny +simulation check at `sim/verilator/result-sim-check.json`. This is normal +firmware compatibility; it is not an additional IHP130 compiler measurement matrix. + +The ICS55 platform auditor captured inputs before this execution and successfully +produced `meta/tiny-ics55-p1/report.json` in the actual APP=ci_smoke variant. This +reuses the platform audit under P3; its original phase label is unchanged. Metrics +were explicitly collected in each actual synthesis/STA variant rather than the +regression runner's final default-bringup observation directory: + +| TINY PDK | Cells | Reported library area | Max WNS (ns) | Max TNS (ns) | +| --- | ---: | ---: | ---: | ---: | +| ICS55 | 197677 | 1786686.260799 | -41.14 | -508669.53 | +| IHP130 | 208547 | 9109320.296998 | -94.28 | -1594880.00 | + +Both min WNS/TNS values are zero in these smoke reports. Full reports are at +`sta/opensta/timing_metrics.rpt` and `meta/metrics.json` within the corresponding +APP=ci_smoke roots. Library area is not placed/routed die area; negative margins +remain unclosed. No 96/192/240 MHz operation, routed/PVT signoff or physical +qualification is claimed, and no half-rate main-SRAM fallback is introduced. + +Lint comparisons remain non-blocking observations: ICS55 Tiny has 459 new +signatures relative to its baseline, IHP130 Tiny 403, and Mini ICS55 1536 new / +118 resolved; all three have zero increased signatures. Counts are not attribution +of every warning to P3. The reports are in each timestamp's lint variant +(`303f95040df3`, `2dc2e5c747e5`, and `56f28cd9230c` respectively), under +`meta/rtl-lint-warnings.json`. Baselines were not edited. + +### Software measurements and remaining boundaries + +Each row below uses the retained P3 workload (1024 words, four jobs, 4096 CPU +iterations, seed `0x12345678`). Cycle counts are raw measured windows; the report +also retains the separate empty-window calibration, retired instructions, bus +observations and CPI. These are different compiler/layout binaries, not a +matched-binary architectural speedup claim. + +| Case | BIN bytes | CPU cycles | Copy cycles | Burst-16 DMA cycles | Chunk-256 cycles | Observed stack bytes | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| compat-os-flat | 11132 | 98682 | 164096 | 18881 | 511389 | 692 | +| perf-o2-plain-flat | 10148 | 73951 | 127157 | 16520 | 373812 | 648 | +| perf-o2-lto-flat | 8592 | 73974 | 127180 | 13980 | 339167 | 552 | +| perf-o3-plain-flat | 10404 | 73951 | 127157 | 16058 | 372690 | 628 | +| perf-o3-lto-flat | 9872 | 73974 | 127180 | 13980 | 339167 | 564 | +| perf-os-plain-flat | 8000 | 74064 | 139487 | 18421 | 394248 | 692 | +| perf-os-lto-flat | 6360 | 74064 | 139484 | 14169 | 343769 | 584 | +| perf-o2-plain-banked | 10184 | 73954 | 114875 | 16523 | 374016 | 648 | + +Compare compatible Os with performance Os to hold optimization/layout fixed; +compare performance O2 with/without LTO to isolate that selection; compare the +two O2/no-LTO rows for placement. BIN bytes exclude BSS, and observed stack use +does not prove a static upper bound. Each image reserves at least 4096 stack +bytes. Bank conflicts remain unavailable because P4 hardware is not implemented. +The original P1 binaries and their historical phase/source identities are retained. + +Current Mini PIO compatibility is additionally retained at +`build/ics55-2026-10-09-20-00-d0c568d0142e/meta/tiny-r2-p3/pio-retry/evidence.json`. +The initial default 180-second attempt emits SIM_TEST_TIMEOUT despite emulator +exit zero, and fails the simulation checker. A retry of the identical model and +BIN with `--fast-flash -t 1800` passes in 323.930 seconds, including 4/16/33/65-word +frames, IRQ count 5, exact waveform/reset-low checks, underflow/abort recovery, +and observer totals `frames=6 completed=4 underflows=1 aborts=2 dma_starts=0`. +The evidence records the before/after model and BIN hashes, command, manifest, +compile/run logs and a post-build recheck of 487 compiled input sizes/mtimes. +It is explicitly a post-build audit; neither the failed timeout nor the original +unsupported Mini DMA attempt is relabeled as a passing run. + +Product-integrated WS2812 DMA/IRQ service budgets remain deferred under approved +option A to an actual supported route, including Tiny R2-P6. Full-product, +physical, silicon and publication-PDF qualification remain outside these results. +Human review and acceptance of TINY-R2-P3 are required before authorizing any +R2-P4 implementation; this execution does not automatically advance a phase. + +## Subsequent P1 HAL race fixes (2026-10-10) + +This approved follow-up starts from `3e46cc80e2d2243233d284b7cd9583714b0773bb` +and preserves the preceding uncommitted documentation/evidence updates. The +specification remains blob `7e19de49d49efaafacccfa85671e314d53dd15a4`. +Only `crt/src/hal/dma.c`, `crt/src/hal/ws2812.c` and +`tests/c/test_runtime.c` change executable/test source. No public interface, +register, RTL, profile, dependency, warning baseline or metric policy changes. + +- WS2812: completing between INTR_STATE and STATUS reads no longer produces a + false I/O error. Idle without an observed sticky DONE retains ownership and + returns to the caller; the next bounded service checks error, abort, deadline + and completion again. No refill is admitted to an idle core. DMA response + draining and source-buffer lifetime rules are unchanged. +- DMA: one outer interrupt save/restore spans legacy TCD configuration, + descriptor publication and START, including ownership rejection. Nested HAL + helpers preserve the saved mask; the blocking completion wait is outside this + critical section. Test-only IRQ hooks model a pending ISR at mask restoration + and do not appear in target builds. +- Deterministic production-HAL host tests cover an ISR trying to lease the + channel during TCD publication, rejection without modifying an existing lease, + an initially masked caller, completion between WS2812 register reads, delayed + sticky DONE, genuine error/abort and expiry while idle. Existing final-B drain, + timeout ownership and 16-word FIFO admission tests remain enabled. + +New evidence is rooted at +`build/ics55-tiny-2026-10-10-09-00-b4aa59f3e5d9/meta/tiny-r2-p3-races/`. +The timestamp is a fixed build label; each structured result records actual UTC +start/end times. `source.json` records the base revision, dirty state, locked +tool identities, input hashes, source archive and worktree patch. Its combined +ICS55/IHP130 source/test/spec digest is +`dfa171fc1adbdfc5c7c9d31ef4ac7ba1a66710caac0fb13b869ddac89186b2f7`. +This digest's input set differs from the earlier compiler-matrix input set. + +Before fixes, `host-red.{log,json}` records host test exit 10 (TCD race). +After the TCD fix alone, `host-ws-red.{log,json}` records exit 11 (WS2812 race). +Both fail the enclosing Make command. `host-green.{log,json}` passes after both +fixes; `policy.{log,json}` additionally covers the final error/abort/deadline and +interrupt-mask cases, C formatting and policy checks. `focused.{log,json}` has +156 passing tests, including P3, Tiny baseline, DMA/register parity, WS2812 RTL +and publication API behavior. No new MISRA deviation is introduced. These are +partial project checks, not complete MISRA certification. + +Commands run with the existing locked environment: + +```sh +source .cache/retrosoc/development/tiny-r2-p1/activate.sh +make sw-format-check sw-policy-check sw-host-test +python3 -m pytest -q tests/test_tiny_r2_p3.py tests/test_tiny.py \ + tests/test_tiny_r2_baseline.py tests/test_dma.py \ + tests/test_dma_register_parity.py tests/test_ws2812.py \ + tests/test_publication_api_bundle.py +ruff check . +git diff --check +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 --behavioral-only --dry-run +BUILD_TIMESTAMP=2026-10-10-09-00 JOBS=3 JAVA_TOOL_OPTIONS=-Dsbt.server.forcestart=true \ + python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55 +BUILD_TIMESTAMP=2026-10-10-09-00 JOBS=3 JAVA_TOOL_OPTIONS=-Dsbt.server.forcestart=true \ + python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 +BUILD_TIMESTAMP=2026-10-10-09-00 JOBS=3 JAVA_TOOL_OPTIONS=-Dsbt.server.forcestart=true \ + python3 scripts/regress.py --root . --suite pr --soc MINI --pdk ICS55 --behavioral-only +JAVA_TOOL_OPTIONS=-Dsbt.server.forcestart=true make CONFIG=configs/ci/ics55.mk \ + SOC=MINI PDK=ICS55 APP=ci_smoke WS2812_P3_ACCEPTANCE=YES HAVE_CSR=YES \ + LINK_TYPE=ld2_all_sram SIMU=VERILATOR VERILATOR_SIM_ARGS=--fast-flash \ + SOC_SIM_TIME=3600 BUILD_TIMESTAMP=2026-10-10-09-00 JOBS=3 firmware sim +``` + +Regression dry-runs passed; Tiny PR and nightly select identical commands, so +the long run is not duplicated. All three regression executions passed once; +their exact commands and verdicts are retained as +`regress-{tiny-ics55,tiny-ihp130,mini-ics55}.{log,json}` in the new evidence root. +Durations are 3210.491 seconds (Tiny ICS55), 2205.505 seconds (Tiny IHP130), +and 3170.591 seconds (Mini ICS55 behavioral-only). Mini PIO passes once in +423.766 seconds including build, with 4/16/33/65-word frames, five IRQs and +observer totals `frames=6 completed=4 underflows=1 aborts=2 dma_starts=0`. +Its variant is `build/ics55-2026-10-10-09-00-d0c568d0142e/`. + +`audit.py` and `evidence.json` retain the final artifact audit: all 1287 captured +source/test/spec inputs are unchanged, 487 Mini PIO compiled inputs retain their +recorded sizes/mtimes, and the firmware/model/log/result hashes are recorded. +The Mini model audit is explicitly post-build. Eight ordinary regression +simulations are independently checked against their executable success marker, +command exit and forbidden-error rules. All selected targets, including the +current Mini assembly fixture, require `SIM_TEST_PASS`; UART output is not used +to infer success. This evidence is in addition to the separate PIO audit. + +Current Tiny synthesis/netlist/STA results are in +`build/ics55-tiny-2026-10-10-09-00-42d5beccb726/` and +`build/ihp130-tiny-2026-10-10-09-00-bff91e3529ba/`. Both netlist checks pass; +STA command success does not mean timing closure. Observed max WNS/TNS remain +-41.14 ns / -508669.53 ns (ICS55) and -94.28 ns / -1594880.00 ns (IHP130), +with min WNS/TNS zero. Raw reports are `sta/opensta/timing_metrics.rpt`. +Lint baseline comparisons still fail as non-blocking regression observations: +459 new signatures for ICS55 Tiny, 403 for IHP130 Tiny, and 1536 new / 118 +resolved for Mini, with zero increased signatures. No baseline was changed. +Build logs also retain a roughly four-second generated-file clock-skew warning; +these were fresh variant builds, and the input audit passed. + +The old compiler/layout matrix, full Pytest and earlier timing observations stay +bound to `3e46cc8`; they are not reattributed to this follow-up. The compiler +matrix and full Pytest are not rerun for these isolated C changes (no build or +Python implementation changes). Product-integrated WS2812 DMA/IRQ budgets remain +deferred under option A; no Mini DMA-to-MMIO route is added. Human P3 acceptance +and separate approval are still required before R2-P4. Per the maintainer's +instruction, long checks default to a single execution; additional repetitions +need a concrete failure/inconsistency or an explicit frozen acceptance rule. diff --git a/docs/ip/tiny-soc-verification.md b/docs/ip/tiny-soc-verification.md index f95f9e08..df84d24f 100644 --- a/docs/ip/tiny-soc-verification.md +++ b/docs/ip/tiny-soc-verification.md @@ -1,14 +1,58 @@ # Tiny MCU Verification Record -## TINY-R2-P3 implementation in progress (2026-10-08–09) +## TINY-R2-P3 implementation in progress (2026-10-08–10) + +The retained source-bound compiler/layout matrix is complete for commit +`3e46cc80e2d2243233d284b7cd9583714b0773bb`, source digest +`a7fc5112cd6f20df956243c4d081a6a31ede6192cd270e3c13340557b0e58776`. +All eight configurations pass ordinary Tiny acceptance and three deterministic +Verilator cold starts. `compat-os-flat`, `perf-o2-lto-flat` and +`perf-o2-plain-banked` also pass three native Icarus cold starts, with exact +cross-simulator sample identity. The structured measurement report is +`build/ics55-tiny-performance-2026-10-09-17-10-53cc6674d532/meta/tiny-r2-p3/report.json`. +It records software measurements only; it does not close phase acceptance, +physical timing, hardware bank conflict or Mini integrated-DMA gates. + +Quality evidence for that committed source is complete: full Pytest reports 1629 passed and +one environment skip for missing `pypdf`; focused P3 tests and the embedded/RTL +quality checks pass. Its Tiny PR regressions pass for ICS55 and IHP130, and +Mini ICS55 behavioral-only compatibility passes. Negative STA margins and +non-blocking lint observations remain recorded as observations. + +Exact commands, measurements, current evidence roots and remaining qualification +limits are in the final section of the [P3 review-fix record](tiny-soc-r2-p3-review-fixes.md). +Human review and acceptance remain required before any R2-P4 work. + +### Subsequent P1 race-fix follow-up + +The approved follow-up fixes WS2812 completion between register reads and +legacy TCD publication across an ISR lease acquisition. Current source is +`3e46cc8` plus the two HAL fixes and deterministic host tests; the matrix above +remains evidence for the original commit. New evidence is under +`build/ics55-tiny-2026-10-10-09-00-b4aa59f3e5d9/meta/tiny-r2-p3-races/`. +The two new tests fail before their respective fixes and pass afterward; +C format/policy/host checks and 156 focused tests pass. Mini PIO compatibility +also passes once with strict SIM_TEST_PASS and no forbidden markers. Tiny +ICS55/IHP130 PR and Mini ICS55 behavioral regressions each pass once. Final +`evidence.json` checks all 1287 source inputs, 487 Mini PIO compiled inputs and +all eight ordinary regression simulations. Lint baseline differences and +negative STA remain observations; no timing closure is claimed. See the final +review-fix section for commands, hashes, exact paths and unrun gates. + +Per the maintainer's follow-up instruction, long checks default to one execution; +repeat only for a concrete failure/inconsistency or an explicit acceptance +requirement. This follow-up does not repeat the three-cold-start compiler matrix. +It does not waive any frozen qualification requirement or reattribute old runs. + +### Historical development snapshot The 2026-10-09 execution snapshot in the [P3 review-fix record](tiny-soc-r2-p3-review-fixes.md) records all eight ordinary Tiny/Verilator compiler experiments completed, with -selected Icarus groups still running. The current-source full Pytest result is +selected Icarus groups still running at that snapshot. The then-current full Pytest result was 1531 passed, 4 failed, 94 fixture errors and 1 skipped; configuration-declaration -and publication source/fixture drift remain open. IHP130 Tiny PR completes with +and publication source/fixture drift were still open. IHP130 Tiny PR completed with non-blocking lint observations and negative STA margin. These results do not -close the remaining matrix, quality or human phase-acceptance gates. +replace the final results above or imply human phase acceptance. The [P3 review-fix record](tiny-soc-r2-p3-review-fixes.md) closes the retained banked O2 Icarus finding: all three runs completed successfully and match the @@ -28,8 +72,8 @@ is bound at underflow/abort recovery, zero DMA starts and strict TEST_STATUS/SIM_TEST_PASS. Focused checks report 96 passed; C/RTL quality, Ruff and diff checks pass. The old Mini DMA failure remains failed/unsupported evidence, not a PIO pass. -The full compiler/placement matrix, broader current-source regressions and human -phase acceptance remain pending. See the review-fix record for exact commands, +The full compiler/placement matrix and broader regressions were pending in this +initial record; human phase acceptance remains pending. See the review-fix record for exact commands, artifacts, historical attribution and unrun gates. The user explicitly approved the P3 preflight and implementation on source @@ -42,9 +86,9 @@ Original phase IDs, historical evidence and qualification limits are retained. The [P3 runbook](tiny-soc-r2-software-scheduling.md) defines compiler/layout experiments, shared finite DMA/WS2812 service, ownership and exact commands. -Implementation and validation are in progress; this entry does not claim -phase completion or performance improvement. The existing GNU toolchain is -used first, as requested; no dependency version has been changed. +The initial entry recorded implementation and validation in progress; it did not +claim phase completion or performance improvement. The existing GNU toolchain +was used as requested; no dependency version has been changed. Early development testing found a compressed-instruction acceptance bug: `rs_mcu_unmapped_probe` assembled its faulting load as 16-bit `4108`, while @@ -448,7 +492,7 @@ not a prerequisite to the architectural improvements in R2-P3 through R2-P5. | 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. | 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-P3 - Software and DMA Scheduling | Compiler and placement A/B results, finite DMA ownership, burst/chunk scheduling and available-path correctness; approved option A retains product DMA/IRQ budget qualification for the platform that supplies the route. | Matrix retained on `3e46cc8`; subsequent two P1 HAL fixes and focused/compatibility checks complete in the worktree; human review/acceptance 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 | | TINY-R2-P6 - Shared IP and Eight-Channel DMA Integration | Legacy P7 obligations; shared PWM reporting, I2S slave and alternate-input-route prerequisites; common ABI/HAL parity; Crypto banks/lifecycle; truthful capabilities and affected Mini compatibility. | Pending | diff --git a/tests/c/test_runtime.c b/tests/c/test_runtime.c index a1cf4bea..e09337f9 100644 --- a/tests/c/test_runtime.c +++ b/tests/c/test_runtime.c @@ -69,9 +69,47 @@ volatile uint32_t rs_dma_test_mmio[1024]; static uint32_t ws_registers[16]; static uint32_t ws_pushed; static uint32_t ws_pixels[33]; +static uint32_t ws_idle_on_interrupt_read; +static uint32_t dma_irq_enabled = 8U; +static bool dma_irq_pending; +static rs_dma_session_t dma_irq_owner; +static rs_status_t dma_irq_result; + +uint32_t rs_dma_test_irq_save(void) { + const uint32_t saved = dma_irq_enabled; + dma_irq_enabled = 0U; + return saved; +} + +void rs_dma_test_irq_restore(uint32_t saved) { + dma_irq_enabled = saved; + if ((saved != 0U) && dma_irq_pending) { + dma_irq_pending = false; + /* Model START taking effect before an interrupt can execute. */ + if (RS_DMA_CH_REG(3U, RS_DMA_CH_REG_CTRL) == RS_DMA_CH_CTRL_START) { + RS_DMA_CH_REG(3U, RS_DMA_CH_REG_STATUS) = RS_DMA_STATUS_BUSY; + } + dma_irq_result = rs_dma_session_acquire(&dma_irq_owner, 3U); + if (dma_irq_result == RS_OK) { + const rs_dma_config_t config = {.kind = RS_DMA_KIND_MM_TO_MM, + .source = 0x3000U, .destination = 0x4000U, .byte_count = 16U, + .width = RS_DMA_WIDTH_32, .source_increment = true, + .destination_increment = true, .burst_beats = 1U}; + dma_irq_result = rs_dma_session_configure(&dma_irq_owner, &config); + } + } +} uint32_t rs_ws2812_test_read(uint32_t offset) { - return ws_registers[offset / 4U]; + const uint32_t value = ws_registers[offset / 4U]; + if ((offset == 0x30U) && (ws_idle_on_interrupt_read != 0U)) { + ws_registers[0x18U / 4U] = 8U; + if (ws_idle_on_interrupt_read == 1U) { + ws_registers[0x30U / 4U] |= RS_WS2812_INTR_DONE; + } + ws_idle_on_interrupt_read = 0U; + } + return value; } void rs_ws2812_test_write(uint32_t offset, uint32_t value) { @@ -105,6 +143,107 @@ static void ws_test_reset(void) { ws_registers[0x18U / 4U] = 8U; ws_registers[0x24U / 4U] = 3U; ws_pushed = 0U; + ws_idle_on_interrupt_read = 0U; +} + +static int test_dma_tcd_preemption(void) { + static _Alignas(64) rs_dma_tcd_t tcd = { + .source = 0x1000U, .destination = 0x2000U, .byte_count = 32U, .y_count = 1U, + .control = RS_DMA_TCD_VALID | RS_DMA_TCD_SRC_INC | RS_DMA_TCD_DST_INC | + (UINT32_C(1) << RS_DMA_TCD_BURST_SHIFT)}; + rs_status_t result; + bool valid; + ws_test_reset(); + dma_irq_pending = true; + dma_irq_result = RS_OK; + result = rs_dma_submit_tcd(3U, &tcd, 1U); + valid = (result == RS_ETIMEOUT) && (dma_irq_result == RS_EIO) && + !dma_irq_pending && (dma_irq_enabled == 8U) && + (RS_DMA_CH_REG(3U, RS_DMA_CH_REG_TCD_HEAD) == (uint32_t)(uintptr_t)&tcd) && + (RS_DMA_CH_REG(3U, RS_DMA_CH_REG_TCD_COUNT) == 1U) && + (RS_DMA_CH_REG(3U, RS_DMA_CH_REG_SRC_ADDR) == tcd.source); + RS_DMA_CH_REG(3U, RS_DMA_CH_REG_STATUS) = 0U; + (void)rs_dma_session_release(&dma_irq_owner); + if (!valid) { + return 1; + } + /* An already leased direct channel must remain untouched on rejection. */ + if (rs_dma_session_acquire(&dma_irq_owner, 3U) != RS_OK) { + return 2; + } + RS_DMA_CH_REG(3U, RS_DMA_CH_REG_SRC_ADDR) = 0x3000U; + result = rs_dma_submit_tcd(3U, &tcd, 1U); + valid = (result == RS_EIO) && (dma_irq_enabled == 8U) && + (RS_DMA_CH_REG(3U, RS_DMA_CH_REG_TCD_HEAD) == 0U) && + (RS_DMA_CH_REG(3U, RS_DMA_CH_REG_TCD_COUNT) == 0U) && + (RS_DMA_CH_REG(3U, RS_DMA_CH_REG_SRC_ADDR) == 0x3000U); + (void)rs_dma_session_release(&dma_irq_owner); + if (!valid) { + return 3; + } + /* Calling from an already masked context must not enable interrupts. */ + dma_irq_enabled = 0U; + result = rs_dma_submit_tcd(3U, &tcd, 1U); + valid = (result == RS_ETIMEOUT) && (dma_irq_enabled == 0U); + dma_irq_enabled = 8U; + return valid ? 0 : 4; +} + +static int test_ws2812_completion_race(void) { + rs_ws2812_dma_status_t transfer; + for (uint32_t delay = 1U; delay <= 2U; ++delay) { + ws_test_reset(); + if (rs_ws2812_dma_begin(ws_pixels, 4U, 0U, 100U) != RS_OK) { + return 1; + } + /* Complete after INTR_STATE was sampled, before STATUS is sampled. + * The second case also delays the sticky DONE latch until next service. */ + ws_idle_on_interrupt_read = delay; + if (rs_ws2812_dma_service(1U) != RS_OK) { + return 2; + } + (void)rs_ws2812_dma_status(&transfer); + if (!transfer.active || transfer.draining || transfer.dma_pending) { + return 3; + } + ws_registers[0x30U / 4U] |= RS_WS2812_INTR_DONE; + if (rs_ws2812_dma_service(2U) != RS_OK) { + return 4; + } + (void)rs_ws2812_dma_status(&transfer); + if (transfer.active || (transfer.result != RS_OK) || + (ws_registers[0x24U / 4U] != 3U)) { + return 5; + } + } + /* An idle core without DONE must still terminate on error, abort or the + * original deadline; waiting for the sticky latch cannot hide a failure. */ + for (uint32_t failure = 0U; failure < 3U; ++failure) { + const rs_status_t expected = (failure == 2U) ? RS_ETIMEOUT : RS_EIO; + ws_test_reset(); + if (rs_ws2812_dma_begin(ws_pixels, 4U, 0U, 3U) != RS_OK) { + return 6; + } + ws_idle_on_interrupt_read = 2U; + if (rs_ws2812_dma_service(1U) != RS_OK) { + return 7; + } + if (failure == 0U) { + ws_registers[0x2cU / 4U] = 1U; + } else if (failure == 1U) { + ws_registers[0x30U / 4U] = RS_WS2812_INTR_ABORTED; + } + if (rs_ws2812_dma_service(3U) != expected) { + return 8; + } + (void)rs_ws2812_dma_service(4U); + (void)rs_ws2812_dma_service(5U); + (void)rs_ws2812_dma_status(&transfer); + if (transfer.active || (transfer.result != expected)) { + return 9; + } + } + return 0; } static int test_dma_sessions(void) { @@ -2989,6 +3128,8 @@ int main(void) { test_ws2812_helpers(), test_dma_sessions(), test_ws2812_refill(), + test_dma_tcd_preemption(), + test_ws2812_completion_race(), test_timer_helpers(), test_psram_helpers(), test_sdram_helpers(), From 3131762b35e0ebfcd196395f955e7c1005bd18e7 Mon Sep 17 00:00:00 2001 From: maksyuki Date: Sat, 10 Oct 2026 18:04:19 +0800 Subject: [PATCH 02/12] feat: refactor tests for SRAM and CPU in Tiny R2 P4 flow - Updated SRAM instance paths in to reflect new hierarchy. - Enhanced to include additional source files and updated parameterized tests for SRAM configurations. - Modified to align with new SRAM structure and added a test for stale macro hierarchy. - Introduced to validate dual-port local-memory protocol and CPU debug/reset functionality, including comprehensive simulation checks. --- app/apps/ci_smoke/mcu_acceptance.S | 12 + app/apps/ci_smoke/mcu_acceptance.h | 1 + app/apps/ci_smoke/tiny.c | 20 +- docs/ip/tiny-soc-r2-local-memory.md | 387 ++++++++++ docs/ip/tiny-soc-verification.md | 66 +- .../tiny/scripts/generate_chip_config.py | 19 +- physical/smoke/sta/opensta/opensta.tcl | 3 + .../smoke/sta/opensta/tiny_r2_p4_checks.tcl | 54 ++ .../datasheets/tiny/chapter-review.md | 2 +- .../datasheets/tiny/content-review.md | 10 +- publications/datasheets/tiny/datasheet.typ | 17 +- publications/datasheets/tiny/diagrams.typ | 11 +- .../datasheets/tiny/source-contract.json | 10 +- publications/tiny_reference.py | 27 +- rtl/ip/core/mgmt_core_wrapper.sv | 59 +- rtl/ip/core/mgmt_debug_reset.sv | 3 + rtl/ip/core/mgmt_debug_wrapper.sv | 14 +- rtl/rtl_readiness.json | 17 + rtl/rtl_style_audit.json | 7 + rtl/tiny/Makefile | 17 +- rtl/tiny/README.md | 10 +- rtl/tiny/dv/retrosoc_tiny_tb.sv | 154 ++-- rtl/tiny/filelist/top.fl | 7 + rtl/tiny/integration/clock_reset_domains.json | 12 + rtl/tiny/integration/soc_topology.json | 10 + rtl/tiny/top/retrosoc_tiny.sv | 70 +- rtl/tiny/top/tiny_axi4_fabric.sv | 11 +- rtl/tiny/top/tiny_cpu_mem.sv | 259 +++++++ rtl/tiny/top/tiny_cpu_wrapper.sv | 219 ++++++ rtl/tiny/top/tiny_sram.sv | 107 +++ rtl/tiny/top/tiny_sram_axi4.sv | 232 ++++++ rtl/tiny/top/tiny_sram_axi4_demux.sv | 130 ++++ rtl/tiny/top/tiny_sram_group.sv | 206 ++++++ rtl/tiny/top/tiny_sram_port_if.sv | 25 + scripts/generate_tiny.py | 11 + scripts/tiny_ics55_platform.py | 8 +- scripts/tiny_r2_p4.py | 255 +++++++ tests/rtl/tiny_axi4_fabric_tb.sv | 12 +- tests/rtl/tiny_r2_p4_cpu.S | 67 ++ tests/rtl/tiny_r2_p4_cpu.ld | 6 + tests/rtl/tiny_r2_p4_cpu_tb.sv | 282 ++++++++ tests/rtl/tiny_r2_p4_formal.sv | 58 ++ tests/rtl/tiny_r2_p4_memory_tb.sv | 659 ++++++++++++++++++ tests/test_librelane_tiny_flow.py | 4 +- tests/test_publication_tiny.py | 9 +- tests/test_tiny_ics55_platform.py | 20 +- tests/test_tiny_r2_p4.py | 191 +++++ 47 files changed, 3627 insertions(+), 163 deletions(-) create mode 100644 docs/ip/tiny-soc-r2-local-memory.md create mode 100644 physical/smoke/sta/opensta/tiny_r2_p4_checks.tcl create mode 100644 rtl/tiny/top/tiny_cpu_mem.sv create mode 100644 rtl/tiny/top/tiny_cpu_wrapper.sv create mode 100644 rtl/tiny/top/tiny_sram.sv create mode 100644 rtl/tiny/top/tiny_sram_axi4.sv create mode 100644 rtl/tiny/top/tiny_sram_axi4_demux.sv create mode 100644 rtl/tiny/top/tiny_sram_group.sv create mode 100644 rtl/tiny/top/tiny_sram_port_if.sv create mode 100644 scripts/tiny_r2_p4.py create mode 100644 tests/rtl/tiny_r2_p4_cpu.S create mode 100644 tests/rtl/tiny_r2_p4_cpu.ld create mode 100644 tests/rtl/tiny_r2_p4_cpu_tb.sv create mode 100644 tests/rtl/tiny_r2_p4_formal.sv create mode 100644 tests/rtl/tiny_r2_p4_memory_tb.sv create mode 100644 tests/test_tiny_r2_p4.py diff --git a/app/apps/ci_smoke/mcu_acceptance.S b/app/apps/ci_smoke/mcu_acceptance.S index e48bfe8c..cd4ee824 100644 --- a/app/apps/ci_smoke/mcu_acceptance.S +++ b/app/apps/ci_smoke/mcu_acceptance.S @@ -34,3 +34,15 @@ rs_mcu_unmapped_probe: .option pop ret .size rs_mcu_unmapped_probe, .-rs_mcu_unmapped_probe + + .balign 4 + .global rs_mcu_execute_probe + .type rs_mcu_execute_probe,@function +rs_mcu_execute_probe: + mv t0, a0 + .option push + .option arch, +zifencei + fence.i + .option pop + jr t0 + .size rs_mcu_execute_probe, .-rs_mcu_execute_probe diff --git a/app/apps/ci_smoke/mcu_acceptance.h b/app/apps/ci_smoke/mcu_acceptance.h index 3f05d234..2c791b1d 100644 --- a/app/apps/ci_smoke/mcu_acceptance.h +++ b/app/apps/ci_smoke/mcu_acceptance.h @@ -6,5 +6,6 @@ uint32_t rs_mcu_compressed_probe(void); void rs_mcu_atomic_probe(uint32_t *address); void rs_mcu_unmapped_probe(void); +uint32_t rs_mcu_execute_probe(uint32_t address); #endif diff --git a/app/apps/ci_smoke/tiny.c b/app/apps/ci_smoke/tiny.c index df318bc1..4e7bf321 100644 --- a/app/apps/ci_smoke/tiny.c +++ b/app/apps/ci_smoke/tiny.c @@ -92,7 +92,25 @@ static bool rs_mcu_dma(void) { } config.kind = RS_DMA_KIND_MM_TO_STREAM; config.request = RS_DMA_REQUEST_I2S_TX; - return rs_dma_config_validate(0U, &config) != RS_OK; + if (rs_dma_config_validate(0U, &config) == RS_OK) { + return false; + } + /* DMA owns the destination until completion. FENCE.I is executed by the + * assembly helper before entering either the DMA-written or patched code. */ + rs_mcu_source[0] = UINT32_C(0x02a00513); /* addi a0,zero,42 */ + rs_mcu_source[1] = UINT32_C(0x00008067); /* jalr zero,ra,0 */ + config.kind = RS_DMA_KIND_MM_TO_MM; + config.request = RS_DMA_REQUEST_SOFTWARE; + config.byte_count = UINT32_C(8); + config.burst_beats = 1U; + if ((rs_dma_configure(RS_DMA_CHANNEL_BULK, &config) != RS_OK) || + (rs_dma_start(RS_DMA_CHANNEL_BULK) != RS_OK) || + (rs_dma_wait(RS_DMA_CHANNEL_BULK, RS_TIMEOUT_DEFAULT) != RS_OK) || + (rs_mcu_execute_probe((uint32_t)(uintptr_t)rs_mcu_destination) != UINT32_C(42))) { + return false; + } + rs_mcu_destination[0] = UINT32_C(0x02b00513); /* addi a0,zero,43 */ + return rs_mcu_execute_probe((uint32_t)(uintptr_t)rs_mcu_destination) == UINT32_C(43); } static bool rs_mcu_interrupts(void) { diff --git a/docs/ip/tiny-soc-r2-local-memory.md b/docs/ip/tiny-soc-r2-local-memory.md new file mode 100644 index 00000000..76fd0200 --- /dev/null +++ b/docs/ip/tiny-soc-r2-local-memory.md @@ -0,0 +1,387 @@ +# TINY-R2-P4 local CPU/SRAM implementation + +## Approved throughput repair (2026-10-10; validated, review pending) + +The maintainer approved the eight-file P4 throughput-repair preflight after +the read-only review identified the external frontend's per-beat bubbles. +This follow-up remains P4; it does not enable P5 target concurrency. +The initial implementation and its measurements below are retained historical +evidence for the pre-repair snapshot, not qualification of the changed RTL. +Fresh evidence is under +`build/ics55-tiny-2026-10-10-15-15-b4aa59f3e5d9/meta/tiny-r2-p4/`. + +The group now reserves two response slots for its external client, counting +both queued completions and a macro read still in flight. A response pop may +free capacity on the same edge as the next issue. The macro's saved owner/bank +still selects the captured read value; FIFO data cannot be overwritten by +later local accesses. A same-client pending read prevents a simultaneous write +commit into the single-push response FIFO. Local I/D keep one operation each +and their original completion pipeline. + +The external frontend separates issued and retired read positions. W uses a +two-entry Common FIFO containing captured address, data and byte strobes. +Only captured legal beats enter arbitration. Invalid strobes or LAST retain +the existing error policy, including earlier committed beats and later legal +beats after an invalid strobe. B waits for all queued legal writes and their +commit responses. External transaction ownership still lasts through B/RLAST. +These are small register queues, not additional user SRAM or new macros. + +Acceptance adds consecutive 16-beat read/write handshakes at one SYS cycle per +beat after startup, without local competition or bus backpressure. Logs retain +first-beat latency, total transfer time and final-W-to-B delay. Directed checks +fill/drain queues, stop READY, interleave local service, retain old read values +across local writes, exercise buffered malformed writes and drop stale queued +responses on system reset. Induction checks response conservation/capacity in +addition to held-response stability and the existing service-count fairness. +The unchanged CPU debug/reset, discovery/counters and both real-PDK macro +fixtures remain required. + +Use the same immutable P3 flat/banked images and parameters. Each selected +Verilator image runs once; banked Icarus runs once. Compare both with their P3 +samples and the pre-repair P4 report. The repair requires burst-16 cycles below +36713/36489 respectively, truthful reporting of all other workload deltas and +identical banked results across simulators. It does not promise P3 cycle parity +or change compiler/layout settings. Existing shared Mini debug inputs are +unchanged by this repair; matching prior compatibility evidence is retained. + +### Repair results and provenance + +The campaign is complete for review. `source.json`, `report.json`, `audit.py` +and `evidence.json` in the fresh evidence root bind 1972 technical inputs, +six fresh Tiny regression simulations, three matched-image executions, +six real/behavioral SRAM fixtures, two CPU fixtures and control induction. +The combined ICS55/IHP130 input digest is +`58daf68fbb9dee199b3085a73ef07d22eb3de6d00ba875c462e85b76b6091a0d`; +the ICS55 replay input digest is +`a96a129ffc02d0d38fd2eee7ed8fb62893828db3602bbd4459e0a71ffce0c35f`. +Only the approved six technical files differ from the preceding P4 snapshot; +the other two changes are this runbook and the ledger. No commit or staging +operation is part of this campaign. + +All six SRAM runs record `read_gap_max=1 write_gap_max=1 beats=16`, with first +R at three cycles after AR and final R at cycle18; W begins one cycle after +AW, final B is at cycle20, four cycles after final W. These are standalone +uncontended protocol measurements, not end-to-end DMA or physical timing. +All six retain `local_latency_max=3`. The new queue checks cover full-response +backpressure, local writes to already captured read addresses, W contention, +buffered malformed writes, and both queued-R and accepted-but-uncommitted-W +system resets. CPU debug/patch/hart-reset and induction pass in the final suite. + +| Gate | Result under the fresh evidence root | Outcome | +| --- | --- | --- | +| Final Ruff, diff, RTL format/style/readiness | `quality-pass.{json,log}` | Pass | +| Full Pytest | `pytest-full.{json,log}` | 1659 passed, one optional `pypdf` skip; 3903.061 s | +| Tiny ICS55 PR | `regress-tiny-ics55.{json,log}` | Pass; 3396.035 s | +| Tiny IHP130 PR | `regress-tiny-ihp130.{json,log}` | Pass; 2340.150 s | +| Verilator, both immutable images once | `replay-verilator.{json,log}` | Pass; 390.698 s | +| Icarus, immutable banked image once | `replay-iverilog.{json,log}` | Pass; 5154.223 s | +| Cross-simulator report | `report-result.{json,log}`, `report.json` | Complete; all banked observations identical | +| Source/artifact audit | `evidence.json`, `audit.py` | Pass; review/phase acceptance pending | + +The initial `quality-final.{json,log}` is a retained failed development attempt: +the style gate required `s_req_accept`/`s_resp_accept` abbreviations. The names +were corrected before the final source snapshot and all long gates. The first +29 focused checks and additional queued-W-reset check also passed; final-suite +logs qualify the final technical snapshot. No failed attempt is relabeled. + +| Workload | Flat pre-repair P4 → repaired P4 | Banked pre-repair P4 → repaired P4 | +| --- | ---: | ---: | +| Empty | 69 → 69 | 66 → 66 | +| CPU | 73911 → 73911 | 73914 → 73914 | +| CPU copy | 102568 → 102568 | 86214 → 86214 | +| DMA burst1 | 91306 → 91306 | 89365 → 89365 | +| DMA burst16 | 36713 → 17897 | 36489 → 17317 | +| CPU/DMA contention | 303493 → 299881 | 299633 → 299633 | +| TCD contention | 303702 → 299878 | 299645 → 299645 | +| Chunk256 | 377705 → 361285 | 376970 → 359570 | + +Burst16 cycles decrease 51.25%/52.54%, satisfying the approved repair criterion. +They remain 8.34%/4.81% above the original P3 16520/16523 cycles. Burst1 still +retains the earlier P4 overhead versus P3; this repair does not claim universal +baseline parity. Flat local maximum is four cycles in contended windows, with +17697 eligible bank-conflict cycles; banked maximum is three with zero conflicts. +Uncontended local latency is still checked independently. Firmware checksums, +parameters and each image's compiler/layout identity remain unchanged. + +Fresh physical variants are +`build/ics55-tiny-2026-10-10-15-15-42d5beccb726/` and +`build/ihp130-tiny-2026-10-10-15-15-bff91e3529ba/`. +Both pass synthesis, netlist boot and common-SYS/32-macro binding checks. +Their `meta/metrics.json`, `sta/opensta/timing_metrics.rpt`, +`sta/opensta/p4-clock-bindings.rpt` and `sta/opensta/retrosoc_sta.log` retain +the matching observations: + +| PDK | Cells | Library area | Setup WNS / TNS (ns) | Hold WNS / TNS (ns) | +| --- | ---: | ---: | ---: | ---: | +| ICS55 | 207029 | 1805809.700799 | -36.88 / -489807.34 | 0 / 0 | +| IHP130 | 218773 | 9245382.228597 | -97.37 / -1640251.62 | 0 / 0 | + +Setup remains unclosed; library area is not die area and zero observed hold +slack violations are not physical signoff. Lint comparisons to the unchanged +baselines report 468/412 new signatures for ICS55/IHP130, zero increased and +zero resolved. Relative to pre-repair P4 there are exactly two additional +signatures: intentionally unused FIFO `cnt_o`, and `s_full`, which is consumed +by the verification assertions but unused in synthesis-oriented lint. No new +width/latch warning is attributed to the repair. Warning and metrics policy +remain observational and no baseline was edited. + +Commands below used the same timestamp, with `scripts/run_flow.py` retaining +outer command/result/log records. Standalone test artifacts use distinct +`tests-focused`, `tests-reset` and `tests-full` directories under this variant. + +```sh +source .cache/retrosoc/development/tiny-r2-p1/activate.sh +export BUILD_TIMESTAMP=2026-10-10-15-15 JOBS=3 +python3 -m pytest -q tests/test_tiny_r2_p4.py --basetemp=build/ics55-tiny-2026-10-10-15-15-b4aa59f3e5d9/meta/tiny-r2-p4/tests-focused +python3 -m pytest -q 'tests/test_tiny_r2_p4.py::test_four_group_local_memory[BEHAV-iverilog]' --basetemp=build/ics55-tiny-2026-10-10-15-15-b4aa59f3e5d9/meta/tiny-r2-p4/tests-reset +ruff check . +git diff --check +make rtl-format-check rtl-style-check-all rtl-readiness-check-all +python3 -m pytest -q --basetemp=build/ics55-tiny-2026-10-10-15-15-b4aa59f3e5d9/meta/tiny-r2-p4/tests-full +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 nightly --soc TINY --pdk IHP130 --dry-run +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55 +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 SIMU=VERILATOR SOC_SIM_TIME=21600 \ + TINY_P4_REFERENCE=build/ics55-tiny-performance-2026-10-09-17-10-53cc6674d532/meta/tiny-r2-p3/report.json \ + TINY_P4_CASES=perf-o2-plain-flat,perf-o2-plain-banked tiny-r2-p4-replay +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 SIMU=IVERILOG SOC_SIM_TIME=21600 \ + TINY_P4_REFERENCE=build/ics55-tiny-performance-2026-10-09-17-10-53cc6674d532/meta/tiny-r2-p3/report.json \ + TINY_P4_CASES=perf-o2-plain-banked tiny-r2-p4-replay +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 tiny-r2-p4-report +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 APP=ci_smoke SYNTH=YOSYS STA=OPENSTA metrics +make CONFIG=configs/ci/ihp130-tiny.mk SOC=TINY PDK=IHP130 APP=ci_smoke SYNTH=YOSYS STA=OPENSTA metrics +PYTHONDONTWRITEBYTECODE=1 python3 build/ics55-tiny-2026-10-10-15-15-b4aa59f3e5d9/meta/tiny-r2-p4/audit.py +``` + +Tiny PR/nightly dry-runs match; no duplicate nightly execution was needed. +The six fresh ordinary simulations all require `SIM_TEST_PASS`, including +Tiny netlist boot, together with command success and no forbidden error marker. +Mini's two prior behavioral results retain their original source attribution; +the audit verifies that changed technical inputs are confined to Tiny and its +tests. Mini synthesis/STA, the P3 compiler matrix, additional cold repetitions, +C format/policy/host reruns, PDF generation, PVT/MMMC, CDC/RDC and place/route +were not run for this repair. No C/H code or MISRA deviation changed. The +full-suite optional PDF retrieval check remains skipped for missing `pypdf`. +No new frequency, physical qualification or P5 authorization is claimed. + +## Original implementation snapshot + +This implements the approved [P4 contract](tiny-soc.md#tiny-r2-p4---dual-port-hazard3-and-four-bank-local-sram) +from `4db27524a6282ca40d76b935415b89f043d6ea83`, specification blob +`7e19de49d49efaafacccfa85671e314d53dd15a4`. The maintainer approved the preflight +and implementation. No P5 execution, dependency update, publication release or +physical-frequency qualification is implied. Historical phase evidence remains +in the [ledger](tiny-soc-verification.md). + +## Architecture and lifecycle + +Tiny uses locked `hazard3_cpu_2port`, preserving ISA, IRQ, debug, multiply and +branch-prediction settings; A remains disabled. Separate I/D AHB frontends decode +SRAM before the tagged slow-path merge. One accepted D operation spans all +targets. Write data is captured in its AHB data phase; local stores complete +after macro commit. Uncontended local completion is at most three SYS cycles. +Slow errors retain two-cycle AHB completion. Instruction origin reaches the +existing fabric fault path: only NOR boot/NSS0 may be fetched externally, and +peripheral fetches cannot trigger register reads. + +Four 32 KiB groups cover `0x30000000..0x3001ffff`, each with eight existing +4 KiB macros and I/D/external round robin advancing only on macro issue. +Per-client registers capture read responses before macro outputs change. +External W waits and R/B backpressure do not lock local macro service. +Continuously eligible requests wait for at most two other issues, conditional +on progress rather than a wall-clock bound for stopped peers. + +Each external frontend retains one transaction through B/RLAST and alternates +read/write preference. A direction-tagged demux preserves the existing external +fabric attachment. Whole-product different-target DMA read/write concurrency +remains P5; standalone frontend concurrency is not that product qualification. + +Hart reset requests debug halt, prevents new debug instruction injection, drains +accepted local/slow operations and responses, then resets the hart/frontends. +A terminal-cycle AHB overlap must still drain. SRAM/external frontends and +unrelated DMA stay active. System reset invalidates pending responses without +clearing SRAM payload. Five-edge release and Mini's default single-port reset +behavior are preserved; shared debug only adds a pending-state output. + +## ABI, physical bindings and observations + +Shared SRAM register definitions are unchanged: 128 KiB, `BANK_COUNT=32`, +`BANK_BYTES=4096`. Public counters count external AXI events, saturating sums +of simultaneous events; stall cycles use OR across groups. Local CPU traffic +is not silently added. `R2_AHB` observes the separate I/D ports. `R2_P4_LOCAL` +records local completions, latency and address-admission waits; `R2_P4_BANK` +records macro issues, eligible-client waits and conflict cycles (two or more +eligible clients in a group). These are verification-only observations. + +Macro `g,b` is `u_soc.u_sram.gen_group[g].u_group.gen_bank[b].u_ram.u_mem`, +physical index `8*g+b`. IHP130 placement coordinates stay fixed while instance +and supply hooks track both indices. CPU and all 32 macros remain directly on +SYS24; ICS55 PLL stays off and IHP130 remains no-PLL compatibility. Mapping +checks reject stale single-port/flat-bank hierarchies. STA remains observational, +with no 96/192/240 MHz or physical signoff claim. + +The Tiny publication extractor and affected draft prose/diagram follow the new +bindings. No reviewed publication SHA, delivered PDF or release status advances. + +## Acceptance and matched images + +`tests/test_tiny_r2_p4.py` uses real IHP130/ICS55 macro models and a behavioral +model on both simulators: all macro/group boundaries, byte masks, back-to-back +AHB, independent groups, held responses, W-before-AW, bursts/malformed writes, +discovery/counter semantics and reset payload retention. Deterministic readback +uses seed `0x12345678`, 4096 write/read pairs per group. The actual CPU fixture +checks mixed 16/32-bit cross-group fetch, FENCE.I, instruction faults, JTAG +patch/resume, and reset with a held slow response plus an unrelated held B. +Yosys induction proves group control fairness and retained responses with only +macro payload abstracted. Normal Tiny firmware additionally executes code +copied through the real DMA, then CPU-patched code, after completion/FENCE.I. +Its helper is private acceptance assembly, not a new SDK API. + +`tiny-r2-p4-replay` validates retained P3 images/logs, builds a fresh P4 model, +and executes each selected image once. Image source/compiler/layout and new +RTL/model identities are separate; hardware settings must match. Original P3 +same-source/three-cold-start rules are unchanged. `tiny-r2-p4-report` revalidates +artifacts and requires identical banked-image results across the two simulators. + +The reference is +`build/ics55-tiny-performance-2026-10-09-17-10-53cc6674d532/meta/tiny-r2-p3/report.json`. +Select `perf-o2-plain-flat` and `perf-o2-plain-banked` for Verilator and the banked +image for Icarus. Parameters remain 1024 words, four jobs, 4096 CPU iterations, +seed `0x12345678`, burst-1/16 DMA, TCD and chunk-256. Compare each unchanged +binary with its own historical samples, not with a different compiler/layout. +No fixed speedup is predeclared; latency/fairness requirements remain gates. + +Development evidence begins under +`build/ics55-tiny-2026-10-10-10-05-b4aa59f3e5d9/meta/tiny-r2-p4/`. +Early fixture failures and corrected runs are retained separately. The final +campaign below completes planned functional validation and measurements; human +review/phase acceptance remain pending, including the DMA throughput tradeoff. + +## Executed campaign and provenance + +The evidence root above contains `source.json`, `evidence.json`, `audit.py` and +`report.json`. The campaign snapshot contains 1972 unchanged technical inputs, +base commit `4db27524a6282ca40d76b935415b89f043d6ea83` plus the retained patch and +new-file archive, and locked tool/executable/archive identities. Its combined +ICS55/IHP130 digest is +`9fa311a5500711c99b006eacd95cdff04a74659d52b5b46640c42c49773a7f66`. +The ICS55-only replay source set has digest +`2f0d285186f84d41422d8381d09b0a01a2afc066dbd011a11ec7a8a85433ad0d`; +these are different declared input sets, not conflicting identities. + +The final audit rechecks all captured inputs, eight ordinary regression +simulations, the three matched-image executions, six standalone memory runs, +two actual-CPU runs and the group-control induction log. Ordinary firmware and +model artifacts are hashed post-run; the campaign source hashes were captured +before execution and rechecked afterward. Image/model identities and original +P3 phase attribution are retained separately. + +| Gate | Retained result under the evidence root | Outcome | +| --- | --- | --- | +| Format/style/readiness, C policy/host, Ruff/diff | `quality-final.{json,log}` | Pass; no new MISRA deviation; partial checks, not MISRA certification | +| Full Pytest | `pytest-full.{json,log}` | 1659 passed, one optional `pypdf` environment skip; 3856.630 s | +| Tiny ICS55 PR | `regress-tiny-ics55.{json,log}` | Pass; 3295.064 s | +| Tiny IHP130 PR | `regress-tiny-ihp130.{json,log}` | Pass; 2297.373 s | +| Mini ICS55 behavioral-only PR | `regress-mini-ics55.{json,log}` | Pass; 3093.379 s; no Mini physical evidence | +| P4 Verilator replay | `replay-verilator.{json,log}` | Both selected images once; 357.537 s including build/audit | +| P4 Icarus replay | `replay-iverilog.json`, `replay-iverilog.json.log` | Banked image once; 5214.736 s including build/audit | +| Matched-image aggregation | `report-result.json`, `report.log`, `report.json` | `measurement_complete`; all banked samples match across simulators | + +The six standalone memory logs report `local_latency_max=3`, with real +simultaneous group issues. Those protocol fixtures use a 10 ns verification +clock; product replays and timing checks use SAFE24. The banked product replay +also observes three-cycle maximum local latency and zero eligible bank-conflict +cycles. No physical frequency qualification follows from functional models. + +Commands were run through `scripts/run_flow.py` for retained logs/results: + +```sh +source .cache/retrosoc/development/tiny-r2-p1/activate.sh +export BUILD_TIMESTAMP=2026-10-10-10-05 +export JOBS=3 +make rtl-format-check rtl-style-check-all rtl-readiness-check-all +make sw-format-check sw-policy-check sw-host-test +ruff check . +git diff --check +python3 -m pytest -q --basetemp=build/ics55-tiny-2026-10-10-10-05-b4aa59f3e5d9/meta/tiny-r2-p4/tests-full +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 --behavioral-only --dry-run +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55 +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 +JAVA_TOOL_OPTIONS=-Dsbt.server.forcestart=true python3 scripts/regress.py \ + --root . --suite pr --soc MINI --pdk ICS55 --behavioral-only +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 SIMU=VERILATOR SOC_SIM_TIME=21600 \ + TINY_P4_REFERENCE=build/ics55-tiny-performance-2026-10-09-17-10-53cc6674d532/meta/tiny-r2-p3/report.json \ + TINY_P4_CASES=perf-o2-plain-flat,perf-o2-plain-banked tiny-r2-p4-replay +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 SIMU=IVERILOG SOC_SIM_TIME=21600 \ + TINY_P4_REFERENCE=build/ics55-tiny-performance-2026-10-09-17-10-53cc6674d532/meta/tiny-r2-p3/report.json \ + TINY_P4_CASES=perf-o2-plain-banked tiny-r2-p4-replay +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 tiny-r2-p4-report +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 APP=ci_smoke SYNTH=YOSYS STA=OPENSTA metrics +make CONFIG=configs/ci/ihp130-tiny.mk SOC=TINY PDK=IHP130 APP=ci_smoke SYNTH=YOSYS STA=OPENSTA metrics +``` + +Tiny PR and nightly expand to the same commands; nightly was not duplicated. +Each simulation requires command success, its configured terminal marker and +no forbidden-error marker. All selected ordinary targets require SIM_TEST_PASS, +including Tiny netlist boot. UART text alone is insufficient. Development +attempts are not counted as extra qualifying cold starts. + +## Measurements and remaining qualification + +Raw cycles below compare each immutable binary with its own P3 baseline. +The report also retains retired instructions, CPI inputs, empty calibration, +local waits, macro issues/conflicts and external traffic accounting. + +| Workload | Flat P3 → P4 cycles | Banked P3 → P4 cycles | +| --- | ---: | ---: | +| CPU | 73951 → 73911 | 73954 → 73914 | +| CPU copy | 127157 → 102568 | 114875 → 86214 | +| DMA burst 1 | 64577 → 91306 | 64583 → 89365 | +| DMA burst 16 | 16520 → 36713 | 16523 → 36489 | +| CPU/DMA contention | 310368 → 303493 | 310383 → 299633 | +| TCD contention | 310665 → 303702 | 310683 → 299645 | +| Chunk 256 | 373812 → 377705 | 374016 → 376970 | + +CPU-copy cycles decrease by about 19%/25%, while isolated burst-16 DMA takes +about 2.2 times the baseline cycles. The current external frontend processes +issue, completion and bus response separately for each beat; preserving local +response isolation costs streaming throughput. This regression is retained for +review, not hidden by the CPU-copy result or a changed compiler/layout. P5's +external target concurrency alone does not remove these per-beat waits. No +fixed speedup was an acceptance gate, but the tradeoff needs explicit assessment +before phase acceptance or a throughput claim. + +Actual synthesis/STA variants are +`build/ics55-tiny-2026-10-10-10-05-42d5beccb726/` and +`build/ihp130-tiny-2026-10-10-10-05-bff91e3529ba/`. +Both emit `TINY_R2_P4_BINDING_PASS` and pass netlist boot. Their raw reports are +`sta/opensta/timing_metrics.rpt`, `p4-clock-bindings.rpt`, `retrosoc_sta.log` and +`meta/metrics.json`. + +| PDK | Cells | Reported library area | Max WNS / TNS (ns) | Min WNS / TNS (ns) | +| --- | ---: | ---: | ---: | ---: | +| ICS55 | 206311 | 1803352.980799 | -40.55 / -518700.31 | -0.02 / -11.85 | +| IHP130 | 216216 | 9218095.882797 | -97.46 / -1626503.88 | -0.17 / -142.67 | + +Timing is not closed. Library area is not placed/routed die area. Lint baseline +comparisons remain non-blocking observations: Tiny ICS55 466 new signatures, +Tiny IHP130 410 new, Mini ICS55 1537 new / 118 resolved, all with zero increased. +Compared with the preceding P3 lint runs, newly observed signatures are intentional +empty named output connections: Tiny's unused master IDs, exclusive/power/fence +outputs, plus Mini's ignored `reset_pending_o`. Tiny is cacheless with committed +stores, no A/SBA and no Xh3Power, so these outputs do not add interfaces or clock +gating. Baselines and metric policy were not edited. The logs retain the known +generated-file clock skew and successful ABC Liberty fallback observations. + +The P3 eight-configuration matrix and three-cold-start campaign were not rerun. +No place/route, extracted/PVT/CDC/RDC signoff, high-rate qualification, new PDK +rollout or PDF build/visual check was performed. Publication snapshot enforcement +remains enabled; the reviewed PDF source revision was not advanced. Mini +DMA-to-WS2812 stays unsupported under option A, and product DMA/IRQ budgets +remain for a supported integration. P5 and later features are not implemented. diff --git a/docs/ip/tiny-soc-verification.md b/docs/ip/tiny-soc-verification.md index df84d24f..bcc2f0ca 100644 --- a/docs/ip/tiny-soc-verification.md +++ b/docs/ip/tiny-soc-verification.md @@ -1,5 +1,69 @@ # Tiny MCU Verification Record +## TINY-R2-P4 throughput repair; validated, review pending (2026-10-10) + +Following the read-only P4 review, the maintainer approved the bounded-buffer +repair plan for the P2 external-SRAM throughput regression. The changed Tiny +group/frontend keep local I/D single-operation semantics, reserve external +response capacity and overlap burst issuance/retirement; W is captured before +macro arbitration and B follows complete commit-response drain. Common FIFO +and register dependencies, ABI, clock/macro bindings and the pre-P5 external +attachment remain unchanged. No shared Mini debug code is changed by this fix. + +Fresh evidence belongs under +`build/ics55-tiny-2026-10-10-15-15-b4aa59f3e5d9/meta/tiny-r2-p4/`. +The preceding P4 report below remains the pre-repair comparison, and does not +qualify these new changes. The final audit rechecks 1972 technical inputs, +six fresh regression simulations and three immutable-image executions. Full +Pytest reports 1659 passed and one optional `pypdf` skip. Both Tiny PDK PR +regressions pass, including synthesis, netlist boot and clock/macro bindings. +Streaming/backpressure/error/reset checks and response conservation/fairness +induction pass; all six SRAM fixtures retain three-cycle local completion and +one-cycle steady read/write beat spacing. Initial style failures and focused +development attempts remain separate from final qualifying results. + +Matched burst16 DMA cycles fall from 36713 to 17897 (flat) and 36489 to 17317 +(banked), reductions of 51.25% and 52.54%. All banked observations match across +Verilator and Icarus. CPU/copy/burst1 cycles remain unchanged from pre-repair +P4; burst1 overhead and 8.34%/4.81% burst16 overhead versus original P3 remain +explicit. The combined input digest is +`58daf68fbb9dee199b3085a73ef07d22eb3de6d00ba875c462e85b76b6091a0d`. +`evidence.json` binds the source, artifacts, comparison and reused unchanged +Mini behavioral evidence. All long gates ran once; Tiny nightly matched PR +and was not duplicated. Setup WNS remains negative (-36.88 ns ICS55, +-97.37 ns IHP130), with warning/metrics observations and baselines preserved. +The [runbook](tiny-soc-r2-local-memory.md) records exact commands, measurements +and unrun qualifications. Human review remains required; no P5 execution or +physical-frequency qualification is implied. + +## TINY-R2-P4 implementation and validation; review pending (2026-10-10) + +The maintainer approved P4 preflight and implementation on clean source +`4db27524a6282ca40d76b935415b89f043d6ea83`, specification blob +`7e19de49d49efaafacccfa85671e314d53dd15a4`. The [P4 runbook](tiny-soc-r2-local-memory.md) +records architecture, lifecycle, physical binding and matched-image boundaries. +P3 results retain their original source attribution; no historical phase or +physical qualification is automatically promoted. + +Development evidence is under +`build/ics55-tiny-2026-10-10-10-05-b4aa59f3e5d9/meta/tiny-r2-p4/`. +All planned functional gates completed: 1659 Pytest passes and one optional +`pypdf` skip; Tiny ICS55/IHP130 PR and Mini ICS55 behavioral compatibility pass. +Both Tiny netlists pass boot and the four-group/32-macro/common-SYS binding +checks. Local uncontended latency is three cycles; group-control induction +passes. Setup and hold timing remain negative and observational. + +`report.json` retains single-execution matched-binary results; the banked image +matches exactly across Verilator and Icarus. CPU-copy cycles improve about +19%/25% for flat/banked images, while isolated burst-16 DMA cycles increase +about 2.2 times. This external-frontend throughput regression requires review; +no blanket speedup or automatic phase acceptance is claimed. `evidence.json` +rechecks 1972 source inputs and binds the gate, image, model, log and report +artifacts. Exact commands, measurements and unrun qualifications are in the runbook. + +External fabric stays serialized until P5, which is not authorized. Default +Tiny stays ICS55 SAFE24 with PLL off; IHP130 is explicit compatibility. + ## TINY-R2-P3 implementation in progress (2026-10-08–10) The retained source-bound compiler/layout matrix is complete for commit @@ -493,7 +557,7 @@ not a prerequisite to the architectural improvements in R2-P3 through R2-P5. | 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 and available-path correctness; approved option A retains product DMA/IRQ budget qualification for the platform that supplies the route. | Matrix retained on `3e46cc8`; subsequent two P1 HAL fixes and focused/compatibility checks complete in the worktree; human review/acceptance 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-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. | Implementation and planned functional validation complete on `4db2752` plus retained worktree snapshot; matched-image DMA throughput regression and human phase acceptance pending review; timing remains observational | | 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 | | TINY-R2-P6 - Shared IP and Eight-Channel DMA Integration | Legacy P7 obligations; shared PWM reporting, I2S slave and alternate-input-route prerequisites; common ABI/HAL parity; Crypto banks/lifecycle; truthful capabilities and affected Mini compatibility. | Pending | | TINY-R2-P7 - Tiny RCU and Dual-Mode Clock/Reset Integration | Legacy P8 obligations with main SRAM in SYS and XPI in MEM; atomic rate changes, all actual CDC/reset barriers, clock failures, WFI leaf gating and reserved DVP controls. | Pending | diff --git a/physical/librelane/tiny/scripts/generate_chip_config.py b/physical/librelane/tiny/scripts/generate_chip_config.py index 782be724..85a9e08b 100644 --- a/physical/librelane/tiny/scripts/generate_chip_config.py +++ b/physical/librelane/tiny/scripts/generate_chip_config.py @@ -27,7 +27,7 @@ SIDE_ORDER = ("south", "east", "north", "west") EXPECTED_SIGNAL_PADS = 52 CLOCK_PORTS = ("extclk_i_pad", "jtag_tck_i_pad") SRAM_MASTER = "RM_IHPSG13_1P_1024x32_c2_bm_bist" -SRAM_INSTANCE = "u_soc.u_sram.gen_memory.gen_bank[{index}].u_ram.u_mem" +SRAM_INSTANCE = "u_soc.u_sram.gen_group[{group}].u_group.gen_bank[{bank}].u_ram.u_mem" SRAM_COLUMNS = 8 SRAM_ORIGIN = (400, 600) # Detailed routing requires an M2 track inside each bank's 0.26 um bottom-edge @@ -95,7 +95,7 @@ def sram_instances(capacity_kib: int) -> dict[str, tuple[list[int], str]]: SRAM_ORIGIN[0] + column * SRAM_PITCH[0], SRAM_ORIGIN[1] + row * SRAM_PITCH[1], ] - instances[SRAM_INSTANCE.format(index=index)] = (location, "N") + instances[SRAM_INSTANCE.format(group=index // 8, bank=index % 8)] = (location, "N") return instances @@ -122,18 +122,9 @@ def macro_config(instances: dict[str, tuple[list[int], str]], header: str) -> di def macro_hooks(instances: dict[str, tuple[list[int], str]]) -> list[str]: hooks: list[str] = [] for instance in instances: - bracket_start = instance.find("[") - if bracket_start >= 0: - bracket_end = instance.index("]", bracket_start) - instance_pattern = ( - re.escape(instance[:bracket_start]) - + ".*" - + instance[bracket_start + 1 : bracket_end] - + ".*" - + re.escape(instance[bracket_end + 1 :]) - ) - else: - instance_pattern = re.escape(instance) + # OpenDB and synthesis may escape array brackets differently. Handle + # every index (group and bank), retaining the exact non-index hierarchy. + instance_pattern = ".*".join(re.escape(part) for part in re.split(r"[\[\]]", instance)) hooks.extend( [ f"{instance_pattern} VDD VSS VDDARRAY! VSS!", diff --git a/physical/smoke/sta/opensta/opensta.tcl b/physical/smoke/sta/opensta/opensta.tcl index 966a9e50..a8e84504 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 {$top_design eq "retrosoc_tiny_asic"} { + source [file join [file dirname [info script]] tiny_r2_p4_checks.tcl] +} if {[info exists ::env(OPENSTA_TINY_ICS55)] && $::env(OPENSTA_TINY_ICS55) eq "YES"} { source [file join [file dirname [info script]] tiny_ics55_checks.tcl] } diff --git a/physical/smoke/sta/opensta/tiny_r2_p4_checks.tcl b/physical/smoke/sta/opensta/tiny_r2_p4_checks.tcl new file mode 100644 index 00000000..932f5ae2 --- /dev/null +++ b/physical/smoke/sta/opensta/tiny_r2_p4_checks.tcl @@ -0,0 +1,54 @@ +# P4 structural timing prerequisites, common to both supported Tiny PDKs. +# Negative timing remains observational; missing/incorrect clock objects do not. +set p4_dir [file dirname $::env(OPENSTA_REPORT)] +set p4_ram [get_cells -hierarchical -filter {ref_name == RM_IHPSG13_1P_1024x32_c2_bm_bist}] +set p4_clock_pin A_CLK +if {[llength $p4_ram] == 0} { + set p4_ram [get_cells -hierarchical -filter {ref_name == ics55_ecos_sram_1024x32_m8}] + set p4_clock_pin CLK +} +if {[llength $p4_ram] != 32} { error "P4 requires 32 main SRAM macros" } +set p4_expected {} +for {set group 0} {$group < 4} {incr group} { + for {set bank 0} {$bank < 8} {incr bank} { + lappend p4_expected [format {u_soc.u_sram.gen_group[%d].u_group.gen_bank[%d].u_ram.u_mem} $group $bank] + } +} +set p4_seen {} +foreach cell $p4_ram { + set name [string map {/ .} [get_full_name $cell]] + foreach expected $p4_expected { + if {[string equal [string range $name end-[expr {[string length $expected]-1}] end] $expected]} { + lappend p4_seen $expected + } + } +} +if {[lsort $p4_seen] ne [lsort $p4_expected]} { error "P4 macro group hierarchy mismatch" } +set p4_source [get_pins -quiet {u_clock_buffer/clk_o}] +if {[llength $p4_source] != 1} { error "P4 SYS source missing" } +set p4_direct {} +foreach pin [get_fanout -from $p4_source -flat -pin_levels 1 -trace_arcs all] { + dict set p4_direct [get_full_name $pin] 1 +} +set p4_clock_report [open $p4_dir/p4-clock-bindings.rpt w] +foreach cell $p4_ram { + set clocks {} + foreach pin [get_pins -of_objects $cell] { + if {[string match "*/$p4_clock_pin" [get_full_name $pin]]} { lappend clocks $pin } + } + if {[llength $clocks] != 1} { error "P4 macro clock pin missing" } + set name [get_full_name [lindex $clocks 0]] + if {![dict exists $p4_direct $name]} { error "P4 SRAM clock is not direct SYS: $name" } + puts $p4_clock_report $name +} +set p4_cpu [get_pins -hierarchical -quiet {*u_hazard3_cpu_2port*/CLK *u_hazard3_cpu_2port*/CK *u_hazard3_cpu_2port*/CKN}] +set p4_front [get_pins -hierarchical -quiet {*u_cpu_mem*/CLK *u_cpu_mem*/CK *u_cpu_mem*/CKN}] +if {[llength $p4_cpu] == 0 || [llength $p4_front] == 0} { error "P4 CPU/frontend clock endpoints missing" } +foreach pin [concat $p4_cpu $p4_front] { + set name [get_full_name $pin] + if {![dict exists $p4_direct $name]} { error "P4 CPU/frontend does not share SYS: $name" } +} +puts $p4_clock_report "CPU clock endpoints: [llength $p4_cpu]" +puts $p4_clock_report "Frontend clock endpoints: [llength $p4_front]" +close $p4_clock_report +puts "TINY_R2_P4_BINDING_PASS: four groups, 32 macros, common CPU/SRAM SYS; not timing qualification" diff --git a/publications/datasheets/tiny/chapter-review.md b/publications/datasheets/tiny/chapter-review.md index 65609ab7..3ebca872 100644 --- a/publications/datasheets/tiny/chapter-review.md +++ b/publications/datasheets/tiny/chapter-review.md @@ -11,7 +11,7 @@ All **39 frozen entries / 14 IP chapters** are reviewed below. Register, address | 3 | Configuration and Scope / 2 | Covered. IHP130, 128 KiB, 24 MHz target, four DMA channels; hardware RV32IMC versus default RV32IM firmware. | [ihp130-tiny.mk](../../../configs/ci/ihp130-tiny.mk), [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv) | Use the committed profile and identity; keep deferred interfaces and physical claims excluded. | | 4 | Reading and Evidence Conventions / 2 | Covered. Product-specific organization, navigation or revision control; no additional hardware capability. | [datasheet.typ](../../../publications/datasheets/tiny/datasheet.typ), [structure-contract.json](../../../publications/datasheets/tiny/structure-contract.json) | Retain the declared scope, complete source links and independent Tiny identity when updating this section. | | 5 | System Architecture / 17 | Covered. Product-specific organization, navigation or revision control; no additional hardware capability. | [datasheet.typ](../../../publications/datasheets/tiny/datasheet.typ), [structure-contract.json](../../../publications/datasheets/tiny/structure-contract.json) | Retain the declared scope, complete source links and independent Tiny identity when updating this section. | -| 6 | Processor and Integration / 17 | Covered. One RV32IMC MCU hart, A disabled, direct AHB-Lite/AXI32 adapter and debug reset boundary. | [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv), [mgmt_core_wrapper.sv](../../../rtl/ip/core/mgmt_core_wrapper.sv) | Retain actual wrapper parameters; do not infer firmware ISA flags from the hardware C extension. | +| 6 | Processor and Integration / 17 | Working-source P4 update: dual-port RV32IMC hart, A disabled, independent local I/D and tagged slow adapter. Page number belongs to the earlier delivered PDF. | [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv), [tiny_cpu_wrapper.sv](../../../rtl/tiny/top/tiny_cpu_wrapper.sv) | Retain actual wrapper parameters; revalidate publication pages separately before delivery. | | 7 | Clock, Reset and Operating State / 18 | Covered. System and asynchronous JTAG domains; RTC/WDG share the system clock. | [clock_reset_domains.json](../../../rtl/tiny/integration/clock_reset_domains.json), [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv) | Obtain CDC/RDC and clock/power evidence separately; do not claim independent sleep timing. | | 8 | Bus Transactions, Arbitration and Errors / 19 | Covered. One globally active transaction, round-robin acceptance, saved ownership, bounded legal bursts and error/drain behavior. | [tiny_axi4_fabric.sv](../../../rtl/tiny/top/tiny_axi4_fabric.sv), [axi4_error_slave.sv](../../../rtl/ip/interconnect/axi4_error_slave.sv) | Keep local IP capability separate from end-to-end fabric admission; no unlimited liveness guarantee. | | 9 | Memory Map and Access Conventions / 21 | Covered. Twenty canonical address windows, including alias versus physical-capacity distinctions. | [memory_map.json](../../../rtl/tiny/address_map/memory_map.json) | Reconcile every source-map change; do not use unmapped holes as scratch memory. | diff --git a/publications/datasheets/tiny/content-review.md b/publications/datasheets/tiny/content-review.md index 14add1c8..1d1c4840 100644 --- a/publications/datasheets/tiny/content-review.md +++ b/publications/datasheets/tiny/content-review.md @@ -1,11 +1,17 @@ # Tiny Gen1 v0.1 DRAFT content review -The publication describes the independent IHP130 Tiny profile: one RV32IMC -Hazard3 with A disabled, RV32IM default firmware, 128 KiB SRAM, 24 MHz target, +The publication describes the independent IHP130 Tiny profile: one dual-port +RV32IMC Hazard3 with A disabled, RV32IM default firmware, 128 KiB SRAM in four +independently serviced 32 KiB groups, 24 MHz target, four DMA channels and AXI32/APB4. Rill (清溪) is the selected prototype brand. The font/palette/grid are inherited as design rules; product facts are not inherited from Mini's descriptions or qualification records. +The P4 working-source update changes CPU/local-SRAM bindings and the affected +draft architecture prose/figure. It does not advance `tiny.json.source_revision`, +rebuild or relabel a delivered PDF, or add hardware qualification. A later +publication release still requires source review, snapshot refresh and PDF checks. + | Area | Review and publication decision | Follow-up boundary | | --- | --- | --- | | CPU and startup | Publish the actual C/M extensions, disabled A, CSR-enabled firmware and Tiny guard around PSRAM setup. | Keep compiler target separate from implemented ISA; a reset vector is not a completed boot. | diff --git a/publications/datasheets/tiny/datasheet.typ b/publications/datasheets/tiny/datasheet.typ index cd9d68c6..c3749082 100644 --- a/publications/datasheets/tiny/datasheet.typ +++ b/publications/datasheets/tiny/datasheet.typ @@ -29,9 +29,9 @@ - JTAG debug with coordinated hart reset. *Memory and interconnect* - - *128 KiB SRAM*, 32 banks of 4 KiB. + - *128 KiB SRAM*, four independently serviced 32 KiB groups backed by 32 banks of 4 KiB. - Native AXI32 memory paths and one APB4 island. - - CPU and DMA share one active transaction globally. + - CPU instruction/data ports are independent locally; the compatible external attachment remains serialized until P5. - XPI NOR boot and SRAM-resident application layout. *DMA and control* @@ -103,16 +103,18 @@ The document uses the approved Mini visual language; all product facts and sourc = System Architecture == Processor and Integration -Hazard3 is hart zero. The shared wrapper selects the C and M extensions and disables A for Tiny. Its AHB-Lite bus terminates -at a direct AXI32 adapter. JTAG supports the implemented halt/resume, register and system-bus debug path; debug hart reset -waits for the adapter to become idle instead of abandoning an accepted transaction. +Hazard3 is hart zero. The locked dual-port wrapper selects the C and M extensions and disables A for Tiny. Instruction fetch +and load/store use independent AHB ports through separate local SRAM frontends; non-SRAM accesses use the tagged slow path. +JTAG supports halt/resume, register access and halted-hart abstract-command memory access, without an independent system-bus master. +Debug hart reset first halts the hart and drains accepted local I/D and slow-path responses. SRAM and unrelated DMA work stay active. The system has no application hart, MMU/Linux platform, hardware cache-coherent multicore organization or product extension slots. CPU-visible SRAM, XPI and APB routes are the implemented interconnect. The single-hart architecture does not remove the need for software/DMA buffer ownership and fences. #architecture() #source-note("rtl/tiny/top/retrosoc_tiny.sv",title:"Tiny product integration and parameters") -#source-note("rtl/ip/core/mgmt_core_wrapper.sv",title:"CPU ISA, AHB adapter and debug wrapper") +#source-note("rtl/tiny/top/tiny_cpu_wrapper.sv",title:"Dual-port CPU ISA, AHB ports and debug wrapper") +#source-note("rtl/tiny/top/tiny_sram.sv",title:"Four local SRAM groups and external attachment") == Clock, Reset and Operating State CPU, AXI, APB, RTC and watchdog share the configured 24 MHz system clock. CLINT derives a 1 MHz tick. JTAG has its own @@ -130,7 +132,8 @@ advertised as a qualified voltage/frequency operating point. #source-note("rtl/tiny/integration/clock_reset_domains.json",title:"Clock/reset inventory and debug crossings") == Bus Transactions, Arbitration and Errors -The two initiators are CPU and DMA. They share *one active read or write transaction globally*. The fabric saves source, +The two external initiators are CPU slow path and DMA. They share *one active read or write transaction globally* in this +pre-P5 attachment. CPU instruction/data local requests are independently arbitrated within each SRAM group. The fabric saves source, direction, address and attributes, forwards the request, and retains ownership until the terminal B or RLAST handshake. Read/write request arbitration is round-robin; it is not a throughput guarantee or an unrestricted liveness guarantee. diff --git a/publications/datasheets/tiny/diagrams.typ b/publications/datasheets/tiny/diagrams.typ index 4365a9ab..15b0cdf4 100644 --- a/publications/datasheets/tiny/diagrams.typ +++ b/publications/datasheets/tiny/diagrams.typ @@ -26,21 +26,22 @@ }) } #let architecture() = diagram("tiny-architecture",schematic(( - (5,4,65,19,[Hazard3 · hart 0 \ RV32IMC · A disabled]), + (5,4,65,19,[Hazard3 dual-port · hart 0 \ RV32IMC · A disabled]), (104,4,63,19,[JTAG / debug transport \ External TCK domain]), - (5,37,65,15,[AHB-Lite → AXI32 adapter]), + (5,37,65,15,[I/D decode + slow adapter]), (104,37,63,15,[Four-channel DMA \ AXI32 master]), - (30,69,112,19,[Tiny AXI32 fabric \ One active transaction globally]), - (0,110,40,22,[128 KiB SRAM \ 32 × 4 KiB]), + (30,69,112,19,[Tiny AXI32 fabric \ P5 external concurrency pending]), + (0,110,40,22,[128 KiB SRAM \ 4 × 32 KiB groups]), (44,110,40,22,[XPI read path \ Flash alias / NOR]), (88,110,40,22,[APB4 bridge \ 16 targets]), (132,110,40,22,[Error responders \ DECERR / SLVERR]), ),( ((104,13),(70,13)),((37,23),(37,37)), + ((5,44),(0,44),(0,103),(6,103),(6,110)), ((37,52),(37,61),(60,61),(60,69)),((135,52),(135,61),(112,61),(112,69)), ((45,88),(45,99),(20,99),(20,110)),((70,88),(70,103),(64,103),(64,110)), ((103,88),(103,103),(108,103),(108,110)),((127,88),(127,99),(152,99),(152,110)), -),height:134),[Tiny Gen1 integration. System logic runs from the configured 24 MHz clock; JTAG is asynchronous.]) +),height:134),[Tiny Gen1 integration. The left branch represents independent local I/D paths bypassing the external fabric. System logic uses the configured 24 MHz clock; JTAG is asynchronous.]) #let ip-diagram(entry) = { let children=entry.nodes.slice(1) diff --git a/publications/datasheets/tiny/source-contract.json b/publications/datasheets/tiny/source-contract.json index d2346185..a83343d9 100644 --- a/publications/datasheets/tiny/source-contract.json +++ b/publications/datasheets/tiny/source-contract.json @@ -98,7 +98,13 @@ "uart1": 26 }, "sources": [ - "rtl/ip/core/mgmt_core_wrapper.sv", + "rtl/tiny/top/tiny_cpu_wrapper.sv", + "rtl/tiny/top/tiny_cpu_mem.sv", + "rtl/tiny/top/tiny_sram.sv", + "rtl/tiny/top/tiny_sram_group.sv", + "rtl/tiny/top/tiny_sram_axi4.sv", + "rtl/tiny/top/tiny_sram_axi4_demux.sv", + "rtl/tiny/top/tiny_sram_port_if.sv", "rtl/ip/core/mgmt_debug_wrapper.sv", "rtl/ip/core/mgmt_debug_reset.sv", "rtl/ip/interconnect/ahbl2axi4.sv", @@ -122,7 +128,7 @@ "(s_last_addr[31:12] == s_selected_addr.addr[31:12])", "s_accept = (s_state_q == Idle) && s_selected_valid;", "Response: if (s_terminal) s_state_d = Idle;", - "s_tgt_d = s_protocol_legal ? s_decoded_target : 3'd4;", + "s_tgt_d = !s_protocol_legal ? 3'd4 : s_fetch_denied ? ((s_decoded_target == 3'd2) ? 3'd4 : 3'd3) : s_decoded_target;", "assign s_protocol_legal = !s_selected_addr.lock && (s_selected_addr.size <= 3'd2) &&\n ((s_selected_addr.addr & ((32'd1 << s_selected_addr.size) - 32'd1)) == 32'd0) &&\n ((s_selected_addr.burst == 2'b01) ||\n ((s_selected_addr.burst == 2'b00) && (s_selected_addr.len == 8'd0))) &&\n (s_selected_addr.len <= 8'd15) && !s_last_addr[32] &&\n (s_last_addr[31:12] == s_selected_addr.addr[31:12]) &&\n (decode_target(\n s_last_addr[31:0]\n ) == s_decoded_target) && ((s_decoded_target != 3'd2) || (s_selected_addr.len == 8'd0));", "assign s_terminal = (s_state_q == Response) &&\n (s_write_q ? (s_target_bvalid[s_tgt_q] && s_bready[s_owner_q]) :\n (s_target_rvalid[s_tgt_q] && s_rready[s_owner_q] && s_target_rlast[s_tgt_q]));" ] diff --git a/publications/tiny_reference.py b/publications/tiny_reference.py index 42c4a022..2b4c5732 100644 --- a/publications/tiny_reference.py +++ b/publications/tiny_reference.py @@ -26,6 +26,9 @@ 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" +CPU = "rtl/tiny/top/tiny_cpu_wrapper.sv" +CPU_MEM = "rtl/tiny/top/tiny_cpu_mem.sv" +SRAM = "rtl/tiny/top/tiny_sram.sv" FAMILIES = {"xpi", "sram", "gpio", "dma", "timer", "pwm", "rtc", "wdg", "uart", "i2c", "clint", "archinfo"} MANAGED = {"publication_media", "hazard3", "cluster_common", "cluster_archinfo", "cluster_pwm", "cluster_rtc", "cluster_wdg"} SUPPORTED_SYSCTRL = { @@ -65,21 +68,27 @@ def facts(root: Path = ROOT) -> dict: if any(profile.get(key) != value for key, value in required.items()): raise ValueError("Tiny publication profile changed; review configuration and ISA boundary") top = (root / TOP).read_text(encoding="utf-8") - ram = instance_parameters(top, "onchip_ram", "u_sram") + ram_reg = instance_parameters((root / SRAM).read_text(), "onchip_ram_reg", "u_reg") + ram = {"CapacityKiB": ram_reg.get("CapacityKiB"), + "DataWidth": 8 * ram_reg.get("DataBytes", 0), "IdWidth": 1} 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, "Ics55SmallBanks": 1}: + cpu = instance_parameters(top, "tiny_cpu_wrapper", "u_cpu") + if ram != {"CapacityKiB": 128, "DataWidth": 32, "IdWidth": 1} or ram_reg.get("Present") != 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"} + # P4 changes local routing, not physical geometry or the book's IHP130 + # compatibility selection. Snapshot enforcement still precedes publication. + read_topology(root / TOPOLOGY, root / MAP) + require_snippets(root, TOP, ["tiny_sram u_sram", "tiny_sram_axi4_demux u_sram_attachment", + ".ID_WIDTH(1)", ".DATA_WIDTH(32)"]) + require_snippets(root, SRAM, ["group < 4", "tiny_sram_group u_group"]) 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}: + if cpu != {"ExternalIrqCount": 30, "EnableAtomics": 0, "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])", + require_snippets(root, CPU, ["hazard3_cpu_2port", ".EXTENSION_C(1)", ".EXTENSION_A(EnableAtomics)"]) + require_snippets(root, CPU_MEM, [".TwoCycleErrors(1'b1)", "ErrorFirst", "ErrorLast"]) + require_snippets(root, TOP, ["u_masters_axi4_if[2]", ".slow_axi4(u_masters_axi4_if[0])", ".axi4(u_masters_axi4_if[1])", ".cfg_apb4(u_sram_apb4_if)", "assign s_tick = s_tick_count_q == 5'd23;"]) require_snippets(root, SYSCTRL, ["(apb4.paddr[1:0] != 2'd0)", "(apb4.pstrb != 4'hf)", diff --git a/rtl/ip/core/mgmt_core_wrapper.sv b/rtl/ip/core/mgmt_core_wrapper.sv index 6afa424c..51bacb5b 100644 --- a/rtl/ip/core/mgmt_core_wrapper.sv +++ b/rtl/ip/core/mgmt_core_wrapper.sv @@ -72,36 +72,37 @@ module mgmt_core_wrapper #( .JtagIdcode (`SOC_JTAG_IDCODE), .ResetSyncStages(ResetSyncStages) ) u_mgmt_debug_wrapper ( - .clk_i (clk_i), - .rst_n_i (rst_n_i), - .bridge_idle_i (s_ahbl_idle), - .jtag_tck_i (jtag_tck_i), - .jtag_tms_i (jtag_tms_i), - .jtag_tdi_i (jtag_tdi_i), - .jtag_trst_n_i (jtag_trst_n_i), - .jtag_tdo_o (jtag_tdo_o), - .core_rst_n_o (s_core_rst_n), - .dbg_req_halt_o (s_dbg_req_halt), - .dbg_req_halt_on_reset_o (s_dbg_req_halt_on_reset), - .dbg_req_resume_o (s_dbg_req_resume), - .dbg_halted_i (s_dbg_halted), - .dbg_running_i (s_dbg_running), - .dbg_data0_rdata_o (s_dbg_data0_rdata), - .dbg_data0_wdata_i (s_dbg_data0_wdata), - .dbg_data0_wen_i (s_dbg_data0_wen), - .dbg_instr_data_o (s_dbg_instr_data), - .dbg_instr_data_vld_o (s_dbg_instr_data_vld), - .dbg_instr_data_rdy_i (s_dbg_instr_data_rdy), + .clk_i(clk_i), + .rst_n_i(rst_n_i), + .bridge_idle_i(s_ahbl_idle), + .jtag_tck_i(jtag_tck_i), + .jtag_tms_i(jtag_tms_i), + .jtag_tdi_i(jtag_tdi_i), + .jtag_trst_n_i(jtag_trst_n_i), + .jtag_tdo_o(jtag_tdo_o), + .core_rst_n_o(s_core_rst_n), + .reset_pending_o(), // Mini retains its existing reset policy. + .dbg_req_halt_o(s_dbg_req_halt), + .dbg_req_halt_on_reset_o(s_dbg_req_halt_on_reset), + .dbg_req_resume_o(s_dbg_req_resume), + .dbg_halted_i(s_dbg_halted), + .dbg_running_i(s_dbg_running), + .dbg_data0_rdata_o(s_dbg_data0_rdata), + .dbg_data0_wdata_i(s_dbg_data0_wdata), + .dbg_data0_wen_i(s_dbg_data0_wen), + .dbg_instr_data_o(s_dbg_instr_data), + .dbg_instr_data_vld_o(s_dbg_instr_data_vld), + .dbg_instr_data_rdy_i(s_dbg_instr_data_rdy), .dbg_instr_caught_exception_i(s_dbg_instr_caught_exception), - .dbg_instr_caught_ebreak_i (s_dbg_instr_caught_ebreak), - .dbg_sbus_addr_o (s_dbg_sbus_addr), - .dbg_sbus_write_o (s_dbg_sbus_write), - .dbg_sbus_size_o (s_dbg_sbus_size), - .dbg_sbus_vld_o (s_dbg_sbus_vld), - .dbg_sbus_rdy_i (s_dbg_sbus_rdy), - .dbg_sbus_err_i (s_dbg_sbus_err), - .dbg_sbus_wdata_o (s_dbg_sbus_wdata), - .dbg_sbus_rdata_i (s_dbg_sbus_rdata) + .dbg_instr_caught_ebreak_i(s_dbg_instr_caught_ebreak), + .dbg_sbus_addr_o(s_dbg_sbus_addr), + .dbg_sbus_write_o(s_dbg_sbus_write), + .dbg_sbus_size_o(s_dbg_sbus_size), + .dbg_sbus_vld_o(s_dbg_sbus_vld), + .dbg_sbus_rdy_i(s_dbg_sbus_rdy), + .dbg_sbus_err_i(s_dbg_sbus_err), + .dbg_sbus_wdata_o(s_dbg_sbus_wdata), + .dbg_sbus_rdata_i(s_dbg_sbus_rdata) ); hazard3_cpu_1port #( diff --git a/rtl/ip/core/mgmt_debug_reset.sv b/rtl/ip/core/mgmt_debug_reset.sv index 9505eabc..526d568f 100644 --- a/rtl/ip/core/mgmt_debug_reset.sv +++ b/rtl/ip/core/mgmt_debug_reset.sv @@ -19,12 +19,15 @@ module mgmt_debug_reset #( input logic reset_req_i, input logic bridge_idle_i, output logic core_rst_n_o, + output logic reset_pending_o, output logic reset_done_o ); logic s_pending_d, s_pending_q; logic s_reset_active_d, s_reset_active_q; + assign reset_pending_o = reset_req_i || s_pending_q || s_reset_active_q; + always_comb begin s_pending_d = s_pending_q; s_reset_active_d = s_reset_active_q; diff --git a/rtl/ip/core/mgmt_debug_wrapper.sv b/rtl/ip/core/mgmt_debug_wrapper.sv index 5d6bad1c..91ba858f 100644 --- a/rtl/ip/core/mgmt_debug_wrapper.sv +++ b/rtl/ip/core/mgmt_debug_wrapper.sv @@ -23,6 +23,7 @@ module mgmt_debug_wrapper #( input logic jtag_trst_n_i, output logic jtag_tdo_o, output logic core_rst_n_o, + output logic reset_pending_o, output logic dbg_req_halt_o, output logic dbg_req_halt_on_reset_o, @@ -154,12 +155,13 @@ module mgmt_debug_wrapper #( 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), - .bridge_idle_i(bridge_idle_i), - .core_rst_n_o (core_rst_n_o), - .reset_done_o (s_reset_done) + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .reset_req_i (s_sys_reset_req || s_hart_reset_req), + .bridge_idle_i (bridge_idle_i), + .core_rst_n_o (core_rst_n_o), + .reset_pending_o(reset_pending_o), + .reset_done_o (s_reset_done) ); endmodule diff --git a/rtl/rtl_readiness.json b/rtl/rtl_readiness.json index 616992fc..2edf6e96 100644 --- a/rtl/rtl_readiness.json +++ b/rtl/rtl_readiness.json @@ -1,6 +1,23 @@ { "schema_version": 1, "targets": [ + { + "name": "tiny-r2-p4", + "paths": ["rtl/tiny/top"], + "status": "prototype", + "baseline_revision": "4db27524a6282ca40d76b935415b89f043d6ea83", + "configuration_digest": null, + "configuration_profiles": ["configs/ci/ics55-tiny.mk", "configs/ci/ihp130-tiny.mk"], + "required_evidence": [], + "synthesis_intent": { + "registers": "Common registers; independent I/D request state and per-group responses", + "reset": "five-edge release; hart halt/drain leaves SRAM and unrelated DMA active", + "clock_enable": "CPU and all 32 main-SRAM macros use direct SAFE24 SYS", + "memory": "four groups of eight tc_sram_1024x32 macros; no payload reset or extra SRAM", + "pipeline": "AHB capture, macro issue, registered response; uncontended completion within three cycles" + }, + "waivers": [] + }, { "name": "owned-rtl", "paths": [ diff --git a/rtl/rtl_style_audit.json b/rtl/rtl_style_audit.json index ad5f310d..b19702c4 100644 --- a/rtl/rtl_style_audit.json +++ b/rtl/rtl_style_audit.json @@ -437,7 +437,14 @@ "tiny_archinfo.sv", "tiny_axi42apb4.sv", "tiny_axi4_fabric.sv", + "tiny_cpu_mem.sv", + "tiny_cpu_wrapper.sv", "tiny_reset_tree.sv", + "tiny_sram.sv", + "tiny_sram_axi4.sv", + "tiny_sram_axi4_demux.sv", + "tiny_sram_group.sv", + "tiny_sram_port_if.sv", "tiny_sysctrl.sv" ] }, diff --git a/rtl/tiny/Makefile b/rtl/tiny/Makefile index d327bb2f..ab9c7035 100644 --- a/rtl/tiny/Makefile +++ b/rtl/tiny/Makefile @@ -200,4 +200,19 @@ tiny-r2-p3-matrix: | manifest tiny-r2-p3-report: $(PYTHON) $(TINY_P3_TOOL) --root $(ROOT_PATH) --variant-root $(VARIANT_ROOT) report -.PHONY: tiny-r2-p3-image tiny-r2-p3-sim tiny-r2-p3-matrix tiny-r2-p3-report \ No newline at end of file +.PHONY: tiny-r2-p3-image tiny-r2-p3-sim tiny-r2-p3-matrix tiny-r2-p3-report + +# P4 preserves old image identity and replays each selected image exactly once. +TINY_P4_REFERENCE ?= +TINY_P4_CASES ?= perf-o2-plain-flat,perf-o2-plain-banked +tiny-r2-p4-replay: | manifest + $(PYTHON) $(ROOT_PATH)/scripts/tiny_r2_p4.py --root $(ROOT_PATH) --variant-root $(VARIANT_ROOT) \ + --reference $(TINY_P4_REFERENCE) --cases $(TINY_P4_CASES) --simulator $(SIM_TOOL_NAME) \ + --vvp $(VVP) --timeout $(SOC_SIM_TIME) -- \ + make --no-print-directory -j1 -B CONFIG=$(CONFIG_PATH) BUILD_TIMESTAMP=$(BUILD_TIMESTAMP) \ + $(foreach var,$(CONFIG_KEY_VARS),$(var)=$($(var))) SIMU=$(SIMU) SYNTH=NONE STA=NONE comp + +tiny-r2-p4-report: + $(PYTHON) $(ROOT_PATH)/scripts/tiny_r2_p4.py --root $(ROOT_PATH) --variant-root $(VARIANT_ROOT) --report + +.PHONY: tiny-r2-p4-replay tiny-r2-p4-report diff --git a/rtl/tiny/README.md b/rtl/tiny/README.md index e172e693..a31224b8 100644 --- a/rtl/tiny/README.md +++ b/rtl/tiny/README.md @@ -9,6 +9,12 @@ 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. +The [R2-P4 implementation](../../docs/ip/tiny-soc-r2-local-memory.md) connects the +official dual-port CPU to four independently serviced 32 KiB local SRAM groups. +The 32 physical macros and register ABI are unchanged. Only non-SRAM CPU work +uses the tagged slow path; the external fabric stays serialized until P5. +Source presence alone is not completed phase or physical qualification. + `top` owns product integration, `address_map`, `pin_map` and `integration` own canonical address, pad, IRQ and clock/reset inputs, `filelist` selects sources, `dv` owns product verification, and `mk` owns product configuration. Reusable @@ -43,8 +49,8 @@ final timing closure is mandatory. Software/DMA scheduling is `TINY-R2-P3`, loca `TINY-R2-P9`, system qualification `TINY-R2-P10` and complete-product physical qualification `TINY-R2-P11` (historical IHP130 title; now default ICS55). Legacy `TINY-P0` through `TINY-P12` remain in -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 +the contract history. Executable profiles retain SAFE24 and four DMA channels; +ICS55's present PLL stays off. 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 diff --git a/rtl/tiny/dv/retrosoc_tiny_tb.sv b/rtl/tiny/dv/retrosoc_tiny_tb.sv index 8adbfac3..c90ab682 100644 --- a/rtl/tiny/dv/retrosoc_tiny_tb.sv +++ b/rtl/tiny/dv/retrosoc_tiny_tb.sv @@ -230,57 +230,117 @@ module retrosoc_tiny_tb; `undef _BASELINE_AXI end - // The baseline has one AHB port. HPROT[0] distinguishes instruction/data; - // these observations do not assert future independent local-I/D latency. - longint unsigned s_ahb_cycle = 0; - longint unsigned s_ahb_start = 0; - longint unsigned s_ahb_accept[2], s_ahb_done[2], s_ahb_latency[2]; - logic s_ahb_pending = 1'b0, s_ahb_measured = 1'b0; - logic s_ahb_data = 1'b0, s_ahb_previous_enable = 1'b0; - integer s_ahb_window = 0; - wire s_ahb_enable = u_dut.u_soc.s_perf_enable && s_baseline_enabled; - `define _BASELINE_AHB u_dut.u_soc.u_cpu.u_ahbl_if - always @(posedge s_clk) begin - s_ahb_cycle = s_ahb_cycle + 1; - if (!u_dut.u_soc.s_rst_n) begin - s_ahb_pending = 1'b0; - s_ahb_measured = 1'b0; - s_ahb_previous_enable = 1'b0; - s_ahb_window = 0; - end else if (s_baseline_enabled) begin - if (s_ahb_enable && !s_ahb_previous_enable) begin - s_ahb_window = s_ahb_window + 1; - s_ahb_measured = 1'b0; - for (int kind = 0; kind < 2; kind++) begin - s_ahb_accept[kind] = 0; - s_ahb_done[kind] = 0; - s_ahb_latency[kind] = 0; + // Verification-only per-group issue/conflict accounting. A bank conflict is + // two or more eligible macro clients in one cycle, not an AXI address stall. + for (genvar group = 0; group < 4; group++) begin : gen_bank_observer + longint unsigned s_issues[3], s_waits[3]; + longint unsigned s_conflicts = 0; + logic s_previous_enable = 0; + integer s_window = 0; + wire s_enable = u_dut.u_soc.s_perf_enable && s_baseline_enabled; + wire [2:0] s_req = u_dut.u_soc.u_sram.gen_group[group].u_group.s_req; + wire [2:0] s_grant = u_dut.u_soc.u_sram.gen_group[group].u_group.s_grant; + always @(posedge s_clk) begin + if (!u_dut.u_soc.s_rst_n) begin + s_previous_enable = 0; + s_window = 0; + end else if (s_baseline_enabled) begin + if (s_enable && !s_previous_enable) begin + s_window = s_window + 1; + s_conflicts = 0; + for (int client = 0; client < 3; client++) begin + s_issues[client] = 0; + s_waits[client] = 0; + end end + if (s_enable) begin + if ((s_req & (s_req - 3'd1)) != 3'd0) s_conflicts = s_conflicts + 1; + for (int client = 0; client < 3; client++) begin + if (s_grant[client]) s_issues[client] = s_issues[client] + 1; + if (s_req[client] && !s_grant[client]) s_waits[client] = s_waits[client] + 1; + end + end + if (!s_enable && s_previous_enable) + $display( + "R2_P4_BANK window=%0d group=%0d issues_i=%0d issues_d=%0d issues_external=%0d conflicts=%0d wait_i=%0d wait_d=%0d wait_external=%0d", + s_window, + group, + s_issues[0], + s_issues[1], + s_issues[2], + s_conflicts, + s_waits[0], + s_waits[1], + s_waits[2] + ); + s_previous_enable = s_enable; end - if (`_BASELINE_AHB.hready) begin - if (s_ahb_pending && s_ahb_measured && s_ahb_enable) begin - s_ahb_done[s_ahb_data] = s_ahb_done[s_ahb_data] + 1; - if ((s_ahb_cycle - s_ahb_start) > s_ahb_latency[s_ahb_data]) - s_ahb_latency[s_ahb_data] = s_ahb_cycle - s_ahb_start; - end - s_ahb_pending = `_BASELINE_AHB.htrans[1]; - s_ahb_measured = s_ahb_pending && s_ahb_enable; - if (s_ahb_measured) begin - s_ahb_data = `_BASELINE_AHB.hprot[0]; - s_ahb_start = s_ahb_cycle; - s_ahb_accept[s_ahb_data] = s_ahb_accept[s_ahb_data] + 1; - end - end - if (!s_ahb_enable && s_ahb_previous_enable) begin - for (int kind = 0; kind < 2; kind++) begin - $display("R2_AHB window=%0d kind=%0d accepted=%0d completed=%0d latency_max=%0d", - s_ahb_window, kind, s_ahb_accept[kind], s_ahb_done[kind], s_ahb_latency[kind]); - end - end - s_ahb_previous_enable = s_ahb_enable; end end - `undef _BASELINE_AHB + + // P4 retains the original per-kind record format, now observed on two + // independent ports. External AXI counts remain external traffic only. + for (genvar port_index = 0; port_index < 2; port_index++) begin : gen_ahb_observer + longint unsigned s_cycle = 0, s_start = 0; + longint unsigned s_accepted = 0, s_completed = 0, s_latency = 0; + longint unsigned s_local_accepted = 0, s_local_completed = 0, s_local_latency = 0; + longint unsigned s_admission_wait = 0; + logic s_pending = 0, s_measured = 0, s_local = 0, s_previous_enable = 0; + integer s_window = 0; + wire s_enable = u_dut.u_soc.s_perf_enable && s_baseline_enabled; + `define _P4_AHB u_dut.u_soc.u_cpu_ahbl_if[port_index] + always @(posedge s_clk) begin + s_cycle = s_cycle + 1; + if (!u_dut.u_soc.s_core_rst_n) begin + s_pending = 0; + s_measured = 0; + s_previous_enable = 0; + s_window = 0; + end else if (s_baseline_enabled) begin + if (s_enable && !s_previous_enable) begin + s_window = s_window + 1; + s_measured = 0; + s_accepted = 0; + s_completed = 0; + s_latency = 0; + s_local_accepted = 0; + s_local_completed = 0; + s_local_latency = 0; + s_admission_wait = 0; + end + if (s_enable && `_P4_AHB.htrans[1] && !`_P4_AHB.hready) + s_admission_wait = s_admission_wait + 1; + if (`_P4_AHB.hready) begin + if (s_pending && s_measured && s_enable) begin + s_completed = s_completed + 1; + if ((s_cycle - s_start) > s_latency) s_latency = s_cycle - s_start; + if (s_local) begin + s_local_completed = s_local_completed + 1; + if ((s_cycle - s_start) > s_local_latency) s_local_latency = s_cycle - s_start; + end + end + s_pending = `_P4_AHB.htrans[1]; + s_measured = s_pending && s_enable; + if (s_measured) begin + s_start = s_cycle; + s_accepted = s_accepted + 1; + s_local = (`_P4_AHB.haddr >= 32'h30000000) && (`_P4_AHB.haddr < 32'h30020000); + if (s_local) s_local_accepted = s_local_accepted + 1; + end + end + if (!s_enable && s_previous_enable) begin + $display("R2_AHB window=%0d kind=%0d accepted=%0d completed=%0d latency_max=%0d", + s_window, port_index, s_accepted, s_completed, s_latency); + $display( + "R2_P4_LOCAL window=%0d port=%0d accepted=%0d completed=%0d latency_max=%0d admission_wait=%0d", + s_window, port_index, s_local_accepted, s_local_completed, s_local_latency, + s_admission_wait); + end + s_previous_enable = s_enable; + end + end + `undef _P4_AHB + end `endif retrosoc_tiny_asic u_dut ( diff --git a/rtl/tiny/filelist/top.fl b/rtl/tiny/filelist/top.fl index 08b0c7ab..dd87d59b 100644 --- a/rtl/tiny/filelist/top.fl +++ b/rtl/tiny/filelist/top.fl @@ -3,5 +3,12 @@ /top/tiny_sysctrl.sv /top/tiny_archinfo.sv /top/tiny_reset_tree.sv +/top/tiny_sram_port_if.sv +/top/tiny_sram_group.sv +/top/tiny_sram_axi4.sv +/top/tiny_sram_axi4_demux.sv +/top/tiny_sram.sv +/top/tiny_cpu_mem.sv +/top/tiny_cpu_wrapper.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 e9bb824d..1c692f05 100644 --- a/rtl/tiny/integration/clock_reset_domains.json +++ b/rtl/tiny/integration/clock_reset_domains.json @@ -1,5 +1,17 @@ { "schema_version": 2, + "local_memory": { + "phase": "TINY-R2-P4", + "clock_domain": "system", + "cpu_instance": "u_soc.u_cpu.u_hazard3_cpu_2port", + "frontend_instance": "u_soc.u_cpu_mem", + "macro_instance": "u_soc.u_sram.gen_group[{group}].u_group.gen_bank[{bank}].u_ram.u_mem", + "groups": 4, + "macros_per_group": 8, + "hart_reset": "halt then drain accepted I/D and slow responses; SRAM/DMA stay active", + "main_sram_payload_reset": "none", + "local_uncontended_max_cycles": 3 + }, "inactive_macros": { "ics55_pll": { "cell": "PLL_TOP", diff --git a/rtl/tiny/integration/soc_topology.json b/rtl/tiny/integration/soc_topology.json index 004795d2..9f9795bd 100644 --- a/rtl/tiny/integration/soc_topology.json +++ b/rtl/tiny/integration/soc_topology.json @@ -1,5 +1,15 @@ { "schema_version": 1, + "cpu": {"module": "hazard3_cpu_2port", "local_ports": 2, "data_outstanding": 1}, + "main_sram": { + "groups": 4, + "group_bytes": 32768, + "macros_per_group": 8, + "macro_bytes": 4096, + "base": "0x30000000", + "macro_instance": "u_soc.u_sram.gen_group[{group}].u_group.gen_bank[{bank}].u_ram.u_mem", + "external_attachment": "serialized-pre-P5" + }, "gpio_pins": 32, "irq_vector_width": 32, "external_irq_count": 30, diff --git a/rtl/tiny/top/retrosoc_tiny.sv b/rtl/tiny/top/retrosoc_tiny.sv index 7278291b..9227531a 100644 --- a/rtl/tiny/top/retrosoc_tiny.sv +++ b/rtl/tiny/top/retrosoc_tiny.sv @@ -23,9 +23,10 @@ module retrosoc_tiny ( 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 s_master_rst_n [2]; + logic s_target_rst_n [5]; + logic s_debug_halted; + logic s_core_rst_n, s_cpu_bus_idle, s_cpu_quiesce; logic [31:0] s_irq; logic s_fault_valid, s_fault_master, s_fault_write; logic [31:0] s_fault_addr; @@ -38,6 +39,20 @@ 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; + ahbl_if u_cpu_ahbl_if[2] ( + .hclk (clk_i), + .hresetn(s_core_rst_n) + ); + tiny_sram_port_if u_cpu_local_if[2] (); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) u_sram_axi4_if[4] ( + .aclk (clk_i), + .aresetn(s_sram_rst_n) + ); `include "tiny_apb_interfaces.svh" uart_if u_uart0_if (); uart_if u_uart1_if (); @@ -135,11 +150,10 @@ uart_if u_uart0_if (); .dat_i (s_tick_count_d), .dat_o (s_tick_count_q) ); - mgmt_core_wrapper #( - .ExternalIrqCount (30), - .EnableAtomics (1'b0), - .TwoCycleBusErrors(1'b1), - .ResetSyncStages (5) + tiny_cpu_wrapper #( + .ExternalIrqCount(30), + .EnableAtomics (1'b0), + .ResetSyncStages (5) ) u_cpu ( .clk_i (clk_i), .rst_n_i (s_cpu_ready_rst_n), @@ -150,7 +164,19 @@ uart_if u_uart0_if (); .jtag_trst_n_i (jtag_trst_n_i), .jtag_tdo_o (jtag_tdo_o), .debug_halted_o(s_debug_halted), - .axi4 (u_masters_axi4_if[0]) + .bus_idle_i (s_cpu_bus_idle), + .core_rst_n_o (s_core_rst_n), + .quiesce_o (s_cpu_quiesce), + .cpu (u_cpu_ahbl_if) + ); + tiny_cpu_mem u_cpu_mem ( + .clk_i (clk_i), + .rst_n_i (s_core_rst_n), + .quiesce_i (s_cpu_quiesce), + .idle_o (s_cpu_bus_idle), + .cpu (u_cpu_ahbl_if), + .local_ports(u_cpu_local_if), + .slow_axi4 (u_masters_axi4_if[0]) ); tiny_axi4_fabric u_fabric ( .clk_i (clk_i), @@ -167,18 +193,20 @@ uart_if u_uart0_if (); .cpu_wait_o (s_cpu_wait), .dma_wait_o (s_dma_wait) ); - onchip_ram #( - .CapacityKiB (128), - .DataWidth (32), - .IdWidth (1), - .Ics55SmallBanks(1'b1) - ) u_sram ( - .clk_i (clk_i), - .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), - .perf_clear_i (s_perf_clear) + tiny_sram_axi4_demux u_sram_attachment ( + .clk_i (clk_i), + .rst_n_i (s_sram_rst_n), + .upstream(u_targets_axi4_if[0]), + .groups (u_sram_axi4_if) + ); + tiny_sram u_sram ( + .clk_i (clk_i), + .rst_n_i (s_sram_rst_n), + .local_ports (u_cpu_local_if), + .external_ports(u_sram_axi4_if), + .cfg_apb4 (u_sram_apb4_if), + .perf_enable_i (s_perf_enable), + .perf_clear_i (s_perf_clear) ); apb4_xpi u_xpi ( .clk_i (clk_i), diff --git a/rtl/tiny/top/tiny_axi4_fabric.sv b/rtl/tiny/top/tiny_axi4_fabric.sv index a4473d48..7b84e24b 100644 --- a/rtl/tiny/top/tiny_axi4_fabric.sv +++ b/rtl/tiny/top/tiny_axi4_fabric.sv @@ -56,6 +56,7 @@ module tiny_axi4_fabric ( logic [32:0] s_last_addr; logic [ 2:0] s_decoded_target; logic s_protocol_legal; + logic s_fetch_denied; logic [1:0] s_awvalid, s_arvalid, s_wvalid, s_wlast, s_bready, s_rready; logic [1:0][31:0] s_wdata; logic [1:0][ 3:0] s_wstrb; @@ -90,6 +91,11 @@ module tiny_axi4_fabric ( s_last_addr[31:0] ) == s_decoded_target) && ((s_decoded_target != 3'd2) || (s_selected_addr.len == 8'd0)); assign s_accept = (s_state_q == Idle) && s_selected_valid; + // The tagged CPU slow ingress preserves instruction origin. Reject fetches + // outside the read-idempotent NOR apertures before reaching any APB target. + assign s_fetch_denied = !s_selected[1] && !s_selected[0] && s_selected_addr.prot[2] && !( + `SOC_ADDR_IS_FLASH(s_selected_addr.addr) + || ((s_selected_addr.addr >= 32'h5000_0000) && (s_selected_addr.addr < 32'h5400_0000))); assign s_terminal = (s_state_q == Response) && (s_write_q ? (s_target_bvalid[s_tgt_q] && s_bready[s_owner_q]) : (s_target_rvalid[s_tgt_q] && s_rready[s_owner_q] && s_target_rlast[s_tgt_q])); @@ -126,8 +132,9 @@ module tiny_axi4_fabric ( if (s_accept) begin s_owner_d = s_selected[1]; s_write_d = s_selected[0]; - s_addr_d = s_selected_addr; - s_tgt_d = s_protocol_legal ? s_decoded_target : 3'd4; + s_addr_d = s_selected_addr; + s_tgt_d = !s_protocol_legal ? 3'd4 : + s_fetch_denied ? ((s_decoded_target == 3'd2) ? 3'd4 : 3'd3) : s_decoded_target; s_state_d = Addr; end Addr: diff --git a/rtl/tiny/top/tiny_cpu_mem.sv b/rtl/tiny/top/tiny_cpu_mem.sv new file mode 100644 index 00000000..bba3bdf2 --- /dev/null +++ b/rtl/tiny/top/tiny_cpu_mem.sv @@ -0,0 +1,259 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 +`include "mmap_define.svh" + +// Independent one-operation I/D frontends. AHB write data belongs to the cycle +// following address acceptance. Local stores complete only after macro commit. +// Only non-SRAM work enters the tagged, non-posted slow-path merge. +module tiny_cpu_mem ( + input logic clk_i, + input logic rst_n_i, + input logic quiesce_i, + output logic idle_o, + ahbl_if.slave cpu [2], + tiny_sram_port_if.master local_ports[2], + axi4_if.master slow_axi4 +); + typedef enum logic [2:0] { + Idle, + WriteCapture, + LocalIssue, + LocalWait, + SlowIssue, + SlowWait, + ErrorFirst, + ErrorLast + } state_e; + typedef struct packed { + state_e state; + logic [31:0] addr, data; + logic [2:0] size; + logic write; + logic [3:0] strb; + } request_t; + request_t s_req_d[2], s_req_q[2]; + logic [1:0] s_terminal, s_err, s_slow_req, s_slow_grant, s_front_idle; + logic s_slow_selected, s_slow_valid, s_slow_accept; + typedef enum logic [1:0] { + SlowIdle, + SlowAddress, + SlowData, + SlowResponse + } slow_state_e; + typedef struct packed { + slow_state_e state; + logic owner, write, error; + logic [2:0] size; + logic [31:0] addr, data, rdata; + } slow_t; + slow_t s_slow_d, s_slow_q; + ahbl_if u_slow_ahbl_if ( + .hclk (clk_i), + .hresetn(rst_n_i) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) u_slow_axi4_if ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + logic s_adapter_idle; + + for (genvar port_index = 0; port_index < 2; port_index++) begin : gen_port + assign s_slow_req[port_index] = s_req_q[port_index].state == SlowIssue; + assign s_front_idle[port_index] = (s_req_q[port_index].state == Idle) && + (quiesce_i || !cpu[port_index].htrans[1]); + assign s_err[port_index] = (s_req_q[port_index].state == ErrorFirst) || + (s_req_q[port_index].state == ErrorLast) || + ((s_req_q[port_index].state == SlowWait) && + (s_slow_q.state == SlowResponse) && (s_slow_q.owner == 1'(port_index)) && s_slow_q.error); + assign s_terminal[port_index] = (s_req_q[port_index].state == ErrorLast) || + ((s_req_q[port_index].state == LocalWait) && local_ports[port_index].rsp_valid) || + ((s_req_q[port_index].state == SlowWait) && + (s_slow_q.state == SlowResponse) && (s_slow_q.owner == 1'(port_index)) && !s_slow_q.error); + assign cpu[port_index].hready = + ((s_req_q[port_index].state == Idle) && !quiesce_i) || s_terminal[port_index]; + assign cpu[port_index].hresp = s_err[port_index]; + assign cpu[port_index].hrdata = (s_req_q[port_index].state == LocalWait) ? + local_ports[port_index].rdata : s_slow_q.rdata; + assign local_ports[port_index].req_valid = s_req_q[port_index].state == LocalIssue; + assign local_ports[port_index].addr = s_req_q[port_index].addr; + assign local_ports[port_index].write = s_req_q[port_index].write; + assign local_ports[port_index].wdata = s_req_q[port_index].data; + assign local_ports[port_index].wstrb = s_req_q[port_index].strb; + assign local_ports[port_index].rsp_ready = s_req_q[port_index].state == LocalWait; + + always_comb begin + s_req_d[port_index] = s_req_q[port_index]; + unique case (s_req_q[port_index].state) + WriteCapture: begin + unique case (s_req_q[port_index].size) + 3'd0: begin + s_req_d[port_index].data = {24'd0, cpu[port_index].hwdata[7:0]} << + (8 * s_req_q[port_index].addr[1:0]); + s_req_d[port_index].strb = 4'b0001 << s_req_q[port_index].addr[1:0]; + end + 3'd1: begin + s_req_d[port_index].data = {16'd0, cpu[port_index].hwdata[15:0]} << + (8 * s_req_q[port_index].addr[1:0]); + s_req_d[port_index].strb = 4'b0011 << s_req_q[port_index].addr[1:0]; + end + default: begin + s_req_d[port_index].data = cpu[port_index].hwdata; + s_req_d[port_index].strb = 4'b1111; + end + endcase + s_req_d[port_index].state = + `SOC_ADDR_IS_SRAM(s_req_q[port_index].addr) + ? LocalIssue : SlowIssue; + end + LocalIssue: if (local_ports[port_index].req_ready) s_req_d[port_index].state = LocalWait; + SlowIssue: + if (s_slow_accept && s_slow_grant[port_index]) s_req_d[port_index].state = SlowWait; + SlowWait: if (s_err[port_index]) s_req_d[port_index].state = ErrorLast; + ErrorFirst: s_req_d[port_index].state = ErrorLast; + default: begin + end + endcase + if (cpu[port_index].hready) begin + s_req_d[port_index].state = Idle; + // Capture even a terminal-cycle overlap during quiesce: that address + // was acknowledged by HREADY and cannot be silently discarded. + if (cpu[port_index].htrans[1]) begin + s_req_d[port_index].addr = cpu[port_index].haddr; + s_req_d[port_index].size = cpu[port_index].hsize; + s_req_d[port_index].write = cpu[port_index].hwrite; + s_req_d[port_index].strb = '0; + if ((cpu[port_index].htrans != 2'b10) || cpu[port_index].hmastlock || + (cpu[port_index].hsize > 3'd2) || + ((cpu[port_index].haddr & ((32'd1 << cpu[port_index].hsize) - 32'd1)) != 32'd0) || + ((port_index == 0) && (cpu[port_index].hwrite || (cpu[port_index].hsize != 3'd2)))) + s_req_d[port_index].state = ErrorFirst; + else if (cpu[port_index].hwrite) s_req_d[port_index].state = WriteCapture; + else + s_req_d[port_index].state = + `SOC_ADDR_IS_SRAM(cpu[port_index].haddr) + ? LocalIssue : SlowIssue; + end + end + end + dffr #( + .DATA_WIDTH($bits(request_t)) + ) u_request ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_req_d[port_index]), + .dat_o (s_req_q[port_index]) + ); + end + + assign idle_o = (&s_front_idle) && (s_slow_q.state == SlowIdle) && s_adapter_idle; + assign s_slow_accept = (s_slow_q.state == SlowIdle) && s_slow_valid; + round_robin_arbiter #( + .CLIENTS(2) + ) u_slow_arbiter ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .advance_i (s_slow_accept), + .request_i (s_slow_req), + .grant_o (s_slow_grant), + .selected_o(s_slow_selected), + .valid_o (s_slow_valid) + ); + always_comb begin + s_slow_d = s_slow_q; + unique case (s_slow_q.state) + SlowIdle: + if (s_slow_accept) begin + s_slow_d.owner = s_slow_selected; + s_slow_d.addr = s_req_q[s_slow_selected].addr; + s_slow_d.write = s_req_q[s_slow_selected].write; + s_slow_d.size = s_req_q[s_slow_selected].size; + // The shared AHB adapter applies lane shifting itself. + s_slow_d.data = s_req_q[s_slow_selected].data >> (8 * s_req_q[s_slow_selected].addr[1:0]); + s_slow_d.state = SlowAddress; + end + SlowAddress: if (u_slow_ahbl_if.hready) s_slow_d.state = SlowData; + SlowData: + if (u_slow_ahbl_if.hready) begin + s_slow_d.rdata = u_slow_ahbl_if.hrdata; + s_slow_d.error = u_slow_ahbl_if.hresp; + s_slow_d.state = SlowResponse; + end + SlowResponse: s_slow_d.state = SlowIdle; + default: s_slow_d.state = SlowIdle; + endcase + end + dffr #( + .DATA_WIDTH($bits(slow_t)) + ) u_slow_state ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_slow_d), + .dat_o (s_slow_q) + ); + assign u_slow_ahbl_if.haddr = s_slow_q.addr; + assign u_slow_ahbl_if.hwrite = s_slow_q.write; + assign u_slow_ahbl_if.hsize = s_slow_q.size; + assign u_slow_ahbl_if.hwdata = s_slow_q.data; + assign u_slow_ahbl_if.htrans = (s_slow_q.state == SlowAddress) ? 2'b10 : 2'b00; + assign u_slow_ahbl_if.hburst = '0; + assign u_slow_ahbl_if.hprot = {3'b001, s_slow_q.owner}; + assign u_slow_ahbl_if.hmastlock = 1'b0; + ahbl2axi4 #( + .TwoCycleErrors(1'b1) + ) u_slow_adapter ( + .ahbl (u_slow_ahbl_if), + .axi4 (u_slow_axi4_if), + .idle_o(s_adapter_idle) + ); + // Forward every channel unchanged except the saved instruction attribute. + // Tiny's fabric uses it to reject fetches before peripheral side effects. + assign slow_axi4.awid = u_slow_axi4_if.awid; + assign slow_axi4.awaddr = u_slow_axi4_if.awaddr; + assign slow_axi4.awlen = u_slow_axi4_if.awlen; + assign slow_axi4.awsize = u_slow_axi4_if.awsize; + assign slow_axi4.awburst = u_slow_axi4_if.awburst; + assign slow_axi4.awlock = u_slow_axi4_if.awlock; + assign slow_axi4.awcache = u_slow_axi4_if.awcache; + assign slow_axi4.awprot = {!s_slow_q.owner, u_slow_axi4_if.awprot[1:0]}; + assign slow_axi4.awqos = u_slow_axi4_if.awqos; + assign slow_axi4.awregion = u_slow_axi4_if.awregion; + assign slow_axi4.awuser = u_slow_axi4_if.awuser; + assign slow_axi4.awvalid = u_slow_axi4_if.awvalid; + assign slow_axi4.arid = u_slow_axi4_if.arid; + assign slow_axi4.araddr = u_slow_axi4_if.araddr; + assign slow_axi4.arlen = u_slow_axi4_if.arlen; + assign slow_axi4.arsize = u_slow_axi4_if.arsize; + assign slow_axi4.arburst = u_slow_axi4_if.arburst; + assign slow_axi4.arlock = u_slow_axi4_if.arlock; + assign slow_axi4.arcache = u_slow_axi4_if.arcache; + assign slow_axi4.arprot = {!s_slow_q.owner, u_slow_axi4_if.arprot[1:0]}; + assign slow_axi4.arqos = u_slow_axi4_if.arqos; + assign slow_axi4.arregion = u_slow_axi4_if.arregion; + assign slow_axi4.aruser = u_slow_axi4_if.aruser; + assign slow_axi4.arvalid = u_slow_axi4_if.arvalid; + assign slow_axi4.wdata = u_slow_axi4_if.wdata; + assign slow_axi4.wstrb = u_slow_axi4_if.wstrb; + assign slow_axi4.wlast = u_slow_axi4_if.wlast; + assign slow_axi4.wuser = u_slow_axi4_if.wuser; + assign slow_axi4.wvalid = u_slow_axi4_if.wvalid; + assign slow_axi4.rready = u_slow_axi4_if.rready; + assign slow_axi4.bready = u_slow_axi4_if.bready; + assign u_slow_axi4_if.awready = slow_axi4.awready; + assign u_slow_axi4_if.arready = slow_axi4.arready; + assign u_slow_axi4_if.wready = slow_axi4.wready; + assign u_slow_axi4_if.rvalid = slow_axi4.rvalid; + assign u_slow_axi4_if.rid = slow_axi4.rid; + assign u_slow_axi4_if.rdata = slow_axi4.rdata; + assign u_slow_axi4_if.rresp = slow_axi4.rresp; + assign u_slow_axi4_if.rlast = slow_axi4.rlast; + assign u_slow_axi4_if.ruser = slow_axi4.ruser; + assign u_slow_axi4_if.bvalid = slow_axi4.bvalid; + assign u_slow_axi4_if.bid = slow_axi4.bid; + assign u_slow_axi4_if.bresp = slow_axi4.bresp; + assign u_slow_axi4_if.buser = slow_axi4.buser; +endmodule diff --git a/rtl/tiny/top/tiny_cpu_wrapper.sv b/rtl/tiny/top/tiny_cpu_wrapper.sv new file mode 100644 index 00000000..530d2b8e --- /dev/null +++ b/rtl/tiny/top/tiny_cpu_wrapper.sv @@ -0,0 +1,219 @@ +// Copyright (c) 2023-2026 Yuchi Miao +// retroSoC is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +`include "mmap_define.svh" + +module tiny_cpu_wrapper #( + parameter int ExternalIrqCount = 30, + parameter bit EnableAtomics = 1'b0, + parameter int ResetSyncStages = 3 +) ( + // verilog_format: off -- preserve reviewed column alignment + input logic clk_i, + input logic rst_n_i, + input logic [ExternalIrqCount+1:0] irq_i, + input logic jtag_tck_i, + input logic jtag_tms_i, + input logic jtag_tdi_i, + input logic jtag_trst_n_i, + output logic jtag_tdo_o, + output logic debug_halted_o, + input logic bus_idle_i, + output logic core_rst_n_o, + output logic quiesce_o, + ahbl_if.master cpu[2] + // verilog_format: on +); + + logic s_pwrup_req; + logic s_reset_pending; + logic s_hart_idle; + logic s_core_rst_n; + logic s_dbg_req_halt; + logic s_dbg_req_halt_on_reset; + logic s_dbg_req_resume; + logic s_dbg_halted; + logic s_dbg_running; + logic [31:0] s_dbg_data0_rdata; + logic [31:0] s_dbg_data0_wdata; + logic s_dbg_data0_wen; + logic [31:0] s_dbg_instr_data; + logic s_dbg_instr_data_vld; + logic s_dbg_instr_data_rdy; + logic s_dbg_instr_caught_exception; + logic s_dbg_instr_caught_ebreak; + logic [31:0] s_dbg_sbus_addr; + logic s_dbg_sbus_write; + logic [ 1:0] s_dbg_sbus_size; + logic s_dbg_sbus_vld; + logic s_dbg_sbus_rdy; + logic s_dbg_sbus_err; + logic [31:0] s_dbg_sbus_wdata; + logic [31:0] s_dbg_sbus_rdata; + + assign debug_halted_o = s_dbg_halted; + assign core_rst_n_o = s_core_rst_n; + // Request a normal debug halt before draining/resetting the core. Never cut + // off an accepted AHB transfer to manufacture an idle indication. + assign quiesce_o = s_reset_pending && s_dbg_halted; + assign s_hart_idle = bus_idle_i && (!s_reset_pending || s_dbg_halted); + + mgmt_debug_wrapper #( + .JtagIdcode (`SOC_JTAG_IDCODE), + .ResetSyncStages(ResetSyncStages) + ) u_mgmt_debug_wrapper ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .bridge_idle_i (s_hart_idle), + .jtag_tck_i (jtag_tck_i), + .jtag_tms_i (jtag_tms_i), + .jtag_tdi_i (jtag_tdi_i), + .jtag_trst_n_i (jtag_trst_n_i), + .jtag_tdo_o (jtag_tdo_o), + .core_rst_n_o (s_core_rst_n), + .reset_pending_o (s_reset_pending), + .dbg_req_halt_o (s_dbg_req_halt), + .dbg_req_halt_on_reset_o (s_dbg_req_halt_on_reset), + .dbg_req_resume_o (s_dbg_req_resume), + .dbg_halted_i (s_dbg_halted), + .dbg_running_i (s_dbg_running), + .dbg_data0_rdata_o (s_dbg_data0_rdata), + .dbg_data0_wdata_i (s_dbg_data0_wdata), + .dbg_data0_wen_i (s_dbg_data0_wen), + .dbg_instr_data_o (s_dbg_instr_data), + .dbg_instr_data_vld_o (s_dbg_instr_data_vld), + .dbg_instr_data_rdy_i (s_dbg_instr_data_rdy), + .dbg_instr_caught_exception_i(s_dbg_instr_caught_exception), + .dbg_instr_caught_ebreak_i (s_dbg_instr_caught_ebreak), + .dbg_sbus_addr_o (s_dbg_sbus_addr), + .dbg_sbus_write_o (s_dbg_sbus_write), + .dbg_sbus_size_o (s_dbg_sbus_size), + .dbg_sbus_vld_o (s_dbg_sbus_vld), + .dbg_sbus_rdy_i (s_dbg_sbus_rdy), + .dbg_sbus_err_i (s_dbg_sbus_err), + .dbg_sbus_wdata_o (s_dbg_sbus_wdata), + .dbg_sbus_rdata_i (s_dbg_sbus_rdata) + ); + + hazard3_cpu_2port #( + .RESET_VECTOR (`SOC_CPU_RESET_ADDR), + .MTVEC_INIT (32'h0000_0000), + .EXTENSION_A (EnableAtomics), + .EXTENSION_C (1), + .EXTENSION_E (0), + .EXTENSION_M (1), + .EXTENSION_ZBA (1), + .EXTENSION_ZBB (1), + .EXTENSION_ZBC (1), + .EXTENSION_ZBKB (1), + .EXTENSION_ZBKX (1), + .EXTENSION_ZBS (1), + .EXTENSION_ZCB (0), + .EXTENSION_ZCLSD (0), + .EXTENSION_ZCMP (0), + .EXTENSION_ZIFENCEI (1), + .EXTENSION_ZILSD (1), + .EXTENSION_XH3BEXTM (1), + .EXTENSION_XH3IRQ (1), + .EXTENSION_XH3PMPM (0), + .EXTENSION_XH3POWER (0), + .CSR_M_MANDATORY (1), + .CSR_M_TRAP (1), + .CSR_COUNTER (1), + .U_MODE (0), + .PMP_REGIONS (0), + .PMP_GRAIN (0), + .PMP_MATCH_NAPOT (1), + .PMP_MATCH_TOR (0), + .PMP_HARDWIRED (0), + .PMP_HARDWIRED_ADDR (0), + .PMP_HARDWIRED_CFG (0), + .DEBUG_SUPPORT (1), + .BREAKPOINT_TRIGGERS(2), + .NUM_IRQS (ExternalIrqCount), + .IRQ_PRIORITY_BITS (2), + .IRQ_INPUT_BYPASS ({ExternalIrqCount{1'b0}}), + .MVENDORID_VAL (32'h0), + .MCONFIGPTR_VAL (32'h0), + .REDUCED_BYPASS (0), + .MULDIV_UNROLL (2), + .MUL_FAST (1), + .MUL_FASTER (1), + .MULH_FAST (1), + .FAST_BRANCHCMP (1), + .RESET_REGFILE (1), + .BRANCH_PREDICTOR (1), + .MTVEC_WMASK (32'hfffffffd) + ) u_hazard3_cpu_2port ( + .clk (clk_i), + .clk_always_on (clk_i), + .rst_n (s_core_rst_n), + .pwrup_req (s_pwrup_req), + .pwrup_ack (s_pwrup_req), + .clk_en (), + .unblock_out (), + .unblock_in (1'b0), + .i_haddr (cpu[0].haddr), + .i_hwrite (cpu[0].hwrite), + .i_htrans (cpu[0].htrans), + .i_hsize (cpu[0].hsize), + .i_hburst (cpu[0].hburst), + .i_hprot (cpu[0].hprot), + .i_hmastlock (cpu[0].hmastlock), + .i_hmaster (), + .i_hready (cpu[0].hready), + .i_hresp (cpu[0].hresp), + .i_hwdata (cpu[0].hwdata), + .i_hrdata (cpu[0].hrdata), + .d_haddr (cpu[1].haddr), + .d_hwrite (cpu[1].hwrite), + .d_htrans (cpu[1].htrans), + .d_hsize (cpu[1].hsize), + .d_hburst (cpu[1].hburst), + .d_hprot (cpu[1].hprot), + .d_hmastlock (cpu[1].hmastlock), + .d_hmaster (), + .d_hready (cpu[1].hready), + .d_hresp (cpu[1].hresp), + .d_hwdata (cpu[1].hwdata), + .d_hrdata (cpu[1].hrdata), + .d_hexcl (), + .d_hexokay (1'b1), + .fence_i_vld (), + .fence_d_vld (), + .fence_rdy (1'b1), + .dbg_req_halt (s_dbg_req_halt || s_reset_pending), + .dbg_req_halt_on_reset (s_dbg_req_halt_on_reset), + .dbg_req_resume (s_dbg_req_resume && !s_reset_pending), + .dbg_halted (s_dbg_halted), + .dbg_running (s_dbg_running), + .dbg_data0_rdata (s_dbg_data0_rdata), + .dbg_data0_wdata (s_dbg_data0_wdata), + .dbg_data0_wen (s_dbg_data0_wen), + .dbg_instr_data (s_dbg_instr_data), + .dbg_instr_data_vld (s_dbg_instr_data_vld && !s_reset_pending), + .dbg_instr_data_rdy (s_dbg_instr_data_rdy), + .dbg_instr_caught_exception(s_dbg_instr_caught_exception), + .dbg_instr_caught_ebreak (s_dbg_instr_caught_ebreak), + .dbg_sbus_addr (s_dbg_sbus_addr), + .dbg_sbus_write (s_dbg_sbus_write), + .dbg_sbus_size (s_dbg_sbus_size), + .dbg_sbus_vld (s_dbg_sbus_vld), + .dbg_sbus_rdy (s_dbg_sbus_rdy), + .dbg_sbus_err (s_dbg_sbus_err), + .dbg_sbus_wdata (s_dbg_sbus_wdata), + .dbg_sbus_rdata (s_dbg_sbus_rdata), + .mhartid_val ('0), + .eco_version ('0), + .irq (irq_i[ExternalIrqCount+1:2]), + .soft_irq (irq_i[0]), + .timer_irq (irq_i[1]) + ); +endmodule diff --git a/rtl/tiny/top/tiny_sram.sv b/rtl/tiny/top/tiny_sram.sv new file mode 100644 index 00000000..0d814167 --- /dev/null +++ b/rtl/tiny/top/tiny_sram.sv @@ -0,0 +1,107 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +module tiny_sram ( + input logic clk_i, + input logic rst_n_i, + input logic perf_enable_i, + input logic perf_clear_i, + tiny_sram_port_if.slave local_ports [2], + axi4_if.slave external_ports[4], + apb4_if.slave cfg_apb4 +); + logic [3:0] s_local_ready[2], s_local_valid[2]; + logic [31:0] s_local_data[2] [4]; + logic [ 3:0] s_events [6]; + logic [31:0] s_count_d[6], s_count_q[6]; + logic [ 2:0] s_increment[6]; + logic [32:0] s_sum [6]; + + for (genvar group = 0; group < 4; group++) begin : gen_group + tiny_sram_port_if u_ports[3] (); + for (genvar client = 0; client < 2; client++) begin : gen_local + assign u_ports[client].req_valid = local_ports[client].req_valid && + (local_ports[client].addr[16:15] == 2'(group)); + assign u_ports[client].addr = local_ports[client].addr; + assign u_ports[client].write = local_ports[client].write; + assign u_ports[client].wdata = local_ports[client].wdata; + assign u_ports[client].wstrb = local_ports[client].wstrb; + assign u_ports[client].rsp_ready = local_ports[client].rsp_ready && + (local_ports[client].addr[16:15] == 2'(group)); + assign s_local_ready[client][group] = u_ports[client].req_ready; + assign s_local_valid[client][group] = u_ports[client].rsp_valid; + assign s_local_data[client][group] = u_ports[client].rdata; + end + tiny_sram_group u_group ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .ports (u_ports) + ); + tiny_sram_axi4 #( + .GroupBase(32'h3000_0000 + 32'(group) * 32'h8000) + ) u_external ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .axi4 (external_ports[group]), + .memory (u_ports[2]) + ); + assign s_events[0][group] = external_ports[group].arvalid && external_ports[group].arready; + assign s_events[1][group] = external_ports[group].awvalid && external_ports[group].awready; + assign s_events[2][group] = external_ports[group].rvalid && external_ports[group].rready; + assign s_events[3][group] = external_ports[group].wvalid && external_ports[group].wready; + assign s_events[4][group] = + (external_ports[group].arvalid && !external_ports[group].arready) || + (external_ports[group].awvalid && !external_ports[group].awready) || + (external_ports[group].wvalid && !external_ports[group].wready) || + (external_ports[group].rvalid && !external_ports[group].rready) || + (external_ports[group].bvalid && !external_ports[group].bready); + assign s_events[5][group] = + (external_ports[group].bvalid && external_ports[group].bready && + (external_ports[group].bresp != 2'b00)) || + (external_ports[group].rvalid && external_ports[group].rready && + external_ports[group].rlast && (external_ports[group].rresp != 2'b00)); + end + for (genvar client = 0; client < 2; client++) begin : gen_local_response + assign local_ports[client].req_ready = s_local_ready[client][local_ports[client].addr[16:15]]; + assign local_ports[client].rsp_valid = s_local_valid[client][local_ports[client].addr[16:15]]; + assign local_ports[client].rdata = s_local_data[client][local_ports[client].addr[16:15]]; + end + // Preserve the public external-AXI counter meanings. Four simultaneous stalls + // are one stalled cycle; request/beat events are counted individually. + for (genvar counter = 0; counter < 6; counter++) begin : gen_counter + if (counter == 4) begin : gen_cycle_count + assign s_increment[counter] = {2'b0, |s_events[counter]}; + end else begin : gen_event_count + assign s_increment[counter] = {2'b0, s_events[counter][0]} + + {2'b0, s_events[counter][1]} + {2'b0, s_events[counter][2]} + + {2'b0, s_events[counter][3]}; + end + assign s_sum[counter] = {1'b0, s_count_q[counter]} + {30'd0, s_increment[counter]}; + assign s_count_d[counter] = perf_clear_i ? 32'd0 : + perf_enable_i ? (s_sum[counter][32] ? 32'hffff_ffff : s_sum[counter][31:0]) : + s_count_q[counter]; + dffr #( + .DATA_WIDTH(32) + ) u_counter ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_count_d[counter]), + .dat_o (s_count_q[counter]) + ); + end + onchip_ram_reg #( + .Present (1'b1), + .CapacityKiB(128), + .DataBytes (4) + ) u_reg ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .apb4 (cfg_apb4), + .perf_read_requests_i (s_count_q[0]), + .perf_write_requests_i (s_count_q[1]), + .perf_read_beats_i (s_count_q[2]), + .perf_write_beats_i (s_count_q[3]), + .perf_stall_cycles_i (s_count_q[4]), + .perf_error_responses_i(s_count_q[5]) + ); +endmodule diff --git a/rtl/tiny/top/tiny_sram_axi4.sv b/rtl/tiny/top/tiny_sram_axi4.sv new file mode 100644 index 00000000..9c777a70 --- /dev/null +++ b/rtl/tiny/top/tiny_sram_axi4.sv @@ -0,0 +1,232 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// One external transaction per group. Address ownership lasts through the +// terminal response, but the macro is arbitrated separately for every beat. +// W may precede AW on the bus; it is backpressured until its AW is accepted. +module tiny_sram_axi4 #( + parameter logic [31:0] GroupBase = 32'h3000_0000 +) ( + input logic clk_i, + input logic rst_n_i, + axi4_if.slave axi4, + tiny_sram_port_if.master memory +); + typedef enum logic [2:0] { + Idle, + ReadStream, + ReadError, + WriteStream, + WriteResponse, + WriteDrain + } state_e; + typedef struct packed { + state_e state; + logic prefer_write; + logic id; + logic [31:0] addr; + logic [7:0] len, beat; + logic [2:0] size; + logic [1:0] burst, resp; + logic [8:0] issued, completed; + logic last; + } transaction_t; + typedef struct packed { + logic [31:0] addr, data; + logic [3:0] strb; + } write_t; + transaction_t s_transaction_d, s_transaction_q; + write_t s_write_input, s_write_output; + logic [31:0] s_next_addr; + logic [1:0] s_ar_resp, s_aw_resp; + logic [3:0] s_lane_mask; + logic s_expected_last, s_write_legal; + logic s_write_full, s_write_empty, s_write_push, s_write_pop; + logic s_req_accept, s_resp_accept, s_write_accept; + + function automatic logic [1:0] address_response(input logic [31:0] addr_i, + input logic [7:0] len_i, input logic [2:0] size_i, + input logic [1:0] burst_i, input logic lock_i); + logic [32:0] bytes_count, last_addr; + logic wrap_legal; + bytes_count = ({25'd0, len_i} + 33'd1) << size_i; + wrap_legal = (len_i == 8'd1) || (len_i == 8'd3) || (len_i == 8'd7) || (len_i == 8'd15); + last_addr = {1'b0, addr_i} + bytes_count - 33'd1; + if (burst_i == 2'b00) last_addr = {1'b0, addr_i} + (33'd1 << size_i) - 33'd1; + if (burst_i == 2'b10) + last_addr = ({1'b0, addr_i} & ~(bytes_count - 33'd1)) + bytes_count - 33'd1; + if (lock_i || (len_i > 8'd15) || (size_i > 3'd2) || + (burst_i == 2'b11) || ((burst_i == 2'b10) && !wrap_legal) || + ((addr_i & ((32'd1 << size_i) - 32'd1)) != 32'd0) || last_addr[32] || + (addr_i[31:12] != last_addr[31:12])) + return 2'b10; + if ((addr_i < GroupBase) || (last_addr[31:0] > (GroupBase + 32'h7fff))) return 2'b11; + return 2'b00; + endfunction + + assign s_ar_resp = address_response( + axi4.araddr, axi4.arlen, axi4.arsize, axi4.arburst, axi4.arlock + ); + assign s_aw_resp = address_response( + axi4.awaddr, axi4.awlen, axi4.awsize, axi4.awburst, axi4.awlock + ); + assign axi4.arready = (s_transaction_q.state == Idle) && + !(s_transaction_q.prefer_write && axi4.awvalid); + assign axi4.awready = (s_transaction_q.state == Idle) && + (s_transaction_q.prefer_write || !axi4.arvalid); + assign axi4.wready = ((s_transaction_q.state == WriteStream) && !s_transaction_q.last && + (!s_write_full || s_write_pop)) || (s_transaction_q.state == WriteDrain); + assign axi4.rvalid = (s_transaction_q.state == ReadError) || + ((s_transaction_q.state == ReadStream) && memory.rsp_valid); + assign axi4.rid = s_transaction_q.id; + assign axi4.rdata = (s_transaction_q.state == ReadStream) ? memory.rdata : 32'd0; + assign axi4.rresp = s_transaction_q.resp; + assign axi4.rlast = s_transaction_q.beat == s_transaction_q.len; + assign axi4.ruser = '0; + assign axi4.bvalid = s_transaction_q.state == WriteResponse; + assign axi4.bid = s_transaction_q.id; + assign axi4.bresp = s_transaction_q.resp; + assign axi4.buser = '0; + + // Read responses stay in the group's reserved queue until R handshakes. + // Issuance and retirement have independent positions within the same burst. + assign memory.req_valid = ((s_transaction_q.state == ReadStream) && + (s_transaction_q.issued <= {1'b0, s_transaction_q.len})) || + ((s_transaction_q.state == WriteStream) && !s_write_empty); + assign memory.addr = memory.write ? s_write_output.addr : s_transaction_q.addr; + assign memory.write = s_transaction_q.state == WriteStream; + assign memory.wdata = s_write_output.data; + assign memory.wstrb = s_write_output.strb; + assign memory.rsp_ready = ((s_transaction_q.state == ReadStream) && axi4.rready) || + (s_transaction_q.state == WriteStream); + assign s_req_accept = memory.req_valid && memory.req_ready; + assign s_resp_accept = memory.rsp_valid && memory.rsp_ready; + assign s_write_accept = axi4.wvalid && axi4.wready; + assign s_write_push = (s_transaction_q.state == WriteStream) && s_write_accept && s_write_legal; + assign s_write_pop = (s_transaction_q.state == WriteStream) && s_req_accept; + assign s_write_input = {s_transaction_q.addr, axi4.wdata, axi4.wstrb}; + // Capture W before it becomes eligible at the macro. A full queue can + // replace its head on the same edge that the arbiter commits that word. + fifo #( + .DATA_WIDTH ($bits(write_t)), + .BUFFER_DEPTH(2) + ) u_write_fifo ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .flush_i(1'b0), + .push_i (s_write_push), + .full_o (s_write_full), + .dat_i (s_write_input), + .pop_i (s_write_pop), + .empty_o(s_write_empty), + .dat_o (s_write_output), + .cnt_o () // Full/empty and accepted/completed counts cover ownership. + ); + assign s_lane_mask = 4'(((32'd1 << (32'd1 << s_transaction_q.size)) - 32'd1) + << s_transaction_q.addr[1:0]); + assign s_expected_last = s_transaction_q.beat == s_transaction_q.len; + assign s_write_legal = ((axi4.wstrb & ~s_lane_mask) == 4'd0) && (axi4.wlast == s_expected_last); + axi4_addr_gen #( + .ADDR_WIDTH(32) + ) u_address ( + .alen_i (s_transaction_q.len), + .asize_i (s_transaction_q.size), + .aburst_i(s_transaction_q.burst), + .addr_i (s_transaction_q.addr), + .addr_o (s_next_addr) + ); + + always_comb begin + s_transaction_d = s_transaction_q; + unique case (s_transaction_q.state) + Idle: begin + s_transaction_d.beat = '0; + s_transaction_d.issued = '0; + s_transaction_d.completed = '0; + s_transaction_d.last = 1'b0; + if (axi4.arvalid && axi4.arready) begin + s_transaction_d.prefer_write = 1'b1; + s_transaction_d.id = axi4.arid; + s_transaction_d.addr = axi4.araddr; + // Match the shared target's malformed-address error response length. + s_transaction_d.len = (s_ar_resp == 2'b10) ? 8'd0 : axi4.arlen; + s_transaction_d.size = axi4.arsize; + s_transaction_d.burst = axi4.arburst; + s_transaction_d.resp = s_ar_resp; + s_transaction_d.state = (s_ar_resp == 2'b00) ? ReadStream : ReadError; + end else if (axi4.awvalid && axi4.awready) begin + s_transaction_d.prefer_write = 1'b0; + s_transaction_d.id = axi4.awid; + s_transaction_d.addr = axi4.awaddr; + s_transaction_d.len = (s_aw_resp == 2'b10) ? 8'd0 : axi4.awlen; + s_transaction_d.size = axi4.awsize; + s_transaction_d.burst = axi4.awburst; + s_transaction_d.resp = s_aw_resp; + s_transaction_d.state = (s_aw_resp == 2'b00) ? WriteStream : WriteDrain; + end + end + ReadStream: begin + if (s_req_accept) begin + s_transaction_d.issued = s_transaction_q.issued + 9'd1; + s_transaction_d.addr = s_next_addr; + end + if (s_resp_accept) begin + s_transaction_d.completed = s_transaction_q.completed + 9'd1; + if (axi4.rlast) s_transaction_d.state = Idle; + else s_transaction_d.beat = s_transaction_q.beat + 8'd1; + end + end + ReadError: + if (axi4.rready) begin + if (axi4.rlast) s_transaction_d.state = Idle; + else s_transaction_d.beat = s_transaction_q.beat + 8'd1; + end + WriteStream: begin + if (s_write_accept) begin + s_transaction_d.last = s_expected_last || axi4.wlast; + s_transaction_d.beat = s_transaction_q.beat + 8'd1; + s_transaction_d.addr = s_next_addr; + if (!s_write_legal) s_transaction_d.resp = 2'b10; + end + if (s_req_accept) s_transaction_d.issued = s_transaction_q.issued + 9'd1; + if (s_resp_accept) s_transaction_d.completed = s_transaction_q.completed + 9'd1; + // Errors do not discard older queued writes. B acknowledges all + // accepted legal beats only after their macro-commit responses drain. + if (s_transaction_q.last && s_write_empty && + (s_transaction_q.issued == s_transaction_q.completed)) + s_transaction_d.state = WriteResponse; + end + WriteDrain: + if (axi4.wvalid) begin + if (s_expected_last || axi4.wlast) s_transaction_d.state = WriteResponse; + else s_transaction_d.beat = s_transaction_q.beat + 8'd1; + end + WriteResponse: if (axi4.bready) s_transaction_d.state = Idle; + default: s_transaction_d.state = Idle; + endcase + end + dffr #( + .DATA_WIDTH($bits(transaction_t)) + ) u_transaction ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_transaction_d), + .dat_o (s_transaction_q) + ); +`ifdef HAVE_SVA + assert property (@(posedge clk_i) disable iff (!rst_n_i) + s_transaction_q.completed <= s_transaction_q.issued); + assert property (@(posedge clk_i) disable iff (!rst_n_i) + axi4.bvalid |-> s_write_empty && + (s_transaction_q.completed == s_transaction_q.issued)); + assert property (@(posedge clk_i) disable iff (!rst_n_i) + axi4.rvalid && !axi4.rready |=> axi4.rvalid && + $stable( + {axi4.rdata, axi4.rid, axi4.rresp, axi4.rlast} + )); + assert property (@(posedge clk_i) disable iff (!rst_n_i) + axi4.bvalid && !axi4.bready |=> axi4.bvalid && $stable( + {axi4.bid, axi4.bresp} + )); +`endif +endmodule diff --git a/rtl/tiny/top/tiny_sram_axi4_demux.sv b/rtl/tiny/top/tiny_sram_axi4_demux.sv new file mode 100644 index 00000000..f2283861 --- /dev/null +++ b/rtl/tiny/top/tiny_sram_axi4_demux.sv @@ -0,0 +1,130 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// Compatibility attachment to the pre-P5 fabric. Independent direction tags +// route responses; this module does not arbitrate or lock the SRAM macros. +module tiny_sram_axi4_demux ( + input logic clk_i, + input logic rst_n_i, + axi4_if.slave upstream, + axi4_if.master groups [4] +); + logic s_read_busy_d, s_read_busy_q; + logic s_write_busy_d, s_write_busy_q; + logic s_read_terminal, s_write_terminal; + logic [1:0] s_read_group_d, s_read_group_q; + logic [1:0] s_write_group_d, s_write_group_q; + logic [3:0] s_arready, s_awready, s_wready, s_rvalid, s_bvalid, s_rlast; + logic [3:0] s_rid, s_bid, s_ruser, s_buser; + logic [1:0] s_rresp[4], s_bresp[4]; + logic [31:0] s_rdata[4]; + + assign upstream.arready = !s_read_busy_q && s_arready[upstream.araddr[16:15]]; + assign upstream.awready = !s_write_busy_q && s_awready[upstream.awaddr[16:15]]; + assign upstream.wready = s_write_busy_q && s_wready[s_write_group_q]; + assign upstream.rvalid = s_read_busy_q && s_rvalid[s_read_group_q]; + assign upstream.rdata = s_rdata[s_read_group_q]; + assign upstream.rresp = s_rresp[s_read_group_q]; + assign upstream.rlast = s_rlast[s_read_group_q]; + assign upstream.rid = s_rid[s_read_group_q]; + assign upstream.ruser = s_ruser[s_read_group_q]; + assign upstream.bvalid = s_write_busy_q && s_bvalid[s_write_group_q]; + assign upstream.bresp = s_bresp[s_write_group_q]; + assign upstream.bid = s_bid[s_write_group_q]; + assign upstream.buser = s_buser[s_write_group_q]; + assign s_read_terminal = upstream.rvalid && upstream.rready && upstream.rlast; + assign s_write_terminal = upstream.bvalid && upstream.bready; + assign s_read_busy_d = (s_read_busy_q && !s_read_terminal) || + (upstream.arvalid && upstream.arready); + assign s_write_busy_d = (s_write_busy_q && !s_write_terminal) || + (upstream.awvalid && upstream.awready); + assign s_read_group_d = upstream.araddr[16:15]; + assign s_write_group_d = upstream.awaddr[16:15]; + + dffr #( + .DATA_WIDTH(1) + ) u_read_busy ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_read_busy_d), + .dat_o (s_read_busy_q) + ); + dffr #( + .DATA_WIDTH(1) + ) u_write_busy ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_write_busy_d), + .dat_o (s_write_busy_q) + ); + dffer #( + .DATA_WIDTH(2) + ) u_read_group ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .en_i (upstream.arvalid && upstream.arready), + .dat_i (s_read_group_d), + .dat_o (s_read_group_q) + ); + dffer #( + .DATA_WIDTH(2) + ) u_write_group ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .en_i (upstream.awvalid && upstream.awready), + .dat_i (s_write_group_d), + .dat_o (s_write_group_q) + ); + + for (genvar group = 0; group < 4; group++) begin : gen_group + assign groups[group].arid = upstream.arid; + assign groups[group].araddr = upstream.araddr; + assign groups[group].arlen = upstream.arlen; + assign groups[group].arsize = upstream.arsize; + assign groups[group].arburst = upstream.arburst; + assign groups[group].arlock = upstream.arlock; + assign groups[group].arcache = upstream.arcache; + assign groups[group].arprot = upstream.arprot; + assign groups[group].arqos = upstream.arqos; + assign groups[group].arregion = upstream.arregion; + assign groups[group].aruser = upstream.aruser; + assign groups[group].awid = upstream.awid; + assign groups[group].awaddr = upstream.awaddr; + assign groups[group].awlen = upstream.awlen; + assign groups[group].awsize = upstream.awsize; + assign groups[group].awburst = upstream.awburst; + assign groups[group].awlock = upstream.awlock; + assign groups[group].awcache = upstream.awcache; + assign groups[group].awprot = upstream.awprot; + assign groups[group].awqos = upstream.awqos; + assign groups[group].awregion = upstream.awregion; + assign groups[group].awuser = upstream.awuser; + assign groups[group].wdata = upstream.wdata; + assign groups[group].wstrb = upstream.wstrb; + assign groups[group].wlast = upstream.wlast; + assign groups[group].wuser = upstream.wuser; + + assign groups[group].arvalid = upstream.arvalid && !s_read_busy_q && + (upstream.araddr[16:15] == 2'(group)); + assign groups[group].awvalid = upstream.awvalid && !s_write_busy_q && + (upstream.awaddr[16:15] == 2'(group)); + assign groups[group].wvalid = upstream.wvalid && s_write_busy_q && + (s_write_group_q == 2'(group)); + assign groups[group].rready = upstream.rready && s_read_busy_q && (s_read_group_q == 2'(group)); + assign groups[group].bready = upstream.bready && s_write_busy_q && + (s_write_group_q == 2'(group)); + assign s_arready[group] = groups[group].arready; + assign s_awready[group] = groups[group].awready; + assign s_wready[group] = groups[group].wready; + assign s_rvalid[group] = groups[group].rvalid; + assign s_bvalid[group] = groups[group].bvalid; + assign s_rlast[group] = groups[group].rlast; + assign s_rid[group] = groups[group].rid; + assign s_bid[group] = groups[group].bid; + assign s_ruser[group] = groups[group].ruser; + assign s_buser[group] = groups[group].buser; + assign s_rresp[group] = groups[group].rresp; + assign s_bresp[group] = groups[group].bresp; + assign s_rdata[group] = groups[group].rdata; + end +endmodule diff --git a/rtl/tiny/top/tiny_sram_group.sv b/rtl/tiny/top/tiny_sram_group.sv new file mode 100644 index 00000000..a1c1c357 --- /dev/null +++ b/rtl/tiny/top/tiny_sram_group.sv @@ -0,0 +1,206 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// One independently arbitrated 32 KiB group. Arbitration advances on the +// physical macro edge, never on an AXI address or burst reservation. Captured +// responses isolate all three clients from subsequent macro output changes. +module tiny_sram_group ( + input logic clk_i, + input logic rst_n_i, + tiny_sram_port_if.slave ports [3] +); + logic [2:0] s_req, s_grant; + logic [1:0] s_selected; + logic s_issue; + logic [1:0] s_rsp_valid_d; + logic [2:0] s_rsp_valid_q; + logic [31:0] s_rsp_data_d[2], s_rsp_data_q[3]; + logic s_read_pending_d, s_read_pending_q; + logic [1:0] s_read_owner_d, s_read_owner_q; + logic [2:0] s_read_bank_d, s_read_bank_q; + logic [2:0] s_write; + logic [31:0] s_addr[3], s_wdata[3]; + logic [ 3:0] s_wstrb [3]; + logic [31:0] s_bank_data [8]; + logic [ 7:0] s_bank_select; + + for (genvar client = 0; client < 3; client++) begin : gen_client + assign s_addr[client] = ports[client].addr; + assign s_write[client] = ports[client].write; + assign s_wdata[client] = ports[client].wdata; + assign s_wstrb[client] = ports[client].wstrb; + assign ports[client].req_ready = s_grant[client]; + assign ports[client].rsp_valid = s_rsp_valid_q[client]; + assign ports[client].rdata = s_rsp_data_q[client]; + + if (client < 2) begin : gen_local_response + assign s_req[client] = ports[client].req_valid && !s_rsp_valid_q[client] && + !(s_read_pending_q && (s_read_owner_q == 2'(client))); + always_comb begin + s_rsp_valid_d[client] = s_rsp_valid_q[client] && !ports[client].rsp_ready; + s_rsp_data_d[client] = s_rsp_data_q[client]; + if (s_read_pending_q && (s_read_owner_q == 2'(client))) begin + s_rsp_valid_d[client] = 1'b1; + s_rsp_data_d[client] = s_bank_data[s_read_bank_q]; + end + if (s_grant[client] && s_write[client]) begin + s_rsp_valid_d[client] = 1'b1; + s_rsp_data_d[client] = '0; + end + end + dffr #( + .DATA_WIDTH(1) + ) u_response_valid ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_rsp_valid_d[client]), + .dat_o (s_rsp_valid_q[client]) + ); + dffr #( + .DATA_WIDTH(32) + ) u_response_data ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_rsp_data_d[client]), + .dat_o (s_rsp_data_q[client]) + ); + end else begin : gen_external_response + logic s_empty, s_full, s_pop, s_push, s_pending; + logic [ 1:0] s_count; + logic [ 2:0] s_reserved; + logic [31:0] s_data; + assign s_pending = s_read_pending_q && (s_read_owner_q == 2'(client)); + assign s_reserved = {1'b0, s_count} + {2'b0, s_pending}; + assign s_pop = ports[client].rsp_ready && !s_empty; + // Include the macro read already in flight. A returning word must fit + // even if the consumer removes READY immediately after this issue. + // A same-client read return and write commit cannot share one FIFO push. + assign s_req[client] = ports[client].req_valid && + ((s_reserved < 3'd2) || ((s_reserved == 3'd2) && s_pop)) && + !(s_pending && s_write[client]); + assign s_push = s_pending || (s_grant[client] && s_write[client]); + assign s_data = s_pending ? s_bank_data[s_read_bank_q] : 32'd0; + assign s_rsp_valid_q[client] = !s_empty; + fifo #( + .DATA_WIDTH (32), + .BUFFER_DEPTH(2) + ) u_response_fifo ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .flush_i(1'b0), + .push_i (s_push), + .full_o (s_full), + .dat_i (s_data), + .pop_i (s_pop), + .empty_o(s_empty), + .dat_o (s_rsp_data_q[client]), + .cnt_o (s_count) + ); +`ifdef FORMAL + always_ff @(posedge clk_i) begin + if (rst_n_i) begin + assert (s_reserved <= 3'd2); + assert (!s_push || !s_full || s_pop); + assert (!(s_pending && s_grant[client] && s_write[client])); + end + end +`endif +`ifdef HAVE_SVA + assert property (@(posedge clk_i) disable iff (!rst_n_i) s_reserved <= 3'd2); + assert property (@(posedge clk_i) disable iff (!rst_n_i) !s_push || !s_full || s_pop); +`endif + end + end + + round_robin_arbiter #( + .CLIENTS(3) + ) u_arbiter ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .advance_i (s_issue), + .request_i (s_req), + .grant_o (s_grant), + .selected_o(s_selected), + .valid_o (s_issue) + ); + assign s_read_pending_d = s_issue && !s_write[s_selected]; + assign s_read_owner_d = s_selected; + assign s_read_bank_d = s_addr[s_selected][14:12]; + dffr #( + .DATA_WIDTH(1) + ) u_read_pending ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_read_pending_d), + .dat_o (s_read_pending_q) + ); + dffer #( + .DATA_WIDTH(2) + ) u_read_owner ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .en_i (s_read_pending_d), + .dat_i (s_read_owner_d), + .dat_o (s_read_owner_q) + ); + dffer #( + .DATA_WIDTH(3) + ) u_read_bank ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .en_i (s_read_pending_d), + .dat_i (s_read_bank_d), + .dat_o (s_read_bank_q) + ); + + for (genvar bank = 0; bank < 8; bank++) begin : gen_bank + assign s_bank_select[bank] = s_issue && (s_addr[s_selected][14:12] == 3'(bank)); + tc_sram_1024x32 u_ram ( + .clk_i (clk_i), + .cs_i (s_bank_select[bank]), + .addr_i(s_addr[s_selected][11:2]), + .data_i(s_wdata[s_selected]), + .mask_i(s_wstrb[s_selected]), + .wren_i(s_write[s_selected]), + .data_o(s_bank_data[bank]) + ); + end + +`ifdef FORMAL + // Service-count proof: only continuously eligible requests accumulate + // bypasses. No wall-clock liveness is assumed for a withheld response. + for (genvar client = 0; client < 3; client++) begin : gen_formal + logic [1:0] s_bypasses_q; + logic s_past_valid_q = 1'b0; + always_ff @(posedge clk_i) begin + s_past_valid_q <= 1'b1; + if (!rst_n_i || !s_req[client] || s_grant[client]) s_bypasses_q <= '0; + else if (s_issue) s_bypasses_q <= s_bypasses_q + 2'd1; + if (rst_n_i) begin + assert ($onehot0(s_grant)); + assert (s_bypasses_q <= 2'd2); + assert (!(s_req[client] && (s_bypasses_q == 2'd2) && s_issue && !s_grant[client])); + if (client < 2) + assert (!(s_rsp_valid_q[client] && s_read_pending_q && (s_read_owner_q == 2'(client)))); + end + if (s_past_valid_q && rst_n_i && $past( + rst_n_i && ports[client].rsp_valid && !ports[client].rsp_ready + )) begin + assert (ports[client].rsp_valid); + assert (ports[client].rdata == $past(ports[client].rdata)); + end + end + end +`endif +`ifdef HAVE_SVA + assert property (@(posedge clk_i) disable iff (!rst_n_i) $onehot0(s_grant)); + assert property (@(posedge clk_i) disable iff (!rst_n_i) $onehot0(s_bank_select)); + for (genvar client = 0; client < 3; client++) begin : gen_assert + assert property (@(posedge clk_i) disable iff (!rst_n_i) + ports[client].rsp_valid && !ports[client].rsp_ready |=> + ports[client].rsp_valid && $stable( + ports[client].rdata + )); + end +`endif +endmodule diff --git a/rtl/tiny/top/tiny_sram_port_if.sv b/rtl/tiny/top/tiny_sram_port_if.sv new file mode 100644 index 00000000..ff856f59 --- /dev/null +++ b/rtl/tiny/top/tiny_sram_port_if.sv @@ -0,0 +1,25 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// One word operation, byte strobes, one ordered response per accepted request. +// Local I/D retain their address through the response handshake (bank routing) +// and have one operation outstanding. A fixed-group external client may have +// two operations outstanding, including a reserved macro read return. It may +// advance its request address after acceptance; responses remain in order. +interface tiny_sram_port_if; + logic req_valid, req_ready; + logic [31:0] addr, wdata; + logic write; + logic [3:0] wstrb; + logic rsp_valid, rsp_ready; + logic [31:0] rdata; + + modport master( + output req_valid, addr, wdata, write, wstrb, rsp_ready, + input req_ready, rsp_valid, rdata + ); + modport slave( + input req_valid, addr, wdata, write, wstrb, rsp_ready, + output req_ready, rsp_valid, rdata + ); +endinterface diff --git a/scripts/generate_tiny.py b/scripts/generate_tiny.py index 8c359363..eef20acc 100644 --- a/scripts/generate_tiny.py +++ b/scripts/generate_tiny.py @@ -23,6 +23,17 @@ def read_topology(path: Path, memory_map: Path) -> dict: regions = {r["symbol"] for r in json.loads(memory_map.read_text())["regions"]} if document.get("schema_version") != 1 or document.get("dma_channels") != 4: raise ValueError("Tiny requires topology schema 1 and four DMA channels") + if document.get("cpu") != {"module": "hazard3_cpu_2port", "local_ports": 2, + "data_outstanding": 1}: + raise ValueError("Tiny P4 requires independent I/D and one ordered data operation") + memory = document.get("main_sram", {}) + if any(memory.get(key) != value for key, value in { + "groups": 4, "group_bytes": 32768, "macros_per_group": 8, + "macro_bytes": 4096, "base": "0x30000000", + "macro_instance": "u_soc.u_sram.gen_group[{group}].u_group.gen_bank[{bank}].u_ram.u_mem", + "external_attachment": "serialized-pre-P5", + }.items()): + raise ValueError("Tiny P4 SRAM geometry/attachment differs from its frozen contract") if document.get("irq_vector_width") != 32 or document.get("external_irq_count") != 30: raise ValueError("Tiny requires a 32-bit IRQ vector and 30 external inputs") names: set[str] = set() diff --git a/scripts/tiny_ics55_platform.py b/scripts/tiny_ics55_platform.py index 0ddba621..c96ce358 100644 --- a/scripts/tiny_ics55_platform.py +++ b/scripts/tiny_ics55_platform.py @@ -183,6 +183,12 @@ def inspect_netlist(document: dict) -> dict: 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") + expected_ram = {f"u_soc.u_sram.gen_group[{g}].u_group.gen_bank[{n}].u_ram.u_mem" + for g in range(4) for n in range(8)} + observed_ram = {next((name for name in expected_ram + if c["path"].replace("/", ".").endswith("." + name)), "") for c in ram} + if observed_ram != expected_ram: + raise ValueError("Tiny P4 SRAM group/macro hierarchy is missing or stale") 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} @@ -202,7 +208,7 @@ def inspect_netlist(document: dict) -> dict: 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"] + cpu = [c for c in leaves if "u_hazard3_cpu_2port" 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") diff --git a/scripts/tiny_r2_p4.py b/scripts/tiny_r2_p4.py new file mode 100644 index 00000000..455645e0 --- /dev/null +++ b/scripts/tiny_r2_p4.py @@ -0,0 +1,255 @@ +#!/usr/bin/env python3 +"""Replay immutable P3 images on P4 RTL without relabeling image provenance.""" +from __future__ import annotations + +import argparse +from pathlib import Path +import sys +import subprocess + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT)) +from scripts import tiny_r2_baseline as b # noqa: E402 +from scripts import tiny_r2_p3 as p3 # noqa: E402 + +PHASE = "TINY-R2-P4" +HARDWARE_KEYS = ("SOC", "PDK", "HAVE_PLL", "HAVE_SRAM_IF", "HAVE_SRAM_MACRO", + "SRAM_SIZE_KIB", "PDK_BEHAV", "HAVE_HP", "EXT_CLK_HZ", "AUD_CLK_HZ", + "CLINT_TIMEBASE_HZ", "MGMT_CPU_CLK_HZ", "JTAG_IDCODE") +LOCAL_FIELDS = {"window", "port", "accepted", "completed", "latency_max", "admission_wait"} +BANK_FIELDS = {"window", "group", "issues_i", "issues_d", "issues_external", "conflicts", + "wait_i", "wait_d", "wait_external"} + + +def inputs(root: Path, variant: Path) -> dict: + result = p3.inputs(root, variant) + # Publication bindings are tested source consumers, not generated PDFs. + names = subprocess.check_output( + ["git", "ls-files", "--cached", "--others", "--exclude-standard", "-z", "--", "publications"], + cwd=root, text=True).split("\0") + for name in names: + path = root / name + if name and path.is_file() and path.suffix in (".py", ".json", ".typ"): + result[name] = p3.sha256(path) + return result + + +def compatible_hardware(image: dict, model: dict) -> None: + a, z = (c["configuration"] for c in (image, model)) + if any(key not in a or key not in z or a[key] != z[key] for key in HARDWARE_KEYS): + raise ValueError("retained image and candidate hardware configuration differ") + + +def parse_log(log: str, flow: dict) -> dict: + samples = p3.parse_log(log, flow) + rows = [b.parse_fields(line, LOCAL_FIELDS) for line in log.splitlines() + if line.startswith("R2_P4_LOCAL ")] + rows.sort(key=lambda row: (row["window"], row["port"])) + if [(r["window"], r["port"]) for r in rows] != [(w, p) for w in range(1, 9) for p in range(2)]: + raise ValueError("missing or duplicate P4 local observations") + for row in rows: + if row["completed"] > row["accepted"] or row["accepted"] - row["completed"] > 1: + raise ValueError("P4 local request/response accounting mismatch") + samples["local"] = rows + banks = [b.parse_fields(line, BANK_FIELDS) for line in log.splitlines() + if line.startswith("R2_P4_BANK ")] + banks.sort(key=lambda row: (row["window"], row["group"])) + if [(r["window"], r["group"]) for r in banks] != [(w, g) for w in range(1, 9) for g in range(4)]: + raise ValueError("missing or duplicate P4 bank observations") + samples["banks"] = banks + return samples + + +def reference_cases(path: Path, names: list[str]) -> list[dict]: + report = b.read_json(path) + if report.get("phase") != p3.PHASE or report.get("status") != "measurement_complete": + raise ValueError("reference is not a completed P3 measurement report") + matrix = b.read_json(b.check_artifact(report["matrix"])) + if matrix.get("status") != "passed": + raise ValueError("reference matrix did not pass") + if not names or len(set(names)) != len(names): + raise ValueError("select distinct retained cases") + result = [] + for name in names: + matching = [c for c in report["cases"] if c["name"] == name] + original = [c for c in matrix["cases"] if c["name"] == name] + if len(matching) != 1 or len(original) != 1 or matching[0]["image"] != original[0]["image"]: + raise ValueError("unknown or mismatched retained case") + case = matching[0] + p3.validate_image(b.check_artifact(case["image"])) + # Recheck the original logs, not just a report's claimed numeric values. + samples = p3.validate_simulation(original[0]["simulations"]["verilator"], + case["image"], "verilator") + if samples != case["samples"]: + raise ValueError("reference report differs from its retained execution") + result.append(case) + return result + + +def replay(args) -> None: + root, variant = args.root, args.variant_root + parent = variant / "meta/tiny-r2-p4/replay" / args.simulator + directory = b.new_attempt(parent, "attempt-") + record = {"phase": PHASE, "status": "running", "repetitions": 1, "cases": []} + b.dump(parent / "latest.json", {"path": str(directory / "record.json")}) + try: + reference = b.artifact(args.reference) + cases = reference_cases(args.reference, args.cases.split(",")) + configuration = p3.configuration(variant) + for case in cases: + compatible_hardware(b.read_json(b.check_artifact(case["image"]))["configuration"], + configuration) + before = inputs(root, variant) + tools = b.tools_identity(root) + command = args.command[1:] if args.command[:1] == ["--"] else args.command + if not command: + raise ValueError("fresh model compilation command required") + p3.execute_make(root, directory, "compile-model", command) + if before != inputs(root, variant): + raise ValueError("source changed while compiling candidate model") + runtime = b.runtime_inputs(root, variant, args.simulator) + model = variant / "sim" / args.simulator / ("emu" if args.simulator == "verilator" else "simv") + record.update(reference=reference, configuration=configuration, tools=tools, + model=b.artifact(model), source_digest=b.digest_map(before), + source=b.snapshot(root, directory / "inputs", before | runtime), + compilation={s: b.artifact(directory / f"compile-model.{s}") for s in ("json", "log")}) + launch = [str(model)] + if args.simulator == "iverilog": + record["runtime_tool"] = b.vvp_identity(args.vvp, tools["iverilog"]) + launch.insert(0, record["runtime_tool"]["executable"]["path"]) + for case in cases: + image = p3.validate_image(b.check_artifact(case["image"])) + begin, end = image["layout"]["descriptor_range"] + cmd = [*launch, "+firmware=" + image["artifacts"]["tiny_p3.hex"]["path"], + "+tiny_r2_p3", f"+baseline_tcd_base={begin:x}", f"+baseline_tcd_end={end:x}", + "+max_cycles=100000000"] + label = case["name"] + flow = b.execute(root, directory, label, + ["timeout", "--foreground", f"{args.timeout}s", *cmd], quiet=True) + samples = parse_log((directory / f"{label}.log").read_text(), flow) + p3.validate_image(b.check_artifact(case["image"])) + record["cases"].append({"name": label, "image": case["image"], "samples": samples, + "baseline_samples": case["samples"], + "command": cmd, "timeout_seconds": args.timeout, + "flow": b.artifact(directory / f"{label}.json"), + "log": b.artifact(directory / f"{label}.log")}) + b.dump(directory / "record.json", record) + if before != inputs(root, variant) or runtime != b.runtime_inputs(root, variant, args.simulator): + raise ValueError("candidate inputs changed during execution") + b.check_artifact(record["model"]) + b.check_artifact(reference) + for case in cases: + p3.validate_image(b.check_artifact(case["image"])) + for tool in tools.values(): + b.check_artifact(tool["executable"]) + record["status"] = "passed" + except (OSError, ValueError) as error: + record.update(status="failed", error=str(error)) + raise + finally: + b.dump(directory / "record.json", record) + print(f"P4 {args.simulator}: one execution per image; {directory}") + + +def validate_replay(path: Path, simulator: str) -> dict: + record = b.read_json(path) + if record.get("phase") != PHASE or record.get("status") != "passed" or record.get("repetitions") != 1: + raise ValueError("not a completed single-execution P4 replay") + for field in ("model", "reference"): + b.check_artifact(record[field]) + for value in record["compilation"].values(): + b.check_artifact(value) + for field in ("inputs", "snapshot", "patch"): + b.check_artifact(record["source"][field]) + captured = b.read_json(Path(record["source"]["inputs"]["path"])) + if b.digest_map(captured) != record["source"]["input_digest"]: + raise ValueError("candidate source digest mismatch") + for tool in record["tools"].values(): + b.check_artifact(tool["executable"]) + b.check_artifact(tool["archive"]) + prefix = [record["model"]["path"]] + if simulator == "iverilog": + runtime = b.check_artifact(record["runtime_tool"]["executable"]) + compiler = Path(record["tools"]["iverilog"]["executable"]["path"]) + if runtime.resolve().parent != compiler.resolve().parent: + raise ValueError("unmatched Icarus runtime") + prefix.insert(0, str(runtime)) + reference = reference_cases(Path(record["reference"]["path"]), [c["name"] for c in record["cases"]]) + for case, original in zip(record["cases"], reference, strict=True): + if case["image"] != original["image"] or case["baseline_samples"] != original["samples"]: + raise ValueError("comparison changed the retained image or reference measurements") + image = p3.validate_image(b.check_artifact(case["image"])) + compatible_hardware(image["configuration"], record["configuration"]) + first, end = image["layout"]["descriptor_range"] + expected = [*prefix, "+firmware=" + image["artifacts"]["tiny_p3.hex"]["path"], + "+tiny_r2_p3", f"+baseline_tcd_base={first:x}", f"+baseline_tcd_end={end:x}", + "+max_cycles=100000000"] + flow = b.read_json(b.check_artifact(case["flow"])) + if case["command"] != expected or case["timeout_seconds"] <= 0 or flow["command"] != [ + "timeout", "--foreground", f"{case['timeout_seconds']}s", *expected]: + raise ValueError("model/image execution provenance mismatch") + if parse_log(b.check_artifact(case["log"]).read_text(), flow) != case["samples"]: + raise ValueError("reported candidate samples differ from the log") + return record + + +def report(args) -> None: + base = args.variant_root / "meta/tiny-r2-p4" + runs = {} + for simulator in ("verilator", "iverilog"): + pointer = b.read_json(base / "replay" / simulator / "latest.json") + path = Path(pointer["path"]) + runs[simulator] = (validate_replay(path, simulator), b.artifact(path)) + before = b.digest_map(inputs(args.root, args.variant_root)) + if any(run[0]["source_digest"] != before for run in runs.values()): + raise ValueError("candidate source changed after measurement") + cases = {c["name"]: c for c in runs["verilator"][0]["cases"]} + if set(cases) != {"perf-o2-plain-flat", "perf-o2-plain-banked"}: + raise ValueError("P4 comparison requires both approved placement images") + cross = runs["iverilog"][0]["cases"] + if len(cross) != 1 or cross[0]["name"] != "perf-o2-plain-banked": + raise ValueError("P4 requires one banked Icarus execution") + if any(cross[0][key] != cases["perf-o2-plain-banked"][key] for key in ("image", "samples")): + raise ValueError("cross-simulator image/results differ") + comparisons = [] + for name, case in cases.items(): + rows = [] + for old, new in zip(case["baseline_samples"]["cases"], case["samples"]["cases"], strict=True): + rows.append({"name": new["name"], "baseline_cycles": old["cycles"], + "candidate_cycles": new["cycles"], "baseline_instructions": old["instructions"], + "candidate_instructions": new["instructions"], + "cycle_ratio": old["cycles"] / new["cycles"]}) + comparisons.append({"name": name, "image": case["image"], "workloads": rows, + "local": case["samples"]["local"], "banks": case["samples"]["banks"]}) + b.dump(base / "report.json", {"phase": PHASE, "status": "measurement_complete", + "source_digest": before, "comparisons": comparisons, + "executions": {key: value[1] for key, value in runs.items()}, + "qualification": "matched-binary SAFE24 simulation; not phase/physical acceptance"}) + print(f"P4 matched-image report: {base / 'report.json'}") + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--root", type=Path, required=True) + parser.add_argument("--variant-root", type=Path, required=True) + parser.add_argument("--report", action="store_true") + parser.add_argument("--reference", type=Path) + parser.add_argument("--cases") + parser.add_argument("--simulator", choices=("verilator", "iverilog")) + parser.add_argument("--vvp", default="vvp") + parser.add_argument("--timeout", type=int, default=21600) + parser.add_argument("command", nargs=argparse.REMAINDER) + args = parser.parse_args() + if args.timeout <= 0: + parser.error("timeout must be positive") + if args.report: + report(args) + else: + if args.reference is None or not args.cases or not args.simulator: + parser.error("replay requires reference, cases and simulator") + replay(args) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/rtl/tiny_axi4_fabric_tb.sv b/tests/rtl/tiny_axi4_fabric_tb.sv index 58f0cff4..0914d5ff 100644 --- a/tests/rtl/tiny_axi4_fabric_tb.sv +++ b/tests/rtl/tiny_axi4_fabric_tb.sv @@ -47,6 +47,7 @@ module tiny_axi4_fabric_tb; logic [1:0][ 0:0] arlock; logic [1:0][ 0:0] arid; logic [1:0][ 0:0] arvalid; + logic [1:0][ 2:0] arprot; logic [1:0][ 0:0] rready; logic [1:0][ 0:0] awready; logic [1:0][ 0:0] arready; @@ -113,7 +114,7 @@ module tiny_axi4_fabric_tb; assign masters[m].awregion = '0; assign masters[m].awuser = '0; assign masters[m].arcache = '0; - assign masters[m].arprot = '0; + assign masters[m].arprot = arprot[m]; assign masters[m].arqos = '0; assign masters[m].arregion = '0; assign masters[m].aruser = '0; @@ -238,6 +239,7 @@ module tiny_axi4_fabric_tb; arlock = '0; arid = '0; arvalid = '0; + arprot = '0; rready = '0; completions[0] = 0; completions[1] = 0; @@ -267,6 +269,14 @@ module tiny_axi4_fabric_tb; target_requests[3]!=3 || target_requests[4]!=4 || cpu_wait==0 || dma_wait==0) $fatal(1, "accounting/fairness mismatch faults=%0d", faults); read_request(0, 32'h30000000, 1, 2, 1, 0); + arprot[0] = 3'b100; + read_request(0, 32'h10001000, 1, 2, 1, 2); + read_request(0, 32'h54000000, 1, 2, 1, 3); + if (target_requests[2] != 2 || target_requests[1] != 1) + $fatal(1, "instruction request reached a peripheral or non-NSS0 window"); + read_request(0, 32'h00000000, 1, 2, 1, 0); + read_request(0, 32'h50000000, 1, 2, 1, 0); + if (target_requests[1] != 3) $fatal(1, "NOR instruction windows not reachable"); $display("SIM_TEST_PASS Tiny fabric"); $finish; end diff --git a/tests/rtl/tiny_r2_p4_cpu.S b/tests/rtl/tiny_r2_p4_cpu.S new file mode 100644 index 00000000..ce8b62a0 --- /dev/null +++ b/tests/rtl/tiny_r2_p4_cpu.S @@ -0,0 +1,67 @@ +.section .text +.global _start +.option norvc +_start: + li sp, 0x3001f000 + la t0, trap + csrw mtvec, t0 + li s0, 0 + # A C.NOP, followed by a 32-bit ADDI crossing the group boundary, C.RET. + li s1, 0x30007ffc + li s2, 3 +1: li t0, 0x05130001 + sw t0, 0(s1) + li t0, 0x80820090 + sw t0, 4(s1) + fence.i + jalr ra, s1, 0 + li t0, 9 + bne a0, t0, fail + li t0, 0x8000 + add s1, s1, t0 + addi s2, s2, -1 + bnez s2, 1b + # Instruction-side failure returns to RA via the trap handler. + li t0, 0x10001000 + jalr ra, t0, 0 + li t0, 1 + bne s0, t0, fail + # A debugger will patch this code and release the SRAM flag while halted. + li t0, 0x30000080 + li t1, 0x00100513 + sw t1, 0(t0) + li t1, 0x00008067 + sw t1, 4(t0) + li t1, 0x3001ffe0 + sw zero, 0(t1) + fence.i + jalr ra, t0, 0 + li t0, 1 + bne a0, t0, fail + li t0, 0x1000b084 + li t1, 1 + sw t1, 0(t0) + li t0, 0x3001ffe0 +2: lw t1, 0(t0) + beqz t1, 2b + fence.i + li t0, 0x30000080 + jalr ra, t0, 0 + li t0, 2 + bne a0, t0, fail + li t0, 0x1000b084 + li t1, 0x80000001 + sw t1, 0(t0) +3: j 3b +trap: + csrr t2, mcause + li t3, 1 + bne t2, t3, fail + addi s0, s0, 1 + csrw mepc, ra + mret +fail: + li t0, 0x1000b084 + li t1, 0x80000000 + sw t1, 0(t0) +4: j 4b diff --git a/tests/rtl/tiny_r2_p4_cpu.ld b/tests/rtl/tiny_r2_p4_cpu.ld new file mode 100644 index 00000000..51fe0f35 --- /dev/null +++ b/tests/rtl/tiny_r2_p4_cpu.ld @@ -0,0 +1,6 @@ +ENTRY(_start) +SECTIONS { + . = 0; + .text : { *(.text*) } + /DISCARD/ : { *(.riscv.attributes) *(.comment) } +} diff --git a/tests/rtl/tiny_r2_p4_cpu_tb.sv b/tests/rtl/tiny_r2_p4_cpu_tb.sv new file mode 100644 index 00000000..f8bc13de --- /dev/null +++ b/tests/rtl/tiny_r2_p4_cpu_tb.sv @@ -0,0 +1,282 @@ +`timescale 1ns / 1ps +module tiny_r2_p4_cpu_tb; + logic clk_i = 0, rst_n_i = 0; + always #5 clk_i = ~clk_i; + wire s_rst_n = rst_n_i; + logic s_jtag_tck_driver = 0, s_jtag_tms_driver = 1, s_jtag_tdi_driver = 0; + wire s_jtag_tdo; + logic core_rst_n, quiesce, idle, halted; + ahbl_if cpu[2] ( + .hclk (clk_i), + .hresetn(core_rst_n) + ); + tiny_sram_port_if local_ports[2] (); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) slow ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) ext[4] ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + apb4_if cfg ( + .pclk (clk_i), + .presetn(rst_n_i) + ); + logic [7:0] rom [0:4095]; + string firmware; + logic read_pending = 0, aw_pending = 0, write_pending = 0, hold_read = 0; + logic [31:0] read_data = 0, write_addr = 0; + logic [ 1:0] read_resp = 0; + integer starts = 0; + logic completed = 0; + logic [31:0] status; + bit ignored; + `include "tiny_jtag_smoke.svh" + +tiny_cpu_wrapper #( + .EnableAtomics (0), + .ResetSyncStages(5) + ) u_cpu ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .irq_i (32'd0), + .jtag_tck_i (s_jtag_tck_driver), + .jtag_tms_i (s_jtag_tms_driver), + .jtag_tdi_i (s_jtag_tdi_driver), + .jtag_trst_n_i (rst_n_i), + .jtag_tdo_o (s_jtag_tdo), + .debug_halted_o(halted), + .bus_idle_i (idle), + .core_rst_n_o (core_rst_n), + .quiesce_o (quiesce), + .cpu (cpu) + ); + tiny_cpu_mem u_cpu_mem ( + .clk_i (clk_i), + .rst_n_i (core_rst_n), + .quiesce_i (quiesce), + .idle_o (idle), + .cpu (cpu), + .local_ports(local_ports), + .slow_axi4 (slow) + ); + tiny_sram u_sram ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .perf_enable_i (1'b0), + .perf_clear_i (1'b0), + .local_ports (local_ports), + .external_ports(ext), + .cfg_apb4 (cfg) + ); + // Verification-only slow memory, not a product Boot ROM. It also supplies + // the terminal mailbox and instruction fault used by this CPU fixture. + assign slow.arready = !read_pending; + assign slow.rvalid = read_pending && !hold_read; + assign slow.rdata = read_data; + assign slow.rresp = read_resp; + assign slow.rid = 0; + assign slow.rlast = 1; + assign slow.ruser = 0; + assign slow.awready = !aw_pending && !write_pending; + assign slow.wready = aw_pending; + assign slow.bvalid = write_pending; + assign slow.bresp = 0; + assign slow.bid = 0; + assign slow.buser = 0; + always @(posedge clk_i) begin + if (!rst_n_i) begin + read_pending <= 0; + aw_pending <= 0; + write_pending <= 0; + end else begin + if (slow.rvalid && slow.rready) read_pending <= 0; + if (slow.arvalid && slow.arready) begin + read_pending <= 1; + if (slow.araddr < 4096) begin + read_resp <= 0; + read_data <= { + rom[slow.araddr+3], rom[slow.araddr+2], rom[slow.araddr+1], rom[slow.araddr] + }; + end else begin + read_resp <= 3; + read_data <= 0; + if (!slow.arprot[2]) $fatal(1, "unexpected slow data read %h", slow.araddr); + end + end + if (slow.awvalid && slow.awready) begin + aw_pending <= 1; + write_addr <= slow.awaddr; + end + if (slow.wvalid && slow.wready) begin + aw_pending <= 0; + write_pending <= 1; + if (write_addr != 32'h1000b084) $fatal(1, "unexpected slow write %h", write_addr); + if (slow.wdata == 1) starts <= starts + 1; + else if (slow.wdata == 32'h80000001) completed <= 1; + else $fatal(1, "SIM_TEST_FAIL CPU fixture code=%h", slow.wdata); + end + if (slow.bvalid && slow.bready) write_pending <= 0; + end + end + for (genvar g = 0; g < 4; g++) begin : gen_init + initial begin + ext[g].awid = 0; + ext[g].awaddr = 0; + ext[g].awlen = 0; + ext[g].awsize = 2; + ext[g].awburst = 1; + ext[g].awlock = 0; + ext[g].awcache = 0; + ext[g].awprot = 0; + ext[g].awqos = 0; + ext[g].awregion = 0; + ext[g].awuser = 0; + ext[g].awvalid = 0; + ext[g].wdata = 0; + ext[g].wstrb = 15; + ext[g].wlast = 1; + ext[g].wuser = 0; + ext[g].wvalid = 0; + ext[g].bready = 0; + ext[g].arid = 0; + ext[g].araddr = 0; + ext[g].arlen = 0; + ext[g].arsize = 2; + ext[g].arburst = 1; + ext[g].arlock = 0; + ext[g].arcache = 0; + ext[g].arprot = 0; + ext[g].arqos = 0; + ext[g].arregion = 0; + ext[g].aruser = 0; + ext[g].arvalid = 0; + ext[g].rready = 0; + end + end + task automatic abstract_wait; + integer polls; + logic [31:0] value; + polls = 0; + do begin + jtag_dmi(0, 7'h16, 0, value); + polls++; + end while (value[12] && polls < 128); + if (value[12] || value[10:8] != 0) $fatal(1, "abstract command status %h", value); + endtask + task automatic set_gpr(input logic [4:0] regnum, input logic [31:0] value); + logic [31:0] unused; + jtag_dmi(1, 7'h04, value, unused); + jtag_dmi(1, 7'h17, 32'h00231000 | {27'd0, regnum}, unused); + abstract_wait(); + endtask + task automatic execute_debug(input logic [31:0] instruction); + logic [31:0] unused; + jtag_dmi(1, 7'h20, instruction, unused); + jtag_dmi(1, 7'h21, 32'h00100073, unused); + jtag_dmi(1, 7'h17, 32'h00240000, unused); + abstract_wait(); + endtask + task automatic debug_store(input logic [31:0] address, input logic [31:0] value); + set_gpr(28, address); + set_gpr(29, value); + execute_debug(32'h01de2023); // sw t4,0(t3) + endtask + initial begin + #10000000; + $display( + "CPU_DIAG starts=%0d completed=%b core_reset=%b halted=%b pending=%b idle=%b quiesce=%b hold=%b read=%b", + starts, completed, core_rst_n, halted, u_cpu.s_reset_pending, idle, quiesce, hold_read, + read_pending); + $display("CPU_DIAG I addr=%h trans=%b ready=%b D addr=%h trans=%b ready=%b slowstate=%0d", + cpu[0].haddr, cpu[0].htrans, cpu[0].hready, cpu[1].haddr, cpu[1].htrans, + cpu[1].hready, u_cpu_mem.s_slow_q.state); + $fatal(1, "SIM_TEST_TIMEOUT Tiny P4 CPU"); + end + initial begin + if (!$value$plusargs("firmware=%s", firmware)) $fatal(1, "missing CPU fixture image"); + $readmemh(firmware, rom); + cfg.paddr = 0; + cfg.pprot = 0; + cfg.psel = 0; + cfg.penable = 0; + cfg.pwrite = 0; + cfg.pwdata = 0; + cfg.pstrb = 15; + repeat (6) jtag_tick(1, 0, ignored); + @(negedge clk_i); + rst_n_i = 1; + repeat (12) jtag_tick(1, 0, ignored); + jtag_idle(1); + jtag_ir(5'h11); + wait (starts == 1); + $display("P4_CPU_STAGE boot-and-boundaries"); + jtag_dmi(1, 7'h10, 1, status); + jtag_dmi(1, 7'h10, 32'h80000001, status); + wait (halted); + $display("P4_CPU_STAGE halted"); + debug_store(32'h30000080, 32'h00200513); + debug_store(32'h3001ffe0, 1); + execute_debug(32'h0000100f); // FENCE.I before debugger resume + jtag_dmi(1, 7'h10, 32'h40000001, status); + jtag_dmi(1, 7'h10, 1, status); + wait (completed); + $display("P4_CPU_STAGE patched-code-executed"); + // A different external owner retains a B response across hart reset. + @(negedge clk_i); + ext[2].awaddr = 32'h30010100; + ext[2].awvalid = 1; + do @(posedge clk_i); while (!ext[2].awready); + @(negedge clk_i); + ext[2].awvalid = 0; + ext[2].wdata = 32'hdecafbad; + ext[2].wvalid = 1; + do @(posedge clk_i); while (!ext[2].wready); + @(negedge clk_i); + ext[2].wvalid = 0; + wait (ext[2].bvalid); + @(negedge clk_i); + hold_read = 1; + wait (read_pending); + jtag_dmi(1, 7'h10, 3, status); + repeat (32) begin + @(posedge clk_i); + if (!core_rst_n || !ext[2].bvalid) $fatal(1, "hart reset discarded accepted work"); + end + @(negedge clk_i); + hold_read = 0; + wait (!core_rst_n); + $display("P4_CPU_STAGE hart-reset-drained"); + if (!ext[2].bvalid) $fatal(1, "hart reset reset external SRAM frontend"); + jtag_dmi(1, 7'h10, 1, status); + wait (core_rst_n); + wait (starts == 2); + @(negedge clk_i); + ext[2].bready = 1; + @(posedge clk_i); + @(negedge clk_i); + ext[2].bready = 0; + ext[2].araddr = 32'h30010100; + ext[2].arvalid = 1; + do @(posedge clk_i); while (!ext[2].arready); + @(negedge clk_i); + ext[2].arvalid = 0; + ext[2].rready = 1; + do @(posedge clk_i); while (!ext[2].rvalid); + if (ext[2].rdata !== 32'hdecafbad || ext[2].rresp != 0) + $fatal(1, "SRAM payload lost on hart reset"); + $display("SIM_TEST_PASS Tiny P4 CPU compressed-boundaries fence-i debug-patch hart-drain"); + $finish; + end +endmodule diff --git a/tests/rtl/tiny_r2_p4_formal.sv b/tests/rtl/tiny_r2_p4_formal.sv new file mode 100644 index 00000000..515907c1 --- /dev/null +++ b/tests/rtl/tiny_r2_p4_formal.sv @@ -0,0 +1,58 @@ +module tiny_r2_p4_formal ( + input logic clk_i, + input logic rst_n_i +); + logic s_started_q = 1'b0; + (* anyseq *) logic [2:0] s_valid, s_ready, s_write; + tiny_sram_port_if u_ports[3] (); + always_ff @(posedge clk_i) begin + s_started_q <= 1'b1; + assume (rst_n_i == s_started_q); + end + for (genvar client = 0; client < 3; client++) begin : gen_client + logic [2:0] s_outstanding_q = '0; + wire s_accept = u_ports[client].req_valid && u_ports[client].req_ready; + wire s_complete = u_ports[client].rsp_valid && u_ports[client].rsp_ready; + always_ff @(posedge clk_i) begin + if (!rst_n_i) s_outstanding_q <= '0; + else begin + case ({ + s_accept, s_complete + }) + 2'b10: s_outstanding_q <= s_outstanding_q + 3'd1; + 2'b01: s_outstanding_q <= s_outstanding_q - 3'd1; + default: begin + end + endcase + assert (s_outstanding_q <= ((client == 2) ? 3'd2 : 3'd1)); + assert (!u_ports[client].rsp_valid || (s_outstanding_q != 3'd0)); + end + end + assign u_ports[client].req_valid = s_valid[client]; + assign u_ports[client].rsp_ready = s_ready[client]; + assign u_ports[client].write = s_write[client]; + assign u_ports[client].addr = 32'h30000000 + 32'(client) * 32'h1000; + assign u_ports[client].wdata = 32'(client); + assign u_ports[client].wstrb = 4'hf; + end + tiny_sram_group u_dut ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .ports (u_ports) + ); +endmodule + +// Abstract only the macro payload: arbitration, storage reservation, response +// capture and all control logic are the production group implementation. +module tc_sram_1024x32 ( + input logic clk_i, + cs_i, + input logic [ 9:0] addr_i, + input logic [31:0] data_i, + input logic [ 3:0] mask_i, + input logic wren_i, + output logic [31:0] data_o +); + (* anyseq *) logic [31:0] s_value; + assign data_o = s_value; +endmodule diff --git a/tests/rtl/tiny_r2_p4_memory_tb.sv b/tests/rtl/tiny_r2_p4_memory_tb.sv new file mode 100644 index 00000000..984c1b12 --- /dev/null +++ b/tests/rtl/tiny_r2_p4_memory_tb.sv @@ -0,0 +1,659 @@ +`timescale 1ns / 1ps +module tiny_r2_p4_memory_tb; + logic clk_i = 0, rst_n_i = 0, quiesce = 0, idle; + logic perf_enable = 1, perf_clear = 0; + always #5 clk_i = ~clk_i; + ahbl_if cpu[2] ( + .hclk (clk_i), + .hresetn(rst_n_i) + ); + tiny_sram_port_if local_ports[2] (); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) ext[4] ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) slow ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + apb4_if cfg ( + .pclk (clk_i), + .presetn(rst_n_i) + ); + logic [31:0] addr[2], data[2], rdata[2]; + logic [2:0] size [2]; + logic [1:0] trans[2]; + logic [1:0] wr, ready, resp; + logic [31:0] awaddr[4], wdata[4], araddr[4], erdata[4]; + logic [3:0] wstrb[4]; + logic [7:0] awlen[4], arlen[4]; + logic [2:0] awsize[4], arsize[4]; + logic [1:0] awburst[4], arburst[4], bresp[4], rresp[4]; + logic [3:0] awvalid, wvalid, wlast, bready, arvalid, rready; + logic [3:0] awready, wready, bvalid, arready, rvalid, rlast; + integer parallel_issues = 0; + integer local_latency_max = 0; + logic [31:0] observed; + logic [31:0] seed = 32'h12345678; + + tiny_cpu_mem u_cpu_mem ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .quiesce_i (quiesce), + .idle_o (idle), + .cpu (cpu), + .local_ports(local_ports), + .slow_axi4 (slow) + ); + tiny_sram u_sram ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .perf_enable_i (perf_enable), + .perf_clear_i (perf_clear), + .local_ports (local_ports), + .external_ports(ext), + .cfg_apb4 (cfg) + ); + axi4_error_slave #( + .Response(2'b11) + ) u_slow_error ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .axi4 (slow) + ); + for (genvar p = 0; p < 2; p++) begin : gen_cpu + assign cpu[p].haddr = addr[p]; + assign cpu[p].hwdata = data[p]; + assign cpu[p].hsize = size[p]; + assign cpu[p].htrans = trans[p]; + assign cpu[p].hwrite = wr[p]; + assign cpu[p].hburst = 0; + assign cpu[p].hprot = 4'(p); + assign cpu[p].hmastlock = 0; + assign ready[p] = cpu[p].hready; + assign resp[p] = cpu[p].hresp; + assign rdata[p] = cpu[p].hrdata; + end + for (genvar g = 0; g < 4; g++) begin : gen_ext + assign ext[g].awaddr = awaddr[g]; + assign ext[g].awlen = awlen[g]; + assign ext[g].awsize = awsize[g]; + assign ext[g].awburst = awburst[g]; + assign ext[g].awvalid = awvalid[g]; + assign ext[g].wdata = wdata[g]; + assign ext[g].wstrb = wstrb[g]; + assign ext[g].wlast = wlast[g]; + assign ext[g].wvalid = wvalid[g]; + assign ext[g].bready = bready[g]; + assign ext[g].araddr = araddr[g]; + assign ext[g].arlen = arlen[g]; + assign ext[g].arsize = arsize[g]; + assign ext[g].arburst = arburst[g]; + assign ext[g].arvalid = arvalid[g]; + assign ext[g].rready = rready[g]; + assign ext[g].awid = '0; + assign ext[g].awlock = '0; + assign ext[g].awcache = '0; + assign ext[g].awprot = '0; + assign ext[g].awqos = '0; + assign ext[g].awregion = '0; + assign ext[g].awuser = '0; + assign ext[g].wuser = '0; + assign ext[g].arid = '0; + assign ext[g].arlock = '0; + assign ext[g].arcache = '0; + assign ext[g].arprot = '0; + assign ext[g].arqos = '0; + assign ext[g].arregion = '0; + assign ext[g].aruser = '0; + assign awready[g] = ext[g].awready; + assign wready[g] = ext[g].wready; + assign bvalid[g] = ext[g].bvalid; + assign bresp[g] = ext[g].bresp; + assign arready[g] = ext[g].arready; + assign rvalid[g] = ext[g].rvalid; + assign rresp[g] = ext[g].rresp; + assign rlast[g] = ext[g].rlast; + assign erdata[g] = ext[g].rdata; + end + always @(posedge clk_i) begin + if ((int'(u_sram.gen_group[0].u_group.s_issue) + + int'(u_sram.gen_group[1].u_group.s_issue) + + int'(u_sram.gen_group[2].u_group.s_issue) + + int'(u_sram.gen_group[3].u_group.s_issue)) > 1) + parallel_issues++; + end + task automatic ahb(input integer p, input bit write_access, input logic [31:0] address, + input logic [2:0] width, input logic [31:0] value, input bit error_expected, + input integer max_latency, output logic [31:0] result); + time accepted; + integer error_cycles; + @(negedge clk_i); + addr[p] = address; + size[p] = width; + wr[p] = write_access; + trans[p] = 2; + do @(posedge clk_i); while (!ready[p]); + accepted = $time; + @(negedge clk_i); + trans[p] = 0; + data[p] = value; + error_cycles = 0; + do begin + @(posedge clk_i); + if (resp[p]) error_cycles++; + end while (!ready[p]); + result = rdata[p]; + if (resp[p] != error_expected || (error_expected && error_cycles != 2)) + $fatal(1, "AHB error protocol p=%0d addr=%h errors=%0d", p, address, error_cycles); + if (max_latency != 0 && (($time - accepted) / 10) > max_latency) + $fatal(1, "local latency p=%0d addr=%h cycles=%0d", p, address, ($time - accepted) / 10); + if (max_latency != 0 && (($time - accepted) / 10) > local_latency_max) + local_latency_max = integer'(($time - accepted) / 10); + @(negedge clk_i); + endtask + task automatic store(input logic [31:0] address, input logic [31:0] value, + input logic [2:0] width); + logic [31:0] unused; + ahb(1, 1, address, width, value, 0, 3, unused); + endtask + task automatic load(input integer p, input logic [31:0] address, input logic [31:0] value, + input integer max_latency); + logic [31:0] got; + ahb(p, 0, address, 2, 0, 0, max_latency, got); + if (got !== value) + $fatal(1, "local data p=%0d addr=%h got=%h expected=%h", p, address, got, value); + endtask + task automatic read_external(input integer g, input logic [31:0] address, + input logic [31:0] value, input integer hold_cycles, + input logic [1:0] expected_resp); + @(negedge clk_i); + araddr[g] = address; + arvalid[g] = 1; + do @(posedge clk_i); while (!arready[g]); + @(negedge clk_i); + arvalid[g] = 0; + while (!rvalid[g]) @(negedge clk_i); + repeat (hold_cycles) begin + @(posedge clk_i); + if (!rvalid[g] || erdata[g] !== value || rresp[g] != expected_resp || !rlast[g]) + $fatal(1, "external response changed under backpressure"); + @(negedge clk_i); + end + if (erdata[g] !== value || rresp[g] != expected_resp || !rlast[g]) + $fatal(1, "external read g=%0d got=%h expected=%h resp=%b", g, erdata[g], value, rresp[g]); + rready[g] = 1; + @(posedge clk_i); + @(negedge clk_i); + rready[g] = 0; + endtask + task automatic write_external(input integer g, input logic [31:0] address, + input logic [31:0] value, input logic [3:0] mask, + input integer delay_data); + @(negedge clk_i); + awaddr[g] = address; + awvalid[g] = 1; + do @(posedge clk_i); while (!awready[g]); + @(negedge clk_i); + awvalid[g] = 0; + repeat (delay_data) @(negedge clk_i); + wdata[g] = value; + wstrb[g] = mask; + wvalid[g] = 1; + wlast[g] = 1; + do @(posedge clk_i); while (!wready[g]); + @(negedge clk_i); + wvalid[g] = 0; + bready[g] = 1; + do @(posedge clk_i); while (!bvalid[g]); + if (bresp[g] != 0) $fatal(1, "external write"); + @(negedge clk_i); + bready[g] = 0; + endtask + task automatic config_read(input logic [11:0] offset, input logic [31:0] expected); + @(negedge clk_i); + cfg.paddr = {20'h10017, offset}; + cfg.psel = 1; + cfg.penable = 0; + @(negedge clk_i); + cfg.penable = 1; + do @(posedge clk_i); while (!cfg.pready); + if (cfg.pslverr || cfg.prdata !== expected) + $fatal(1, "SRAM ABI/perf offset=%h got=%h expected=%h", offset, cfg.prdata, expected); + @(negedge clk_i); + cfg.psel = 0; + cfg.penable = 0; + endtask + task automatic burst_and_errors; + // Sixteen captured write beats, with W asserted before AW. + @(negedge clk_i); + wdata[0] = 32'hcafe0000; + wstrb[0] = 15; + wvalid[0] = 1; + wlast[0] = 0; + repeat (3) begin + @(posedge clk_i); + if (wready[0]) $fatal(1, "W accepted without AW ownership"); + end + @(negedge clk_i); + awaddr[0] = 32'h30000fc0; + awlen[0] = 15; + awvalid[0] = 1; + do @(posedge clk_i); while (!awready[0]); + @(negedge clk_i); + awvalid[0] = 0; + for (integer beat = 0; beat < 16; beat++) begin + wdata[0] = 32'hcafe0000 + beat; + wlast[0] = (beat == 15); + do @(posedge clk_i); while (!wready[0]); + @(negedge clk_i); + end + wvalid[0] = 0; + wait (bvalid[0]); + load(0, 32'h30000ffc, 32'hcafe000f, 3); // B held while local read completes. + if (!bvalid[0] || bresp[0] != 0) $fatal(1, "B not retained"); + @(negedge clk_i); + bready[0] = 1; + @(posedge clk_i); + @(negedge clk_i); + bready[0] = 0; + awlen[0] = 0; + araddr[0] = 32'h30000fc0; + arlen[0] = 15; + arvalid[0] = 1; + do @(posedge clk_i); while (!arready[0]); + @(negedge clk_i); + arvalid[0] = 0; + for (integer beat = 0; beat < 16; beat++) begin + wait (rvalid[0]); + repeat (beat % 4) @(negedge clk_i); + if (erdata[0] !== (32'hcafe0000 + beat) || rresp[0] != 0 || rlast[0] != (beat == 15)) + $fatal(1, "burst read ordering/last at beat %0d", beat); + rready[0] = 1; + @(posedge clk_i); + @(negedge clk_i); + rready[0] = 0; + end + arlen[0] = 0; + // A malformed write cannot commit bytes. Test strobes and early/missing LAST. + for (integer bad = 0; bad < 3; bad++) begin + @(negedge clk_i); + awaddr[0] = 32'h30000fc0; + awvalid[0] = 1; + awsize[0] = (bad == 0) ? 1 : 2; + awlen[0] = (bad == 1) ? 3 : 0; + do @(posedge clk_i); while (!awready[0]); + @(negedge clk_i); + awvalid[0] = 0; + wvalid[0] = 1; + wdata[0] = 0; + wstrb[0] = (bad == 0) ? 4'h8 : 4'hf; + wlast[0] = (bad != 2); + do @(posedge clk_i); while (!wready[0]); + @(negedge clk_i); + wvalid[0] = 0; + bready[0] = 1; + do @(posedge clk_i); while (!bvalid[0]); + if (bresp[0] != 2) $fatal(1, "malformed write accepted"); + @(negedge clk_i); + bready[0] = 0; + load(0, 32'h30000fc0, 32'hcafe0000, 3); + end + awsize[0] = 2; + awlen[0] = 0; + wlast[0] = 1; + wstrb[0] = 15; + endtask + task automatic back_to_back; + // The next address is already present when the preceding write completes. + @(negedge clk_i); + addr[1] = 32'h30000100; + size[1] = 2; + wr[1] = 1; + trans[1] = 2; + do @(posedge clk_i); while (!ready[1]); + for (integer word_index = 0; word_index < 8; word_index++) begin + @(negedge clk_i); + data[1] = 32'hf00d0000 + word_index; + addr[1] = 32'h30000104 + word_index * 4; + trans[1] = (word_index == 7) ? 0 : 2; + do @(posedge clk_i); while (!ready[1]); + if (resp[1]) $fatal(1, "pipelined write error"); + end + @(negedge clk_i); + for (integer word_index = 0; word_index < 8; word_index++) + load(0, 32'h30000100 + word_index * 4, 32'hf00d0000 + word_index, 3); + endtask + task automatic streaming_burst; + time started, first_beat, previous_beat, final_write; + integer read_gap_max, write_gap_max; + read_gap_max = 0; + write_gap_max = 0; + @(negedge clk_i); + awaddr[0] = 32'h30000400; + awlen[0] = 15; + awvalid[0] = 1; + do @(posedge clk_i); while (!awready[0]); + started = $time; + @(negedge clk_i); + awvalid[0] = 0; + wvalid[0] = 1; + for (integer beat = 0; beat < 16; beat++) begin + wdata[0] = 32'hface0000 + beat; + wlast[0] = (beat == 15); + do @(posedge clk_i); while (!wready[0]); + if (beat == 0) first_beat = $time; + else begin + if (($time - previous_beat) != 10) $fatal(1, "write streaming bubble beat=%0d", beat); + write_gap_max = 1; + end + previous_beat = $time; + @(negedge clk_i); + end + final_write = previous_beat; + wvalid[0] = 0; + bready[0] = 1; + do @(posedge clk_i); while (!bvalid[0]); + if (bresp[0] != 0) $fatal(1, "streaming write response"); + $display("P4_STREAM_WRITE first=%0d total=%0d b_after_last=%0d", (first_beat - started) / 10, + ($time - started) / 10, ($time - final_write) / 10); + @(negedge clk_i); + bready[0] = 0; + awlen[0] = 0; + araddr[0] = 32'h30000400; + arlen[0] = 15; + arvalid[0] = 1; + rready[0] = 1; + do @(posedge clk_i); while (!arready[0]); + started = $time; + @(negedge clk_i); + arvalid[0] = 0; + for (integer beat = 0; beat < 16; beat++) begin + do @(posedge clk_i); while (!rvalid[0]); + if (erdata[0] !== (32'hface0000 + beat) || rresp[0] != 0 || rlast[0] != (beat == 15)) + $fatal(1, "streaming read ordering beat=%0d", beat); + if (beat == 0) first_beat = $time; + else begin + if (($time - previous_beat) != 10) $fatal(1, "read streaming bubble beat=%0d", beat); + read_gap_max = 1; + end + previous_beat = $time; + @(negedge clk_i); + end + rready[0] = 0; + arlen[0] = 0; + $display("P4_STREAM_READ first=%0d total=%0d", (first_beat - started) / 10, + (previous_beat - started) / 10); + $display("P4_STREAM read_gap_max=%0d write_gap_max=%0d beats=16", read_gap_max, write_gap_max); + endtask + + task automatic buffered_read; + // Two reserved words must survive a full queue, local writes to their + // addresses, and READY dropping again immediately after the first pop. + @(negedge clk_i); + araddr[0] = 32'h30000400; + arlen[0] = 15; + arvalid[0] = 1; + do @(posedge clk_i); while (!arready[0]); + @(negedge clk_i); + arvalid[0] = 0; + repeat (8) @(negedge clk_i); + store(32'h30000400, 32'habcd0000, 2); + store(32'h30000404, 32'habcd0001, 2); + store(32'h30000408, 32'habcd0002, 2); + for (integer beat = 0; beat < 16; beat++) begin + rready[0] = 1; + do @(posedge clk_i); while (!rvalid[0]); + if (erdata[0] !== ((beat == 2) ? 32'habcd0002 : (32'hface0000 + beat)) || + rresp[0] != 0 || rlast[0] != (beat == 15)) + $fatal(1, "reserved response lost/reordered beat=%0d", beat); + @(negedge clk_i); + rready[0] = 0; + repeat (beat % 5 + 1) @(negedge clk_i); + end + arlen[0] = 0; + endtask + + task automatic buffered_write_errors; + integer beats, stalls; + // Earlier queued beats must commit before an error B. Later legal beats + // after a bad strobe keep the existing target policy; early LAST ends W. + for (integer bad = 0; bad < 3; bad++) begin + for (integer n = 0; n < 8; n++) store(32'h30000600 + n * 4, 32'h12340000 + n, 2); + beats = (bad == 1) ? 4 : 8; + stalls = 0; + @(negedge clk_i); + awaddr[0] = 32'h30000600; + awlen[0] = 7; + awsize[0] = (bad == 0) ? 1 : 2; + awvalid[0] = 1; + do @(posedge clk_i); while (!awready[0]); + @(negedge clk_i); + awvalid[0] = 0; + fork + begin + for (integer beat = 0; beat < beats; beat++) begin + wvalid[0] = 1; + wdata[0] = 32'hbeefbeef; + wstrb[0] = (bad == 0) ? ((beat == 3) ? 4'hf : ((beat % 2) == 0 ? 4'h3 : 4'hc)) : 4'hf; + wlast[0] = (bad == 1) ? (beat == 3) : ((bad == 0) && (beat == 7)); + do begin + @(posedge clk_i); + if (!wready[0]) stalls++; + end while (!wready[0]); + @(negedge clk_i); + wvalid[0] = 0; + if (beat == 4) repeat (3) @(negedge clk_i); + end + end + begin + repeat (6) load(0, 32'h30000020, 32'h98765432, 0); + end + begin + repeat (6) load(1, 32'h30000020, 32'h98765432, 0); + end + join + wait (bvalid[0]); + if (bresp[0] != 2) $fatal(1, "buffered malformed write response"); + if (bad != 0 && stalls == 0) $fatal(1, "write buffer full path not exercised"); + for (integer n = 0; n < 8; n++) begin + if (bad == 0) begin + load(0, 32'h30000600 + n * 4, + (n == 1) ? 32'h1234beef : ((n < 4) ? 32'hbeefbeef : (32'h12340000 + n)), 3); + end else begin + load(0, 32'h30000600 + n * 4, (n < (beats - 1)) ? 32'hbeefbeef : (32'h12340000 + n), 3); + end + end + @(negedge clk_i); + bready[0] = 1; + @(posedge clk_i); + @(negedge clk_i); + bready[0] = 0; + end + awlen[0] = 0; + awsize[0] = 2; + wlast[0] = 1; + wstrb[0] = 15; + endtask + initial begin + #20000000; + $fatal(1, "SIM_TEST_TIMEOUT Tiny P4 memory"); + end + initial begin + wr = 0; + trans[0] = 0; + trans[1] = 0; + addr[0] = 0; + addr[1] = 0; + data[0] = 0; + data[1] = 0; + size[0] = 2; + size[1] = 2; + awvalid = 0; + wvalid = 0; + wlast = 0; + bready = 0; + arvalid = 0; + rready = 0; + for (integer g = 0; g < 4; g++) begin + awaddr[g] = 0; + awlen[g] = 0; + awsize[g] = 2; + awburst[g] = 1; + araddr[g] = 0; + arlen[g] = 0; + arsize[g] = 2; + arburst[g] = 1; + wdata[g] = 0; + wstrb[g] = 15; + end + cfg.paddr = 0; + cfg.pprot = 0; + cfg.psel = 0; + cfg.penable = 0; + cfg.pwrite = 0; + cfg.pwdata = 0; + cfg.pstrb = 15; + repeat (5) @(negedge clk_i); + rst_n_i = 1; + config_read(12'h00c, 131072); + config_read(12'h010, 32); + config_read(12'h014, 4096); + // First/last words of all 32 physical macros and every 32 KiB boundary. + for (integer bank = 0; bank < 32; bank++) begin + store(32'h30000000 + bank * 4096, 32'ha5000000 + bank, 2); + store(32'h30000ffc + bank * 4096, 32'h5a000000 + bank, 2); + load(0, 32'h30000000 + bank * 4096, 32'ha5000000 + bank, 3); + load(1, 32'h30000ffc + bank * 4096, 32'h5a000000 + bank, 3); + end + store(32'h30000020, 32'h11223344, 2); + store(32'h30000021, 32'haa, 0); + store(32'h30000022, 32'hbbcc, 1); + load(0, 32'h30000020, 32'hbbccaa44, 3); + for (integer lane = 0; lane < 4; lane++) begin + store(32'h30000028, 32'h11223344, 2); + store(32'h30000028 + lane, 32'haa, 0); + load(0, 32'h30000028, (32'h11223344 & ~(32'hff << (lane * 8))) | (32'haa << (lane * 8)), 3); + end + store(32'h30000028, 32'h11223344, 2); + store(32'h30000028, 32'haabb, 1); + load(0, 32'h30000028, 32'h1122aabb, 3); + back_to_back(); + fork + load(0, 32'h30000000, 32'ha5000000, 3); + load(1, 32'h30008000, 32'ha5000008, 3); + join + if (parallel_issues == 0) $fatal(1, "different groups serialized"); + fork + load(0, 32'h30000000, 32'ha5000000, 0); + load(1, 32'h30000000, 32'ha5000000, 0); + read_external(0, 32'h30000000, 32'ha5000000, 0, 0); + join + // A held R must retain old data while local writes use that same macro. + fork + read_external(0, 32'h30000020, 32'hbbccaa44, 31, 0); + begin + wait (rvalid[0]); + store(32'h30000020, 32'h98765432, 2); + load(0, 32'h30000020, 32'h98765432, 3); + end + join + // Waiting for external W does not lock the macro against CPU accesses. + fork + write_external(0, 32'h30000024, 32'hdeadbeef, 15, 31); + begin + wait (wready[0]); + load(0, 32'h30000020, 32'h98765432, 3); + end + join + load(1, 32'h30000024, 32'hdeadbeef, 3); + @(negedge clk_i); + perf_clear = 1; + @(negedge clk_i); + perf_clear = 0; + fork + read_external(0, 32'h30000000, 32'ha5000000, 7, 0); + read_external(1, 32'h30008000, 32'ha5000008, 7, 0); + read_external(2, 32'h30010000, 32'ha5000010, 7, 0); + read_external(3, 32'h30018000, 32'ha5000018, 7, 0); + join + config_read(12'h020, 4); + config_read(12'h024, 0); + config_read(12'h028, 4); + config_read(12'h030, 7); // Four parallel stalls count once per clock. + burst_and_errors(); + streaming_burst(); + buffered_read(); + buffered_write_errors(); + read_external(0, 32'h30008000, 0, 0, 3); + ahb(1, 0, 32'h70000000, 2, 0, 1, 0, observed); + ahb(1, 0, 32'h30000001, 2, 0, 1, 0, observed); + // Deterministic word/byte readback across all groups without touching a + // simulator memory array: every operation travels through the real ports. + for (integer g = 0; g < 4; g++) begin + for (integer n = 0; n < 4096; n++) begin + seed = seed ^ (seed << 13); + seed = seed ^ (seed >> 17); + seed = seed ^ (seed << 5); + store(32'h30000000 + g * 32768 + (n % 8192) * 4, seed, 2); + load(0, 32'h30000000 + g * 32768 + (n % 8192) * 4, seed, 3); + end + end + store(32'h3001fff0, 32'h76543210, 2); + @(negedge clk_i); + araddr[3] = 32'h3001fff0; + arlen[3] = 3; + arvalid[3] = 1; + do @(posedge clk_i); while (!arready[3]); + @(negedge clk_i); + arvalid[3] = 0; + wait (rvalid[3]); + repeat (4) @(negedge clk_i); + @(negedge clk_i); + rst_n_i = 0; + repeat (3) @(negedge clk_i); + if (rvalid != 0 || bvalid != 0) $fatal(1, "system reset retained a stale response"); + rst_n_i = 1; + arlen[3] = 0; + load(0, 32'h3001fff0, 32'h76543210, 3); + read_external(3, 32'h3001fff0, 32'h76543210, 0, 0); + // Reset after W acceptance, before the captured word reaches a macro. + // The cancelled word and its completion must not replay into a new session. + @(negedge clk_i); + awaddr[3] = 32'h3001fff0; + awlen[3] = 3; + awvalid[3] = 1; + do @(posedge clk_i); while (!awready[3]); + @(negedge clk_i); + awvalid[3] = 0; + wdata[3] = 32'hbad0bad0; + wlast[3] = 0; + wvalid[3] = 1; + do @(posedge clk_i); while (!wready[3]); + @(negedge clk_i); + wvalid[3] = 0; + rst_n_i = 0; + repeat (3) @(negedge clk_i); + rst_n_i = 1; + awlen[3] = 0; + wlast[3] = 1; + repeat (5) @(negedge clk_i); + if (bvalid != 0 || rvalid != 0) $fatal(1, "cancelled W response replayed"); + read_external(3, 32'h3001fff0, 32'h76543210, 0, 0); + $display("P4_BUFFER backpressure errors reset conservation PASS"); + $display("P4_MEMORY local_latency_max=%0d parallel_issue_cycles=%0d", local_latency_max, + parallel_issues); + $display("SIM_TEST_PASS Tiny P4 memory seed=12345678 random_per_group=4096"); + $finish; + end +endmodule diff --git a/tests/test_librelane_tiny_flow.py b/tests/test_librelane_tiny_flow.py index fd2da88f..600c4259 100644 --- a/tests/test_librelane_tiny_flow.py +++ b/tests/test_librelane_tiny_flow.py @@ -107,10 +107,10 @@ def test_chip_config_covers_every_onchip_sram_bank(tmp_path: Path) -> None: macro = config["MACROS"]["RM_IHPSG13_1P_1024x32_c2_bm_bist"] assert len(macro["instances"]) == 32 assert ( - "u_soc.u_sram.gen_memory.gen_bank[0].u_ram.u_mem" in macro["instances"] + "u_soc.u_sram.gen_group[0].u_group.gen_bank[0].u_ram.u_mem" in macro["instances"] ) assert ( - "u_soc.u_sram.gen_memory.gen_bank[31].u_ram.u_mem" in macro["instances"] + "u_soc.u_sram.gen_group[3].u_group.gen_bank[7].u_ram.u_mem" in macro["instances"] ) for instance in macro["instances"].values(): x, y = instance["location"] diff --git a/tests/test_publication_tiny.py b/tests/test_publication_tiny.py index 01d42098..08215d1d 100644 --- a/tests/test_publication_tiny.py +++ b/tests/test_publication_tiny.py @@ -71,7 +71,8 @@ def test_reset_update_does_not_bypass_reviewed_snapshot(monkeypatch): @pytest.fixture def source_tree(tmp_path): - sources = [tiny.TOP, tiny.RESET_TREE, 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, + tiny.CPU, tiny.CPU_MEM, tiny.SRAM, "configs/ci/ihp130-tiny.mk", tiny.MAP, tiny.TOPOLOGY, (tiny.BOOK / "source-contract.json").as_posix()] for relative in sources: target = tmp_path / relative @@ -81,9 +82,9 @@ 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.SRAM, ".CapacityKiB(128)", ".CapacityKiB(32)"), + (tiny.SRAM, "group < 4", "group < 2"), + (tiny.SRAM, ".DataBytes(4)", ".DataBytes(8)"), (tiny.TOP, ".NumChannels (4)", ".NumChannels (8)"), (tiny.TOP, ".EnableStreams(1'b0)", ".EnableStreams(1'b1)"), (tiny.TOP, ".EnableAtomics (1'b0)", ".EnableAtomics (1'b1)"), diff --git a/tests/test_tiny_ics55_platform.py b/tests/test_tiny_ics55_platform.py index 0c441912..6aee5dd7 100644 --- a/tests/test_tiny_ics55_platform.py +++ b/tests/test_tiny_ics55_platform.py @@ -109,10 +109,11 @@ def mapped_netlist(): else "input" for k in ports}} cells = {"u_clock_buffer": cell("clock_buffer", clk_i=7, clk_o=2), "receiver": cell("P65_1233_PBMUX", PAD=1, C=7, IE="1", OE="0", CS="1"), - "u_soc.u_cpu.u_hazard3_cpu_1port.core.pc_reg": cell("DFF", CK=2), + "u_soc.u_cpu.u_hazard3_cpu_2port.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)}) + cells.update({f"u_soc.u_sram.gen_group[{i // 8}].u_group.gen_bank[{i % 8}].u_ram.u_mem": + cell(RAM, CLK=2) for i in range(32)}) return {"modules": { "retrosoc_tiny_asic": {"ports": {"extclk_i_pad": {"direction": "inout", "bits": [1]}, "jtag_tck_i_pad": {"direction": "inout", "bits": [11]}}, @@ -201,24 +202,33 @@ def test_hierarchical_constant_output_is_resolved(mapped_netlist): "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"] + first_ram = "u_soc.u_sram.gen_group[0].u_group.gen_bank[0].u_ram.u_mem" if mutation == "missing_pll": del cells["pll"] elif mutation == "missing_ram": - del cells["bank0"] + del cells[first_ram] 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] + cells[first_ram]["connections"]["CLK"] = [99] elif mutation == "wrong_cpu": - cells["u_soc.u_cpu.u_hazard3_cpu_1port.core.pc_reg"]["connections"]["CK"] = [99] + cells["u_soc.u_cpu.u_hazard3_cpu_2port.core.pc_reg"]["connections"]["CK"] = [99] else: cells["pll"]["connections"]["CKOUT1"] = [2] with pytest.raises(ValueError): inspect_netlist(mapped_netlist) +def test_stale_p4_macro_hierarchy_is_rejected(mapped_netlist): + cells = mapped_netlist["modules"]["retrosoc_tiny_asic"]["cells"] + first_ram = "u_soc.u_sram.gen_group[0].u_group.gen_bank[0].u_ram.u_mem" + cells["u_soc.u_sram.gen_memory.gen_bank[0].u_ram.u_mem"] = cells.pop(first_ram) + with pytest.raises(ValueError, match="hierarchy"): + 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" diff --git a/tests/test_tiny_r2_p4.py b/tests/test_tiny_r2_p4.py new file mode 100644 index 00000000..fd6a92cd --- /dev/null +++ b/tests/test_tiny_r2_p4.py @@ -0,0 +1,191 @@ +"""Tiny dual-port local-memory protocol and source-bound replay acceptance.""" +from __future__ import annotations + +import os +from pathlib import Path +import shutil +import subprocess + +import pytest + +from scripts.check_simulation import DEFAULT_FAILURE +from scripts.rtl.generate_memory_map import generate as generate_memory_map +from scripts import tiny_r2_p4 as p4 + +ROOT = Path(__file__).resolve().parents[1] +COMMON = ROOT / "rtl/managed/clusterip/common/rtl" +TINY = ROOT / "rtl/tiny/top" + + +def run(command, directory, label, timeout=180): + result = subprocess.run(command, text=True, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, timeout=timeout, + env={**os.environ, "CCACHE_DISABLE": "1"}) + (directory / f"{label}.log").write_text(result.stdout) + assert result.returncode == 0, result.stdout[-12000:] + if label != "sv2v": # Converted source contains literal assertion/error strings. + assert not DEFAULT_FAILURE.search(result.stdout), result.stdout[-12000:] + return result.stdout + + +@pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) +@pytest.mark.parametrize("pdk", ["BEHAV", "ICS55", "IHP130"]) +def test_four_group_local_memory(tmp_path, simulator, pdk): + for tool in (simulator, "sv2v", "vvp"): + assert shutil.which(tool), f"required tool unavailable: {tool}" + generate_memory_map(ROOT / "rtl/tiny/address_map/memory_map.json", tmp_path / "map", "YES", 128) + sources = [COMMON / p for p in ( + "utils/register.sv", "utils/fifo.sv", "stream/round_robin_arbiter.sv", "interface/axi4_if.sv", + "interface/axi4_addr_gen.sv", "interface/apb4_if.sv", "interface/ahbl_if.sv")] + sources += [TINY / p for p in ( + "tiny_sram_port_if.sv", "tiny_sram_group.sv", "tiny_sram_axi4.sv", + "tiny_sram.sv", "tiny_cpu_mem.sv")] + sources += [ROOT / p for p in ( + "rtl/tech/tc_sram.sv", "rtl/ip/memory/onchip_ram_reg.sv", + "rtl/ip/interconnect/ahbl2axi4.sv", "rtl/ip/interconnect/axi4_error_slave.sv", + "tests/rtl/tiny_r2_p4_memory_tb.sv")] + defines = ["-DSV_ASSRT_DISABLE", f"-DPDK_{pdk}", "-DHAVE_SRAM_MACRO"] + vendor = [] + if pdk == "IHP130": + base = ROOT / "physical/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_sram/verilog" + vendor = [base / "RM_IHPSG13_1P_core_behavioral_bm_bist.v", + base / "RM_IHPSG13_1P_1024x32_c2_bm_bist.v"] + defines += ["-DFUNCTIONAL", "-DSYNTHESIS"] + elif pdk == "ICS55": + vendor = [ROOT / ".cache/retrosoc/pdk/ics55/sram/ics55_ecos_sram_1024x32_m8/verilog/ics55_ecos_sram_1024x32_m8_core.v"] + assert all(p.is_file() for p in vendor), "locked SRAM models unavailable" + includes = [f"-I{p}" for p in (COMMON, COMMON / "interface", tmp_path / "map/rtl", + ROOT / "rtl/ip/memory")] + top = "tiny_r2_p4_memory_tb" + output = tmp_path / "sim" + if simulator == "verilator": + run([simulator, "--binary", "--timing", "--assert", "-Wno-fatal", "-j", "3", + "-DHAVE_SVA", "--top-module", top, *defines, *includes, + *(str(p) for p in sources + vendor), "--Mdir", str(tmp_path / "obj"), "-o", str(output)], + tmp_path, "compile") + launch = [str(output)] + else: + # Keep vendor models outside sv2v, as in the executable Tiny flow. + converted = run(["sv2v", *defines, *includes, *(str(p) for p in sources)], tmp_path, "sv2v") + (tmp_path / "converted.v").write_text(converted) + run([simulator, "-g2012", "-gno-specify", "-s", top, + *defines, "-o", str(output), str(tmp_path / "converted.v"), *(str(p) for p in vendor)], + tmp_path, "compile") + launch = ["vvp", str(output)] + content = run(launch, tmp_path, "sim") + assert "SIM_TEST_PASS Tiny P4 memory" in content + assert "P4_STREAM read_gap_max=1 write_gap_max=1 beats=16" in content + assert "P4_BUFFER backpressure errors reset conservation PASS" in content + + +@pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) +def test_actual_dual_port_cpu_debug_and_reset(tmp_path, simulator): + for tool in (simulator, "sv2v", "vvp", "riscv32-unknown-elf-gcc", "riscv32-unknown-elf-objcopy"): + assert shutil.which(tool), f"required tool unavailable: {tool}" + generate_memory_map(ROOT / "rtl/tiny/address_map/memory_map.json", tmp_path / "map", "YES", 128) + run(["riscv32-unknown-elf-gcc", "-march=rv32imc_zicsr_zifencei", "-mabi=ilp32", + "-nostdlib", "-Wl,--build-id=none", "-T", str(ROOT / "tests/rtl/tiny_r2_p4_cpu.ld"), + str(ROOT / "tests/rtl/tiny_r2_p4_cpu.S"), "-o", str(tmp_path / "cpu.elf")], tmp_path, "firmware") + run(["riscv32-unknown-elf-objcopy", "-O", "verilog", str(tmp_path / "cpu.elf"), + str(tmp_path / "cpu.hex")], tmp_path, "objcopy") + sources = [COMMON / p for p in ( + "utils/register.sv", "utils/fifo.sv", "stream/round_robin_arbiter.sv", "interface/axi4_if.sv", + "interface/axi4_addr_gen.sv", "interface/apb4_if.sv", "interface/ahbl_if.sv", + "clkrst/rst_sync.sv", "cdc/cdc_sync.sv")] + sources += [ROOT / "rtl/managed/hazard3/hdl" / line.removeprefix("/hazard3/") + for line in (ROOT / "rtl/tiny/filelist/core_hazard3.fl").read_text().splitlines() + if line.startswith("/hazard3/")] + sources += [TINY / p for p in ( + "tiny_sram_port_if.sv", "tiny_sram_group.sv", "tiny_sram_axi4.sv", + "tiny_sram.sv", "tiny_cpu_mem.sv", "tiny_cpu_wrapper.sv")] + sources += [ROOT / p for p in ( + "rtl/tech/tc_sram.sv", "rtl/tech/tc_clk.sv", "rtl/ip/memory/onchip_ram_reg.sv", + "rtl/ip/interconnect/ahbl2axi4.sv", "rtl/ip/core/mgmt_debug_wrapper.sv", + "rtl/ip/core/mgmt_debug_reset.sv", "tests/rtl/tiny_r2_p4_cpu_tb.sv")] + includes = [f"-I{p}" for p in (COMMON, COMMON / "interface", tmp_path / "map/rtl", + ROOT / "rtl/ip/memory", ROOT / "rtl/managed/hazard3/hdl", + ROOT / "rtl/tiny/dv")] + defines = ["-DSV_ASSRT_DISABLE", "-DPDK_BEHAV", "-DSOC_JTAG_IDCODE=32'hDEADBEEF"] + output = tmp_path / "sim" + top = "tiny_r2_p4_cpu_tb" + if simulator == "verilator": + run([simulator, "--binary", "--timing", "--assert", "-Wno-fatal", "-j", "3", + "-DHAVE_SVA", "--top-module", top, *defines, *includes, + *(str(p) for p in sources), "--Mdir", str(tmp_path / "obj"), "-o", str(output)], + tmp_path, "compile") + launch = [str(output)] + else: + converted = run(["sv2v", *defines, *includes, *(str(p) for p in sources)], tmp_path, "sv2v") + (tmp_path / "converted.v").write_text(converted) + run([simulator, "-g2012", "-s", top, "-o", str(output), str(tmp_path / "converted.v")], + tmp_path, "compile") + launch = ["vvp", str(output)] + content = run([*launch, f"+firmware={tmp_path / 'cpu.hex'}"], tmp_path, "sim") + assert "SIM_TEST_PASS Tiny P4 CPU" in content + + +def test_group_control_induction(tmp_path): + for tool in ("sv2v", "yosys"): + assert shutil.which(tool), f"required tool unavailable: {tool}" + sources = [COMMON / "utils/register.sv", COMMON / "utils/fifo.sv", + COMMON / "stream/round_robin_arbiter.sv", + TINY / "tiny_sram_port_if.sv", TINY / "tiny_sram_group.sv", + ROOT / "tests/rtl/tiny_r2_p4_formal.sv"] + converted = run(["sv2v", "-DFORMAL", "-DSV_ASSRT_DISABLE", f"-I{COMMON}", + *(str(p) for p in sources)], tmp_path, "sv2v") + (tmp_path / "formal.v").write_text(converted) + script = (f"read_verilog -formal {tmp_path / 'formal.v'}; " + "prep -top tiny_r2_p4_formal -flatten; memory_map; async2sync; dffunmap; opt; " + "sat -seq 12 -tempinduct -prove-asserts -verify -set-init-zero") + content = run(["yosys", "-p", script], tmp_path, "formal", timeout=180) + assert "Induction step proven: SUCCESS!" in content + + +def p4_log(): + from test_tiny_r2_p3 import sample_log + lines = [sample_log()] + for window in range(1, 9): + for port in range(2): + lines.append(f"R2_P4_LOCAL window={window} port={port} accepted=3 completed=3 " + "latency_max=3 admission_wait=0") + for group in range(4): + lines.append(f"R2_P4_BANK window={window} group={group} issues_i=2 issues_d=1 " + "issues_external=0 conflicts=0 wait_i=0 wait_d=0 wait_external=0") + return "\n".join(lines) + + +def test_p4_acceptance_retains_local_and_external_accounting(): + samples = p4.parse_log(p4_log(), {"status": "passed", "exit_code": 0}) + assert len(samples["local"]) == 16 and len(samples["banks"]) == 32 + assert samples["cases"][2]["checksum"] == 0xc4a58dc5 + + +@pytest.mark.parametrize("mutation", ["exit", "marker", "missing", "duplicate", "accounting"]) +def test_p4_rejects_incomplete_or_failed_evidence(mutation): + text = p4_log() + flow = {"status": "passed", "exit_code": 0} + if mutation == "exit": + flow["exit_code"] = 124 + elif mutation == "marker": + text += "\nSIM_TEST_TIMEOUT" + elif mutation == "missing": + text = "\n".join(line for line in text.splitlines() if not line.startswith("R2_P4_BANK window=8")) + elif mutation == "duplicate": + text += "\n" + next(line for line in text.splitlines() if line.startswith("R2_P4_LOCAL")) + else: + text = text.replace("accepted=3 completed=3", "accepted=3 completed=4") + with pytest.raises(ValueError): + p4.parse_log(text, flow) + + +@pytest.mark.parametrize("key", p4.HARDWARE_KEYS) +def test_p4_rejects_mismatched_hardware(key): + values = dict.fromkeys(p4.HARDWARE_KEYS, "same") + with pytest.raises(ValueError): + p4.compatible_hardware({"configuration": values}, {"configuration": values | {key: "other"}}) + + +def test_p4_preserves_independent_image_compiler_identity(): + hardware = dict.fromkeys(p4.HARDWARE_KEYS, "same") + p4.compatible_hardware({"configuration": hardware | {"SW_OPT": "O2"}}, + {"configuration": hardware | {"SW_OPT": "APP"}}) From bbbfcb8bf97b0e7999a3c920db8175b20ba96993 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 Oct 2026 10:53:16 +0000 Subject: [PATCH 03/12] build(deps): bump actions/upload-artifact from 4.3.3 to 7.0.2 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.3 to 7.0.2. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/65462800fd760344b1a7b4382951275a0abb4808...cf430e030ddbb5b0abf93d22962f4752f3646cd9) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: 7.0.2 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/_regression.yml | 2 +- .github/workflows/development-environment.yml | 2 +- .github/workflows/release.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/_regression.yml b/.github/workflows/_regression.yml index 2c2a24d9..5c76cffe 100644 --- a/.github/workflows/_regression.yml +++ b/.github/workflows/_regression.yml @@ -132,7 +132,7 @@ jobs: - name: Upload reports and structured results if: always() - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: ${{ inputs.suite }}-regression-${{ github.run_id }}-${{ github.run_attempt }} path: | diff --git a/.github/workflows/development-environment.yml b/.github/workflows/development-environment.yml index c95bc215..2fbbe8c6 100644 --- a/.github/workflows/development-environment.yml +++ b/.github/workflows/development-environment.yml @@ -110,7 +110,7 @@ jobs: - name: Upload reports and structured results if: always() - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: environment-${{ matrix.environment }}-ihp130-${{ github.run_id }}-${{ github.run_attempt }} path: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 78306082..fe4ffdc5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,7 +46,7 @@ jobs: run: make CONFIG=configs/ci/ihp130.mk package - name: Upload workflow artifact - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: retrosoc-${{ github.ref_name }} path: dist/** From 4e6ab1a6fb445521703f0bfa412d3a1e8a38cd69 Mon Sep 17 00:00:00 2001 From: maksyuki Date: Sun, 11 Oct 2026 22:55:39 +0800 Subject: [PATCH 04/12] feat: add Tiny R2 P5 fault handling and reset isolation tests - Introduced for formal verification of fault handling in the Tiny R2 P5 architecture. - Added to test target reset isolation and ensure proper handling of reset signals across multiple targets. - Created to validate system-level interactions, including CPU and DMA progress during resets. - Enhanced to include fault counting and master tracking for concurrent fault events. - Developed to automate testing of concurrency, fault accounting, and retained-image acceptance. - Updated Python test suite to validate new configurations and ensure proper integration with Tiny R2 P5 architecture. --- docs/ip/tiny-soc-r2-concurrent-fabric.md | 296 ++++++++++ docs/ip/tiny-soc-verification.md | 60 ++ .../datasheets/tiny/chapter-review.md | 2 +- .../datasheets/tiny/content-review.md | 2 +- publications/datasheets/tiny/datasheet.typ | 18 +- publications/datasheets/tiny/diagrams.typ | 4 +- .../datasheets/tiny/source-contract.json | 27 +- publications/tiny_reference.py | 12 +- rtl/rtl_readiness.json | 17 + rtl/rtl_style_audit.json | 3 +- rtl/tiny/Makefile | 16 + rtl/tiny/README.md | 4 +- rtl/tiny/dv/retrosoc_tiny_tb.sv | 59 ++ rtl/tiny/filelist/top.fl | 3 +- rtl/tiny/integration/clock_reset_domains.json | 10 + rtl/tiny/integration/soc_topology.json | 16 +- rtl/tiny/top/retrosoc_tiny.sv | 158 ++++-- rtl/tiny/top/tiny_axi42apb4.sv | 41 +- rtl/tiny/top/tiny_axi4_fabric.sv | 522 ++++++++--------- rtl/tiny/top/tiny_axi4_target.sv | 365 ++++++++++++ rtl/tiny/top/tiny_cpu_mem.sv | 40 +- rtl/tiny/top/tiny_fault_collector.sv | 60 ++ rtl/tiny/top/tiny_sram_axi4_demux.sv | 130 ----- rtl/tiny/top/tiny_sysctrl.sv | 13 +- scripts/generate_tiny.py | 41 +- scripts/tiny_r2_p5.py | 260 +++++++++ tests/rtl/tiny_axi4_fabric_tb.sv | 127 +++-- tests/rtl/tiny_r2_p4_cpu_tb.sv | 17 +- tests/rtl/tiny_r2_p4_memory_tb.sv | 30 +- tests/rtl/tiny_r2_p5_cpu.S | 144 +++++ tests/rtl/tiny_r2_p5_fabric_formal.sv | 105 ++++ tests/rtl/tiny_r2_p5_fabric_tb.sv | 523 ++++++++++++++++++ tests/rtl/tiny_r2_p5_fault_formal.sv | 55 ++ tests/rtl/tiny_r2_p5_reset_tb.sv | 303 ++++++++++ tests/rtl/tiny_r2_p5_system_tb.sv | 189 +++++++ tests/rtl/tiny_sysctrl_tb.sv | 42 +- tests/test_publication_tiny.py | 2 + tests/test_tiny.py | 8 +- tests/test_tiny_r2_p5.py | 189 +++++++ 39 files changed, 3286 insertions(+), 627 deletions(-) create mode 100644 docs/ip/tiny-soc-r2-concurrent-fabric.md create mode 100644 rtl/tiny/top/tiny_axi4_target.sv create mode 100644 rtl/tiny/top/tiny_fault_collector.sv delete mode 100644 rtl/tiny/top/tiny_sram_axi4_demux.sv create mode 100644 scripts/tiny_r2_p5.py create mode 100644 tests/rtl/tiny_r2_p5_cpu.S create mode 100644 tests/rtl/tiny_r2_p5_fabric_formal.sv create mode 100644 tests/rtl/tiny_r2_p5_fabric_tb.sv create mode 100644 tests/rtl/tiny_r2_p5_fault_formal.sv create mode 100644 tests/rtl/tiny_r2_p5_reset_tb.sv create mode 100644 tests/rtl/tiny_r2_p5_system_tb.sv create mode 100644 tests/test_tiny_r2_p5.py diff --git a/docs/ip/tiny-soc-r2-concurrent-fabric.md b/docs/ip/tiny-soc-r2-concurrent-fabric.md new file mode 100644 index 00000000..4d8de63b --- /dev/null +++ b/docs/ip/tiny-soc-r2-concurrent-fabric.md @@ -0,0 +1,296 @@ +# TINY-R2-P5 concurrent fabric + +This implements the approved P5 preflight on +`3131762b35e0ebfcd196395f955e7c1005bd18e7`, using frozen specification blob +`7e19de49d49efaafacccfa85671e314d53dd15a4`. The default is Tiny/ICS55 SAFE24 +with its present PLL parked off; IHP130/no PLL remains compatibility. +The [ledger](tiny-soc-verification.md) separates phase evidence from approval. +Functional validation and matched-image measurements are complete for review; +no P6, frequency or physical qualification is implied. + +## Target-local reset repair (2026-10-11) + +The P5 review found that a target reset was previously used only as an +admission enable while an accepted controller session remained live. The +fabric now carries the explicit per-target reset leaf into each controller. +Admission and downstream AXI VALID/W/RREADY are blocked while that leaf is +asserted. An accepted Address or Response session changes to an abort response +and returns one DECERR RLAST or B response with the saved owner and ID; the +response remains held until the upstream terminal handshake. Response spill +registers flush during the target reset, so an old downstream response cannot +be replayed. The normal terminal handshake releases the fabric owner credit +and records the reset termination as one fault event. + +The focused reset-isolation fixture covers an accepted SRAM read held before +response and an accepted XPI/APB write held before W, under both Icarus and +Verilator. It checks DECERR termination, no stale response replay, and owner +credit release. These local reruns are separate from the earlier matched-image +campaign; the campaign evidence and PPA/STA attribution remain unchanged until +a new approved evidence capture is made. The retained focused test output is +under `build/ics55-tiny-2026-10-10-18-58-b4aa59f3e5d9/meta/tiny-r2-p5/reset-repair-tests/`. + +## Ownership and bounds + +The three ingress slots are CPU slow path 0, central DMA 1 and reserved SDIO 2. +Only slots 0/1 have product producers. CPU has one combined read/write credit; +DMA and the reserved interface each have one read and one write credit. +The real DMA already reserves complete write data before starting a burst; +its shared scheduler and all request/channel assignments are unchanged. + +Targets 0–3 directly attach the existing SRAM group frontends through Common +AXI connectors. Target 4 is XPI memory. Targets 5–20 follow the existing APB +peripheral order, with GPIO's two windows sharing its one branch. Targets 21/22 +are the unchanged DECERR/SLVERR responders. The generator emits the decoder +and sixteen single-peripheral bridge instances from canonical topology. +There is no product-wide APB or external transaction lock. + +Each target admits one combined transaction and saves owner, direction and +address attributes on the upstream address handshake. Admission reserves both +target and ingress direction, even while the downstream address is stalled. +Owner round robin advances on admission; the chosen owner's direction +preference alternates when both directions compete. W waits for saved AW +ownership and downstream AW acceptance, and stops after the terminal data beat. +Bounded Common response spill registers isolate held R/B values. Ownership and +ingress credit last through the upstream B/RLAST handshake, including buffered +terminal responses. A continuously eligible owner can be bypassed by at most +two other admissions; progress requires clocks and peer completion. + +Local CPU I/D, group arbitration, P4 response reservations and main SRAM macro +paths are unchanged. The local uncontended bound remains three SYS cycles, +and the P4 external SRAM streaming fixture still requires one beat per cycle. +All new logic uses SYS; APB bridges follow their existing peripheral reset +leaves. Target reset must be released for admission. System reset invalidates +all buffered sessions without clearing SRAM payload. Hart reset drains CPU +work only. Runtime peripheral resets still require P7's quiescence controls. + +## Faults and public compatibility + +Each target retains its first failed captured read beat's address/response +until terminal upstream completion; a successful final beat cannot erase it. +Write errors retain the accepted AW address. Six external direction events and +two local CPU events feed the collector. CPU frontend errors carry SLVERR; +external errors propagated through AHB are not counted again locally. + +The collector increments the existing count by every simultaneous event, +using four-bit multiplicity and a 33-bit saturating sum. It selects metadata +by ascending owner, CPU instruction before data, then read before write. +Equal keys use stable input order. SYSCTRL preserves a valid first snapshot; +W1C clears validity only and a simultaneous hardware event wins over clear. +FAULT_MASTER adds reserved owner 2 without changing offset or owner 0/1 meanings. +FAULT_DETAIL remains raw AXI response 2/3. The existing HAL byte-sized owner +field needs no change. No SDK API, register generator, interrupt, DMA channel, +capability or pad assignment is added. + +The old CPU/DMA wait registers retain their predicate: a requesting owner with +no address handshake in that cycle counts once. R2_P5_FABRIC records separate +active-target maxima, DMA simultaneous read/write ownership and simultaneous +read/write beat handshakes. These are testbench observations. The original +P3/P4 record formats and evidence retain their own identities. + +## Verification and reproducibility + +Focused protocol tests use all three owner slots, saved-address perturbations, +W-before-AW, held addresses/data/responses, independent control branches, +same-target contention and 96 deterministic randomized reads. Mixed-response +bursts align two terminal faults while their final responses are OKAY. +SYSCTRL tests cover multi-event count, owner 2, hardware-over-W1C and saturation. +The saturation test seeds the Common counter register, then exercises the +real adder and readback; it does not simulate billions of events. + +The system fixture instantiates production retrosoc_tiny, CPU, DMA, SRAM, +bridges and SYSCTRL. Only the XPI boundary is replaced with a controllable +verification endpoint and a fixture-only reset ROM. Real DMA copies B2 to B3 +with concurrent read/write beats and CPU readback checks. With an XPI mapped +response held, SRAM code and SYSCTRL access continue. A held XPI APB write +then verifies independent controls and hart-reset isolation. Finally CPU aborts +a held DMA read, checks that BUSY survives accepted work, releases the response +and verifies ABORTED. Ordinary regressions retain real pin-level XPI/NOR models. +This fixture is neither a product Boot ROM nor an XPI-device qualification. + +The first fixture attempted hart reset while holding the XPI memory response; +an accepted CPU instruction also needed that target, so reset correctly waited +for drain. The corrected isolation case retains DMA at the independent APB +branch and permits CPU progress. Other development attempts preserve sv2v +diagnostics separately from converted RTL and observe declared interface +instances rather than module-modport aliases for Icarus compatibility. + +Yosys induction checks production target ownership, response retention, +terminal-fault attribution and service-count fairness. A combinational proof +checks the collector's count and lexicographic metadata selection for arbitrary +event vectors. These are scoped control proofs, not full-chip formal signoff. + +The P5 replay entrypoint uses the unchanged P3 flat/banked images referenced by +the repaired P4 report: +`build/ics55-tiny-2026-10-10-15-15-b4aa59f3e5d9/meta/tiny-r2-p4/report.json`. +The old reports retain their original 4db27524-based snapshot attribution; +preflight matched all 1972 technical inputs to the committed P5 starting point. +P5 report identity, source/configuration/tool hashes and observations are new. +Parameters stay 1024 words, four jobs, 4096 CPU iterations and seed 0x12345678. +Each Verilator image runs once and banked Icarus once; all banked observations +must agree. There is no frozen overall speedup requirement or compiler/layout +experiment. Different-bank concurrency is proved separately because the +retained banked placement concentrates DMA storage in B3. + +Fresh development evidence begins at +`build/ics55-tiny-2026-10-10-18-25-b4aa59f3e5d9/meta/tiny-r2-p5/`. +The completed campaign and its monitor-only follow-up are recorded below. +Documentation remains here rather than in generated directories. +The Tiny draft/source extractor follows P5 routing and the fault field width; +its reviewed publication SHA and PDF status are not advanced. + +## Completed campaign and monitor correction + +The final evidence root is +`build/ics55-tiny-2026-10-10-18-58-b4aa59f3e5d9/meta/tiny-r2-p5/`. +`source.json` binds the initial 2010-input campaign to combined ICS55/IHP130 +digest `e85f4245883bf35f152d607480adf48edcf49debadb46d8b0bde92050a792b11`. +Both Tiny PR regressions, full Pytest and the first three image executions +passed on that snapshot. The initial aggregation in `report-result.{json,log}` +correctly failed: Icarus reported active-target maxima one greater than +Verilator in all eight banked windows, while every other sample was identical. + +The locked Icarus 13 installation miscounts the temporary expression +`$countones(~a_23_bit_vector)`. `countones_probe.sv` and its retained log +demonstrate an expected count 1 returning 3/6, while a named vector and explicit +bit counting return 1. The correction counts the declared 23 bits explicitly, +rejects unknown target states, and adds an opt-in count 0..23 self-check. No +postprocessing subtraction, relaxed comparison, tool upgrade or DUT fix is used. + +`monitor-source.json` captures the final combined digest +`0f3ed21f6fd6e932ff2532aab8c9964e71cf4b972e4c27e2c8ca6ab1f22c1015`. +Its only technical difference from the initial capture is +`rtl/tiny/dv/retrosoc_tiny_tb.sv`, inside P5 opt-in observation/self-check code. +The DUT, firmware, verification fixtures, policies and tool inputs are identical. +The ordinary regression commands do not enable this branch, and netlist boot +excludes it. Their results, full-suite results and PPA retain their original +snapshot attribution; they were not rerun or relabeled as new executions. +The audit checks this exact single-file delta and the original command flags. + +Both Verilator images and banked Icarus were rebuilt/replayed after the fix, +and the monitor self-check passed on both new models. The final ICS55-only +replay input digest is +`df14713ed7177d988d0e3fa0a038984589584788eda994dd7480f01a8dfcab7c`. +`report-final.{json,log}` and `report.json` confirm exact equality of all banked +observations across simulators, including the corrected active-target statistic. +`monitor-diagnosis.json` preserves the original discrepancy, logs and input +identity. Final model copies are retained beside their attempt records and +bound by `model-backup-*.json`; the initial Icarus model is separately retained. + +| Gate | Retained result | Scope/outcome | +| --- | --- | --- | +| Focused development suite | Earlier `18-25` root, `focused-final.{json,log}` | 106 passed before final campaign | +| Initial quality | `quality.{json,log}` | Ruff, diff, RTL format/style/readiness pass; 11.524 s | +| Full Pytest | `pytest-full.{json,log}` | 1679 passed, optional `pypdf` skip; 3867.891 s; original snapshot | +| Tiny ICS55 PR | `regress-tiny-ics55.{json,log}` | Pass; 4273.573 s; original snapshot | +| Tiny IHP130 PR | `regress-tiny-ihp130.{json,log}` | Pass; 3028.340 s; original snapshot | +| Initial image executions | `replay-verilator` / `replay-iverilog` results | Commands pass; 609.465/5952.278 s; cross-simulator aggregation fails as above | +| Monitor follow-up quality | `monitor-quality.{json,log}` | Pass; 11.714 s | +| Count self-checks | `monitor-check-{verilator,iverilog}.{json,log}` | Pass; instrumentation-only, not firmware acceptance | +| Final image executions | `replay-verilator-monitor` / `replay-iverilog-monitor` results | Pass; 618.974/5978.016 s; one run per selected image | +| Final report and audit | `report-final.{json,log}`, `report.json`, `evidence.json`, `audit.py` | Measurement complete; source-bound functional validation complete, review pending | + +All six ordinary simulations satisfy command success, configured SIM_TEST_PASS +and absence of forbidden error markers. In the final full suite, six P4 SRAM +fixtures preserve three-cycle local latency and one-cycle streaming spacing; +two P4 CPU fixtures, two P5 fabric fixtures, two real-CPU/DMA system fixtures +and three scoped formal logs pass. The system fixture reports 13 simultaneous +DMA read/write beat cycles and 27 independent control completions on both tools, +with hart-reset isolation and abort drain. SYSCTRL tests additionally check +multiple faults, reserved owner 2, raw detail, clear precedence and saturation. +These are functional results, not physical or XPI-device qualification. + +## Measurements and qualification limits + +The final runs preserve the immutable P3 image identities and compare them +with repaired P4 measurements, without changing compiler or placement: + +| Workload | Flat P4 → P5 cycles | Banked P4 → P5 cycles | +| --- | ---: | ---: | +| Empty | 69 → 69 | 66 → 66 | +| CPU | 73911 → 73911 | 73914 → 73914 | +| CPU copy | 102568 → 102568 | 86214 → 86214 | +| DMA burst1 | 91306 → 65527 | 89365 → 65502 | +| DMA burst16 | 17897 → 16974 | 17317 → 15802 | +| CPU/DMA contention | 299881 → 299990 | 299633 → 299738 | +| TCD contention | 299878 → 300027 | 299645 → 299750 | +| Chunk256 | 361285 → 361514 | 359570 → 361355 | + +The flat burst16 window records a maximum of three active targets, 124 cycles +with simultaneous DMA read/write ownership and 48 simultaneous read/write beat +cycles. Banked DMA stays in B3 and has zero DMA read/write overlap; up to two +targets are active because independent CPU control can overlap that bank. +The separate system fixture establishes different-bank DMA concurrency. +CPU/copy cycles are unchanged. Small contention/TCD increases and the 0.50% +banked chunk256 increase remain visible; no universal speedup is claimed. + +Synthesis/STA observations for the unchanged DUT use +`build/ics55-tiny-2026-10-10-18-58-42d5beccb726/` and +`build/ihp130-tiny-2026-10-10-18-58-bff91e3529ba/`. +Both pass synthesis, netlist boot and the existing 32-macro/direct-SYS binding +checks. Reports are `meta/metrics.json`, `sta/opensta/timing_metrics.rpt`, +`sta/opensta/p4-clock-bindings.rpt` and `sta/opensta/retrosoc_sta.log`. + +| PDK | Cells | Library area | Max WNS / TNS (ns) | Min WNS / TNS (ns) | +| --- | ---: | ---: | ---: | ---: | +| ICS55 | 241309 | 1906156.940799 | -64.75 / -932689.50 | 0 / 0 | +| IHP130 | 256995 | 9888378.712794 | -111.93 / -2594456.50 | 0 / 0 | + +Library area grows 5.56%/6.95% versus repaired P4. Max timing margins worsen; +timing remains unclosed and observational before the final product campaign. +Library area is not routed die area, and zero observed min violations is not +PVT/hold signoff. No new timing exception or rate is introduced. + +The unchanged warning baselines report 469/413 new signatures for ICS55/IHP130, +zero increased and zero resolved. Relative to repaired P4, two new signatures +are intentional empty `owner_idle_o`/`target_idle_o` connections; their future +lifecycle use and verification visibility are documented at the instances. +The former unused interface `aresetn` signature disappears because target +admission now consumes it. There is no new width/latch signature. The known +NFS clock-skew warning and successful ABC Liberty-cache fallback remain logged. +No warning baseline or metrics policy was changed. + +Mini behavioral compatibility retains its earlier P4 evidence and source +identity. The audit rechecks the unchanged shared/Mini/SDK/configuration inputs; +there is no new Mini execution or physical claim. No C/H implementation or +MISRA deviation changed. The P3 compiler matrix and three cold repetitions, +full C netlist campaign, PDF build/visual checks, CDC/RDC/PVT/MMMC, place/route, +high-rate qualification and P6+ work were not performed. Only the concretely +affected image measurements were repeated for the monitor defect. + +## Executed commands + +Commands used the locked activation and `BUILD_TIMESTAMP=2026-10-10-18-58`, +`JOBS=3`. `scripts/run_flow.py` retains the exact outer commands and JSON/log +results named above. New evidence is under `meta/tiny-r2-p5`; source snapshots, +tool identities, input digests, models, images and raw logs remain separate. + +```sh +source .cache/retrosoc/development/tiny-r2-p1/activate.sh +export BUILD_TIMESTAMP=2026-10-10-18-58 JOBS=3 +make rtl-format-check rtl-style-check-all rtl-readiness-check-all +ruff check . +git diff --check +python3 -m pytest -q --basetemp=build/ics55-tiny-2026-10-10-18-58-b4aa59f3e5d9/meta/tiny-r2-p5/tests-full +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 nightly --soc TINY --pdk IHP130 --dry-run +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55 +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 SIMU=VERILATOR SOC_SIM_TIME=21600 \ + TINY_P5_REFERENCE=build/ics55-tiny-2026-10-10-15-15-b4aa59f3e5d9/meta/tiny-r2-p4/report.json \ + TINY_P5_CASES=perf-o2-plain-flat,perf-o2-plain-banked tiny-r2-p5-replay +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 SIMU=IVERILOG SOC_SIM_TIME=21600 \ + TINY_P5_REFERENCE=build/ics55-tiny-2026-10-10-15-15-b4aa59f3e5d9/meta/tiny-r2-p4/report.json \ + TINY_P5_CASES=perf-o2-plain-banked tiny-r2-p5-replay +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 tiny-r2-p5-report +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 APP=ci_smoke SYNTH=YOSYS STA=OPENSTA metrics +make CONFIG=configs/ci/ihp130-tiny.mk SOC=TINY PDK=IHP130 APP=ci_smoke SYNTH=YOSYS STA=OPENSTA metrics +PYTHONDONTWRITEBYTECODE=1 python3 build/ics55-tiny-2026-10-10-18-58-b4aa59f3e5d9/meta/tiny-r2-p5/audit.py +``` + +Tiny PR/nightly definitions matched and were not executed twice. The first +report attempt failed; only the monitor-corrected executions qualify the final +cross-simulator report. The original cold executions are not a three-repeat +qualification campaign. No commit, staging, push, PR or automatic next-phase +transition belongs to this implementation. diff --git a/docs/ip/tiny-soc-verification.md b/docs/ip/tiny-soc-verification.md index bcc2f0ca..e61dc467 100644 --- a/docs/ip/tiny-soc-verification.md +++ b/docs/ip/tiny-soc-verification.md @@ -1,5 +1,65 @@ # Tiny MCU Verification Record +## TINY-R2-P5 target-reset isolation repair; focused validation (2026-10-11) + +Following the P5 review, the fabric now receives the explicit 23-entry target +reset vector. A reset blocks new admission and downstream handshakes, flushes +response spill state, and converts any accepted target session into a held +DECERR terminal response. The existing owner credit is released only by the +upstream RLAST/B handshake, preventing stale response replay or a permanently +busy direction. No target map, public register, DMA capacity, clock profile or +later phase was changed. + +The focused command +`PYTHONDONTWRITEBYTECODE=1 python3 -m pytest -q tests/test_tiny_r2_p5.py -k 'target_reset_isolation or concurrent_fabric'` passed 4 tests: both +Icarus and Verilator reset-isolation tests and both existing concurrent-fabric +tests. `make rtl-format-check rtl-style-check-all rtl-readiness-check-all` +also passed. This is a focused implementation check; the prior P5 campaign +reports remain source-bound to their recorded snapshots and have not been +rewritten or relabeled. The retained focused logs are under +`build/ics55-tiny-2026-10-10-18-58-b4aa59f3e5d9/meta/tiny-r2-p5/reset-repair-tests/`. + +## TINY-R2-P5 implementation; validated, review pending (2026-10-10) + +The maintainer approved the single-phase P5 plan on clean commit +`3131762b35e0ebfcd196395f955e7c1005bd18e7`, specification blob +`7e19de49d49efaafacccfa85671e314d53dd15a4`. The P4 input hashes match this +starting source, but earlier reports retain their original snapshot attribution. +The [P5 runbook](tiny-soc-r2-concurrent-fabric.md) records target-local ownership, +sixteen APB branches, reserved inactive SDIO owner 2, concurrent-fault accounting +and the unchanged public ABI/clock/memory boundaries. + +The completed campaign is under +`build/ics55-tiny-2026-10-10-18-58-b4aa59f3e5d9/meta/tiny-r2-p5/`; +the earlier `18-25` root retains development attempts. Full Pytest reports +1679 passed and one optional `pypdf` skip. Both Tiny PDK PR regressions pass, +including synthesis, netlist boot and clock/macro binding. The real CPU/DMA +fixture records 13 overlapping DMA read/write beat cycles, 27 independent control +completions, hart-reset isolation and abort drain on both simulators. Scoped +target-control/fault proofs and retained P4 latency/streaming checks pass. + +Initial image executions passed but aggregation correctly failed on an Icarus 13 +temporary-expression population-count defect. The only subsequent source change +is P5 opt-in testbench instrumentation. `source.json` retains the original +snapshot for the unchanged DUT/full-suite/ordinary-regression/PPA results; +`monitor-source.json` records the corrected monitor. Only affected image +measurements and monitor checks were repeated. `monitor-diagnosis.json` retains +the reproduction and original discrepancy; no comparison rule was relaxed. + +Final `report.json` has identical banked observations across simulators. Against +repaired P4, burst1 cycles change 91306→65527 flat and 89365→65502 banked; burst16 +changes 17897→16974 and 17317→15802. CPU/copy cycles are unchanged; small +contention/TCD/chunk256 increases remain explicit. The final combined input +digest is `0f3ed21f6fd6e932ff2532aab8c9964e71cf4b972e4c27e2c8ca6ab1f22c1015`. +`evidence.json` rechecks 2010 inputs and binds both snapshot scopes, six ordinary +simulations, final matched-image runs, 15 focused/formal artifacts and reused +unchanged Mini compatibility evidence. Library area grows 5.56%/6.95%; max WNS +is -64.75 ns ICS55 and -111.93 ns IHP130. Timing remains unclosed/observational. +The runbook records exact commands, source attribution, warning observations, +measurements and unrun qualifications. Human phase acceptance is pending. +Shared DMA/Mini/SDK behavior, dependencies and warning/metrics policy remain +unchanged. No P6 execution or physical-frequency qualification is authorized. + ## TINY-R2-P4 throughput repair; validated, review pending (2026-10-10) Following the read-only P4 review, the maintainer approved the bounded-buffer diff --git a/publications/datasheets/tiny/chapter-review.md b/publications/datasheets/tiny/chapter-review.md index 3ebca872..df80a38b 100644 --- a/publications/datasheets/tiny/chapter-review.md +++ b/publications/datasheets/tiny/chapter-review.md @@ -13,7 +13,7 @@ All **39 frozen entries / 14 IP chapters** are reviewed below. Register, address | 5 | System Architecture / 17 | Covered. Product-specific organization, navigation or revision control; no additional hardware capability. | [datasheet.typ](../../../publications/datasheets/tiny/datasheet.typ), [structure-contract.json](../../../publications/datasheets/tiny/structure-contract.json) | Retain the declared scope, complete source links and independent Tiny identity when updating this section. | | 6 | Processor and Integration / 17 | Working-source P4 update: dual-port RV32IMC hart, A disabled, independent local I/D and tagged slow adapter. Page number belongs to the earlier delivered PDF. | [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv), [tiny_cpu_wrapper.sv](../../../rtl/tiny/top/tiny_cpu_wrapper.sv) | Retain actual wrapper parameters; revalidate publication pages separately before delivery. | | 7 | Clock, Reset and Operating State / 18 | Covered. System and asynchronous JTAG domains; RTC/WDG share the system clock. | [clock_reset_domains.json](../../../rtl/tiny/integration/clock_reset_domains.json), [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv) | Obtain CDC/RDC and clock/power evidence separately; do not claim independent sleep timing. | -| 8 | Bus Transactions, Arbitration and Errors / 19 | Covered. One globally active transaction, round-robin acceptance, saved ownership, bounded legal bursts and error/drain behavior. | [tiny_axi4_fabric.sv](../../../rtl/tiny/top/tiny_axi4_fabric.sv), [axi4_error_slave.sv](../../../rtl/ip/interconnect/axi4_error_slave.sv) | Keep local IP capability separate from end-to-end fabric admission; no unlimited liveness guarantee. | +| 8 | Bus Transactions, Arbitration and Errors / 19 | P5 source draft updated: target-local ownership, bounded independent DMA R/W, separate APB branches and concurrent fault count. | [tiny_axi4_fabric.sv](../../../rtl/tiny/top/tiny_axi4_fabric.sv), [tiny_axi4_target.sv](../../../rtl/tiny/top/tiny_axi4_target.sv) | Page19 belongs to the prior PDF; no new PDF or unconditional liveness claim. SDIO owner2 is reserved only. | | 9 | Memory Map and Access Conventions / 21 | Covered. Twenty canonical address windows, including alias versus physical-capacity distinctions. | [memory_map.json](../../../rtl/tiny/address_map/memory_map.json) | Reconcile every source-map change; do not use unmapped holes as scratch memory. | | 10 | Interrupt and DMA Request Routing / 22 | Covered. Fourteen allocated core causes, thirty external positions, enabled DMA request map and channel conventions. | [soc_topology.json](../../../rtl/tiny/integration/soc_topology.json), [dma_math.c](../../../crt/src/hal/dma_math.c) | Keep core input bits, external ordinals, CSR positions and DMA selectors distinct. | | 11 | Peripherals / 23 | Covered. Fourteen IP chapters and 337 source-extracted register definitions, with instance-specific routing. | [catalog.json](../../../publications/datasheets/tiny/catalog.json), [tiny_reference.py](../../../publications/tiny_reference.py) | Update source inventory, frozen register keys and rendered-coverage checks together after reviewed ABI changes. | diff --git a/publications/datasheets/tiny/content-review.md b/publications/datasheets/tiny/content-review.md index 1d1c4840..e2ecf055 100644 --- a/publications/datasheets/tiny/content-review.md +++ b/publications/datasheets/tiny/content-review.md @@ -15,7 +15,7 @@ publication release still requires source review, snapshot refresh and PDF check | Area | Review and publication decision | Follow-up boundary | | --- | --- | --- | | CPU and startup | Publish the actual C/M extensions, disabled A, CSR-enabled firmware and Tiny guard around PSRAM setup. | Keep compiler target separate from implemented ISA; a reset vector is not a completed boot. | -| Fabric | Document one globally active CPU/DMA read or write, saved ownership, naturally aligned bounded bursts and terminal response/drain. | Do not claim arbitrary AXI4 features, independent read/write concurrency or unrestricted liveness. | +| Fabric | P5 draft: independent target ownership, one combined CPU slow operation, DMA one read/one write, 16 APB branches and lossless concurrent-fault accounting. Owner2 is reserved, inactive SDIO. | No SDIO integration, arbitrary AXI4 features or unconditional liveness claim. Snapshot/PDF revision is not advanced. | | Address/IRQ/pads | Derive all 20 regions, 14 allocated core causes and 52 logical signal pads; retain 32 GPIO alternate entries. | Logical pads are not package pin numbers or a board schematic. | | IP/register scope | Fourteen chapters, thirteen unique register families; Tiny SYSCTRL is decoded independently and ARCHINFO overrides the SoC identity. | Reject missing/extra decode and defaults belonging to other products. | | DMA | Four channels, request mask 0x07F9, no stream modes, 64-byte descriptors and source-derived field layout. | **Discovery discrepancy:** the four-bit maximum-burst field reads zero after truncating 16. Publish that value and the actual 16-beat limit separately; propose a future ABI/RTL review, do not change RTL here. | diff --git a/publications/datasheets/tiny/datasheet.typ b/publications/datasheets/tiny/datasheet.typ index c3749082..1580990b 100644 --- a/publications/datasheets/tiny/datasheet.typ +++ b/publications/datasheets/tiny/datasheet.typ @@ -31,7 +31,7 @@ *Memory and interconnect* - *128 KiB SRAM*, four independently serviced 32 KiB groups backed by 32 banks of 4 KiB. - Native AXI32 memory paths and one APB4 island. - - CPU instruction/data ports are independent locally; the compatible external attachment remains serialized until P5. + - Independent local instruction/data ports and target-local external ownership. - XPI NOR boot and SRAM-resident application layout. *DMA and control* @@ -132,10 +132,18 @@ advertised as a qualified voltage/frequency operating point. #source-note("rtl/tiny/integration/clock_reset_domains.json",title:"Clock/reset inventory and debug crossings") == Bus Transactions, Arbitration and Errors -The two external initiators are CPU slow path and DMA. They share *one active read or write transaction globally* in this -pre-P5 attachment. CPU instruction/data local requests are independently arbitrated within each SRAM group. The fabric saves source, -direction, address and attributes, forwards the request, and retains ownership until the terminal B or RLAST handshake. -Read/write request arbitration is round-robin; it is not a throughput guarantee or an unrestricted liveness guarantee. +The two active external initiators are CPU slow path and DMA. A third ingress is reserved for future SDIO integration and +has no active producer. Each SRAM group, XPI memory endpoint and APB peripheral has independent transaction ownership. +CPU slow traffic admits one combined operation; DMA can have one read and one write outstanding across different targets. +Each target admits one combined read or write. CPU local instruction/data requests remain independently arbitrated within +each SRAM group. Saved ownership includes accepted address and response buffers and lasts through the upstream B/RLAST handshake. +Owner arbitration is round-robin with fair direction selection. Progress bounds require a running clock and peer progress. +Separate APB bridges keep independent controls outside a FIFO-waiting peripheral's transaction. + +Fault accounting counts each failed transaction once, including errors before the final read beat. Simultaneous events +increment the saturating count individually; first-fault metadata uses ascending owner, instruction before data, then read +before write. CPU is owner0, DMA owner1, and owner2 is reserved. FAULT_DETAIL retains raw AXI response2/3. Clearing validity +does not clear the count, and a simultaneous hardware event wins over clear. Memory transfers support naturally aligned 1/2/4-byte beats and up to 16 INCR beats. Single-beat FIXED is also admitted. The complete address span must remain within one decoded target and one 4 KiB page without overflow. MMIO accepts only diff --git a/publications/datasheets/tiny/diagrams.typ b/publications/datasheets/tiny/diagrams.typ index 15b0cdf4..fe355048 100644 --- a/publications/datasheets/tiny/diagrams.typ +++ b/publications/datasheets/tiny/diagrams.typ @@ -30,10 +30,10 @@ (104,4,63,19,[JTAG / debug transport \ External TCK domain]), (5,37,65,15,[I/D decode + slow adapter]), (104,37,63,15,[Four-channel DMA \ AXI32 master]), - (30,69,112,19,[Tiny AXI32 fabric \ P5 external concurrency pending]), + (30,69,112,19,[Tiny AXI32 fabric \ Independent target ownership]), (0,110,40,22,[128 KiB SRAM \ 4 × 32 KiB groups]), (44,110,40,22,[XPI read path \ Flash alias / NOR]), - (88,110,40,22,[APB4 bridge \ 16 targets]), + (88,110,40,22,[16 APB4 branches \ One per peripheral]), (132,110,40,22,[Error responders \ DECERR / SLVERR]), ),( ((104,13),(70,13)),((37,23),(37,37)), diff --git a/publications/datasheets/tiny/source-contract.json b/publications/datasheets/tiny/source-contract.json index a83343d9..04931cf6 100644 --- a/publications/datasheets/tiny/source-contract.json +++ b/publications/datasheets/tiny/source-contract.json @@ -103,7 +103,6 @@ "rtl/tiny/top/tiny_sram.sv", "rtl/tiny/top/tiny_sram_group.sv", "rtl/tiny/top/tiny_sram_axi4.sv", - "rtl/tiny/top/tiny_sram_axi4_demux.sv", "rtl/tiny/top/tiny_sram_port_if.sv", "rtl/ip/core/mgmt_debug_wrapper.sv", "rtl/ip/core/mgmt_debug_reset.sv", @@ -118,19 +117,19 @@ "app/apps/bringup/tiny.mk", "app/apps/ci_smoke/tiny.mk", "scripts/generate_tiny.py", - "tests/test_tiny.py" + "tests/test_tiny.py", + "rtl/tiny/top/tiny_axi4_target.sv", + "rtl/tiny/top/tiny_fault_collector.sv" ], "bindings": [ { "file": "rtl/tiny/top/tiny_axi4_fabric.sv", "text": [ - "(s_selected_addr.len <= 8'd15)", - "(s_last_addr[31:12] == s_selected_addr.addr[31:12])", - "s_accept = (s_state_q == Idle) && s_selected_valid;", - "Response: if (s_terminal) s_state_d = Idle;", - "s_tgt_d = !s_protocol_legal ? 3'd4 : s_fetch_denied ? ((s_decoded_target == 3'd2) ? 3'd4 : 3'd3) : s_decoded_target;", - "assign s_protocol_legal = !s_selected_addr.lock && (s_selected_addr.size <= 3'd2) &&\n ((s_selected_addr.addr & ((32'd1 << s_selected_addr.size) - 32'd1)) == 32'd0) &&\n ((s_selected_addr.burst == 2'b01) ||\n ((s_selected_addr.burst == 2'b00) && (s_selected_addr.len == 8'd0))) &&\n (s_selected_addr.len <= 8'd15) && !s_last_addr[32] &&\n (s_last_addr[31:12] == s_selected_addr.addr[31:12]) &&\n (decode_target(\n s_last_addr[31:0]\n ) == s_decoded_target) && ((s_decoded_target != 3'd2) || (s_selected_addr.len == 8'd0));", - "assign s_terminal = (s_state_q == Response) &&\n (s_write_q ? (s_target_bvalid[s_tgt_q] && s_bready[s_owner_q]) :\n (s_target_rvalid[s_tgt_q] && s_rready[s_owner_q] && s_target_rlast[s_tgt_q]));" + " function automatic logic [4:0] checked_target(\n input logic [31:0] addr_i, input logic [7:0] len_i,\n input logic [2:0] size_i, input logic [1:0] burst_i,\n input logic lock_i, input logic instruction_i);\n logic [32:0] last_addr;\n logic [4:0] decoded;\n decoded = decode_target(addr_i);\n last_addr = {1'b0, addr_i} + (({25'd0, len_i} + 33'd1) << size_i) - 33'd1;\n if (lock_i || (size_i > 3'd2) || (len_i > 8'd15) ||\n ((addr_i & ((32'd1 << size_i) - 32'd1)) != 32'd0) ||\n !((burst_i == 2'b01) || ((burst_i == 2'b00) && (len_i == 8'd0))) ||\n last_addr[32] || (last_addr[31:12] != addr_i[31:12]) ||\n (decode_target(last_addr[31:0]) != decoded) ||\n ((decoded >= 5'd5) && (decoded <= 5'd20) && (len_i != 8'd0)))\n return 5'd22;\n if (instruction_i && !(`SOC_ADDR_IS_FLASH(addr_i) ||\n ((addr_i >= 32'h5000_0000) && (addr_i < 32'h5400_0000))))\n return ((decoded >= 5'd5) && (decoded <= 5'd20)) ? 5'd22 : 5'd21;\n return decoded;\n endfunction", + "assign s_busy_d = (s_busy_q & ~s_done) | s_admit;", + "assign owner_idle_o[owner] = !(s_busy_q[2*owner] || s_busy_q[2*owner+1]);", + "assign s_eligible[2*owner] = rst_n_i && owner_idle_o[owner] && masters[owner].arvalid && (!masters[owner].awvalid || !s_cpu_prefer_write_q);", + "assign s_eligible[2*owner+1] = rst_n_i && owner_idle_o[owner] && masters[owner].awvalid && (!masters[owner].arvalid || s_cpu_prefer_write_q);" ] }, { @@ -183,6 +182,16 @@ "RS_DMA_CH_REG(channel, RS_DMA_CH_REG_TCD_COUNT) = UINT32_C(1);", "current = (rs_dma_tcd_t *)(uintptr_t)current->next_ptr;" ] + }, + { + "file": "rtl/tiny/top/tiny_axi4_target.sv", + "text": [ + "assign s_accept = (s_req_q.state == Idle) && enable_i && s_valid;", + " assign s_terminal = (s_req_q.state == Response) &&\n (s_req_q.write ? (s_b_valid && s_bready[s_req_q.owner]) :\n (s_r_valid && s_rready[s_req_q.owner] && s_r_data[1]));", + "assign fault_valid_o = s_terminal && s_req_q.error_seen;", + "if (!s_req_q.error_seen && (target.rresp != 2'b00)) begin s_req_d.error_seen = 1'b1; s_req_d.error_addr = s_req_q.read_addr; s_req_d.error_resp = target.rresp; end", + "if (s_terminal) s_req_d.state = Idle;" + ] } ] } diff --git a/publications/tiny_reference.py b/publications/tiny_reference.py index 2b4c5732..df366c3a 100644 --- a/publications/tiny_reference.py +++ b/publications/tiny_reference.py @@ -77,8 +77,8 @@ def facts(root: Path = ROOT) -> dict: raise ValueError("Tiny SRAM parameters changed") # P4 changes local routing, not physical geometry or the book's IHP130 # compatibility selection. Snapshot enforcement still precedes publication. - read_topology(root / TOPOLOGY, root / MAP) - require_snippets(root, TOP, ["tiny_sram u_sram", "tiny_sram_axi4_demux u_sram_attachment", + topology = read_topology(root / TOPOLOGY, root / MAP) + require_snippets(root, TOP, ["tiny_sram u_sram", "gen_sram_attachment", "axi4_connector u_connector", ".ID_WIDTH(1)", ".DATA_WIDTH(32)"]) require_snippets(root, SRAM, ["group < 4", "tiny_sram_group u_group"]) if dma != {"NumChannels": 4, "MaxBurstBeats": 16, "FifoDepth": 32, "RequestMask": 0x7F9, "EnableStreams": 0}: @@ -88,7 +88,10 @@ def facts(root: Path = ROOT) -> dict: require_snippets(root, RESET_TREE, [".STAGE(5)", "assign cpu_ready_rst_n_o = &leaf_rst_n_o;"]) require_snippets(root, CPU, ["hazard3_cpu_2port", ".EXTENSION_C(1)", ".EXTENSION_A(EnableAtomics)"]) require_snippets(root, CPU_MEM, [".TwoCycleErrors(1'b1)", "ErrorFirst", "ErrorLast"]) - require_snippets(root, TOP, ["u_masters_axi4_if[2]", ".slow_axi4(u_masters_axi4_if[0])", + require_snippets(root, TOP, ["u_masters_axi4_if[3]", "u_targets_axi4_if[23]", + "assign u_masters_axi4_if[2].arvalid = 1'b0;", + "assign u_masters_axi4_if[2].awvalid = 1'b0;", + ".slow_axi4(u_masters_axi4_if[0])", ".axi4(u_masters_axi4_if[1])", ".cfg_apb4(u_sram_apb4_if)", "assign s_tick = s_tick_count_q == 5'd23;"]) require_snippets(root, SYSCTRL, ["(apb4.paddr[1:0] != 2'd0)", "(apb4.pstrb != 4'hf)", @@ -108,6 +111,7 @@ def facts(root: Path = ROOT) -> dict: raise ValueError("Tiny discovery parameters changed") require_snippets(root, ARCHINFO, ["32'h5449_4e59 : u_info_apb4_if.prdata", ".device_id_valid_i(1'b0)", ".device_id_read_enable_i(1'b0)"]) return {"profile": profile, "sram": ram, "dma": dma, "cpu": cpu, "archinfo": arch, + "fabric": topology["fabric"], "hardware_isa": "RV32IMC", "firmware_isa": profile["ISA"], "soc_id": 0x54494E59, "system_hz": int(profile["EXT_CLK_HZ"]), "timebase_hz": int(profile["CLINT_TIMEBASE_HZ"])} @@ -139,7 +143,7 @@ def sysctrl_registers(root: Path) -> dict: offsets = {r["symbol"]: int(r["offset"], 0) for r in read(root / MAP)["sysctrl_registers"]} definitions = { "FAULT_STATUS": ("RW1C", "Sticky first fault. Bit 0 clears pending; a new fault can win after clear.", [("PENDING", 0, 0, "A fault record is pending."), ("WRITE", 1, 1, "Faulting transaction was a write."), ("CLASS", 2, 4, "1: decode error; 2: protocol/target error.")]), - "FAULT_MASTER": ("RO", "Master of the captured first fault.", [("MASTER", 0, 0, "0 CPU, 1 DMA.")]), + "FAULT_MASTER": ("RO", "Master of the captured first fault.", [("MASTER", 0, 1, "0 CPU, 1 DMA, 2 reserved SDIO ingress; SDIO is not integrated.")]), "FAULT_DETAIL": ("RO", "Captured AXI response code.", [("RESPONSE", 0, 1, "AXI RRESP/BRESP encoding.")]), "PERF_CTRL": ("RW / command", "Enable counters, clear counters/snapshots or capture a coherent pair. Clear takes precedence over snapshot.", [("ENABLE", 0, 0, "Counter enable state."), ("CLEAR", 1, 1, "Write-one pulse; reads zero."), ("SNAPSHOT", 2, 2, "Write-one pulse; reads zero.")]), "TEST_STATUS": ("RW sticky", "First full-word valid result is sticky until system reset; write mask 0x8000FF01.", [("PASS", 0, 0, "Pass when valid is set."), ("CODE", 8, 15, "Application-defined result code."), ("VALID", 31, 31, "The first valid write locks the result.")]), diff --git a/rtl/rtl_readiness.json b/rtl/rtl_readiness.json index 2edf6e96..d2973028 100644 --- a/rtl/rtl_readiness.json +++ b/rtl/rtl_readiness.json @@ -1,6 +1,23 @@ { "schema_version": 1, "targets": [ + { + "name": "tiny-r2-p5", + "paths": ["rtl/tiny/top"], + "status": "prototype", + "baseline_revision": "3131762b35e0ebfcd196395f955e7c1005bd18e7", + "configuration_digest": null, + "configuration_profiles": ["configs/ci/ics55-tiny.mk", "configs/ci/ihp130-tiny.mk"], + "required_evidence": [], + "synthesis_intent": { + "registers": "Common registers and bounded response spill registers per external target", + "reset": "accepted CPU work drains before hart reset; system reset invalidates all sessions", + "clock_enable": "all fabric, APB, CPU and SRAM logic remains on direct SAFE24 SYS", + "memory": "unchanged 32 SRAM macros; no global SRAM transaction lock", + "pipeline": "one combined transaction per target, CPU one combined, DMA/SDIO one per direction" + }, + "waivers": [] + }, { "name": "tiny-r2-p4", "paths": ["rtl/tiny/top"], diff --git a/rtl/rtl_style_audit.json b/rtl/rtl_style_audit.json index b19702c4..d41e1a1b 100644 --- a/rtl/rtl_style_audit.json +++ b/rtl/rtl_style_audit.json @@ -437,12 +437,13 @@ "tiny_archinfo.sv", "tiny_axi42apb4.sv", "tiny_axi4_fabric.sv", + "tiny_axi4_target.sv", "tiny_cpu_mem.sv", "tiny_cpu_wrapper.sv", + "tiny_fault_collector.sv", "tiny_reset_tree.sv", "tiny_sram.sv", "tiny_sram_axi4.sv", - "tiny_sram_axi4_demux.sv", "tiny_sram_group.sv", "tiny_sram_port_if.sv", "tiny_sysctrl.sv" diff --git a/rtl/tiny/Makefile b/rtl/tiny/Makefile index ab9c7035..04c236d9 100644 --- a/rtl/tiny/Makefile +++ b/rtl/tiny/Makefile @@ -216,3 +216,19 @@ tiny-r2-p4-report: $(PYTHON) $(ROOT_PATH)/scripts/tiny_r2_p4.py --root $(ROOT_PATH) --variant-root $(VARIANT_ROOT) --report .PHONY: tiny-r2-p4-replay tiny-r2-p4-report + +# P5 compares the same P3 binaries to retained P4 observations, with its own +# phase identity and evidence root. The historical P3/P4 commands stay intact. +TINY_P5_REFERENCE ?= +TINY_P5_CASES ?= perf-o2-plain-flat,perf-o2-plain-banked +tiny-r2-p5-replay: | manifest + $(PYTHON) $(ROOT_PATH)/scripts/tiny_r2_p5.py --root $(ROOT_PATH) --variant-root $(VARIANT_ROOT) \ + --reference $(TINY_P5_REFERENCE) --cases $(TINY_P5_CASES) --simulator $(SIM_TOOL_NAME) \ + --vvp $(VVP) --timeout $(SOC_SIM_TIME) -- \ + make --no-print-directory -j1 -B CONFIG=$(CONFIG_PATH) BUILD_TIMESTAMP=$(BUILD_TIMESTAMP) \ + $(foreach var,$(CONFIG_KEY_VARS),$(var)=$($(var))) SIMU=$(SIMU) SYNTH=NONE STA=NONE comp + +tiny-r2-p5-report: + $(PYTHON) $(ROOT_PATH)/scripts/tiny_r2_p5.py --root $(ROOT_PATH) --variant-root $(VARIANT_ROOT) --report + +.PHONY: tiny-r2-p5-replay tiny-r2-p5-report diff --git a/rtl/tiny/README.md b/rtl/tiny/README.md index a31224b8..89d6f9fc 100644 --- a/rtl/tiny/README.md +++ b/rtl/tiny/README.md @@ -12,7 +12,9 @@ validation; backend frequency tests are not SYS switching or timing qualificatio The [R2-P4 implementation](../../docs/ip/tiny-soc-r2-local-memory.md) connects the official dual-port CPU to four independently serviced 32 KiB local SRAM groups. The 32 physical macros and register ABI are unchanged. Only non-SRAM CPU work -uses the tagged slow path; the external fabric stays serialized until P5. +uses the tagged slow path. The [P5 implementation](../../docs/ip/tiny-soc-r2-concurrent-fabric.md) +replaces external serialization with independent target ownership and APB +branches; the third, SDIO ingress remains reserved and inactive until P6. Source presence alone is not completed phase or physical qualification. `top` owns product integration, `address_map`, `pin_map` and `integration` own diff --git a/rtl/tiny/dv/retrosoc_tiny_tb.sv b/rtl/tiny/dv/retrosoc_tiny_tb.sv index c90ab682..79e12ba0 100644 --- a/rtl/tiny/dv/retrosoc_tiny_tb.sv +++ b/rtl/tiny/dv/retrosoc_tiny_tb.sv @@ -230,6 +230,65 @@ module retrosoc_tiny_tb; `undef _BASELINE_AXI end + // P5-only observations supplement the unchanged P3/P4 record formats. They + // are testbench measurements, not new software-visible performance counters. + longint unsigned s_p5_overlap = 0, s_p5_beat_overlap = 0; + integer s_p5_window = 0, s_p5_active_max = 0; + logic s_p5_previous_enable = 0; + wire s_p5_enable = s_baseline_enabled && u_dut.u_soc.s_perf_enable; + // Icarus 13 miscounts the temporary expression $countones(~a_23_bit_vector). + // Count declared bits explicitly and reject unknowns rather than correcting + // a report afterward. This function changes only opt-in P5 observations. + function automatic integer p5_active_targets(input logic [22:0] idle_i); + integer count; + count = 0; + for (integer target_index = 0; target_index < 23; target_index++) begin + if (idle_i[target_index] === 1'b0) count++; + else if (idle_i[target_index] !== 1'b1) $fatal(1, "SIM_TEST_FAIL P5 unknown target state"); + end + return count; + endfunction + initial begin : p5_monitor_check + if ($test$plusargs("tiny_r2_p5_monitor_check")) begin + for (integer active = 0; active <= 23; active++) begin + if (p5_active_targets(23'h7fffff ^ ((23'd1 << active) - 23'd1)) != active) + $fatal(1, "SIM_TEST_FAIL P5 monitor population count"); + end + $display("SIM_TEST_PASS Tiny P5 monitor-check counts=0..23; instrumentation only"); + $finish; + end + end + always @(posedge s_clk) begin + if (!u_dut.u_soc.s_rst_n) begin + s_p5_previous_enable = 0; + s_p5_window = 0; + end else if ($test$plusargs("tiny_r2_p5")) begin + if (s_p5_enable && !s_p5_previous_enable) begin + s_p5_window++; + s_p5_overlap = 0; + s_p5_beat_overlap = 0; + s_p5_active_max = 0; + end + if (s_p5_enable) begin + if (p5_active_targets(u_dut.u_soc.u_fabric.target_idle_o) > s_p5_active_max) + s_p5_active_max = p5_active_targets(u_dut.u_soc.u_fabric.target_idle_o); + if (u_dut.u_soc.u_fabric.s_busy_q[2] && u_dut.u_soc.u_fabric.s_busy_q[3]) s_p5_overlap++; + if (u_dut.u_soc.u_masters_axi4_if[1].rvalid && u_dut.u_soc.u_masters_axi4_if[1].rready && + u_dut.u_soc.u_masters_axi4_if[1].wvalid && u_dut.u_soc.u_masters_axi4_if[1].wready) + s_p5_beat_overlap++; + end + if (!s_p5_enable && s_p5_previous_enable) + $display( + "R2_P5_FABRIC window=%0d active_targets_max=%0d dma_rw_overlap_cycles=%0d dma_rw_beat_overlap_cycles=%0d", + s_p5_window, + s_p5_active_max, + s_p5_overlap, + s_p5_beat_overlap + ); + s_p5_previous_enable = s_p5_enable; + end + end + // Verification-only per-group issue/conflict accounting. A bank conflict is // two or more eligible macro clients in one cycle, not an AXI address stall. for (genvar group = 0; group < 4; group++) begin : gen_bank_observer diff --git a/rtl/tiny/filelist/top.fl b/rtl/tiny/filelist/top.fl index dd87d59b..5ea33070 100644 --- a/rtl/tiny/filelist/top.fl +++ b/rtl/tiny/filelist/top.fl @@ -1,4 +1,6 @@ +/top/tiny_axi4_target.sv /top/tiny_axi4_fabric.sv +/top/tiny_fault_collector.sv /top/tiny_axi42apb4.sv /top/tiny_sysctrl.sv /top/tiny_archinfo.sv @@ -6,7 +8,6 @@ /top/tiny_sram_port_if.sv /top/tiny_sram_group.sv /top/tiny_sram_axi4.sv -/top/tiny_sram_axi4_demux.sv /top/tiny_sram.sv /top/tiny_cpu_mem.sv /top/tiny_cpu_wrapper.sv diff --git a/rtl/tiny/integration/clock_reset_domains.json b/rtl/tiny/integration/clock_reset_domains.json index 1c692f05..3f940e6b 100644 --- a/rtl/tiny/integration/clock_reset_domains.json +++ b/rtl/tiny/integration/clock_reset_domains.json @@ -1,5 +1,15 @@ { "schema_version": 2, + "external_fabric": { + "phase": "TINY-R2-P5", + "clock_domain": "system", + "target_controllers": "u_soc.u_fabric.gen_target[%d].u_controller", + "target_count": 23, + "apb_bridges": "u_soc.u__bridge; corresponding existing peripheral reset leaf", + "fabric_reset": "fabric leaf; target reset blocks admission and aborts accepted target work with DECERR", + "hart_reset": "CPU-only accepted-work drain; DMA and all target responses stay active", + "runtime_peripheral_reset": "not exposed; quiescence-controlled RCU remains P7" + }, "local_memory": { "phase": "TINY-R2-P4", "clock_domain": "system", diff --git a/rtl/tiny/integration/soc_topology.json b/rtl/tiny/integration/soc_topology.json index 9f9795bd..27f27350 100644 --- a/rtl/tiny/integration/soc_topology.json +++ b/rtl/tiny/integration/soc_topology.json @@ -8,7 +8,21 @@ "macro_bytes": 4096, "base": "0x30000000", "macro_instance": "u_soc.u_sram.gen_group[{group}].u_group.gen_bank[{bank}].u_ram.u_mem", - "external_attachment": "serialized-pre-P5" + "external_attachment": "per-target-P5" + }, + "fabric": { + "owners": ["cpu_slow", "central_dma", "sdio_reserved"], + "active_owners": [0, 1], + "cpu_combined_outstanding": 1, + "dma_direction_outstanding": 1, + "sdio_direction_outstanding": 1, + "target_combined_outstanding": 1, + "sram_targets": [0, 1, 2, 3], + "xpi_target": 4, + "apb_target_base": 5, + "decode_error_target": 21, + "protocol_error_target": 22, + "target_count": 23 }, "gpio_pins": 32, "irq_vector_width": 32, diff --git a/rtl/tiny/top/retrosoc_tiny.sv b/rtl/tiny/top/retrosoc_tiny.sv index 9227531a..bb07d932 100644 --- a/rtl/tiny/top/retrosoc_tiny.sv +++ b/rtl/tiny/top/retrosoc_tiny.sv @@ -23,14 +23,21 @@ module retrosoc_tiny ( 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_master_rst_n [ 3]; + logic s_target_rst_n [23]; logic s_debug_halted; logic s_core_rst_n, s_cpu_bus_idle, s_cpu_quiesce; logic [31:0] s_irq; - logic s_fault_valid, s_fault_master, s_fault_write; - logic [31:0] s_fault_addr; - logic [ 1:0] s_fault_resp; + logic s_fault_write; + logic [ 1:0] s_fault_master; + logic [ 3:0] s_fault_count; + logic [5:0] s_external_fault_valid, s_external_fault_instruction; + logic [5:0][31:0] s_external_fault_addr; + logic [5:0][ 1:0] s_external_fault_resp; + logic [1:0] s_local_fault_valid, s_local_fault_write; + logic [ 1:0][31:0] s_local_fault_addr; + logic [31:0] s_fault_addr; + logic [ 1:0] s_fault_resp; logic s_perf_enable, s_perf_clear; logic [63:0] s_cpu_wait, s_dma_wait; logic s_timer0_irq, s_timer1_irq, s_xpi_irq, s_dma_irq; @@ -78,7 +85,7 @@ uart_if u_uart0_if (); .DATA_WIDTH(32), .ID_WIDTH (1), .USER_WIDTH(1) - ) u_masters_axi4_if[2] ( + ) u_masters_axi4_if[3] ( .aclk (clk_i), .aresetn(s_master_rst_n) ); @@ -87,7 +94,7 @@ uart_if u_uart0_if (); .DATA_WIDTH(32), .ID_WIDTH (1), .USER_WIDTH(1) - ) u_targets_axi4_if[5] ( + ) u_targets_axi4_if[23] ( .aclk (clk_i), .aresetn(s_target_rst_n) ); @@ -121,15 +128,44 @@ uart_if u_uart0_if (); .aclk (clk_i), .aresetn(s_dma_rst_n) ); - 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 - }; + assign s_fabric_rst_n = s_leaf_rst_n[0]; + assign s_master_rst_n = '{s_cpu_ready_rst_n, s_dma_rst_n, s_fabric_rst_n}; + assign s_target_rst_n[4] = s_xpi_rst_n; + assign s_target_rst_n[21] = s_fabric_rst_n; + assign s_target_rst_n[22] = s_fabric_rst_n; + // Owner 2 is a reserved native read/write ingress, not an integrated SDIO. + // P6 supplies its real producer; no capability or interrupt is enabled here. + assign u_masters_axi4_if[2].awid = '0; + assign u_masters_axi4_if[2].awaddr = '0; + assign u_masters_axi4_if[2].awlen = '0; + assign u_masters_axi4_if[2].awsize = '0; + assign u_masters_axi4_if[2].awburst = '0; + assign u_masters_axi4_if[2].awlock = 1'b0; + assign u_masters_axi4_if[2].awcache = '0; + assign u_masters_axi4_if[2].awprot = '0; + assign u_masters_axi4_if[2].awqos = '0; + assign u_masters_axi4_if[2].awregion = '0; + assign u_masters_axi4_if[2].awuser = '0; + assign u_masters_axi4_if[2].awvalid = 1'b0; + assign u_masters_axi4_if[2].arid = '0; + assign u_masters_axi4_if[2].araddr = '0; + assign u_masters_axi4_if[2].arlen = '0; + assign u_masters_axi4_if[2].arsize = '0; + assign u_masters_axi4_if[2].arburst = '0; + assign u_masters_axi4_if[2].arlock = 1'b0; + assign u_masters_axi4_if[2].arcache = '0; + assign u_masters_axi4_if[2].arprot = '0; + assign u_masters_axi4_if[2].arqos = '0; + assign u_masters_axi4_if[2].arregion = '0; + assign u_masters_axi4_if[2].aruser = '0; + assign u_masters_axi4_if[2].arvalid = 1'b0; + assign u_masters_axi4_if[2].wdata = '0; + assign u_masters_axi4_if[2].wstrb = '0; + assign u_masters_axi4_if[2].wlast = 1'b0; + assign u_masters_axi4_if[2].wuser = '0; + assign u_masters_axi4_if[2].wvalid = 1'b0; + assign u_masters_axi4_if[2].rready = 1'b0; + assign u_masters_axi4_if[2].bready = 1'b0; tiny_reset_tree u_reset_tree ( .clk_i (clk_i), @@ -170,35 +206,41 @@ uart_if u_uart0_if (); .cpu (u_cpu_ahbl_if) ); tiny_cpu_mem u_cpu_mem ( - .clk_i (clk_i), - .rst_n_i (s_core_rst_n), - .quiesce_i (s_cpu_quiesce), - .idle_o (s_cpu_bus_idle), - .cpu (u_cpu_ahbl_if), - .local_ports(u_cpu_local_if), - .slow_axi4 (u_masters_axi4_if[0]) + .clk_i (clk_i), + .rst_n_i (s_core_rst_n), + .quiesce_i (s_cpu_quiesce), + .idle_o (s_cpu_bus_idle), + .local_fault_valid_o(s_local_fault_valid), + .local_fault_addr_o (s_local_fault_addr), + .local_fault_write_o(s_local_fault_write), + .cpu (u_cpu_ahbl_if), + .local_ports (u_cpu_local_if), + .slow_axi4 (u_masters_axi4_if[0]) ); tiny_axi4_fabric u_fabric ( - .clk_i (clk_i), - .rst_n_i (s_fabric_rst_n), - .masters (u_masters_axi4_if), - .targets (u_targets_axi4_if), - .perf_enable_i (s_perf_enable), - .perf_clear_i (s_perf_clear), - .fault_valid_o (s_fault_valid), - .fault_addr_o (s_fault_addr), - .fault_master_o(s_fault_master), - .fault_write_o (s_fault_write), - .fault_resp_o (s_fault_resp), - .cpu_wait_o (s_cpu_wait), - .dma_wait_o (s_dma_wait) - ); - tiny_sram_axi4_demux u_sram_attachment ( - .clk_i (clk_i), - .rst_n_i (s_sram_rst_n), - .upstream(u_targets_axi4_if[0]), - .groups (u_sram_axi4_if) + .clk_i(clk_i), + .rst_n_i(s_fabric_rst_n), + .target_rst_n_i(s_target_rst_n), + .masters(u_masters_axi4_if), + .targets(u_targets_axi4_if), + .perf_enable_i(s_perf_enable), + .perf_clear_i(s_perf_clear), + .fault_valid_o(s_external_fault_valid), + .fault_addr_o(s_external_fault_addr), + .fault_resp_o(s_external_fault_resp), + .fault_instruction_o(s_external_fault_instruction), + .owner_idle_o(), // Reserved lifecycle observations; RCU remains P7. + .target_idle_o(), // The P5 testbench checks all accepted-work barriers. + .cpu_wait_o(s_cpu_wait), + .dma_wait_o(s_dma_wait) ); + for (genvar group = 0; group < 4; group++) begin : gen_sram_attachment + assign s_target_rst_n[group] = s_sram_rst_n; + axi4_connector u_connector ( + .source(u_targets_axi4_if[group]), + .sink (u_sram_axi4_if[group]) + ); + end tiny_sram u_sram ( .clk_i (clk_i), .rst_n_i (s_sram_rst_n), @@ -211,7 +253,7 @@ uart_if u_uart0_if (); apb4_xpi u_xpi ( .clk_i (clk_i), .rst_n_i (s_xpi_rst_n), - .mem_axi4 (u_targets_axi4_if[1]), + .mem_axi4 (u_targets_axi4_if[4]), .apb4 (u_xpi_apb4_if), .xpi (xpi), .dma_xfer_done_i(s_dma_done), @@ -219,31 +261,41 @@ uart_if u_uart0_if (); .dma_rx_stall_o (s_dma_stall[1]), .irq_o (s_xpi_irq) ); - tiny_axi42apb4 u_apb_bridge ( - .clk_i (clk_i), - .rst_n_i(s_fabric_rst_n), - .axi4 (u_targets_axi4_if[2]), - `include "tiny_apb_connections.svh" - ); - axi4_error_slave #( + `include "tiny_apb_bridges.svh" +axi4_error_slave #( .Response(2'b11) ) u_decode_error ( .clk_i (clk_i), .rst_n_i(s_fabric_rst_n), - .axi4 (u_targets_axi4_if[3]) + .axi4 (u_targets_axi4_if[21]) ); axi4_error_slave #( .Response(2'b10) ) u_protocol_error ( .clk_i (clk_i), .rst_n_i(s_fabric_rst_n), - .axi4 (u_targets_axi4_if[4]) + .axi4 (u_targets_axi4_if[22]) + ); + tiny_fault_collector u_fault_collector ( + .local_valid_i (s_local_fault_valid), + .local_addr_i (s_local_fault_addr), + .local_write_i (s_local_fault_write), + .external_valid_i (s_external_fault_valid), + .external_addr_i (s_external_fault_addr), + .external_resp_i (s_external_fault_resp), + .external_instruction_i(s_external_fault_instruction), + .count_o (s_fault_count), + .addr_o (s_fault_addr), + .master_o (s_fault_master), + .write_o (s_fault_write), + .resp_o (s_fault_resp) ); tiny_sysctrl u_sysctrl ( .clk_i (clk_i), .rst_n_i (s_sysctrl_rst_n), .apb4 (u_sysctrl_apb4_if), - .fault_valid_i (s_fault_valid), + .fault_valid_i (s_fault_count != 4'd0), + .fault_count_i (s_fault_count), .fault_addr_i (s_fault_addr), .fault_write_i (s_fault_write), .fault_master_i(s_fault_master), diff --git a/rtl/tiny/top/tiny_axi42apb4.sv b/rtl/tiny/top/tiny_axi42apb4.sv index 4cd6fdc3..6d055d14 100644 --- a/rtl/tiny/top/tiny_axi42apb4.sv +++ b/rtl/tiny/top/tiny_axi42apb4.sv @@ -4,10 +4,10 @@ // APB setup/access are separate cycles; rejected bursts never assert PSEL. module tiny_axi42apb4 ( - input logic clk_i, - input logic rst_n_i, - axi4_if.slave axi4, - `include "tiny_apb_ports.svh" + input logic clk_i, + input logic rst_n_i, + axi4_if.slave axi4, + apb4_if.master apb4 ); typedef enum logic [2:0] { Idle, @@ -30,8 +30,6 @@ module tiny_axi42apb4 ( logic [1:0] s_resp_d, s_resp_q; logic [7:0] s_len_d, s_len_q; logic [7:0] s_beat_d, s_beat_q; - logic [15:0] s_select, s_ready, s_error; - logic [15:0][31:0] s_rdata; logic s_access, s_enable, s_done, s_err; logic [31:0] s_read_data; @@ -51,18 +49,18 @@ module tiny_axi42apb4 ( assign axi4.rvalid = (s_state_q == ReadResponse) || (s_state_q == ReadError); assign s_access = (s_state_q == Setup) || (s_state_q == Access); assign s_enable = s_state_q == Access; - assign s_done = |(s_select & s_ready); - assign s_err = |(s_select & s_error); - - always_comb begin - s_select = '0; - `include "tiny_apb_decode.svh" -s_read_data = '0; - for (int unsigned target = 0; target < 16; target++) begin - if (s_select[target]) s_read_data = s_rdata[target]; - end - end - `include "tiny_apb_wiring.svh" + // The fabric owns peripheral decoding. Each bridge owns only this APB + // endpoint, including its setup/access and retained response state. + assign s_done = apb4.pready; + assign s_err = apb4.pslverr; + assign s_read_data = apb4.prdata; + assign apb4.paddr = s_addr_q; + assign apb4.pwrite = s_write_q; + assign apb4.pwdata = s_wdata_q; + assign apb4.pstrb = s_wstrb_q; + assign apb4.pprot = 3'd0; + assign apb4.psel = s_access; + assign apb4.penable = s_enable; always_comb begin s_state_d = s_state_q; @@ -111,12 +109,7 @@ s_read_data = '0; s_state_d = WriteResponse; end else s_state_d = Setup; end - Setup: begin - if (s_select == 16'd0) begin - s_resp_d = 2'b11; - s_state_d = s_write_q ? WriteResponse : ReadResponse; - end else s_state_d = Access; - end + Setup: s_state_d = Access; Access: if (s_done) begin s_resp_d = s_err ? 2'b10 : 2'b00; diff --git a/rtl/tiny/top/tiny_axi4_fabric.sv b/rtl/tiny/top/tiny_axi4_fabric.sv index 7b84e24b..09cd698f 100644 --- a/rtl/tiny/top/tiny_axi4_fabric.sv +++ b/rtl/tiny/top/tiny_axi4_fabric.sv @@ -1,304 +1,272 @@ // Copyright (c) 2026 Yuchi Miao // SPDX-License-Identifier: MulanPSL-2.0 - `include "mmap_define.svh" -// One accepted transaction globally. AW and W remain independent: W is -// backpressured until the saved AW has reached its target. The owner and all -// address attributes remain registered until the terminal response handshake. +// Three reserved owner slots, 23 independently owned targets. CPU admits one +// combined operation; DMA and SDIO each admit one read and one write. IDs are +// unchanged: saved target ownership, not ID expansion, routes all responses. module tiny_axi4_fabric ( - input logic clk_i, - input logic rst_n_i, - input logic perf_enable_i, - input logic perf_clear_i, - axi4_if.slave masters [2], - axi4_if.master targets [5], - output logic fault_valid_o, - output logic [31:0] fault_addr_o, - output logic fault_master_o, - output logic fault_write_o, - output logic [ 1:0] fault_resp_o, - output logic [63:0] cpu_wait_o, - output logic [63:0] dma_wait_o + input logic clk_i, + input logic rst_n_i, + input logic perf_enable_i, + input logic perf_clear_i, + input logic target_rst_n_i [23], + axi4_if.slave masters [ 3], + axi4_if.master targets [23], + output logic [ 5:0] fault_valid_o, + output logic [ 5:0][31:0] fault_addr_o, + output logic [ 5:0][ 1:0] fault_resp_o, + output logic [ 5:0] fault_instruction_o, + output logic [ 2:0] owner_idle_o, + output logic [22:0] target_idle_o, + output logic [63:0] cpu_wait_o, + output logic [63:0] dma_wait_o ); - typedef enum logic [1:0] { - Idle, - Addr, - Response - } state_e; - typedef struct packed { - logic [31:0] addr; - logic [7:0] len; - logic [2:0] size; - logic [1:0] burst; - logic lock; - logic [3:0] cache; - logic [2:0] prot; - logic [3:0] qos; - logic [3:0] region; - logic id; - logic user; - } addr_t; - state_e s_state_d, s_state_q; - logic [1:0] s_state_bits_q; - addr_t s_addr_d, s_addr_q; - addr_t s_read_addr [2]; - addr_t s_write_addr [2]; - addr_t s_selected_addr; - logic s_owner_d, s_owner_q; - logic s_write_d, s_write_q; - logic [2:0] s_tgt_d, s_tgt_q; - logic [3:0] s_request, s_unused_grant; - logic [ 1:0] s_selected; - logic s_selected_valid; - logic s_accept; - logic s_terminal; - logic [32:0] s_last_addr; - logic [ 2:0] s_decoded_target; - logic s_protocol_legal; - logic s_fetch_denied; - logic [1:0] s_awvalid, s_arvalid, s_wvalid, s_wlast, s_bready, s_rready; - logic [1:0][31:0] s_wdata; - logic [1:0][ 3:0] s_wstrb; - logic [1:0] s_wuser; - logic [4:0] s_target_awready, s_target_arready, s_target_wready; - logic [4:0] s_target_bvalid, s_target_rvalid, s_target_rlast; - logic [4:0] s_target_bid, s_target_rid, s_target_buser, s_target_ruser; - logic [4:0][1:0] s_target_bresp, s_target_rresp; - logic [4:0][31:0] s_target_rdata; + logic [5:0] s_busy_d, s_busy_q, s_admit, s_done, s_eligible; + logic s_cpu_prefer_write_d, s_cpu_prefer_write_q; + logic [2:0][4:0] s_read_target, s_write_target; + logic [22:0][2:0] s_arready, s_awready, s_wready, s_rvalid, s_bvalid; + logic [22:0][ 2:0][36:0] s_rdata; + logic [22:0][ 2:0][ 3:0] s_bdata; + logic [ 2:0][36:0] s_read_return; + logic [ 2:0][ 3:0] s_write_return; + logic [2:0] s_master_arready, s_master_awready, s_master_wready, s_master_rvalid, s_master_bvalid; + logic [2:0] s_arvalid, s_awvalid; logic [1:0][63:0] s_wait_d, s_wait_q; + logic [22:0] s_fault_valid, s_fault_write, s_fault_instruction; + logic [22:0][31:0] s_fault_addr; + logic [22:0][1:0] s_fault_master, s_fault_resp; - function automatic logic [2:0] decode_target(input logic [31:0] addr_i); - if (`SOC_ADDR_IS_SRAM(addr_i)) return 3'd0; - if (`SOC_ADDR_IS_FLASH(addr_i) || `SOC_ADDR_IS_XPI(addr_i)) return 3'd1; - if (`SOC_ADDR_IS_APB4_PERIPH(addr_i) || `SOC_ADDR_IS_APB4_SYSTEM(addr_i)) return 3'd2; - return 3'd3; + function automatic logic [4:0] decode_target(input logic [31:0] addr_i); + if (`SOC_ADDR_IS_SRAM(addr_i)) return {3'd0, addr_i[16:15]}; + if (`SOC_ADDR_IS_FLASH(addr_i) || `SOC_ADDR_IS_XPI(addr_i)) return 5'd4; + `include "tiny_fabric_decode.svh" + return 5'd21; + endfunction + function automatic logic [4:0] checked_target(input logic [31:0] addr_i, input logic [7:0] len_i, + input logic [2:0] size_i, input logic [1:0] burst_i, + input logic lock_i, input logic instruction_i); + logic [32:0] last_addr; + logic [ 4:0] decoded; + decoded = decode_target(addr_i); + last_addr = {1'b0, addr_i} + (({25'd0, len_i} + 33'd1) << size_i) - 33'd1; + if (lock_i || (size_i > 3'd2) || (len_i > 8'd15) || + ((addr_i & ((32'd1 << size_i) - 32'd1)) != 32'd0) || + !((burst_i == 2'b01) || ((burst_i == 2'b00) && (len_i == 8'd0))) || + last_addr[32] || (last_addr[31:12] != addr_i[31:12]) || + (decode_target( + last_addr[31:0] + ) != decoded) || ((decoded >= 5'd5) && (decoded <= 5'd20) && (len_i != 8'd0))) + return 5'd22; + if (instruction_i && !( + `SOC_ADDR_IS_FLASH(addr_i) + || ((addr_i >= 32'h5000_0000) && (addr_i < 32'h5400_0000)))) + return ((decoded >= 5'd5) && (decoded <= 5'd20)) ? 5'd22 : 5'd21; + return decoded; endfunction - assign s_state_q = state_e'(s_state_bits_q); - assign s_request = {s_awvalid[1], s_arvalid[1], s_awvalid[0], s_arvalid[0]}; - assign s_selected_addr = s_selected[0] ? s_write_addr[s_selected[1]] : s_read_addr[s_selected[1]]; - assign s_decoded_target = decode_target(s_selected_addr.addr); - assign s_last_addr = {1'b0, s_selected_addr.addr} + - (({25'd0, s_selected_addr.len} + 33'd1) << s_selected_addr.size) - 33'd1; - assign s_protocol_legal = !s_selected_addr.lock && (s_selected_addr.size <= 3'd2) && - ((s_selected_addr.addr & ((32'd1 << s_selected_addr.size) - 32'd1)) == 32'd0) && - ((s_selected_addr.burst == 2'b01) || - ((s_selected_addr.burst == 2'b00) && (s_selected_addr.len == 8'd0))) && - (s_selected_addr.len <= 8'd15) && !s_last_addr[32] && - (s_last_addr[31:12] == s_selected_addr.addr[31:12]) && - (decode_target( - s_last_addr[31:0] - ) == s_decoded_target) && ((s_decoded_target != 3'd2) || (s_selected_addr.len == 8'd0)); - assign s_accept = (s_state_q == Idle) && s_selected_valid; - // The tagged CPU slow ingress preserves instruction origin. Reject fetches - // outside the read-idempotent NOR apertures before reaching any APB target. - assign s_fetch_denied = !s_selected[1] && !s_selected[0] && s_selected_addr.prot[2] && !( - `SOC_ADDR_IS_FLASH(s_selected_addr.addr) - || ((s_selected_addr.addr >= 32'h5000_0000) && (s_selected_addr.addr < 32'h5400_0000))); - assign s_terminal = (s_state_q == Response) && - (s_write_q ? (s_target_bvalid[s_tgt_q] && s_bready[s_owner_q]) : - (s_target_rvalid[s_tgt_q] && s_rready[s_owner_q] && s_target_rlast[s_tgt_q])); - assign fault_resp_o = s_write_q ? s_target_bresp[s_tgt_q] : s_target_rresp[s_tgt_q]; - assign fault_valid_o = s_terminal && (fault_resp_o != 2'b00); - assign fault_addr_o = s_addr_q.addr; - assign fault_master_o = s_owner_q; - assign fault_write_o = s_write_q; - assign cpu_wait_o = s_wait_q[0]; - assign dma_wait_o = s_wait_q[1]; - - // Advance on acceptance: the chosen owner is saved until completion, so the - // next grant after completion starts after this source/direction. - round_robin_arbiter #( - .CLIENTS(4) - ) u_arbiter ( - .clk_i (clk_i), - .rst_n_i (rst_n_i), - .advance_i (s_accept), - .request_i (s_request), - .grant_o (s_unused_grant), - .selected_o(s_selected), - .valid_o (s_selected_valid) - ); - - always_comb begin - s_state_d = s_state_q; - s_owner_d = s_owner_q; - s_write_d = s_write_q; - s_addr_d = s_addr_q; - s_tgt_d = s_tgt_q; - unique case (s_state_q) - Idle: - if (s_accept) begin - s_owner_d = s_selected[1]; - s_write_d = s_selected[0]; - s_addr_d = s_selected_addr; - s_tgt_d = !s_protocol_legal ? 3'd4 : - s_fetch_denied ? ((s_decoded_target == 3'd2) ? 3'd4 : 3'd3) : s_decoded_target; - s_state_d = Addr; - end - Addr: - if (s_write_q ? s_target_awready[s_tgt_q] : s_target_arready[s_tgt_q]) s_state_d = Response; - Response: if (s_terminal) s_state_d = Idle; - default: s_state_d = Idle; - endcase - s_wait_d = s_wait_q; - for (int unsigned master = 0; master < 2; master++) begin - if (perf_clear_i) s_wait_d[master] = '0; - else if (perf_enable_i && (s_awvalid[master] || s_arvalid[master]) && - !(s_accept && (s_selected[1] == 1'(master))) && - (s_wait_q[master] != 64'hffff_ffff_ffff_ffff)) - s_wait_d[master] = s_wait_q[master] + 64'd1; - end - end - for (genvar master = 0; master < 2; master++) begin : gen_master - assign s_write_addr[master] = { - masters[master].awaddr, - masters[master].awlen, - masters[master].awsize, - masters[master].awburst, - masters[master].awlock, - masters[master].awcache, - masters[master].awprot, - masters[master].awqos, - masters[master].awregion, - masters[master].awid, - masters[master].awuser - }; - assign s_read_addr[master] = { - masters[master].araddr, - masters[master].arlen, - masters[master].arsize, - masters[master].arburst, - masters[master].arlock, - masters[master].arcache, - masters[master].arprot, - masters[master].arqos, - masters[master].arregion, - masters[master].arid, - masters[master].aruser - }; - assign s_awvalid[master] = masters[master].awvalid; - assign s_arvalid[master] = masters[master].arvalid; - assign s_wdata[master] = masters[master].wdata; - assign s_wstrb[master] = masters[master].wstrb; - assign s_wlast[master] = masters[master].wlast; - assign s_wuser[master] = masters[master].wuser; - assign s_wvalid[master] = masters[master].wvalid; - assign s_bready[master] = masters[master].bready; - assign s_rready[master] = masters[master].rready; - assign masters[master].awready = s_accept && (s_selected == {1'(master), 1'b1}); - assign masters[master].arready = s_accept && (s_selected == {1'(master), 1'b0}); - assign masters[master].wready = (s_state_q == Response) && s_write_q && - (s_owner_q == 1'(master)) && s_target_wready[s_tgt_q]; - assign masters[master].bvalid = (s_state_q == Response) && s_write_q && - (s_owner_q == 1'(master)) && s_target_bvalid[s_tgt_q]; - assign masters[master].rvalid = (s_state_q == Response) && !s_write_q && - (s_owner_q == 1'(master)) && s_target_rvalid[s_tgt_q]; - assign masters[master].bid = s_target_bid[s_tgt_q]; - assign masters[master].bresp = s_target_bresp[s_tgt_q]; - assign masters[master].buser = s_target_buser[s_tgt_q]; - assign masters[master].rid = s_target_rid[s_tgt_q]; - assign masters[master].rdata = s_target_rdata[s_tgt_q]; - assign masters[master].rresp = s_target_rresp[s_tgt_q]; - assign masters[master].rlast = s_target_rlast[s_tgt_q]; - assign masters[master].ruser = s_target_ruser[s_tgt_q]; - end - for (genvar target = 0; target < 5; target++) begin : gen_target - assign targets[target].awaddr = s_addr_q.addr; - assign targets[target].awlen = s_addr_q.len; - assign targets[target].awsize = s_addr_q.size; - assign targets[target].awburst = s_addr_q.burst; - assign targets[target].awlock = s_addr_q.lock; - assign targets[target].awcache = s_addr_q.cache; - assign targets[target].awprot = s_addr_q.prot; - assign targets[target].awqos = s_addr_q.qos; - assign targets[target].awregion = s_addr_q.region; - assign targets[target].awid = s_addr_q.id; - assign targets[target].awuser = s_addr_q.user; - assign targets[target].awvalid = (s_state_q == Addr) && s_write_q && (s_tgt_q == 3'(target)); - assign targets[target].araddr = s_addr_q.addr; - assign targets[target].arlen = s_addr_q.len; - assign targets[target].arsize = s_addr_q.size; - assign targets[target].arburst = s_addr_q.burst; - assign targets[target].arlock = s_addr_q.lock; - assign targets[target].arcache = s_addr_q.cache; - assign targets[target].arprot = s_addr_q.prot; - assign targets[target].arqos = s_addr_q.qos; - assign targets[target].arregion = s_addr_q.region; - assign targets[target].arid = s_addr_q.id; - assign targets[target].aruser = s_addr_q.user; - assign targets[target].arvalid = (s_state_q == Addr) && !s_write_q && (s_tgt_q == 3'(target)); - assign targets[target].wdata = s_wdata[s_owner_q]; - assign targets[target].wstrb = s_wstrb[s_owner_q]; - assign targets[target].wlast = s_wlast[s_owner_q]; - assign targets[target].wuser = s_wuser[s_owner_q]; - assign targets[target].wvalid = (s_state_q == Response) && s_write_q && - (s_tgt_q == 3'(target)) && s_wvalid[s_owner_q]; - assign targets[target].bready = (s_state_q == Response) && s_write_q && - (s_tgt_q == 3'(target)) && s_bready[s_owner_q]; - assign targets[target].rready = (s_state_q == Response) && !s_write_q && - (s_tgt_q == 3'(target)) && s_rready[s_owner_q]; - assign s_target_awready[target] = targets[target].awready; - assign s_target_arready[target] = targets[target].arready; - assign s_target_wready[target] = targets[target].wready; - assign s_target_bvalid[target] = targets[target].bvalid; - assign s_target_bid[target] = targets[target].bid; - assign s_target_bresp[target] = targets[target].bresp; - assign s_target_buser[target] = targets[target].buser; - assign s_target_rvalid[target] = targets[target].rvalid; - assign s_target_rid[target] = targets[target].rid; - assign s_target_rresp[target] = targets[target].rresp; - assign s_target_rdata[target] = targets[target].rdata; - assign s_target_rlast[target] = targets[target].rlast; - assign s_target_ruser[target] = targets[target].ruser; - end + assign s_busy_d = (s_busy_q & ~s_done) | s_admit; + assign s_cpu_prefer_write_d = s_admit[0] ? 1'b1 : s_admit[1] ? 1'b0 : s_cpu_prefer_write_q; + assign cpu_wait_o = s_wait_q[0]; + assign dma_wait_o = s_wait_q[1]; dffr #( - .DATA_WIDTH(2) - ) u_state_reg ( + .DATA_WIDTH(6) + ) u_direction_busy ( .clk_i (clk_i), .rst_n_i(rst_n_i), - .dat_i (s_state_d), - .dat_o (s_state_bits_q) + .dat_i (s_busy_d), + .dat_o (s_busy_q) ); dffr #( .DATA_WIDTH(1) - ) u_owner_reg ( + ) u_cpu_direction ( .clk_i (clk_i), .rst_n_i(rst_n_i), - .dat_i (s_owner_d), - .dat_o (s_owner_q) - ); - dffr #( - .DATA_WIDTH(1) - ) u_write_reg ( - .clk_i (clk_i), - .rst_n_i(rst_n_i), - .dat_i (s_write_d), - .dat_o (s_write_q) - ); - dffr #( - .DATA_WIDTH(63) - ) u_addr_reg ( - .clk_i (clk_i), - .rst_n_i(rst_n_i), - .dat_i (s_addr_d), - .dat_o (s_addr_q) - ); - dffr #( - .DATA_WIDTH(3) - ) u_target_reg ( - .clk_i (clk_i), - .rst_n_i(rst_n_i), - .dat_i (s_tgt_d), - .dat_o (s_tgt_q) + .dat_i (s_cpu_prefer_write_d), + .dat_o (s_cpu_prefer_write_q) ); dffr #( .DATA_WIDTH(128) - ) u_wait_reg ( + ) u_wait_counters ( .clk_i (clk_i), .rst_n_i(rst_n_i), .dat_i (s_wait_d), .dat_o (s_wait_q) ); + + always_comb begin + s_master_arready = '0; + s_master_awready = '0; + s_master_wready = '0; + s_master_rvalid = '0; + s_master_bvalid = '0; + s_read_return = '0; + s_write_return = '0; + fault_valid_o = '0; + fault_addr_o = '0; + fault_resp_o = '0; + fault_instruction_o = '0; + for (int target_index = 0; target_index < 23; target_index++) begin + s_master_arready |= s_arready[target_index]; + s_master_awready |= s_awready[target_index]; + s_master_wready |= s_wready[target_index]; + s_master_rvalid |= s_rvalid[target_index]; + s_master_bvalid |= s_bvalid[target_index]; + for (int owner = 0; owner < 3; owner++) begin + if (s_rvalid[target_index][owner]) s_read_return[owner] = s_rdata[target_index][owner]; + if (s_bvalid[target_index][owner]) s_write_return[owner] = s_bdata[target_index][owner]; + end + if (s_fault_valid[target_index]) begin + fault_valid_o[{s_fault_master[target_index], s_fault_write[target_index]}] = 1'b1; + fault_addr_o[{ + s_fault_master[target_index], s_fault_write[target_index] + }] = s_fault_addr[target_index]; + fault_resp_o[{ + s_fault_master[target_index], s_fault_write[target_index] + }] = s_fault_resp[target_index]; + fault_instruction_o[{ + s_fault_master[target_index], s_fault_write[target_index] + }] = s_fault_instruction[target_index]; + end + end + s_wait_d = s_wait_q; + for (int owner = 0; owner < 2; owner++) begin + if (perf_clear_i) s_wait_d[owner] = '0; + else if (perf_enable_i && (s_arvalid[owner] || s_awvalid[owner]) && + !(s_admit[2*owner] || s_admit[2*owner+1]) && (s_wait_q[owner] != 64'hffff_ffff_ffff_ffff)) + s_wait_d[owner] = s_wait_q[owner] + 64'd1; + end + end + for (genvar owner = 0; owner < 3; owner++) begin : gen_owner + assign s_arvalid[owner] = masters[owner].arvalid; + assign s_awvalid[owner] = masters[owner].awvalid; + assign owner_idle_o[owner] = !(s_busy_q[2*owner] || s_busy_q[2*owner+1]); + assign s_read_target[owner] = checked_target( + masters[owner].araddr, + masters[owner].arlen, + masters[owner].arsize, + masters[owner].arburst, + masters[owner].arlock, + (owner == 0) && masters[owner].arprot[2] + ); + assign s_write_target[owner] = checked_target( + masters[owner].awaddr, + masters[owner].awlen, + masters[owner].awsize, + masters[owner].awburst, + masters[owner].awlock, + 1'b0 + ); + if (owner == 0) begin : gen_combined_credit + assign s_eligible[2*owner] = rst_n_i && owner_idle_o[owner] && masters[owner].arvalid && + (!masters[owner].awvalid || !s_cpu_prefer_write_q); + assign s_eligible[2*owner+1] = rst_n_i && owner_idle_o[owner] && masters[owner].awvalid && + (!masters[owner].arvalid || s_cpu_prefer_write_q); + end else begin : gen_direction_credits + assign s_eligible[2*owner] = rst_n_i && !s_busy_q[2*owner] && masters[owner].arvalid; + assign s_eligible[2*owner+1] = rst_n_i && !s_busy_q[2*owner+1] && masters[owner].awvalid; + end + assign s_admit[2*owner] = masters[owner].arvalid && masters[owner].arready; + assign s_admit[2*owner+1] = masters[owner].awvalid && masters[owner].awready; + assign s_done[2*owner] = masters[owner].rvalid && masters[owner].rready && masters[owner].rlast; + assign s_done[2*owner+1] = masters[owner].bvalid && masters[owner].bready; + assign masters[owner].arready = s_master_arready[owner]; + assign masters[owner].awready = s_master_awready[owner]; + assign masters[owner].wready = s_master_wready[owner]; + assign masters[owner].rvalid = s_master_rvalid[owner]; + assign masters[owner].bvalid = s_master_bvalid[owner]; + assign {masters[owner].rid, masters[owner].rdata, masters[owner].rresp, + masters[owner].rlast, masters[owner].ruser} = s_read_return[owner]; + assign {masters[owner].bid, masters[owner].bresp, masters[owner].buser} = s_write_return[owner]; + end + for (genvar target_index = 0; target_index < 23; target_index++) begin : gen_target + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) u_routes[3] ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + for (genvar owner = 0; owner < 3; owner++) begin : gen_route + logic s_read_selected, s_write_selected; + assign s_read_selected = s_read_target[owner] == 5'(target_index); + assign s_write_selected = s_write_target[owner] == 5'(target_index); + assign u_routes[owner].arid = masters[owner].arid; + assign u_routes[owner].araddr = masters[owner].araddr; + assign u_routes[owner].arlen = masters[owner].arlen; + assign u_routes[owner].arsize = masters[owner].arsize; + assign u_routes[owner].arburst = masters[owner].arburst; + assign u_routes[owner].arlock = masters[owner].arlock; + assign u_routes[owner].arcache = masters[owner].arcache; + assign u_routes[owner].arprot = masters[owner].arprot; + assign u_routes[owner].arqos = masters[owner].arqos; + assign u_routes[owner].arregion = masters[owner].arregion; + assign u_routes[owner].aruser = masters[owner].aruser; + assign u_routes[owner].awid = masters[owner].awid; + assign u_routes[owner].awaddr = masters[owner].awaddr; + assign u_routes[owner].awlen = masters[owner].awlen; + assign u_routes[owner].awsize = masters[owner].awsize; + assign u_routes[owner].awburst = masters[owner].awburst; + assign u_routes[owner].awlock = masters[owner].awlock; + assign u_routes[owner].awcache = masters[owner].awcache; + assign u_routes[owner].awprot = masters[owner].awprot; + assign u_routes[owner].awqos = masters[owner].awqos; + assign u_routes[owner].awregion = masters[owner].awregion; + assign u_routes[owner].awuser = masters[owner].awuser; + assign u_routes[owner].arvalid = s_eligible[2*owner] && s_read_selected; + assign u_routes[owner].awvalid = s_eligible[2*owner+1] && s_write_selected; + assign u_routes[owner].wdata = masters[owner].wdata; + assign u_routes[owner].wstrb = masters[owner].wstrb; + assign u_routes[owner].wlast = masters[owner].wlast; + assign u_routes[owner].wuser = masters[owner].wuser; + assign u_routes[owner].wvalid = masters[owner].wvalid; + assign u_routes[owner].rready = masters[owner].rready; + assign u_routes[owner].bready = masters[owner].bready; + assign s_arready[target_index][owner] = u_routes[owner].arready; + assign s_awready[target_index][owner] = u_routes[owner].awready; + assign s_wready[target_index][owner] = u_routes[owner].wready; + assign s_rvalid[target_index][owner] = u_routes[owner].rvalid; + assign s_bvalid[target_index][owner] = u_routes[owner].bvalid; + assign s_rdata[target_index][owner] = { + u_routes[owner].rid, + u_routes[owner].rdata, + u_routes[owner].rresp, + u_routes[owner].rlast, + u_routes[owner].ruser + }; + assign s_bdata[target_index][owner] = { + u_routes[owner].bid, u_routes[owner].bresp, u_routes[owner].buser + }; + end + tiny_axi4_target u_controller ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .enable_i (target_rst_n_i[target_index]), + .masters (u_routes), + .target (targets[target_index]), + .idle_o (target_idle_o[target_index]), + .fault_valid_o (s_fault_valid[target_index]), + .fault_addr_o (s_fault_addr[target_index]), + .fault_master_o (s_fault_master[target_index]), + .fault_write_o (s_fault_write[target_index]), + .fault_instruction_o(s_fault_instruction[target_index]), + .fault_resp_o (s_fault_resp[target_index]) + ); + end +`ifdef HAVE_SVA + assert property (@(posedge clk_i) disable iff (!rst_n_i) !(s_busy_q[0] && s_busy_q[1])); + assert property (@(posedge clk_i) disable iff (!rst_n_i) (s_admit & s_busy_q) == 6'd0); + assert property (@(posedge clk_i) disable iff (!rst_n_i) (s_done & ~s_busy_q) == 6'd0); + for (genvar owner = 0; owner < 3; owner++) begin : gen_route_assert + logic [22:0] s_reads, s_writes; + for (genvar t = 0; t < 23; t++) begin : gen_column + assign s_reads[t] = s_rvalid[t][owner]; + assign s_writes[t] = s_bvalid[t][owner]; + end + assert property (@(posedge clk_i) disable iff (!rst_n_i) $onehot0(s_reads)); + assert property (@(posedge clk_i) disable iff (!rst_n_i) $onehot0(s_writes)); + end +`endif endmodule diff --git a/rtl/tiny/top/tiny_axi4_target.sv b/rtl/tiny/top/tiny_axi4_target.sv new file mode 100644 index 00000000..cf7cb84c --- /dev/null +++ b/rtl/tiny/top/tiny_axi4_target.sv @@ -0,0 +1,365 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// A single target owns one transaction through the upstream terminal handshake. +// Address capture reserves ownership even while the downstream target is stalled. +// Per-owner direction preference and owner round robin advance on admission. +module tiny_axi4_target ( + input logic clk_i, + input logic rst_n_i, + input logic enable_i, + axi4_if.slave masters [3], + axi4_if.master target, + output logic idle_o, + output logic fault_valid_o, + output logic [31:0] fault_addr_o, + output logic [ 1:0] fault_master_o, + output logic fault_write_o, + output logic fault_instruction_o, + output logic [ 1:0] fault_resp_o +); + typedef enum logic [1:0] { + Idle, + Address, + Response, + AbortResponse + } state_e; + typedef struct packed { + logic [31:0] addr; + logic [7:0] len; + logic [2:0] size; + logic [1:0] burst; + logic lock; + logic [3:0] cache; + logic [2:0] prot; + logic [3:0] qos; + logic [3:0] region; + logic id; + logic user; + } addr_t; + typedef struct packed { + state_e state; + logic [1:0] owner; + logic write; + addr_t addr; + logic [7:0] write_beat; + logic write_done; + logic [31:0] read_addr, error_addr; + logic [1:0] error_resp; + logic error_seen; + } request_t; + request_t s_req_d, s_req_q; + addr_t s_saved_addr, s_selected_addr; + logic [31:0] s_req_addr; + logic s_req_id; + addr_t s_read_addr[3], s_write_addr[3]; + logic [2:0] s_arvalid, s_awvalid, s_wvalid, s_wlast, s_bready, s_rready, s_wuser; + logic [2:0][31:0] s_wdata; + logic [2:0][ 3:0] s_wstrb; + logic [2:0] s_owner_req, s_grant, s_prefer_write_d, s_prefer_write_q; + logic [1:0] s_selected; + logic s_valid, s_selected_write, s_accept, s_terminal; + logic s_r_valid, s_r_ready, s_b_valid, s_b_ready; + logic [36:0] s_r_data; + logic [ 3:0] s_b_data; + logic s_read_capture, s_write_capture; + logic s_abort_read_valid, s_abort_write_valid; + + assign s_saved_addr = s_req_q.addr; + assign s_req_addr = s_saved_addr.addr; + assign s_req_id = s_saved_addr.id; + + assign s_selected_write = s_awvalid[s_selected] && + (s_prefer_write_q[s_selected] || !s_arvalid[s_selected]); + assign s_selected_addr = s_selected_write ? s_write_addr[s_selected] : s_read_addr[s_selected]; + assign s_accept = (s_req_q.state == Idle) && enable_i && s_valid; + assign idle_o = (s_req_q.state == Idle) && !s_r_valid && !s_b_valid; + assign s_abort_read_valid = (s_req_q.state == AbortResponse) && !s_req_q.write; + assign s_abort_write_valid = (s_req_q.state == AbortResponse) && s_req_q.write; + assign s_terminal = + (s_req_q.state == Response) ? + (s_req_q.write ? (s_b_valid && s_bready[s_req_q.owner]) : + (s_r_valid && s_rready[s_req_q.owner] && s_r_data[1])) : + (s_req_q.state == AbortResponse) && + (s_req_q.write ? s_bready[s_req_q.owner] : s_rready[s_req_q.owner]); + assign fault_valid_o = s_terminal && (s_req_q.error_seen || s_req_q.state == AbortResponse); + assign fault_addr_o = s_req_q.error_addr; + assign fault_master_o = s_req_q.owner; + assign fault_write_o = s_req_q.write; + assign fault_instruction_o = !s_req_q.write && s_saved_addr.prot[2]; + assign fault_resp_o = s_req_q.error_resp; + assign s_read_capture = target.rvalid && target.rready; + assign s_write_capture = target.bvalid && target.bready; + + round_robin_arbiter #( + .CLIENTS(3) + ) u_owner_arbiter ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .advance_i (s_accept), + .request_i (s_owner_req), + .grant_o (s_grant), + .selected_o(s_selected), + .valid_o (s_valid) + ); + always_comb begin + s_prefer_write_d = s_prefer_write_q; + if (s_accept) s_prefer_write_d[s_selected] = !s_selected_write; + end + dffr #( + .DATA_WIDTH(3) + ) u_direction_preference ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_prefer_write_d), + .dat_o (s_prefer_write_q) + ); + + for (genvar owner = 0; owner < 3; owner++) begin : gen_owner + assign s_owner_req[owner] = s_arvalid[owner] || s_awvalid[owner]; + assign s_arvalid[owner] = masters[owner].arvalid; + assign s_awvalid[owner] = masters[owner].awvalid; + assign s_read_addr[owner] = { + masters[owner].araddr, + masters[owner].arlen, + masters[owner].arsize, + masters[owner].arburst, + masters[owner].arlock, + masters[owner].arcache, + masters[owner].arprot, + masters[owner].arqos, + masters[owner].arregion, + masters[owner].arid, + masters[owner].aruser + }; + assign s_write_addr[owner] = { + masters[owner].awaddr, + masters[owner].awlen, + masters[owner].awsize, + masters[owner].awburst, + masters[owner].awlock, + masters[owner].awcache, + masters[owner].awprot, + masters[owner].awqos, + masters[owner].awregion, + masters[owner].awid, + masters[owner].awuser + }; + assign s_wvalid[owner] = masters[owner].wvalid; + assign s_wdata[owner] = masters[owner].wdata; + assign s_wstrb[owner] = masters[owner].wstrb; + assign s_wlast[owner] = masters[owner].wlast; + assign s_wuser[owner] = masters[owner].wuser; + assign s_rready[owner] = masters[owner].rready; + assign s_bready[owner] = masters[owner].bready; + assign masters[owner].arready = s_accept && s_grant[owner] && !s_selected_write; + assign masters[owner].awready = s_accept && s_grant[owner] && s_selected_write; + assign masters[owner].wready = (s_req_q.state == Response) && enable_i && s_req_q.write && + !s_req_q.write_done && (s_req_q.owner == 2'(owner)) && target.wready; + assign masters[owner].rvalid = ((s_req_q.state == Response) && enable_i && !s_req_q.write && + (s_req_q.owner == 2'(owner)) && s_r_valid) || + (s_abort_read_valid && (s_req_q.owner == 2'(owner))); + assign masters[owner].rid = s_abort_read_valid ? s_req_id : s_r_data[36]; + assign masters[owner].rdata = s_abort_read_valid ? 32'd0 : s_r_data[35:4]; + assign masters[owner].rresp = s_abort_read_valid ? 2'b11 : s_r_data[3:2]; + assign masters[owner].rlast = s_abort_read_valid ? 1'b1 : s_r_data[1]; + assign masters[owner].ruser = s_abort_read_valid ? 1'b0 : s_r_data[0]; + assign masters[owner].bvalid = ((s_req_q.state == Response) && enable_i && s_req_q.write && + (s_req_q.owner == 2'(owner)) && s_b_valid) || + (s_abort_write_valid && (s_req_q.owner == 2'(owner))); + assign masters[owner].bid = s_abort_write_valid ? s_req_id : s_b_data[3]; + assign masters[owner].bresp = s_abort_write_valid ? 2'b11 : s_b_data[2:1]; + assign masters[owner].buser = s_abort_write_valid ? 1'b0 : s_b_data[0]; + end + + assign target.awaddr = s_saved_addr.addr; + assign target.awlen = s_saved_addr.len; + assign target.awsize = s_saved_addr.size; + assign target.awburst = s_saved_addr.burst; + assign target.awlock = s_saved_addr.lock; + assign target.awcache = s_saved_addr.cache; + assign target.awprot = s_saved_addr.prot; + assign target.awqos = s_saved_addr.qos; + assign target.awregion = s_saved_addr.region; + assign target.awid = s_saved_addr.id; + assign target.awuser = s_saved_addr.user; + assign target.araddr = s_saved_addr.addr; + assign target.arlen = s_saved_addr.len; + assign target.arsize = s_saved_addr.size; + assign target.arburst = s_saved_addr.burst; + assign target.arlock = s_saved_addr.lock; + assign target.arcache = s_saved_addr.cache; + assign target.arprot = s_saved_addr.prot; + assign target.arqos = s_saved_addr.qos; + assign target.arregion = s_saved_addr.region; + assign target.arid = s_saved_addr.id; + assign target.aruser = s_saved_addr.user; + assign target.awvalid = (s_req_q.state == Address) && enable_i && s_req_q.write; + assign target.arvalid = (s_req_q.state == Address) && enable_i && !s_req_q.write; + assign target.wvalid = (s_req_q.state == Response) && enable_i && s_req_q.write && + !s_req_q.write_done && s_wvalid[s_req_q.owner]; + assign target.wdata = s_wdata[s_req_q.owner]; + assign target.wstrb = s_wstrb[s_req_q.owner]; + assign target.wlast = s_wlast[s_req_q.owner]; + assign target.wuser = s_wuser[s_req_q.owner]; + assign target.rready = (s_req_q.state == Response) && enable_i && !s_req_q.write && s_r_ready; + assign target.bready = (s_req_q.state == Response) && enable_i && s_req_q.write && s_b_ready; + spill_register #( + .DATA_WIDTH(37) + ) u_read_response ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .flush_i(!enable_i), + .valid_i((s_req_q.state == Response) && !s_req_q.write && target.rvalid), + .ready_o(s_r_ready), + .data_i ({target.rid, target.rdata, target.rresp, target.rlast, target.ruser}), + .valid_o(s_r_valid), + .ready_i((s_req_q.state == Response) && !s_req_q.write && s_rready[s_req_q.owner]), + .data_o (s_r_data) + ); + spill_register #( + .DATA_WIDTH(4) + ) u_write_response ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .flush_i(!enable_i), + .valid_i((s_req_q.state == Response) && s_req_q.write && target.bvalid), + .ready_o(s_b_ready), + .data_i ({target.bid, target.bresp, target.buser}), + .valid_o(s_b_valid), + .ready_i((s_req_q.state == Response) && s_req_q.write && s_bready[s_req_q.owner]), + .data_o (s_b_data) + ); + always_comb begin + s_req_d = s_req_q; + unique case (s_req_q.state) + Idle: + if (s_accept) begin + s_req_d = '0; + s_req_d.state = Address; + s_req_d.owner = s_selected; + s_req_d.write = s_selected_write; + s_req_d.addr = s_selected_addr; + s_req_d.read_addr = s_selected_addr.addr; + end + Address: begin + if (!enable_i) begin + s_req_d.state = AbortResponse; + s_req_d.error_seen = 1'b1; + s_req_d.error_addr = s_req_addr; + s_req_d.error_resp = 2'b11; + end else if (s_req_q.write ? target.awready : target.arready) s_req_d.state = Response; + end + Response: begin + if (!enable_i) begin + s_req_d.state = AbortResponse; + s_req_d.error_seen = 1'b1; + s_req_d.error_addr = s_req_addr; + s_req_d.error_resp = 2'b11; + end + if (target.wvalid && target.wready) begin + s_req_d.write_beat = s_req_q.write_beat + 8'd1; + if (target.wlast || (s_req_q.write_beat == s_saved_addr.len)) s_req_d.write_done = 1'b1; + end + if (s_read_capture) begin + if (s_saved_addr.burst == 2'b01) + s_req_d.read_addr = s_req_q.read_addr + (32'd1 << s_saved_addr.size); + if (!s_req_q.error_seen && (target.rresp != 2'b00)) begin + s_req_d.error_seen = 1'b1; + s_req_d.error_addr = s_req_q.read_addr; + s_req_d.error_resp = target.rresp; + end + end + if (s_write_capture && !s_req_q.error_seen && (target.bresp != 2'b00)) begin + s_req_d.error_seen = 1'b1; + s_req_d.error_addr = s_saved_addr.addr; + s_req_d.error_resp = target.bresp; + end + if (s_terminal) s_req_d.state = Idle; + end + AbortResponse: if (s_terminal) s_req_d.state = Idle; + default: s_req_d.state = Idle; + endcase + end + dffr #( + .DATA_WIDTH($bits(request_t)) + ) u_request ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_req_d), + .dat_o (s_req_q) + ); + +`ifdef FORMAL + logic s_past_valid_q = 1'b0; + always_ff @(posedge clk_i) begin + s_past_valid_q <= 1'b1; + if (rst_n_i) begin + assert ($onehot0(s_grant)); + assert (!s_accept || idle_o); + assert (!fault_valid_o || s_terminal); + if (s_req_q.state != Idle) assert (s_req_q.owner < 2'd3); + end + if (s_past_valid_q && rst_n_i && $past(rst_n_i && !s_terminal && (s_req_q.state != Idle))) begin + assert (s_req_q.owner == $past(s_req_q.owner)); + assert (s_req_q.write == $past(s_req_q.write)); + assert (s_req_q.addr == $past(s_req_q.addr)); + end + end + for (genvar owner = 0; owner < 3; owner++) begin : gen_fairness + logic [1:0] s_bypass_q; + always_ff @(posedge clk_i) begin + if (!rst_n_i || !s_owner_req[owner] || (s_accept && s_grant[owner])) s_bypass_q <= '0; + else if (s_accept) s_bypass_q <= s_bypass_q + 2'd1; + if (rst_n_i) begin + assert (s_bypass_q <= 2'd2); + assert (!(s_bypass_q == 2'd2 && s_owner_req[owner] && s_accept && !s_grant[owner])); + end + if (s_past_valid_q && rst_n_i && $past(rst_n_i)) begin + if ($past(masters[owner].rvalid && !masters[owner].rready)) begin + assert (masters[owner].rvalid); + assert ({masters[owner].rid, masters[owner].rdata, masters[owner].rresp, + masters[owner].rlast} == $past( + {masters[owner].rid, masters[owner].rdata, masters[owner].rresp, masters[owner].rlast} + )); + end + if ($past(masters[owner].bvalid && !masters[owner].bready)) begin + assert (masters[owner].bvalid); + assert ({masters[owner].bid, masters[owner].bresp} == $past( + {masters[owner].bid, masters[owner].bresp} + )); + end + end + end + end +`endif +`ifdef HAVE_SVA + assert property (@(posedge clk_i) disable iff (!rst_n_i) + target.wvalid && !target.wready |=> target.wvalid && + $stable( + {target.wdata, target.wstrb, target.wlast, target.wuser} + )); + assert property (@(posedge clk_i) disable iff (!rst_n_i) + target.arvalid && !target.arready |=> target.arvalid && + $stable( + {target.araddr, target.arlen, target.arid, target.arsize, target.arburst} + )); + assert property (@(posedge clk_i) disable iff (!rst_n_i) + target.awvalid && !target.awready |=> target.awvalid && + $stable( + {target.awaddr, target.awlen, target.awid, target.awsize, target.awburst} + )); + for (genvar owner = 0; owner < 3; owner++) begin : gen_response_assert + assert property (@(posedge clk_i) disable iff (!rst_n_i) + masters[owner].rvalid && !masters[owner].rready |=> masters[owner].rvalid && + $stable( + {masters[owner].rid, masters[owner].rdata, masters[owner].rresp, masters[owner].rlast} + )); + assert property (@(posedge clk_i) disable iff (!rst_n_i) + masters[owner].bvalid && !masters[owner].bready |=> masters[owner].bvalid && + $stable( + {masters[owner].bid, masters[owner].bresp} + )); + end +`endif +endmodule diff --git a/rtl/tiny/top/tiny_cpu_mem.sv b/rtl/tiny/top/tiny_cpu_mem.sv index bba3bdf2..9caae697 100644 --- a/rtl/tiny/top/tiny_cpu_mem.sv +++ b/rtl/tiny/top/tiny_cpu_mem.sv @@ -6,13 +6,16 @@ // following address acceptance. Local stores complete only after macro commit. // Only non-SRAM work enters the tagged, non-posted slow-path merge. module tiny_cpu_mem ( - input logic clk_i, - input logic rst_n_i, - input logic quiesce_i, - output logic idle_o, - ahbl_if.slave cpu [2], - tiny_sram_port_if.master local_ports[2], - axi4_if.master slow_axi4 + input logic clk_i, + input logic rst_n_i, + input logic quiesce_i, + output logic idle_o, + output logic [1:0] local_fault_valid_o, + output logic [1:0][31:0] local_fault_addr_o, + output logic [1:0] local_fault_write_o, + ahbl_if.slave cpu [2], + tiny_sram_port_if.master local_ports [2], + axi4_if.master slow_axi4 ); typedef enum logic [2:0] { Idle, @@ -26,9 +29,10 @@ module tiny_cpu_mem ( } state_e; typedef struct packed { state_e state; - logic [31:0] addr, data; + logic [31:0] addr, data; logic [2:0] size; logic write; + logic external_error; logic [3:0] strb; } request_t; request_t s_req_d[2], s_req_q[2]; @@ -63,6 +67,10 @@ module tiny_cpu_mem ( logic s_adapter_idle; for (genvar port_index = 0; port_index < 2; port_index++) begin : gen_port + assign local_fault_valid_o[port_index] = + (s_req_q[port_index].state == ErrorLast) && !s_req_q[port_index].external_error; + assign local_fault_addr_o[port_index] = s_req_q[port_index].addr; + assign local_fault_write_o[port_index] = s_req_q[port_index].write; assign s_slow_req[port_index] = s_req_q[port_index].state == SlowIssue; assign s_front_idle[port_index] = (s_req_q[port_index].state == Idle) && (quiesce_i || !cpu[port_index].htrans[1]); @@ -113,7 +121,12 @@ module tiny_cpu_mem ( LocalIssue: if (local_ports[port_index].req_ready) s_req_d[port_index].state = LocalWait; SlowIssue: if (s_slow_accept && s_slow_grant[port_index]) s_req_d[port_index].state = SlowWait; - SlowWait: if (s_err[port_index]) s_req_d[port_index].state = ErrorLast; + SlowWait: + if (s_err[port_index]) begin + s_req_d[port_index].state = ErrorLast; + // The fabric already reports externally completed errors once. + s_req_d[port_index].external_error = 1'b1; + end ErrorFirst: s_req_d[port_index].state = ErrorLast; default: begin end @@ -123,10 +136,11 @@ module tiny_cpu_mem ( // Capture even a terminal-cycle overlap during quiesce: that address // was acknowledged by HREADY and cannot be silently discarded. if (cpu[port_index].htrans[1]) begin - s_req_d[port_index].addr = cpu[port_index].haddr; - s_req_d[port_index].size = cpu[port_index].hsize; - s_req_d[port_index].write = cpu[port_index].hwrite; - s_req_d[port_index].strb = '0; + s_req_d[port_index].addr = cpu[port_index].haddr; + s_req_d[port_index].size = cpu[port_index].hsize; + s_req_d[port_index].write = cpu[port_index].hwrite; + s_req_d[port_index].strb = '0; + s_req_d[port_index].external_error = 1'b0; if ((cpu[port_index].htrans != 2'b10) || cpu[port_index].hmastlock || (cpu[port_index].hsize > 3'd2) || ((cpu[port_index].haddr & ((32'd1 << cpu[port_index].hsize) - 32'd1)) != 32'd0) || diff --git a/rtl/tiny/top/tiny_fault_collector.sv b/rtl/tiny/top/tiny_fault_collector.sv new file mode 100644 index 00000000..927a0b46 --- /dev/null +++ b/rtl/tiny/top/tiny_fault_collector.sv @@ -0,0 +1,60 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// Terminal transaction pulses contribute independently to the count. One +// deterministic winner supplies metadata; SYSCTRL owns validity and W1C. +module tiny_fault_collector ( + input logic [ 1:0] local_valid_i, + input logic [ 1:0][31:0] local_addr_i, + input logic [ 1:0] local_write_i, + input logic [ 5:0] external_valid_i, + input logic [ 5:0][31:0] external_addr_i, + input logic [ 5:0][ 1:0] external_resp_i, + input logic [ 5:0] external_instruction_i, + output logic [ 3:0] count_o, + output logic [31:0] addr_o, + output logic [ 1:0] master_o, + output logic write_o, + output logic [ 1:0] resp_o +); + logic [7:0] s_valid, s_write, s_instruction; + logic [7:0][31:0] s_addr; + logic [7:0][1:0] s_master, s_resp; + logic [7:0][3:0] s_rank; + logic [3:0] s_best_rank; + assign s_valid = {external_valid_i, local_valid_i}; + assign s_addr = {external_addr_i, local_addr_i}; + assign s_write[1:0] = local_write_i; + assign s_master[1:0] = '0; + assign s_resp[1:0] = {2'b10, 2'b10}; + assign s_instruction[1:0] = 2'b01; + for (genvar slot = 0; slot < 6; slot++) begin : gen_external + assign s_write[slot+2] = 1'(slot % 2); + assign s_master[slot+2] = 2'(slot / 2); + assign s_resp[slot+2] = external_resp_i[slot]; + assign s_instruction[slot+2] = (slot < 2) && external_instruction_i[slot]; + end + for (genvar slot = 0; slot < 8; slot++) begin : gen_rank + assign s_rank[slot] = {s_master[slot], !s_instruction[slot], s_write[slot]}; + end + always_comb begin + count_o = '0; + addr_o = '0; + master_o = '0; + write_o = 1'b0; + resp_o = '0; + s_best_rank = 4'hf; + for (int slot = 0; slot < 8; slot++) begin + if (s_valid[slot]) begin + count_o = count_o + 4'd1; + if (s_rank[slot] < s_best_rank) begin + s_best_rank = s_rank[slot]; + addr_o = s_addr[slot]; + master_o = s_master[slot]; + write_o = s_write[slot]; + resp_o = s_resp[slot]; + end + end + end + end +endmodule diff --git a/rtl/tiny/top/tiny_sram_axi4_demux.sv b/rtl/tiny/top/tiny_sram_axi4_demux.sv deleted file mode 100644 index f2283861..00000000 --- a/rtl/tiny/top/tiny_sram_axi4_demux.sv +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) 2026 Yuchi Miao -// SPDX-License-Identifier: MulanPSL-2.0 - -// Compatibility attachment to the pre-P5 fabric. Independent direction tags -// route responses; this module does not arbitrate or lock the SRAM macros. -module tiny_sram_axi4_demux ( - input logic clk_i, - input logic rst_n_i, - axi4_if.slave upstream, - axi4_if.master groups [4] -); - logic s_read_busy_d, s_read_busy_q; - logic s_write_busy_d, s_write_busy_q; - logic s_read_terminal, s_write_terminal; - logic [1:0] s_read_group_d, s_read_group_q; - logic [1:0] s_write_group_d, s_write_group_q; - logic [3:0] s_arready, s_awready, s_wready, s_rvalid, s_bvalid, s_rlast; - logic [3:0] s_rid, s_bid, s_ruser, s_buser; - logic [1:0] s_rresp[4], s_bresp[4]; - logic [31:0] s_rdata[4]; - - assign upstream.arready = !s_read_busy_q && s_arready[upstream.araddr[16:15]]; - assign upstream.awready = !s_write_busy_q && s_awready[upstream.awaddr[16:15]]; - assign upstream.wready = s_write_busy_q && s_wready[s_write_group_q]; - assign upstream.rvalid = s_read_busy_q && s_rvalid[s_read_group_q]; - assign upstream.rdata = s_rdata[s_read_group_q]; - assign upstream.rresp = s_rresp[s_read_group_q]; - assign upstream.rlast = s_rlast[s_read_group_q]; - assign upstream.rid = s_rid[s_read_group_q]; - assign upstream.ruser = s_ruser[s_read_group_q]; - assign upstream.bvalid = s_write_busy_q && s_bvalid[s_write_group_q]; - assign upstream.bresp = s_bresp[s_write_group_q]; - assign upstream.bid = s_bid[s_write_group_q]; - assign upstream.buser = s_buser[s_write_group_q]; - assign s_read_terminal = upstream.rvalid && upstream.rready && upstream.rlast; - assign s_write_terminal = upstream.bvalid && upstream.bready; - assign s_read_busy_d = (s_read_busy_q && !s_read_terminal) || - (upstream.arvalid && upstream.arready); - assign s_write_busy_d = (s_write_busy_q && !s_write_terminal) || - (upstream.awvalid && upstream.awready); - assign s_read_group_d = upstream.araddr[16:15]; - assign s_write_group_d = upstream.awaddr[16:15]; - - dffr #( - .DATA_WIDTH(1) - ) u_read_busy ( - .clk_i (clk_i), - .rst_n_i(rst_n_i), - .dat_i (s_read_busy_d), - .dat_o (s_read_busy_q) - ); - dffr #( - .DATA_WIDTH(1) - ) u_write_busy ( - .clk_i (clk_i), - .rst_n_i(rst_n_i), - .dat_i (s_write_busy_d), - .dat_o (s_write_busy_q) - ); - dffer #( - .DATA_WIDTH(2) - ) u_read_group ( - .clk_i (clk_i), - .rst_n_i(rst_n_i), - .en_i (upstream.arvalid && upstream.arready), - .dat_i (s_read_group_d), - .dat_o (s_read_group_q) - ); - dffer #( - .DATA_WIDTH(2) - ) u_write_group ( - .clk_i (clk_i), - .rst_n_i(rst_n_i), - .en_i (upstream.awvalid && upstream.awready), - .dat_i (s_write_group_d), - .dat_o (s_write_group_q) - ); - - for (genvar group = 0; group < 4; group++) begin : gen_group - assign groups[group].arid = upstream.arid; - assign groups[group].araddr = upstream.araddr; - assign groups[group].arlen = upstream.arlen; - assign groups[group].arsize = upstream.arsize; - assign groups[group].arburst = upstream.arburst; - assign groups[group].arlock = upstream.arlock; - assign groups[group].arcache = upstream.arcache; - assign groups[group].arprot = upstream.arprot; - assign groups[group].arqos = upstream.arqos; - assign groups[group].arregion = upstream.arregion; - assign groups[group].aruser = upstream.aruser; - assign groups[group].awid = upstream.awid; - assign groups[group].awaddr = upstream.awaddr; - assign groups[group].awlen = upstream.awlen; - assign groups[group].awsize = upstream.awsize; - assign groups[group].awburst = upstream.awburst; - assign groups[group].awlock = upstream.awlock; - assign groups[group].awcache = upstream.awcache; - assign groups[group].awprot = upstream.awprot; - assign groups[group].awqos = upstream.awqos; - assign groups[group].awregion = upstream.awregion; - assign groups[group].awuser = upstream.awuser; - assign groups[group].wdata = upstream.wdata; - assign groups[group].wstrb = upstream.wstrb; - assign groups[group].wlast = upstream.wlast; - assign groups[group].wuser = upstream.wuser; - - assign groups[group].arvalid = upstream.arvalid && !s_read_busy_q && - (upstream.araddr[16:15] == 2'(group)); - assign groups[group].awvalid = upstream.awvalid && !s_write_busy_q && - (upstream.awaddr[16:15] == 2'(group)); - assign groups[group].wvalid = upstream.wvalid && s_write_busy_q && - (s_write_group_q == 2'(group)); - assign groups[group].rready = upstream.rready && s_read_busy_q && (s_read_group_q == 2'(group)); - assign groups[group].bready = upstream.bready && s_write_busy_q && - (s_write_group_q == 2'(group)); - assign s_arready[group] = groups[group].arready; - assign s_awready[group] = groups[group].awready; - assign s_wready[group] = groups[group].wready; - assign s_rvalid[group] = groups[group].rvalid; - assign s_bvalid[group] = groups[group].bvalid; - assign s_rlast[group] = groups[group].rlast; - assign s_rid[group] = groups[group].rid; - assign s_bid[group] = groups[group].bid; - assign s_ruser[group] = groups[group].ruser; - assign s_buser[group] = groups[group].buser; - assign s_rresp[group] = groups[group].rresp; - assign s_bresp[group] = groups[group].bresp; - assign s_rdata[group] = groups[group].rdata; - end -endmodule diff --git a/rtl/tiny/top/tiny_sysctrl.sv b/rtl/tiny/top/tiny_sysctrl.sv index 7a0ac57a..13f9db10 100644 --- a/rtl/tiny/top/tiny_sysctrl.sv +++ b/rtl/tiny/top/tiny_sysctrl.sv @@ -7,9 +7,10 @@ module tiny_sysctrl ( input logic rst_n_i, apb4_if.slave apb4, input logic fault_valid_i, + input logic [ 3:0] fault_count_i, input logic [31:0] fault_addr_i, input logic fault_write_i, - input logic fault_master_i, + input logic [ 1:0] fault_master_i, input logic [ 1:0] fault_resp_i, input logic [63:0] cpu_wait_i, input logic [63:0] dma_wait_i, @@ -24,7 +25,8 @@ module tiny_sysctrl ( logic [31:0] s_fault_addr_d, s_fault_addr_q; logic [31:0] s_fault_count_d, s_fault_count_q; logic [4:0] s_fault_stat_d, s_fault_stat_q; - logic s_fault_master_d, s_fault_master_q; + logic [1:0] s_fault_master_d, s_fault_master_q; + logic [32:0] s_fault_sum; logic [1:0] s_fault_resp_d, s_fault_resp_q; logic s_perf_en_d, s_perf_en_q; logic s_wake_d, s_wake_q; @@ -68,7 +70,7 @@ module tiny_sysctrl ( end `APB4_SYSCTRL__FAULT_ADDR: s_read_data = s_fault_addr_q; `APB4_SYSCTRL__FAULT_COUNT: s_read_data = s_fault_count_q; - `APB4_SYSCTRL__FAULT_MASTER: s_read_data = {31'd0, s_fault_master_q}; + `APB4_SYSCTRL__FAULT_MASTER: s_read_data = {30'd0, s_fault_master_q}; `APB4_SYSCTRL__FAULT_DETAIL: s_read_data = {30'd0, s_fault_resp_q}; `APB4_SYSCTRL__PERF_CTRL: begin s_read_data = {31'd0, s_perf_en_q}; @@ -89,6 +91,7 @@ module tiny_sysctrl ( default: s_valid = 1'b0; endcase end + assign s_fault_sum = {1'b0, s_fault_count_q} + {29'd0, fault_count_i}; always_comb begin s_test_d = s_test_q; s_fault_addr_d = s_fault_addr_q; @@ -124,7 +127,7 @@ module tiny_sysctrl ( end if (rtc_wake_i) s_wake_d = 1'b1; if (fault_valid_i) begin - if (s_fault_count_q != 32'hffff_ffff) s_fault_count_d = s_fault_count_q + 32'd1; + s_fault_count_d = s_fault_sum[32] ? 32'hffff_ffff : s_fault_sum[31:0]; if (!s_fault_stat_d[0]) begin s_fault_stat_d = {(fault_resp_i == 2'b11) ? 3'd1 : 3'd2, fault_write_i, 1'b1}; s_fault_addr_d = fault_addr_i; @@ -166,7 +169,7 @@ module tiny_sysctrl ( .dat_o (s_fault_stat_q) ); dffr #( - .DATA_WIDTH(1) + .DATA_WIDTH(2) ) u_fault_master_reg ( .clk_i (clk_i), .rst_n_i(rst_n_i), diff --git a/scripts/generate_tiny.py b/scripts/generate_tiny.py index eef20acc..8fb32b2c 100644 --- a/scripts/generate_tiny.py +++ b/scripts/generate_tiny.py @@ -31,9 +31,17 @@ def read_topology(path: Path, memory_map: Path) -> dict: "groups": 4, "group_bytes": 32768, "macros_per_group": 8, "macro_bytes": 4096, "base": "0x30000000", "macro_instance": "u_soc.u_sram.gen_group[{group}].u_group.gen_bank[{bank}].u_ram.u_mem", - "external_attachment": "serialized-pre-P5", + "external_attachment": "per-target-P5", }.items()): - raise ValueError("Tiny P4 SRAM geometry/attachment differs from its frozen contract") + raise ValueError("Tiny SRAM geometry/P5 attachment differs from its frozen contract") + if document.get("fabric") != { + "owners": ["cpu_slow", "central_dma", "sdio_reserved"], "active_owners": [0, 1], + "cpu_combined_outstanding": 1, "dma_direction_outstanding": 1, + "sdio_direction_outstanding": 1, "target_combined_outstanding": 1, + "sram_targets": [0, 1, 2, 3], "xpi_target": 4, "apb_target_base": 5, + "decode_error_target": 21, "protocol_error_target": 22, "target_count": 23, + }: + raise ValueError("Tiny P5 requires three owner slots, two active owners and 23 targets") if document.get("irq_vector_width") != 32 or document.get("external_irq_count") != 30: raise ValueError("Tiny requires a 32-bit IRQ vector and 30 external inputs") names: set[str] = set() @@ -117,33 +125,22 @@ def generate_bindings(document: dict, output: Path, pdk: str = "IHP130", gpio.extend((f"assign u_gpio_if.{mode}_do_i[{pin}] = {value['do']};", 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] + declarations, bridges, 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"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") + bridges.append(f"assign s_target_rst_n[{index + 5}] = s_{name}_rst_n;") + bridges.append(f"tiny_axi42apb4 u_{name}_bridge (" + f".clk_i(clk_i), .rst_n_i(s_{name}_rst_n), " + f".axi4(u_targets_axi4_if[{index + 5}]), .apb4(u_{name}_apb4_if));") + decode.append(f"if ({' || '.join(f'`SOC_ADDR_IS_{r}(addr_i)' for r in target['regions'])}) " + f"return 5'd{index + 5};") 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] - for index, target in enumerate(document["apb_targets"]): - name = target["name"] - for field, value in {"paddr": "s_addr_q", "pwrite": "s_write_q", - "pwdata": "s_wdata_q", "pstrb": "s_wstrb_q", "pprot": "3'd0", - "psel": f"s_select[{index}] && s_access", - "penable": "s_enable"}.items(): - wiring.append(f"assign {name}.{field} = {value};") - wiring.extend((f"assign s_ready[{index}] = {name}.pready;", - f"assign s_error[{index}] = {name}.pslverr;", - f"assign s_rdata[{index}] = {name}.prdata;")) - atomic_write(output / "rtl/tiny_apb_wiring.svh", "\n".join(wiring) + "\n") + atomic_write(output / "rtl/tiny_apb_bridges.svh", "\n".join(bridges) + "\n") + atomic_write(output / "rtl/tiny_fabric_decode.svh", "\n".join(decode) + "\n") def generate_filelists(output: Path, generated: Path, defines: list[str], diff --git a/scripts/tiny_r2_p5.py b/scripts/tiny_r2_p5.py new file mode 100644 index 00000000..1dbf93b0 --- /dev/null +++ b/scripts/tiny_r2_p5.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python3 +"""Replay immutable P3 images on P5 RTL without relabeling image provenance.""" +from __future__ import annotations + +import argparse +from pathlib import Path +import sys + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT)) +from scripts import tiny_r2_baseline as b # noqa: E402 +from scripts import tiny_r2_p3 as p3 # noqa: E402 +from scripts import tiny_r2_p4 as previous # noqa: E402 + +PHASE = "TINY-R2-P5" +HARDWARE_KEYS = ("SOC", "PDK", "HAVE_PLL", "HAVE_SRAM_IF", "HAVE_SRAM_MACRO", + "SRAM_SIZE_KIB", "PDK_BEHAV", "HAVE_HP", "EXT_CLK_HZ", "AUD_CLK_HZ", + "CLINT_TIMEBASE_HZ", "MGMT_CPU_CLK_HZ", "JTAG_IDCODE") +FABRIC_FIELDS = {"window", "active_targets_max", "dma_rw_overlap_cycles", "dma_rw_beat_overlap_cycles"} + + +def inputs(root: Path, variant: Path) -> dict: + result = previous.inputs(root, variant) + for name in ("AGENTS.md", "docs/engineering.md", "docs/rtl-coding-style.md", + "docs/rtl-coding-style-compliance.md", "rtl/rtl_style_manifest.json", + "rtl/rtl_style_audit.json", "rtl/rtl_readiness.json", ".verible-format", + ".github/workflows/_regression.yml"): + result[name] = b.sha256(root / name) + for directory in (root / "quality", root / "rtl/tiny/mk"): + for path in directory.rglob("*"): + if path.is_file() and path.suffix in (".json", ".mk"): + result[str(path.relative_to(root))] = b.sha256(path) + return result + + +def compatible_hardware(image: dict, model: dict) -> None: + a, z = (c["configuration"] for c in (image, model)) + if any(key not in a or key not in z or a[key] != z[key] for key in HARDWARE_KEYS): + raise ValueError("retained image and candidate hardware configuration differ") + + +def parse_log(log: str, flow: dict) -> dict: + samples = previous.parse_log(log, flow) + rows = [b.parse_fields(line, FABRIC_FIELDS) for line in log.splitlines() + if line.startswith("R2_P5_FABRIC ")] + rows.sort(key=lambda row: row["window"]) + if [row["window"] for row in rows] != list(range(1, 9)): + raise ValueError("missing or duplicate P5 fabric observations") + for row in rows: + if any(value < 0 for value in row.values()) or row["active_targets_max"] > 23: + raise ValueError("invalid P5 fabric observation") + if row["dma_rw_beat_overlap_cycles"] > row["dma_rw_overlap_cycles"]: + raise ValueError("P5 beat overlap exceeds transaction overlap") + if row["dma_rw_overlap_cycles"] and row["active_targets_max"] < 2: + raise ValueError("P5 overlap without independent targets") + samples["fabric"] = rows + return samples + + +def reference_cases(path: Path, names: list[str]) -> list[dict]: + report = b.read_json(path) + if report.get("phase") != previous.PHASE or report.get("status") != "measurement_complete": + raise ValueError("reference is not a completed P4 measurement report") + if not names or len(set(names)) != len(names): + raise ValueError("select distinct retained cases") + original = previous.validate_replay(b.check_artifact(report["executions"]["verilator"]), "verilator") + if original["source_digest"] != report["source_digest"]: + raise ValueError("P4 report source differs from retained execution") + result = [] + for name in names: + matches = [c for c in original["cases"] if c["name"] == name] + comparisons = [c for c in report["comparisons"] if c["name"] == name] + if len(matches) != 1 or len(comparisons) != 1: + raise ValueError("unknown retained P4 case") + case, comparison = matches[0], comparisons[0] + if comparison["image"] != case["image"]: + raise ValueError("P4 comparison changed the retained image") + rows = comparison["workloads"] + if len(rows) != len(case["samples"]["cases"]): + raise ValueError("P4 comparison workload count differs") + for row, sample in zip(rows, case["samples"]["cases"], strict=True): + if (row["name"], row["candidate_cycles"], row["candidate_instructions"]) != ( + sample["name"], sample["cycles"], sample["instructions"]): + raise ValueError("P4 comparison differs from retained samples") + if comparison["local"] != case["samples"]["local"] or comparison["banks"] != case["samples"]["banks"]: + raise ValueError("P4 comparison altered local observations") + result.append({"name": name, "image": case["image"], "samples": case["samples"]}) + return result + + +def replay(args) -> None: + root, variant = args.root, args.variant_root + parent = variant / "meta/tiny-r2-p5/replay" / args.simulator + directory = b.new_attempt(parent, "attempt-") + record = {"phase": PHASE, "status": "running", "repetitions": 1, "cases": []} + b.dump(directory / "record.json", record) + b.dump(parent / "latest.json", {"path": str(directory / "record.json")}) + try: + reference = b.artifact(args.reference) + cases = reference_cases(args.reference, args.cases.split(",")) + configuration = p3.configuration(variant) + for case in cases: + compatible_hardware(b.read_json(b.check_artifact(case["image"]))["configuration"], + configuration) + before = inputs(root, variant) + tools = b.tools_identity(root) + command = args.command[1:] if args.command[:1] == ["--"] else args.command + if not command: + raise ValueError("fresh model compilation command required") + p3.execute_make(root, directory, "compile-model", command) + if before != inputs(root, variant): + raise ValueError("source changed while compiling candidate model") + runtime = b.runtime_inputs(root, variant, args.simulator) + model = variant / "sim" / args.simulator / ("emu" if args.simulator == "verilator" else "simv") + record.update(reference=reference, configuration=configuration, tools=tools, + model=b.artifact(model), source_digest=b.digest_map(before), + source=b.snapshot(root, directory / "inputs", before | runtime), + compilation={s: b.artifact(directory / f"compile-model.{s}") for s in ("json", "log")}) + launch = [str(model)] + if args.simulator == "iverilog": + record["runtime_tool"] = b.vvp_identity(args.vvp, tools["iverilog"]) + launch.insert(0, record["runtime_tool"]["executable"]["path"]) + for case in cases: + image = p3.validate_image(b.check_artifact(case["image"])) + begin, end = image["layout"]["descriptor_range"] + cmd = [*launch, "+firmware=" + image["artifacts"]["tiny_p3.hex"]["path"], + "+tiny_r2_p3", "+tiny_r2_p5", f"+baseline_tcd_base={begin:x}", f"+baseline_tcd_end={end:x}", + "+max_cycles=100000000"] + label = case["name"] + flow = b.execute(root, directory, label, + ["timeout", "--foreground", f"{args.timeout}s", *cmd], quiet=True) + samples = parse_log((directory / f"{label}.log").read_text(), flow) + p3.validate_image(b.check_artifact(case["image"])) + record["cases"].append({"name": label, "image": case["image"], "samples": samples, + "baseline_samples": case["samples"], + "command": cmd, "timeout_seconds": args.timeout, + "flow": b.artifact(directory / f"{label}.json"), + "log": b.artifact(directory / f"{label}.log")}) + b.dump(directory / "record.json", record) + if before != inputs(root, variant) or runtime != b.runtime_inputs(root, variant, args.simulator): + raise ValueError("candidate inputs changed during execution") + b.check_artifact(record["model"]) + b.check_artifact(reference) + for case in cases: + p3.validate_image(b.check_artifact(case["image"])) + for tool in tools.values(): + b.check_artifact(tool["executable"]) + record["status"] = "passed" + except (OSError, ValueError) as error: + record.update(status="failed", error=str(error)) + raise + finally: + b.dump(directory / "record.json", record) + print(f"P5 {args.simulator}: one execution per image; {directory}") + + +def validate_replay(path: Path, simulator: str) -> dict: + record = b.read_json(path) + if record.get("phase") != PHASE or record.get("status") != "passed" or record.get("repetitions") != 1: + raise ValueError("not a completed single-execution P5 replay") + for field in ("model", "reference"): + b.check_artifact(record[field]) + for value in record["compilation"].values(): + b.check_artifact(value) + for field in ("inputs", "snapshot", "patch"): + b.check_artifact(record["source"][field]) + captured = b.read_json(Path(record["source"]["inputs"]["path"])) + if b.digest_map(captured) != record["source"]["input_digest"]: + raise ValueError("candidate source digest mismatch") + for tool in record["tools"].values(): + b.check_artifact(tool["executable"]) + b.check_artifact(tool["archive"]) + prefix = [record["model"]["path"]] + if simulator == "iverilog": + runtime = b.check_artifact(record["runtime_tool"]["executable"]) + compiler = Path(record["tools"]["iverilog"]["executable"]["path"]) + if runtime.resolve().parent != compiler.resolve().parent: + raise ValueError("unmatched Icarus runtime") + prefix.insert(0, str(runtime)) + reference = reference_cases(Path(record["reference"]["path"]), [c["name"] for c in record["cases"]]) + for case, original in zip(record["cases"], reference, strict=True): + if case["image"] != original["image"] or case["baseline_samples"] != original["samples"]: + raise ValueError("comparison changed the retained image or reference measurements") + image = p3.validate_image(b.check_artifact(case["image"])) + compatible_hardware(image["configuration"], record["configuration"]) + first, end = image["layout"]["descriptor_range"] + expected = [*prefix, "+firmware=" + image["artifacts"]["tiny_p3.hex"]["path"], + "+tiny_r2_p3", "+tiny_r2_p5", f"+baseline_tcd_base={first:x}", f"+baseline_tcd_end={end:x}", + "+max_cycles=100000000"] + flow = b.read_json(b.check_artifact(case["flow"])) + if case["command"] != expected or case["timeout_seconds"] <= 0 or flow["command"] != [ + "timeout", "--foreground", f"{case['timeout_seconds']}s", *expected]: + raise ValueError("model/image execution provenance mismatch") + if parse_log(b.check_artifact(case["log"]).read_text(), flow) != case["samples"]: + raise ValueError("reported candidate samples differ from the log") + return record + + +def report(args) -> None: + base = args.variant_root / "meta/tiny-r2-p5" + runs = {} + for simulator in ("verilator", "iverilog"): + pointer = b.read_json(base / "replay" / simulator / "latest.json") + path = Path(pointer["path"]) + runs[simulator] = (validate_replay(path, simulator), b.artifact(path)) + before = b.digest_map(inputs(args.root, args.variant_root)) + if any(run[0]["source_digest"] != before for run in runs.values()): + raise ValueError("candidate source changed after measurement") + cases = {c["name"]: c for c in runs["verilator"][0]["cases"]} + if set(cases) != {"perf-o2-plain-flat", "perf-o2-plain-banked"}: + raise ValueError("P5 comparison requires both approved placement images") + cross = runs["iverilog"][0]["cases"] + if len(cross) != 1 or cross[0]["name"] != "perf-o2-plain-banked": + raise ValueError("P5 requires one banked Icarus execution") + if any(cross[0][key] != cases["perf-o2-plain-banked"][key] for key in ("image", "samples")): + raise ValueError("cross-simulator image/results differ") + comparisons = [] + for name, case in cases.items(): + rows = [] + for old, new in zip(case["baseline_samples"]["cases"], case["samples"]["cases"], strict=True): + rows.append({"name": new["name"], "baseline_cycles": old["cycles"], + "candidate_cycles": new["cycles"], "baseline_instructions": old["instructions"], + "candidate_instructions": new["instructions"], + "cycle_ratio": old["cycles"] / new["cycles"]}) + comparisons.append({"name": name, "image": case["image"], "workloads": rows, + "local": case["samples"]["local"], "banks": case["samples"]["banks"], + "fabric": case["samples"]["fabric"]}) + b.dump(base / "report.json", {"phase": PHASE, "status": "measurement_complete", + "reference_phase": previous.PHASE, + "source_digest": before, "comparisons": comparisons, + "executions": {key: value[1] for key, value in runs.items()}, + "qualification": "matched-binary SAFE24 simulation; not phase/physical acceptance"}) + print(f"P5 matched-image report: {base / 'report.json'}") + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--root", type=Path, required=True) + parser.add_argument("--variant-root", type=Path, required=True) + parser.add_argument("--report", action="store_true") + parser.add_argument("--reference", type=Path) + parser.add_argument("--cases") + parser.add_argument("--simulator", choices=("verilator", "iverilog")) + parser.add_argument("--vvp", default="vvp") + parser.add_argument("--timeout", type=int, default=21600) + parser.add_argument("command", nargs=argparse.REMAINDER) + args = parser.parse_args() + if args.timeout <= 0: + parser.error("timeout must be positive") + if args.report: + report(args) + else: + if args.reference is None or not args.cases or not args.simulator: + parser.error("replay requires reference, cases and simulator") + replay(args) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/rtl/tiny_axi4_fabric_tb.sv b/tests/rtl/tiny_axi4_fabric_tb.sv index 0914d5ff..0371854c 100644 --- a/tests/rtl/tiny_axi4_fabric_tb.sv +++ b/tests/rtl/tiny_axi4_fabric_tb.sv @@ -2,13 +2,14 @@ module tiny_axi4_fabric_tb; logic clk_i = 0; logic rst_n_i = 0; + logic target_rst_n[23]; always #5 clk_i = !clk_i; axi4_if #( .ADDR_WIDTH(32), .DATA_WIDTH(32), .ID_WIDTH (1), .USER_WIDTH(1) - ) masters[2] ( + ) masters[3] ( .aclk (clk_i), .aresetn(rst_n_i) ); @@ -17,66 +18,71 @@ module tiny_axi4_fabric_tb; .DATA_WIDTH(32), .ID_WIDTH (1), .USER_WIDTH(1) - ) targets[5] ( + ) targets[23] ( .aclk (clk_i), .aresetn(rst_n_i) ); - logic fault_valid, fault_master, fault_write; - logic [31:0] fault_addr; - logic [ 1:0] fault_resp; + logic [5:0] fault_valid, fault_instruction; + logic [5:0][31:0] fault_addr; + logic [5:0][ 1:0] fault_resp; logic [63:0] cpu_wait, dma_wait; integer faults = 0; - integer completions [2]; - integer target_requests[5]; - logic [1:0][31:0] awaddr; - logic [1:0][ 7:0] awlen; - logic [1:0][ 2:0] awsize; - logic [1:0][ 1:0] awburst; - logic [1:0][ 0:0] awlock; - logic [1:0][ 0:0] awid; - logic [1:0][ 0:0] awvalid; - logic [1:0][31:0] wdata; - logic [1:0][ 3:0] wstrb; - logic [1:0][ 0:0] wlast; - logic [1:0][ 0:0] wvalid; - logic [1:0][ 0:0] bready; - logic [1:0][31:0] araddr; - logic [1:0][ 7:0] arlen; - logic [1:0][ 2:0] arsize; - logic [1:0][ 1:0] arburst; - logic [1:0][ 0:0] arlock; - logic [1:0][ 0:0] arid; - logic [1:0][ 0:0] arvalid; - logic [1:0][ 2:0] arprot; - logic [1:0][ 0:0] rready; - logic [1:0][ 0:0] awready; - logic [1:0][ 0:0] arready; - logic [1:0][ 0:0] wready; - logic [1:0][ 0:0] bvalid; - logic [1:0][ 0:0] bid; - logic [1:0][ 1:0] bresp; - logic [1:0][ 0:0] rvalid; - logic [1:0][ 0:0] rid; - logic [1:0][ 0:0] rlast; - logic [1:0][31:0] rdata; - logic [1:0][ 1:0] rresp; + integer completions [ 3]; + integer target_requests[23]; + logic [2:0][31:0] awaddr; + logic [2:0][ 7:0] awlen; + logic [2:0][ 2:0] awsize; + logic [2:0][ 1:0] awburst; + logic [2:0][ 0:0] awlock; + logic [2:0][ 0:0] awid; + logic [2:0][ 0:0] awvalid; + logic [2:0][31:0] wdata; + logic [2:0][ 3:0] wstrb; + logic [2:0][ 0:0] wlast; + logic [2:0][ 0:0] wvalid; + logic [2:0][ 0:0] bready; + logic [2:0][31:0] araddr; + logic [2:0][ 7:0] arlen; + logic [2:0][ 2:0] arsize; + logic [2:0][ 1:0] arburst; + logic [2:0][ 0:0] arlock; + logic [2:0][ 0:0] arid; + logic [2:0][ 0:0] arvalid; + logic [2:0][ 2:0] arprot; + logic [2:0][ 0:0] rready; + logic [2:0][ 0:0] awready; + logic [2:0][ 0:0] arready; + logic [2:0][ 0:0] wready; + logic [2:0][ 0:0] bvalid; + logic [2:0][ 0:0] bid; + logic [2:0][ 1:0] bresp; + logic [2:0][ 0:0] rvalid; + logic [2:0][ 0:0] rid; + logic [2:0][ 0:0] rlast; + logic [2:0][31:0] rdata; + logic [2:0][ 1:0] rresp; tiny_axi4_fabric u_dut ( - .clk_i (clk_i), - .rst_n_i (rst_n_i), - .perf_enable_i (1'b1), - .perf_clear_i (1'b0), - .masters (masters), - .targets (targets), - .fault_valid_o (fault_valid), - .fault_addr_o (fault_addr), - .fault_master_o(fault_master), - .fault_write_o (fault_write), - .fault_resp_o (fault_resp), - .cpu_wait_o (cpu_wait), - .dma_wait_o (dma_wait) + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .target_rst_n_i (target_rst_n), + .perf_enable_i (1'b1), + .perf_clear_i (1'b0), + .masters (masters), + .targets (targets), + .fault_valid_o (fault_valid), + .fault_addr_o (fault_addr), + .fault_instruction_o(fault_instruction), + .owner_idle_o (), + .target_idle_o (), + .fault_resp_o (fault_resp), + .cpu_wait_o (cpu_wait), + .dma_wait_o (dma_wait) ); - always @(posedge clk_i) if (fault_valid) faults <= faults + 1; - for (genvar m = 0; m < 2; m++) begin : gen_master + for (genvar target = 0; target < 23; target++) begin : gen_target_reset + assign target_rst_n[target] = rst_n_i; + end + always @(posedge clk_i) faults <= faults + $countones(fault_valid); + for (genvar m = 0; m < 3; m++) begin : gen_master assign masters[m].awaddr = awaddr[m]; assign masters[m].awlen = awlen[m]; assign masters[m].awsize = awsize[m]; @@ -120,11 +126,11 @@ module tiny_axi4_fabric_tb; assign masters[m].aruser = '0; assign masters[m].wuser = '0; end - for (genvar t = 0; t < 5; t++) begin : gen_target + for (genvar t = 0; t < 23; t++) begin : gen_target // Deterministic terminal responder allows independent checks of routing, // burst length and ID preservation without sharing the fabric decoder. axi4_error_slave #( - .Response(t == 3 ? 2'b11 : (t == 4 ? 2'b10 : 2'b00)) + .Response(t == 21 ? 2'b11 : (t == 22 ? 2'b10 : 2'b00)) ) u_response ( .clk_i (clk_i), .rst_n_i(rst_n_i), @@ -243,6 +249,7 @@ module tiny_axi4_fabric_tb; rready = '0; completions[0] = 0; completions[1] = 0; + completions[2] = 0; repeat (5) @(negedge clk_i); rst_n_i = 1; fork @@ -265,18 +272,18 @@ module tiny_axi4_fabric_tb; end join if (completions[0]!=7 || completions[1]!=6 || faults!=7 || - target_requests[0]!=3 || target_requests[1]!=1 || target_requests[2]!=2 || - target_requests[3]!=3 || target_requests[4]!=4 || cpu_wait==0 || dma_wait==0) + target_requests[0]!=2 || target_requests[3]!=1 || target_requests[4]!=1 || + target_requests[6]!=2 || target_requests[21]!=3 || target_requests[22]!=4) $fatal(1, "accounting/fairness mismatch faults=%0d", faults); read_request(0, 32'h30000000, 1, 2, 1, 0); arprot[0] = 3'b100; read_request(0, 32'h10001000, 1, 2, 1, 2); read_request(0, 32'h54000000, 1, 2, 1, 3); - if (target_requests[2] != 2 || target_requests[1] != 1) + if (target_requests[6] != 2 || target_requests[4] != 1) $fatal(1, "instruction request reached a peripheral or non-NSS0 window"); read_request(0, 32'h00000000, 1, 2, 1, 0); read_request(0, 32'h50000000, 1, 2, 1, 0); - if (target_requests[1] != 3) $fatal(1, "NOR instruction windows not reachable"); + if (target_requests[4] != 3) $fatal(1, "NOR instruction windows not reachable"); $display("SIM_TEST_PASS Tiny fabric"); $finish; end diff --git a/tests/rtl/tiny_r2_p4_cpu_tb.sv b/tests/rtl/tiny_r2_p4_cpu_tb.sv index f8bc13de..f45b53f9 100644 --- a/tests/rtl/tiny_r2_p4_cpu_tb.sv +++ b/tests/rtl/tiny_r2_p4_cpu_tb.sv @@ -63,13 +63,16 @@ tiny_cpu_wrapper #( .cpu (cpu) ); tiny_cpu_mem u_cpu_mem ( - .clk_i (clk_i), - .rst_n_i (core_rst_n), - .quiesce_i (quiesce), - .idle_o (idle), - .cpu (cpu), - .local_ports(local_ports), - .slow_axi4 (slow) + .clk_i (clk_i), + .rst_n_i (core_rst_n), + .quiesce_i (quiesce), + .idle_o (idle), + .local_fault_valid_o(), // P5 fault aggregation has its own fixture. + .local_fault_addr_o (), + .local_fault_write_o(), + .cpu (cpu), + .local_ports (local_ports), + .slow_axi4 (slow) ); tiny_sram u_sram ( .clk_i (clk_i), diff --git a/tests/rtl/tiny_r2_p4_memory_tb.sv b/tests/rtl/tiny_r2_p4_memory_tb.sv index 984c1b12..5c5c44ed 100644 --- a/tests/rtl/tiny_r2_p4_memory_tb.sv +++ b/tests/rtl/tiny_r2_p4_memory_tb.sv @@ -45,15 +45,24 @@ module tiny_r2_p4_memory_tb; integer local_latency_max = 0; logic [31:0] observed; logic [31:0] seed = 32'h12345678; + logic [1:0] local_fault_valid, local_fault_write; + logic [1:0][31:0] local_fault_addr; + integer local_fault_count[2] = '{0, 0}; + always @(posedge clk_i) begin + for (integer p = 0; p < 2; p++) if (rst_n_i && local_fault_valid[p]) local_fault_count[p]++; + end tiny_cpu_mem u_cpu_mem ( - .clk_i (clk_i), - .rst_n_i (rst_n_i), - .quiesce_i (quiesce), - .idle_o (idle), - .cpu (cpu), - .local_ports(local_ports), - .slow_axi4 (slow) + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .quiesce_i (quiesce), + .idle_o (idle), + .local_fault_valid_o(local_fault_valid), + .local_fault_addr_o (local_fault_addr), + .local_fault_write_o(local_fault_write), + .cpu (cpu), + .local_ports (local_ports), + .slow_axi4 (slow) ); tiny_sram u_sram ( .clk_i (clk_i), @@ -597,7 +606,14 @@ module tiny_r2_p4_memory_tb; buffered_write_errors(); read_external(0, 32'h30008000, 0, 0, 3); ahb(1, 0, 32'h70000000, 2, 0, 1, 0, observed); + if (local_fault_count[1] != 0) $fatal(1, "external CPU error double-counted locally"); ahb(1, 0, 32'h30000001, 2, 0, 1, 0, observed); + if (local_fault_count[1] != 1 || local_fault_addr[1] != 32'h30000001 || local_fault_write[1]) + $fatal(1, "local data fault metadata missing"); + ahb(0, 1, 32'h30000000, 2, 0, 1, 0, observed); + if (local_fault_count[0] != 1 || !local_fault_write[0]) + $fatal(1, "local instruction fault metadata missing"); + load(1, 32'h30000000, 32'ha5000000, 3); // Deterministic word/byte readback across all groups without touching a // simulator memory array: every operation travels through the real ports. for (integer g = 0; g < 4; g++) begin diff --git a/tests/rtl/tiny_r2_p5_cpu.S b/tests/rtl/tiny_r2_p5_cpu.S new file mode 100644 index 00000000..8e03bacc --- /dev/null +++ b/tests/rtl/tiny_r2_p5_cpu.S @@ -0,0 +1,144 @@ +.section .text +.global _start +.option norvc +_start: + # Verification-only slow ROM. Copy this position-independent image to SRAM. + la t0, _start + la t1, image_end + li t2, 0x30000000 +1: lw t3, 0(t0) + sw t3, 0(t2) + addi t0, t0, 4 + addi t2, t2, 4 + bltu t0, t1, 1b + fence.i + la t0, sram_main + li t1, 0x30000000 + add t0, t0, t1 + jr t0 +sram_main: + li sp, 0x3001f000 + li s0, 0x1000a100 + li s1, 0x1000b000 + li s2, 0x3001ffe0 + lw t0, 0x40(s1) + bnez t0, mapped_done + li t0, 0x30010000 + li t1, 256 + li t2, 0x12345678 +2: sw t2, 0(t0) + addi t0, t0, 4 + addi t2, t2, 1 + addi t1, t1, -1 + bnez t1, 2b + li t0, 0xe0 + sw t0, 4(s0) + li t0, 0x30010000 + sw t0, 8(s0) + li t0, 0x30018000 + sw t0, 12(s0) + li t0, 1024 + sw t0, 16(s0) + sw zero, 20(s0) + li t0, 16 + sw t0, 24(s0) + li t0, 1 + sw t0, 0(s0) + jal ra, wait_dma + li t0, 0x30018000 + li t1, 256 + li t2, 0x12345678 +3: lw t3, 0(t0) + bne t3, t2, fail + addi t0, t0, 4 + addi t2, t2, 1 + addi t1, t1, -1 + bnez t1, 3b + # Start an XPI read whose response is withheld by the boundary model. + li t0, 0x50000000 + sw t0, 8(s0) + li t0, 0x10009040 + sw t0, 12(s0) + li t0, 0x60 + sw t0, 4(s0) + li t0, 4 + sw t0, 16(s0) + li t0, 1 + sw t0, 0(s0) + li t0, 0x30008000 + li t1, 64 +4: sw t1, 0(t0) + addi t0, t0, 4 + addi t1, t1, -1 + bnez t1, 4b + li t0, 1 + sw t0, 0x40(s1) + lw t1, 0x40(s1) + bne t0, t1, fail + sw t0, 0(s2) + # The debugger resets this hart while DMA/XPI stays owned. PERF_CTRL is + # retained by SYSCTRL and selects the resume path after the ROM copy. +5: lw t1, 0x40(s1) + li t0, 1 + bne t0, t1, fail + j 5b +mapped_done: + jal ra, wait_dma + li t0, 0x10009044 + lw t1, 0(t0) + li t0, 0x40000000 + bne t0, t1, fail + li t0, 0xe0 + sw t0, 4(s0) + li t0, 0x30018000 + sw t0, 12(s0) + li t0, 64 + sw t0, 16(s0) + sw zero, 0x40(s1) + li t0, 2 + sw t0, 0(s2) + li t0, 0x50000100 + sw t0, 8(s0) + li t0, 1 + sw t0, 0(s0) + li t1, 128 +7: addi t1, t1, -1 + bnez t1, 7b + li t0, 8 + sw t0, 0(s0) + lw t1, 32(s0) + andi t1, t1, 1 + beqz t1, fail + li t0, 3 + sw t0, 0(s2) + li t2, 100000 +8: lw t1, 32(s0) + andi t0, t1, 1 + beqz t0, 9f + addi t2, t2, -1 + bnez t2, 8b + j fail +9: andi t0, t1, 8 + beqz t0, fail + li t0, 0x80000001 + sw t0, 0x84(s1) +10: j 10b +wait_dma: + li t2, 100000 +11: lw t1, 32(s0) + andi t0, t1, 1 + beqz t0, 12f + addi t2, t2, -1 + bnez t2, 11b + j fail +12: andi t0, t1, 0x14 + li t1, 4 + bne t0, t1, fail + ret +fail: + li t0, 0x1000b084 + li t1, 0x80000000 + sw t1, 0(t0) +13: j 13b +.balign 4 +image_end: diff --git a/tests/rtl/tiny_r2_p5_fabric_formal.sv b/tests/rtl/tiny_r2_p5_fabric_formal.sv new file mode 100644 index 00000000..45d0a53b --- /dev/null +++ b/tests/rtl/tiny_r2_p5_fabric_formal.sv @@ -0,0 +1,105 @@ +module tiny_r2_p5_fabric_formal ( + input logic clk_i, + rst_n_i +); + logic started = 0; + (* anyseq *) logic [2:0] arvalid, awvalid, wvalid, rready, bready; + (* anyseq *) logic [2:0][1:0] length; + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) masters[3] ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) target ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + logic idle, fault, fault_write, fault_instruction; + logic [31:0] fault_addr; + logic [1:0] fault_owner, fault_resp; + always_ff @(posedge clk_i) begin + started <= 1; + assume (rst_n_i == started); + end + tiny_axi4_target u_target ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .enable_i (1'b1), + .masters (masters), + .target (target), + .idle_o (idle), + .fault_valid_o (fault), + .fault_addr_o (fault_addr), + .fault_master_o (fault_owner), + .fault_write_o (fault_write), + .fault_instruction_o(fault_instruction), + .fault_resp_o (fault_resp) + ); + axi4_error_slave #( + .Response(2'b10) + ) u_endpoint ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .axi4 (target) + ); + for (genvar m = 0; m < 3; m++) begin : gen_master + logic outstanding = 0; + wire accept=(masters[m].arvalid && masters[m].arready)||(masters[m].awvalid && masters[m].awready); + wire done=(masters[m].rvalid && masters[m].rready && masters[m].rlast)|| + (masters[m].bvalid && masters[m].bready); + assign masters[m].araddr = 32'h30000000 + 32'(m) * 32'h100; + assign masters[m].awaddr = 32'h30000000 + 32'(m) * 32'h100; + assign masters[m].arlen = {6'd0, length[m]}; + assign masters[m].awlen = 0; + assign masters[m].arsize = 2; + assign masters[m].awsize = 2; + assign masters[m].arburst = 1; + assign masters[m].awburst = 1; + assign masters[m].arlock = 0; + assign masters[m].awlock = 0; + assign masters[m].arid = 1'(m); + assign masters[m].awid = 1'(m); + assign masters[m].arcache = 0; + assign masters[m].awcache = 0; + assign masters[m].arprot = 0; + assign masters[m].awprot = 0; + assign masters[m].arqos = 0; + assign masters[m].awqos = 0; + assign masters[m].arregion = 0; + assign masters[m].awregion = 0; + assign masters[m].aruser = 0; + assign masters[m].awuser = 0; + assign masters[m].arvalid = arvalid[m] && !outstanding; + assign masters[m].awvalid = awvalid[m] && !outstanding; + assign masters[m].wvalid = wvalid[m]; + assign masters[m].wlast = 1; + assign masters[m].wdata = 32'(m); + assign masters[m].wstrb = 15; + assign masters[m].wuser = 0; + assign masters[m].rready = rready[m]; + assign masters[m].bready = bready[m]; + always_ff @(posedge clk_i) begin + if (!rst_n_i) outstanding <= 0; + else begin + if (accept) outstanding <= 1; + if (done) outstanding <= 0; + assert (!(accept && outstanding)); + assert (!done || outstanding); + assert (!(masters[m].arready && masters[m].awready)); + if (done) begin + assert (fault && fault_owner == 2'(m) && fault_resp == 2); + assert (fault_addr == 32'h30000000 + 32'(m) * 32'h100); + end + end + end + end +endmodule diff --git a/tests/rtl/tiny_r2_p5_fabric_tb.sv b/tests/rtl/tiny_r2_p5_fabric_tb.sv new file mode 100644 index 00000000..8b67df48 --- /dev/null +++ b/tests/rtl/tiny_r2_p5_fabric_tb.sv @@ -0,0 +1,523 @@ +`timescale 1ns / 1ps +module tiny_r2_p5_fabric_tb; + logic clk_i = 0; + logic rst_n_i = 0; + logic target_rst_n[23]; + always #5 clk_i = !clk_i; + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) masters[3] ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) targets[23] ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + logic [5:0] fault_valid, fault_instruction; + logic [5:0][31:0] fault_addr; + logic [5:0][ 1:0] fault_resp; + logic [63:0] cpu_wait, dma_wait; + integer faults = 0; + logic [22:0] hold_address = '0, hold_read = '0, hold_write = '0, hold_b = '0; + integer error_beat [23]; + logic [ 2:0] error_last_wait = '0; + logic release_error = 0; + integer simultaneous_faults = 0; + logic [31:0] seed = 32'h12345678; + integer completions [ 3]; + integer target_requests [23]; + logic [ 2:0][31:0] awaddr; + logic [ 2:0][ 7:0] awlen; + logic [ 2:0][ 2:0] awsize; + logic [ 2:0][ 1:0] awburst; + logic [ 2:0][ 0:0] awlock; + logic [ 2:0][ 0:0] awid; + logic [ 2:0][ 0:0] awvalid; + logic [ 2:0][31:0] wdata; + logic [ 2:0][ 3:0] wstrb; + logic [ 2:0][ 0:0] wlast; + logic [ 2:0][ 0:0] wvalid; + logic [ 2:0][ 0:0] bready; + logic [ 2:0][31:0] araddr; + logic [ 2:0][ 7:0] arlen; + logic [ 2:0][ 2:0] arsize; + logic [ 2:0][ 1:0] arburst; + logic [ 2:0][ 0:0] arlock; + logic [ 2:0][ 0:0] arid; + logic [ 2:0][ 0:0] arvalid; + logic [ 2:0][ 2:0] arprot; + logic [ 2:0][ 0:0] rready; + logic [ 2:0][ 0:0] awready; + logic [ 2:0][ 0:0] arready; + logic [ 2:0][ 0:0] wready; + logic [ 2:0][ 0:0] bvalid; + logic [ 2:0][ 0:0] bid; + logic [ 2:0][ 1:0] bresp; + logic [ 2:0][ 0:0] rvalid; + logic [ 2:0][ 0:0] rid; + logic [ 2:0][ 0:0] rlast; + logic [ 2:0][31:0] rdata; + logic [ 2:0][ 1:0] rresp; + tiny_axi4_fabric u_dut ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .target_rst_n_i (target_rst_n), + .perf_enable_i (1'b1), + .perf_clear_i (1'b0), + .masters (masters), + .targets (targets), + .fault_valid_o (fault_valid), + .fault_addr_o (fault_addr), + .fault_instruction_o(fault_instruction), + .owner_idle_o (), + .target_idle_o (), + .fault_resp_o (fault_resp), + .cpu_wait_o (cpu_wait), + .dma_wait_o (dma_wait) + ); + for (genvar target = 0; target < 23; target++) begin : gen_target_reset + assign target_rst_n[target] = rst_n_i; + end + always @(posedge clk_i) begin + faults <= faults + $countones(fault_valid); + if (fault_valid[2] && fault_valid[4]) begin + simultaneous_faults <= simultaneous_faults + 1; + if (fault_addr[2] != 32'h30000004 || fault_addr[4] != 32'h30008004 || + fault_resp[2] != 2 || fault_resp[4] != 2) + $fatal(1, "first nonfinal error metadata lost"); + end + end + for (genvar m = 0; m < 3; m++) begin : gen_master + assign masters[m].awaddr = awaddr[m]; + assign masters[m].awlen = awlen[m]; + assign masters[m].awsize = awsize[m]; + assign masters[m].awburst = awburst[m]; + assign masters[m].awlock = awlock[m]; + assign masters[m].awid = awid[m]; + assign masters[m].awvalid = awvalid[m]; + assign masters[m].wdata = wdata[m]; + assign masters[m].wstrb = wstrb[m]; + assign masters[m].wlast = wlast[m]; + assign masters[m].wvalid = wvalid[m]; + assign masters[m].bready = bready[m]; + assign masters[m].araddr = araddr[m]; + assign masters[m].arlen = arlen[m]; + assign masters[m].arsize = arsize[m]; + assign masters[m].arburst = arburst[m]; + assign masters[m].arlock = arlock[m]; + assign masters[m].arid = arid[m]; + assign masters[m].arvalid = arvalid[m]; + assign masters[m].rready = rready[m]; + assign awready[m] = masters[m].awready; + assign arready[m] = masters[m].arready; + assign wready[m] = masters[m].wready; + assign bvalid[m] = masters[m].bvalid; + assign bid[m] = masters[m].bid; + assign bresp[m] = masters[m].bresp; + assign rvalid[m] = masters[m].rvalid; + assign rid[m] = masters[m].rid; + assign rlast[m] = masters[m].rlast; + assign rdata[m] = masters[m].rdata; + assign rresp[m] = masters[m].rresp; + assign masters[m].awcache = '0; + assign masters[m].awprot = '0; + assign masters[m].awqos = '0; + assign masters[m].awregion = '0; + assign masters[m].awuser = '0; + assign masters[m].arcache = '0; + assign masters[m].arprot = arprot[m]; + assign masters[m].arqos = '0; + assign masters[m].arregion = '0; + assign masters[m].aruser = '0; + assign masters[m].wuser = '0; + end + for (genvar t = 0; t < 23; t++) begin : gen_target + tiny_p5_target_model #( + .ErrorResponse(t == 21 ? 3 : (t == 22 ? 2 : 0)) + ) u_response ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .hold_address_i(hold_address[t]), + .hold_read_i (hold_read[t]), + .hold_write_i (hold_write[t]), + .hold_b_i (hold_b[t]), + .error_beat_i (error_beat[t]), + .axi4 (targets[t]) + ); + initial begin + target_requests[t] = 0; + error_beat[t] = -1; + end + always @(posedge clk_i) begin + if ((targets[t].arvalid && targets[t].arready) || (targets[t].awvalid && targets[t].awready)) + target_requests[t] <= target_requests[t] + 1; + end + end + task automatic read_request(input int m, input logic [31:0] addr, input int beats, + input logic [2:0] size, input logic [1:0] burst, + input logic [1:0] expected); + logic [35:0] held; + @(negedge clk_i); + araddr[m] = addr; + arlen[m] = 8'(beats - 1); + arsize[m] = size; + arburst[m] = burst; + arid[m] = 1'(m); + arvalid[m] = 1; + do @(posedge clk_i); while (!arready[m]); + @(negedge clk_i); + arvalid[m] = 0; + araddr[m] = 32'hbad00000; // The accepted address, not live pins, owns responses. + for (int beat = 0; beat < beats; beat++) begin + do @(posedge clk_i); while (!rvalid[m]); + held = {rid[m], rlast[m], rresp[m], rdata[m]}; + repeat (3) begin + @(negedge clk_i); + if (!rvalid[m] || held != {rid[m], rlast[m], rresp[m], rdata[m]}) + $fatal(1, "read changed under backpressure"); + end + if (rresp[m] != expected || rid[m] != 1'(m) || rlast[m] != (beat == beats - 1)) + $fatal( + 1, + "read response/ID/length mismatch m=%0d addr=%x beat=%0d resp=%0d", + m, + addr, + beat, + rresp[m] + ); + if (expected == 0 && rdata[m] !== (32'h10000000 ^ (addr + (32'(beat) << size)))) + $fatal(1, "read data routed from wrong target/beat"); + rready[m] = 1; + @(posedge clk_i); + @(negedge clk_i); + rready[m] = 0; + end + completions[m]++; + endtask + task automatic write_request(input int m, input logic [31:0] addr, input int beats, + input logic [1:0] expected, input bit early_w); + @(negedge clk_i); + awaddr[m] = addr; + awlen[m] = 8'(beats - 1); + awsize[m] = 2; + awburst[m] = 1; + awid[m] = 1'(m); + wdata[m] = 32'hb0000000 ^ addr; + wstrb[m] = 4'b0101; + wlast[m] = (beats == 1); + if (early_w) begin + wvalid[m] = 1; + repeat (4) begin + @(posedge clk_i); + if (wready[m]) $fatal(1, "W accepted before AW"); + end + @(negedge clk_i); + end + awvalid[m] = 1; + do @(posedge clk_i); while (!awready[m]); + @(negedge clk_i); + awvalid[m] = 0; + awaddr[m] = 32'hbad08000; + if (!early_w) repeat (5) @(negedge clk_i); + for (int beat = 0; beat < beats; beat++) begin + wvalid[m] = 1; + wlast[m] = (beat == beats - 1); + wdata[m] = 32'hb0000000 ^ addr ^ 32'(beat); + do @(posedge clk_i); while (!wready[m]); + @(negedge clk_i); + wvalid[m] = 0; + repeat (2) @(negedge clk_i); + end + do @(posedge clk_i); while (!bvalid[m]); + repeat (3) begin + @(negedge clk_i); + if (!bvalid[m] || bresp[m] != expected || bid[m] != 1'(m)) + $fatal(1, "write response mismatch"); + end + bready[m] = 1; + @(posedge clk_i); + @(negedge clk_i); + bready[m] = 0; + completions[m]++; + endtask + + task automatic mixed_error_read(input int m, input logic [31:0] address); + @(negedge clk_i); + araddr[m] = address; + arlen[m] = 3; + arsize[m] = 2; + arburst[m] = 1; + arvalid[m] = 1; + arid[m] = 1'(m); + do @(posedge clk_i); while (!arready[m]); + @(negedge clk_i); + arvalid[m] = 0; + for (integer beat = 0; beat < 4; beat++) begin + wait (rvalid[m]); + if (beat == 3) begin + error_last_wait[m] = 1; + wait (release_error); + end + @(negedge clk_i); + if (rresp[m] != (beat == 1 ? 2 : 0) || rlast[m] != (beat == 3)) + $fatal(1, "mixed read response was altered"); + rready[m] = 1; + @(posedge clk_i); + @(negedge clk_i); + rready[m] = 0; + end + endtask + task automatic random_reads(input int m, input logic [31:0] initial_seed); + logic [31:0] state; + logic [31:0] address; + integer beats; + state = initial_seed; + for (integer n = 0; n < 32; n++) begin + state = state ^ (state << 13); + state = state ^ (state >> 17); + state = state ^ (state << 5); + address = 32'h30000000 + (state % 4) * 32768 + ((state >> 4) % 16) * 64; + beats = 1 + integer'((state >> 12) % 16); + read_request(m, address, beats, 2, 1, 0); + end + endtask + initial begin + awaddr = '0; + awlen = '0; + awsize = '0; + awburst = '0; + awlock = '0; + awid = '0; + awvalid = '0; + wdata = '0; + wstrb = '0; + wlast = '0; + wvalid = '0; + bready = '0; + araddr = '0; + arlen = '0; + arsize = '0; + arburst = '0; + arlock = '0; + arid = '0; + arvalid = '0; + arprot = '0; + rready = '0; + for (integer m = 0; m < 3; m++) completions[m] = 0; + repeat (5) @(negedge clk_i); + rst_n_i = 1; + // XPI can withhold its first response indefinitely without owning APB/SRAM. + hold_read[4] = 1; + fork + read_request(1, 32'h50000000, 16, 2, 1, 0); + begin + wait (target_requests[4] == 1); + read_request(0, 32'h1000b000, 1, 2, 1, 0); + read_request(0, 32'h30000000, 4, 2, 1, 0); + if (!u_dut.s_busy_q[2] || u_dut.target_idle_o[4]) + $fatal(1, "XPI test did not retain ownership"); + hold_read[4] = 0; + end + join + // One owner's read and write can own different targets simultaneously. + hold_read[0] = 1; + hold_b[1] = 1; + hold_write[1] = 1; + fork + read_request(1, 32'h30000000, 16, 2, 1, 0); + write_request(1, 32'h30008000, 16, 0, 1); + begin + wait (u_dut.s_busy_q[2] && u_dut.s_busy_q[3]); + read_request(2, 32'h30010000, 4, 2, 1, 0); + if (u_dut.target_idle_o[0] || u_dut.target_idle_o[1]) + $fatal(1, "different targets serialized"); + hold_read[0] = 0; + hold_b[1] = 0; + hold_write[1] = 0; + end + join + // A waiting APB FIFO branch cannot block independent control admission. + hold_read[12] = 1; + fork + read_request(1, 32'h10009000, 1, 2, 1, 0); + begin + wait (!u_dut.target_idle_o[12]); + read_request(0, 32'h1000a000, 1, 2, 1, 0); + hold_read[12] = 0; + end + join + fork + begin + repeat (8) read_request(0, 32'h30000000, 4, 2, 1, 0); + end + begin + repeat (8) write_request(1, 32'h30000000, 4, 0, 0); + end + begin + repeat (8) read_request(2, 32'h30000000, 4, 2, 1, 0); + end + join + if (cpu_wait == 0 || dma_wait == 0) $fatal(1, "same-target wait counters never advanced"); + // Even when both CPU directions request different targets together, + // the fabric must retain the CPU's one-combined-operation contract. + fork + read_request(0, 32'h30000000, 4, 2, 1, 0); + write_request(0, 32'h30008000, 4, 0, 0); + join + error_beat[0] = 1; + error_beat[1] = 1; + fork + mixed_error_read(1, 32'h30000000); + mixed_error_read(2, 32'h30008000); + begin + wait (error_last_wait[1] && error_last_wait[2]); + @(negedge clk_i); + release_error = 1; + end + join + if (simultaneous_faults != 1) $fatal(1, "simultaneous terminal faults lost"); + error_beat[0] = -1; + error_beat[1] = -1; + fork + random_reads(0, 32'h12345678); + random_reads(1, 32'h87654321); + random_reads(2, 32'hc0ffee01); + join + // An accepted address buffered ahead of a stopped target remains owned. + @(negedge clk_i); + hold_address[0] = 1; + araddr[1] = 32'h30000000; + arlen[1] = 0; + arsize[1] = 2; + arburst[1] = 1; + arvalid[1] = 1; + do @(posedge clk_i); while (!arready[1]); + @(negedge clk_i); + arvalid[1] = 0; + repeat (8) @(negedge clk_i); + if (u_dut.owner_idle_o[1] || u_dut.target_idle_o[0]) $fatal(1, "buffered AR not outstanding"); + arvalid = '0; + awvalid = '0; + wvalid = '0; + rready = '0; + bready = '0; + rst_n_i = 0; + repeat (5) @(negedge clk_i); + hold_address = '0; + rst_n_i = 1; + repeat (4) @(negedge clk_i); + if (rvalid != 0 || bvalid != 0 || u_dut.owner_idle_o != 7) $fatal(1, "old session replayed"); + read_request(2, 32'h30000000, 1, 2, 1, 0); + $display("SIM_TEST_PASS Tiny P5 fabric seed=12345678 random_reads=96 concurrent_faults=2"); + $finish; + end + initial begin + repeat (100000) @(posedge clk_i); + $fatal(1, "SIM_TEST_TIMEOUT Tiny P5 fabric"); + end +endmodule + +// Verification endpoint: delays only the creation of VALID, never withdraws a +// presented response. Per-address data and write checks detect crossed routes. +module tiny_p5_target_model #( + parameter logic [1:0] ErrorResponse = 0 +) ( + input logic clk_i, + rst_n_i, + hold_address_i, + hold_read_i, + hold_write_i, + hold_b_i, + input integer error_beat_i, + axi4_if.slave axi4 +); + logic active = 0, writing = 0, write_done = 0, id = 0; + logic [31:0] address = 0, start_address = 0; + logic [7:0] length = 0, beat = 0; + logic [2:0] size = 0; + logic [1:0] burst = 0; + assign axi4.arready = !active && !hold_address_i; + assign axi4.awready = !active && !hold_address_i && !axi4.arvalid; + assign axi4.wready = active && writing && !write_done && !hold_write_i; + assign axi4.ruser = 0; + assign axi4.buser = 0; + always @(posedge clk_i) begin + if (!rst_n_i) begin + active <= 0; + writing <= 0; + write_done <= 0; + axi4.rvalid <= 0; + axi4.bvalid <= 0; + axi4.rdata <= 0; + axi4.rresp <= 0; + axi4.rlast <= 0; + axi4.rid <= 0; + axi4.bid <= 0; + axi4.bresp <= 0; + end else begin + if (axi4.arvalid && axi4.arready) begin + active <= 1; + writing <= 0; + address <= axi4.araddr; + start_address <= axi4.araddr; + length <= axi4.arlen; + beat <= 0; + id <= axi4.arid; + size <= axi4.arsize; + burst <= axi4.arburst; + end + if (axi4.awvalid && axi4.awready) begin + active <= 1; + writing <= 1; + write_done <= 0; + address <= axi4.awaddr; + start_address <= axi4.awaddr; + length <= axi4.awlen; + beat <= 0; + id <= axi4.awid; + size <= axi4.awsize; + burst <= axi4.awburst; + end + if (active && !writing && !axi4.rvalid && !hold_read_i) begin + axi4.rvalid <= 1; + axi4.rid <= id; + axi4.rdata <= 32'h10000000 ^ address; + axi4.rresp<=(ErrorResponse != 0) ? ErrorResponse : ((integer'(beat)==error_beat_i) ? 2 : 0); + axi4.rlast <= beat == length; + end + if (axi4.rvalid && axi4.rready) begin + axi4.rvalid <= 0; + if (axi4.rlast) active <= 0; + else begin + beat <= beat + 1; + if (burst == 1) address <= address + (32'd1 << size); + end + end + if (axi4.wvalid && axi4.wready) begin + if (axi4.wdata !== (32'hb0000000 ^ start_address ^ {24'd0, beat})) + $fatal(1, "write data crossed AW ownership"); + if (axi4.wlast || beat == length) write_done <= 1; + else beat <= beat + 1; + end + if (active && writing && write_done && !hold_b_i) begin + axi4.bvalid <= 1; + axi4.bid <= id; + axi4.bresp <= ErrorResponse; + end + if (axi4.bvalid && axi4.bready) begin + axi4.bvalid <= 0; + active <= 0; + write_done <= 0; + end + end + end +endmodule diff --git a/tests/rtl/tiny_r2_p5_fault_formal.sv b/tests/rtl/tiny_r2_p5_fault_formal.sv new file mode 100644 index 00000000..8d07699b --- /dev/null +++ b/tests/rtl/tiny_r2_p5_fault_formal.sv @@ -0,0 +1,55 @@ +module tiny_r2_p5_fault_formal; + (* anyseq *)logic [ 7:0] valid; + (* anyseq *)logic [ 7:0][31:0] addresses; + (* anyseq *)logic [ 1:0] local_write; + (* anyseq *)logic [ 5:0] instruction; + (* anyseq *)logic [ 5:0][ 1:0] responses; + logic [ 3:0] count; + logic [31:0] address; + logic [1:0] owner, response; + logic write_access; + logic found; + integer expected_owner, expected_write; + tiny_fault_collector u_dut ( + .local_valid_i (valid[1:0]), + .local_addr_i (addresses[1:0]), + .local_write_i (local_write), + .external_valid_i (valid[7:2]), + .external_addr_i (addresses[7:2]), + .external_resp_i (responses), + .external_instruction_i(instruction), + .count_o (count), + .addr_o (address), + .master_o (owner), + .write_o (write_access), + .resp_o (response) + ); + always_comb begin + assert (count == $countones(valid)); + found = 0; + expected_owner = 0; + expected_write = 0; + // Enumerate the specified lexicographic order, independently of the + // collector's packed rank and minimum selection implementation. + for (integer m = 0; m < 3; m++) begin + for (integer path = 0; path < 2; path++) begin + for (integer direction = 0; direction < 2; direction++) begin + for (integer event_index = 0; event_index < 8; event_index++) begin + expected_owner = (event_index < 2) ? 0 : ((event_index - 2) / 2); + expected_write=(event_index<2)?integer'(local_write[event_index]):((event_index-2)%2); + if(!found && valid[event_index] && expected_owner==m && expected_write==direction && + ((event_index<2)?(event_index==path): + (((event_index<4)&&instruction[event_index-2])?(path==0):(path==1)))) begin + found = 1; + assert (owner == 2'(m)); + assert (write_access == 1'(direction)); + assert (address == addresses[event_index]); + assert (response == ((event_index < 2) ? 2'b10 : responses[event_index-2])); + end + end + end + end + end + if (valid == 0) assert ({address, owner, write_access, response} == 0); + end +endmodule diff --git a/tests/rtl/tiny_r2_p5_reset_tb.sv b/tests/rtl/tiny_r2_p5_reset_tb.sv new file mode 100644 index 00000000..605a02ab --- /dev/null +++ b/tests/rtl/tiny_r2_p5_reset_tb.sv @@ -0,0 +1,303 @@ +`timescale 1ns / 1ps + +module tiny_r2_p5_reset_tb; + logic clk_i = 0; + logic rst_n_i = 0; + logic target_rst_n[23]; + always #5 clk_i = !clk_i; + + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) masters[3] ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) targets[23] ( + .aclk (clk_i), + .aresetn(target_rst_n) + ); + logic [5:0] fault_valid, fault_instruction; + logic [ 5:0][31:0] fault_addr; + logic [ 5:0][ 1:0] fault_resp; + logic [ 2:0] owner_idle; + logic [22:0] target_idle; + logic [63:0] cpu_wait, dma_wait; + logic hold_address[23]; + logic hold_read [23]; + logic hold_write [23]; + logic hold_b [23]; + logic [2:0] arvalid = '0, rready = '0, awvalid = '0, wvalid = '0, bready = '0; + logic [2:0][31:0] araddr = '0, awaddr = '0, wdata = '0; + logic [2:0][7:0] arlen = '0, awlen = '0; + logic [2:0][2:0] arsize = '0, awsize = '0; + logic [2:0][1:0] arburst = '0, awburst = '0; + logic [2:0] arlock = '0, awlock = '0; + logic [2:0] arid = '0, awid = '0; + logic [2:0][3:0] wstrb = '0; + logic [2:0] wlast = '0; + logic [2:0][2:0] arprot = '0; + logic [2:0] arready, awready, rvalid, bvalid; + logic [2:0][1:0] rresp, bresp; + logic [2:0] rlast; + + tiny_axi4_fabric u_dut ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .target_rst_n_i (target_rst_n), + .perf_enable_i (1'b0), + .perf_clear_i (1'b0), + .masters (masters), + .targets (targets), + .fault_valid_o (fault_valid), + .fault_addr_o (fault_addr), + .fault_resp_o (fault_resp), + .fault_instruction_o(fault_instruction), + .owner_idle_o (owner_idle), + .target_idle_o (target_idle), + .cpu_wait_o (cpu_wait), + .dma_wait_o (dma_wait) + ); + + for (genvar owner = 0; owner < 3; owner++) begin : gen_master + assign masters[owner].araddr = araddr[owner]; + assign masters[owner].arlen = arlen[owner]; + assign masters[owner].arsize = arsize[owner]; + assign masters[owner].arburst = arburst[owner]; + assign masters[owner].arlock = arlock[owner]; + assign masters[owner].arid = arid[owner]; + assign masters[owner].arvalid = arvalid[owner]; + assign masters[owner].arprot = arprot[owner]; + assign masters[owner].awaddr = awaddr[owner]; + assign masters[owner].awlen = awlen[owner]; + assign masters[owner].awsize = awsize[owner]; + assign masters[owner].awburst = awburst[owner]; + assign masters[owner].awlock = awlock[owner]; + assign masters[owner].awid = awid[owner]; + assign masters[owner].awvalid = awvalid[owner]; + assign masters[owner].wdata = wdata[owner]; + assign masters[owner].wstrb = wstrb[owner]; + assign masters[owner].wlast = wlast[owner]; + assign masters[owner].wvalid = wvalid[owner]; + assign masters[owner].rready = rready[owner]; + assign masters[owner].bready = bready[owner]; + assign masters[owner].arcache = '0; + assign masters[owner].arqos = '0; + assign masters[owner].arregion = '0; + assign masters[owner].aruser = '0; + assign masters[owner].awcache = '0; + assign masters[owner].awprot = '0; + assign masters[owner].awqos = '0; + assign masters[owner].awregion = '0; + assign masters[owner].awuser = '0; + assign masters[owner].wuser = '0; + assign arready[owner] = masters[owner].arready; + assign awready[owner] = masters[owner].awready; + assign rvalid[owner] = masters[owner].rvalid; + assign rresp[owner] = masters[owner].rresp; + assign rlast[owner] = masters[owner].rlast; + assign bvalid[owner] = masters[owner].bvalid; + assign bresp[owner] = masters[owner].bresp; + end + + for (genvar target = 0; target < 23; target++) begin : gen_target + tiny_r2_p5_reset_target_model u_model ( + .clk_i (clk_i), + .rst_n_i (target_rst_n[target]), + .hold_address_i(hold_address[target]), + .hold_read_i (hold_read[target]), + .hold_write_i (hold_write[target]), + .hold_b_i (hold_b[target]), + .axi4 (targets[target]) + ); + end + + task automatic reset_read(input logic [31:0] address, input int target); + @(negedge clk_i); + araddr[0] = address; + arlen[0] = 0; + arsize[0] = 2; + arburst[0] = 1; + arid[0] = 0; + arvalid[0] = 1; + do @(posedge clk_i); while (!arready[0]); + @(negedge clk_i); + arvalid[0] = 0; + repeat (2) @(posedge clk_i); + if (owner_idle[0] || target_idle[target]) $fatal(1, "read was not retained"); + target_rst_n[target] = 0; + do @(posedge clk_i); while (!rvalid[0]); + if (rresp[0] != 2'b11 || !rlast[0]) $fatal(1, "reset read did not terminate with DECERR"); + rready[0] = 1; + @(posedge clk_i); + @(negedge clk_i); + rready[0] = 0; + target_rst_n[target] = 1; + repeat (2) @(posedge clk_i); + if (!owner_idle[0]) $fatal(1, "read credit was not released after reset"); + endtask + + task automatic reset_write(input logic [31:0] address, input int target); + @(negedge clk_i); + awaddr[1] = address; + awlen[1] = 0; + awsize[1] = 2; + awburst[1] = 1; + awid[1] = 1; + awvalid[1] = 1; + do @(posedge clk_i); while (!awready[1]); + @(negedge clk_i); + awvalid[1] = 0; + repeat (2) @(posedge clk_i); + if (owner_idle[1] || target_idle[target]) $fatal(1, "write was not retained"); + target_rst_n[target] = 0; + do @(posedge clk_i); while (!bvalid[1]); + if (bresp[1] != 2'b11) $fatal(1, "reset write did not terminate with DECERR"); + bready[1] = 1; + @(posedge clk_i); + @(negedge clk_i); + bready[1] = 0; + target_rst_n[target] = 1; + repeat (2) @(posedge clk_i); + if (!owner_idle[1]) $fatal(1, "write credit was not released after reset"); + endtask + + task automatic reset_buffered_read; + @(negedge clk_i); + araddr[0] = 32'h30000000; + arlen[0] = 0; + arsize[0] = 2; + arburst[0] = 1; + arid[0] = 0; + arvalid[0] = 1; + do @(posedge clk_i); while (!arready[0]); + @(negedge clk_i); + arvalid[0] = 0; + do @(posedge clk_i); while (!rvalid[0]); + if (rresp[0] != 2'b00) $fatal(1, "buffered read did not reach response spill"); + target_rst_n[0] = 0; + do @(posedge clk_i); while (!rvalid[0]); + if (rresp[0] != 2'b11 || !rlast[0]) $fatal(1, "buffered response was replayed after reset"); + rready[0] = 1; + @(posedge clk_i); + @(negedge clk_i); + rready[0] = 0; + target_rst_n[0] = 1; + repeat (2) @(posedge clk_i); + if (!owner_idle[0]) $fatal(1, "buffered read credit was not released after reset"); + endtask + + initial begin + for (integer target = 0; target < 23; target++) begin + target_rst_n[target] = 0; + hold_address[target] = 0; + hold_read[target] = 0; + hold_write[target] = 0; + hold_b[target] = 0; + end + hold_read[0] = 1; + hold_write[12] = 1; + repeat (4) @(negedge clk_i); + rst_n_i = 1; + for (integer target = 0; target < 23; target++) target_rst_n[target] = 1; + reset_read(32'h30000000, 0); + hold_read[0] = 0; + reset_buffered_read(); + reset_write(32'h10009000, 12); + if (fault_valid != 0) $fatal(1, "fault pulse was not consumed"); + $display("SIM_TEST_PASS Tiny P5 target reset isolation"); + $finish; + end + initial begin + repeat (2000) @(posedge clk_i); + $fatal(1, "SIM_TEST_TIMEOUT Tiny P5 target reset isolation"); + end +endmodule + +module tiny_r2_p5_reset_target_model ( + input logic clk_i, + input logic rst_n_i, + input logic hold_address_i, + input logic hold_read_i, + input logic hold_write_i, + input logic hold_b_i, + axi4_if.slave axi4 +); + logic active = 0; + logic writing = 0; + logic write_done = 0; + logic id = 0; + logic [31:0] address = 0; + logic [ 7:0] length = 0; + logic [ 7:0] beat = 0; + + assign axi4.arready = !active && !hold_address_i && rst_n_i; + assign axi4.awready = !active && !hold_address_i && !axi4.arvalid && rst_n_i; + assign axi4.wready = active && writing && !write_done && !hold_write_i && rst_n_i; + assign axi4.ruser = 0; + assign axi4.buser = 0; + + always @(posedge clk_i) begin + if (!rst_n_i) begin + active <= 0; + writing <= 0; + write_done <= 0; + axi4.rvalid <= 0; + axi4.bvalid <= 0; + axi4.rdata <= 0; + axi4.rresp <= 0; + axi4.rlast <= 0; + axi4.rid <= 0; + axi4.bid <= 0; + axi4.bresp <= 0; + end else begin + if (axi4.arvalid && axi4.arready) begin + active <= 1; + writing <= 0; + address <= axi4.araddr; + length <= axi4.arlen; + beat <= 0; + id <= axi4.arid; + end + if (axi4.awvalid && axi4.awready) begin + active <= 1; + writing <= 1; + write_done <= 0; + address <= axi4.awaddr; + length <= axi4.awlen; + beat <= 0; + id <= axi4.awid; + end + if (active && !writing && !axi4.rvalid && !hold_read_i) begin + axi4.rvalid <= 1; + axi4.rid <= id; + axi4.rdata <= 32'h10000000 ^ address; + axi4.rresp <= 0; + axi4.rlast <= beat == length; + end + if (axi4.rvalid && axi4.rready) begin + axi4.rvalid <= 0; + active <= 0; + end + if (axi4.wvalid && axi4.wready) begin + write_done <= 1; + end + if (active && writing && write_done && !hold_b_i) begin + axi4.bvalid <= 1; + axi4.bid <= id; + axi4.bresp <= 0; + end + if (axi4.bvalid && axi4.bready) begin + axi4.bvalid <= 0; + active <= 0; + end + end + end +endmodule diff --git a/tests/rtl/tiny_r2_p5_system_tb.sv b/tests/rtl/tiny_r2_p5_system_tb.sv new file mode 100644 index 00000000..454f32d0 --- /dev/null +++ b/tests/rtl/tiny_r2_p5_system_tb.sv @@ -0,0 +1,189 @@ +`timescale 1ns / 1ps +module tiny_r2_p5_system_tb; + logic clk_i = 0, rst_n_i = 0; + always #20.833 clk_i = ~clk_i; + wire s_rst_n = rst_n_i; + logic s_jtag_tck_driver = 0, s_jtag_tms_driver = 1, s_jtag_tdi_driver = 0; + wire s_jtag_tdo; + logic done, pass; + logic [7:0] code; + gpio_if gpio (); + xpi_if xpi (); + integer overlap_cycles = 0, progress = 0, controls = 0, controls_before = 0; + logic hart_reset_observed = 0; + logic [31:0] status; + bit ignored; + `include "tiny_jtag_smoke.svh" +retrosoc_tiny u_dut ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .jtag_tck_i (s_jtag_tck_driver), + .jtag_tms_i (s_jtag_tms_driver), + .jtag_tdi_i (s_jtag_tdi_driver), + .jtag_trst_n_i(rst_n_i), + .jtag_tdo_o (s_jtag_tdo), + .uart0_rx_i (1'b1), + .uart1_rx_i (1'b1), + .gpio (gpio), + .xpi (xpi), + .test_done_o (done), + .test_pass_o (pass), + .test_code_o (code) + ); + assign gpio.di_i = 0; + assign xpi.io_di_i = 0; + always @(posedge clk_i) begin + if (rst_n_i) begin + if(u_dut.u_masters_axi4_if[1].rvalid && u_dut.u_masters_axi4_if[1].rready && + u_dut.u_masters_axi4_if[1].wvalid && u_dut.u_masters_axi4_if[1].wready) + overlap_cycles++; + if((u_dut.u_fabric.s_busy_q[2] || u_dut.u_fabric.s_busy_q[3]) && + (!u_dut.u_fabric.target_idle_o[4] || !u_dut.u_fabric.target_idle_o[12]) && + u_dut.u_sysctrl_apb4_if.psel && u_dut.u_sysctrl_apb4_if.penable && u_dut.u_sysctrl_apb4_if.pready) + controls++; + if(u_dut.u_cpu_local_if[1].req_valid && u_dut.u_cpu_local_if[1].req_ready && + u_dut.u_cpu_local_if[1].write && u_dut.u_cpu_local_if[1].addr==32'h3001ffe0) begin + progress = integer'(u_dut.u_cpu_local_if[1].wdata); + if (progress == 2) u_dut.u_xpi.hold_mapped = 1; + if (progress == 3) begin + if (!u_dut.u_fabric.s_busy_q[2]) $fatal(1, "abort discarded accepted read"); + u_dut.u_xpi.hold_mapped = 0; + end + end + end + end + initial begin + repeat (6) jtag_tick(1, 0, ignored); + @(negedge clk_i); + rst_n_i = 1; + repeat (12) jtag_tick(1, 0, ignored); + jtag_idle(1); + jtag_ir(5'h11); + wait (progress == 1); + $display("P5_SYSTEM_STAGE independent-progress"); + if (controls == 0 || overlap_cycles == 0 || !u_dut.u_fabric.s_busy_q[2]) + $fatal(1, "CPU/control progress or real DMA overlap absent"); + // Move the held DMA work to an independent APB branch before resetting + // the hart. CPU debug entry may fetch the reset/exception ROM aperture; + // accepted CPU work must be allowed to drain rather than discarded. + @(negedge clk_i); + u_dut.u_xpi.hold_mapped = 0; + wait (u_dut.u_xpi_apb4_if.psel && u_dut.u_xpi_apb4_if.penable && !u_dut.u_xpi_apb4_if.pready); + controls_before = controls; + repeat (64) @(posedge clk_i); + if (controls == controls_before) $fatal(1, "APB FIFO wait blocked independent control"); + jtag_dmi(1, 7'h10, 1, status); + jtag_dmi(1, 7'h10, 3, status); + $display("P5_SYSTEM_STAGE reset-request pending=%b", u_dut.u_cpu.s_reset_pending); + wait (!u_dut.s_core_rst_n); + if (!u_dut.u_fabric.s_busy_q[3] || u_dut.u_fabric.target_idle_o[12]) + $fatal(1, "hart reset cancelled unrelated DMA"); + hart_reset_observed = 1; + $display("P5_SYSTEM_STAGE hart-reset"); + jtag_dmi(1, 7'h10, 1, status); + wait (u_dut.s_core_rst_n); + @(negedge clk_i); + u_dut.u_xpi.hold_apb = 0; + $display("P5_SYSTEM_STAGE released"); + wait (done); + if (!pass || !hart_reset_observed || progress != 3) + $fatal(1, "SIM_TEST_FAIL P5 CPU/DMA code=%0d", code); + repeat (20) @(posedge clk_i); + $display( + "P5_SYSTEM dma_overlap_cycles=%0d independent_controls=%0d hart_reset=1 abort_drained=1", + overlap_cycles, controls); + $display("SIM_TEST_PASS Tiny P5 system"); + $finish; + end + initial begin + repeat (500000) @(posedge clk_i); + $display( + "P5_DIAG core_rst=%b pending=%b halted=%b idle=%b quiesce=%b hold=%b busy=%b I=%h D=%h", + u_dut.s_core_rst_n, u_dut.u_cpu.s_reset_pending, u_dut.s_debug_halted, + u_dut.s_cpu_bus_idle, u_dut.s_cpu_quiesce, u_dut.u_xpi.hold_mapped, + u_dut.u_fabric.s_busy_q, u_dut.u_cpu_ahbl_if[0].haddr, u_dut.u_cpu_ahbl_if[1].haddr); + $fatal(1, "SIM_TEST_TIMEOUT Tiny P5 system progress=%0d", progress); + end +endmodule + +// Verification-only XPI boundary substitute. The production Tiny integration, +// CPU, DMA, SRAM, APB branches and SYSCTRL remain real. This does not qualify an +// XPI device or replace the pin-level NOR regression. The small reset ROM is +// only a fixture boot source, never a product Boot ROM. +module apb4_xpi ( + input logic clk_i, + rst_n_i, + dma_xfer_done_i, + output logic dma_tx_stall_o, + dma_rx_stall_o, + irq_o, + apb4_if.slave apb4, + axi4_if.slave mem_axi4, + xpi_if.dut xpi +); + logic [7:0] rom [0:4095]; + string firmware; + logic active = 0, id = 0, hold_mapped = 1, hold_apb = 1; + logic [31:0] captured_write = 0; + logic [31:0] address = 0; + logic [7:0] beat = 0, length = 0; + initial begin + if (!$value$plusargs("firmware=%s", firmware)) $fatal(1, "firmware required"); + $readmemh(firmware, rom); + end + assign dma_tx_stall_o = 0; + assign dma_rx_stall_o = 0; + assign irq_o = 0; + assign xpi.sck_o = 0; + assign xpi.nss_o = '1; + assign xpi.io_oe_o = 0; + assign xpi.io_do_o = 0; + assign apb4.pready=apb4.psel && apb4.penable && + !(apb4.pwrite && apb4.paddr[11:0]==12'h040 && hold_apb); + assign apb4.pslverr = 0; + assign apb4.prdata = (apb4.paddr[11:0] == 12'h044) ? captured_write : 32'd0; + assign mem_axi4.arready = !active; + assign mem_axi4.awready = 0; + assign mem_axi4.wready = 0; + assign mem_axi4.bvalid = 0; + assign mem_axi4.bid = 0; + assign mem_axi4.bresp = 0; + assign mem_axi4.buser = 0; + assign mem_axi4.rresp = 0; + assign mem_axi4.ruser = 0; + always @(posedge clk_i) begin + if (rst_n_i && apb4.psel && apb4.penable && apb4.pready && apb4.pwrite) + captured_write <= apb4.pwdata; + if (!rst_n_i) begin + active <= 0; + mem_axi4.rvalid <= 0; + mem_axi4.rdata <= 0; + mem_axi4.rid <= 0; + mem_axi4.rlast <= 0; + end else begin + if (mem_axi4.arvalid && mem_axi4.arready) begin + active <= 1; + address <= mem_axi4.araddr; + id <= mem_axi4.arid; + beat <= 0; + length <= mem_axi4.arlen; + end + if (active && !mem_axi4.rvalid && !(hold_mapped && address >= 32'h50000000)) begin + mem_axi4.rvalid <= 1; + mem_axi4.rid <= id; + mem_axi4.rlast <= beat == length; + if (address < 4096) + mem_axi4.rdata <= {rom[address+3], rom[address+2], rom[address+1], rom[address]}; + else mem_axi4.rdata <= 32'h10000000 ^ address; + end + if (mem_axi4.rvalid && mem_axi4.rready) begin + mem_axi4.rvalid <= 0; + if (mem_axi4.rlast) active <= 0; + else begin + address <= address + 4; + beat <= beat + 1; + end + end + end + end +endmodule diff --git a/tests/rtl/tiny_sysctrl_tb.sv b/tests/rtl/tiny_sysctrl_tb.sv index 39433bdd..b0d92800 100644 --- a/tests/rtl/tiny_sysctrl_tb.sv +++ b/tests/rtl/tiny_sysctrl_tb.sv @@ -10,6 +10,8 @@ module tiny_sysctrl_tb; .presetn(rst_n_i) ); logic fault_valid = 0; + logic [ 3:0] fault_count = 1; + logic [ 1:0] fault_master = 1; logic [31:0] fault_addr = 32'h38000000; logic [63:0] cpu_wait = 64'h123456789abcdef0; logic [63:0] dma_wait = 64'h1122334455667788; @@ -22,9 +24,10 @@ module tiny_sysctrl_tb; .rst_n_i (rst_n_i), .apb4 (apb4), .fault_valid_i (fault_valid), + .fault_count_i (fault_count), .fault_addr_i (fault_addr), .fault_write_i (1'b1), - .fault_master_i(1'b1), + .fault_master_i(fault_master), .fault_resp_i (2'b11), .cpu_wait_i (cpu_wait), .dma_wait_i (dma_wait), @@ -73,6 +76,43 @@ module tiny_sysctrl_tb; apb4_write(`APB4_SYSCTRL__FAULT_STATUS, 1, 4'hf, 0); apb4_read(`APB4_SYSCTRL__FAULT_STATUS, 0, value); if (value[0]) $fatal(1, "fault W1C failed"); + // Multiple terminal events count independently. A hardware event on the + // clear edge wins and records the new first owner without clearing count. + fork + apb4_write(`APB4_SYSCTRL__FAULT_STATUS, 1, 4'hf, 0); + begin + wait (apb4.psel && apb4.penable && apb4.pwrite); + fault_count = 5; + fault_master = 2; + fault_addr = 32'h30018004; + fault_valid = 1; + @(posedge clk_i); + @(negedge clk_i); + fault_valid = 0; + end + join + apb4_read(`APB4_SYSCTRL__FAULT_COUNT, 0, value); + if (value != 7) $fatal(1, "concurrent fault count lost"); + apb4_read(`APB4_SYSCTRL__FAULT_MASTER, 0, value); + if (value != 2) $fatal(1, "third owner truncated"); + apb4_read(`APB4_SYSCTRL__FAULT_DETAIL, 0, value); + if (value != 3) $fatal(1, "raw AXI response changed"); + apb4_read(`APB4_SYSCTRL__FAULT_STATUS, 0, value); + if (!value[0]) $fatal(1, "W1C defeated simultaneous hardware event"); + // Seed only the counter corner, then exercise the actual wide adder and + // register update. Billions of event pulses are not useful simulation. + @(negedge clk_i); + force u_dut.u_fault_count_reg.dat_o = 32'hfffffffc; + fault_count = 8; + fault_valid = 1; + @(posedge clk_i); + @(negedge clk_i); + release u_dut.u_fault_count_reg.dat_o; + @(posedge clk_i); + @(negedge clk_i); + fault_valid = 0; + apb4_read(`APB4_SYSCTRL__FAULT_COUNT, 0, value); + if (value != 32'hffffffff) $fatal(1, "multi-event saturation wrapped"); @(negedge clk_i); wake = 1; @(negedge clk_i); diff --git a/tests/test_publication_tiny.py b/tests/test_publication_tiny.py index 08215d1d..2bbe916d 100644 --- a/tests/test_publication_tiny.py +++ b/tests/test_publication_tiny.py @@ -27,6 +27,8 @@ def test_tiny_configuration_and_inventory_are_derived(data): assert data["facts"]["dma"]["RequestMask"] == 0x7F9 assert data["facts"]["dma"]["EnableStreams"] == 0 assert data["facts"]["cpu"]["ResetSyncStages"] == 5 + assert data["facts"]["fabric"]["target_count"] == 23 + assert data["facts"]["fabric"]["active_owners"] == [0, 1] 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 diff --git a/tests/test_tiny.py b/tests/test_tiny.py index a1116257..67019395 100644 --- a/tests/test_tiny.py +++ b/tests/test_tiny.py @@ -97,15 +97,19 @@ def test_tiny_protocol_contracts(tmp_path: Path, simulator: str, block: str) -> pytest.skip("locked RTL/tool dependency unavailable") generated = tmp_path / "map" generate_memory_map(TINY / "address_map/memory_map.json", generated, "YES", 128) + generate_bindings(read_topology(TINY / "integration/soc_topology.json", + TINY / "address_map/memory_map.json"), tmp_path / "bindings") top = "tiny_axi4_fabric_tb" if block == "fabric" else "tiny_sysctrl_tb" sources = [COMMON / "utils/register.sv"] if block == "fabric": sources += [COMMON / "interface/axi4_if.sv", COMMON / "stream/round_robin_arbiter.sv", - ROOT / "rtl/ip/interconnect/axi4_error_slave.sv", TINY / "top/tiny_axi4_fabric.sv"] + COMMON / "utils/spill_register.sv", + ROOT / "rtl/ip/interconnect/axi4_error_slave.sv", + TINY / "top/tiny_axi4_target.sv", TINY / "top/tiny_axi4_fabric.sv"] else: sources += [COMMON / "interface/apb4_if.sv", TINY / "top/tiny_sysctrl.sv"] sources.append(ROOT / "tests/rtl" / f"{top}.sv") - includes = [f"-I{p}" for p in (COMMON, COMMON / "interface", generated / "rtl", + includes = [f"-I{p}" for p in (COMMON, COMMON / "interface", generated / "rtl", tmp_path / "bindings/rtl", ROOT / "rtl/ip/peripheral", ROOT / "tests/rtl")] output = tmp_path / "sim" if simulator == "verilator": diff --git a/tests/test_tiny_r2_p5.py b/tests/test_tiny_r2_p5.py new file mode 100644 index 00000000..1cc99542 --- /dev/null +++ b/tests/test_tiny_r2_p5.py @@ -0,0 +1,189 @@ +"""P5 target concurrency, fault accounting and retained-image acceptance.""" +from __future__ import annotations + +import shutil +import subprocess +import json +from pathlib import Path + +import pytest + +from scripts.generate_tiny import generate_bindings, generate_filelists, read_topology +from scripts.rtl.filelist import parse_filelists +from scripts.rtl.generate_memory_map import generate as generate_memory_map +from test_tiny_r2_p4 import run +from scripts import tiny_r2_p5 as p5 + +ROOT = Path(__file__).resolve().parents[1] +COMMON = ROOT / "rtl/managed/clusterip/common/rtl" +TINY = ROOT / "rtl/tiny/top" + + +def sources(tmp_path): + generate_memory_map(ROOT / "rtl/tiny/address_map/memory_map.json", tmp_path / "map", "YES", 128) + generate_bindings(read_topology(ROOT / "rtl/tiny/integration/soc_topology.json", + ROOT / "rtl/tiny/address_map/memory_map.json"), tmp_path / "bindings") + files = [COMMON / p for p in ("utils/register.sv", "utils/spill_register.sv", + "utils/fifo.sv", "stream/round_robin_arbiter.sv", "interface/axi4_if.sv", + "interface/axi4_addr_gen.sv", "interface/apb4_if.sv", "interface/ahbl_if.sv")] + files += [TINY / p for p in ("tiny_axi4_target.sv", "tiny_axi4_fabric.sv", + "tiny_axi42apb4.sv", "tiny_fault_collector.sv", "tiny_sysctrl.sv")] + files += [ROOT / "rtl/ip/interconnect/axi4_error_slave.sv"] + includes = [f"-I{p}" for p in (COMMON, COMMON / "interface", tmp_path / "map/rtl", + tmp_path / "bindings/rtl", ROOT / "rtl/ip/peripheral", ROOT / "tests/rtl")] + return files, includes + + +def simulate(tmp_path, simulator, top, files, includes, arguments=()): + for tool in (simulator, "sv2v", "vvp"): + assert shutil.which(tool), f"required tool unavailable: {tool}" + binary = tmp_path / "sim" + if simulator == "verilator": + run([simulator, "--binary", "--timing", "--assert", "-Wno-fatal", "-j", "3", + "-DHAVE_SVA", "-DSV_ASSRT_DISABLE", "--top-module", top, *includes, + *(str(p) for p in files), "--Mdir", str(tmp_path / "obj"), "-o", str(binary)], + tmp_path, "compile", timeout=300) + launch = [str(binary)] + else: + # Conversion warnings are diagnostics, never Verilog source text. + converted = subprocess.run(["sv2v", "-DSV_ASSRT_DISABLE", *includes, + *(str(p) for p in files)], text=True, + capture_output=True, timeout=300) + (tmp_path / "sv2v.log").write_text(converted.stderr) + assert converted.returncode == 0, converted.stderr + (tmp_path / "converted.v").write_text(converted.stdout) + run([simulator, "-g2012", "-s", top, "-o", str(binary), str(tmp_path / "converted.v")], + tmp_path, "compile") + launch = ["vvp", str(binary)] + log = run([*launch, *arguments], tmp_path, "sim", timeout=180) + assert "SIM_TEST_PASS Tiny P5" in log + return log + + +@pytest.mark.parametrize("simulator", ["iverilog", "verilator"]) +def test_concurrent_fabric(tmp_path, simulator): + files, includes = sources(tmp_path) + files.append(ROOT / "tests/rtl/tiny_r2_p5_fabric_tb.sv") + log = simulate(tmp_path, simulator, "tiny_r2_p5_fabric_tb", files, includes) + assert "random_reads=96 concurrent_faults=2" in log + + +@pytest.mark.parametrize("simulator", ["iverilog", "verilator"]) +def test_target_reset_isolation(tmp_path, simulator): + files, includes = sources(tmp_path) + files.append(ROOT / "tests/rtl/tiny_r2_p5_reset_tb.sv") + log = simulate(tmp_path, simulator, "tiny_r2_p5_reset_tb", files, includes) + assert "SIM_TEST_PASS Tiny P5 target reset isolation" in log + + +@pytest.mark.parametrize("simulator", ["iverilog", "verilator"]) +def test_real_cpu_dma_progress_and_abort(tmp_path, simulator): + sources(tmp_path) + firmware = tmp_path / "cpu.elf" + run(["riscv32-unknown-elf-gcc", "-march=rv32imc_zicsr_zifencei", "-mabi=ilp32", + "-nostdlib", "-Wl,--build-id=none", "-T", str(ROOT / "tests/rtl/tiny_r2_p4_cpu.ld"), + str(ROOT / "tests/rtl/tiny_r2_p5_cpu.S"), "-o", str(firmware)], tmp_path, "firmware") + run(["riscv32-unknown-elf-objcopy", "-O", "verilog", str(firmware), str(tmp_path / "cpu.hex")], + tmp_path, "objcopy") + generate_filelists(tmp_path / "filelists", tmp_path / "bindings", []) + resolved = parse_filelists(tmp_path / "filelists" / name for name in ( + "inc.fl", "commonip.fl", "clusterip.fl", "core_hazard3.fl", "ip.fl", "tech.fl", "top.fl")) + # Only the XPI boundary is substituted; the production CPU/DMA/SoC stay real. + files = [p for p in resolved.files if p.name not in ("apb4_xpi.sv", "retrosoc_tiny_asic.sv")] + files.append(ROOT / "tests/rtl/tiny_r2_p5_system_tb.sv") + run(["python3", str(ROOT / "rtl/managed/clusterip/archinfo/scripts/generate_metadata.py"), + "--repo-root", str(ROOT), "--lock", str(ROOT / "dependencies/dependencies.lock.json"), + "--config-digest", "000000000000", "--output-sv", str(tmp_path / "archinfo_integration_metadata.svh"), + "--output-c", str(tmp_path / "archinfo_integration_metadata.h")], tmp_path, "metadata") + includes = [f"-I{p}" for p in (*resolved.incdirs, tmp_path, tmp_path / "map/rtl", + tmp_path / "bindings/rtl", ROOT / "rtl/tiny/dv")] + includes += ["-DPDK_BEHAV", "-DRETROSOC_SOC__TINY", "-DSOC_JTAG_IDCODE=32'hDEADBEEF"] + log = simulate(tmp_path, simulator, "tiny_r2_p5_system_tb", files, includes, + [f"+firmware={tmp_path / 'cpu.hex'}"]) + assert "hart_reset=1 abort_drained=1" in log + + +@pytest.mark.parametrize("kind", ["fabric", "fault"]) +def test_protocol_proofs(tmp_path, kind): + for tool in ("sv2v", "yosys"): + assert shutil.which(tool), f"required tool unavailable: {tool}" + files, includes = sources(tmp_path) + top = f"tiny_r2_p5_{kind}_formal" + files.append(ROOT / f"tests/rtl/{top}.sv") + converted = subprocess.run(["sv2v", "-DFORMAL", "-DSV_ASSRT_DISABLE", *includes, + *(str(p) for p in files)], text=True, capture_output=True, timeout=180) + (tmp_path / "sv2v.log").write_text(converted.stderr) + assert converted.returncode == 0, converted.stderr + (tmp_path / "formal.v").write_text(converted.stdout) + proof = "sat -prove-asserts -verify" if kind == "fault" else ( + "sat -seq 12 -tempinduct -prove-asserts -verify -set-init-zero") + script = (f"read_verilog -formal {tmp_path / 'formal.v'}; prep -top {top} -flatten; " + f"memory_map; async2sync; dffunmap; opt; {proof}") + log = run(["yosys", "-p", script], tmp_path, "formal", timeout=300) + assert "SUCCESS!" in log + + +def p5_log(): + from test_tiny_r2_p4 import p4_log + return p4_log() + "\n" + "\n".join( + f"R2_P5_FABRIC window={window} active_targets_max=3 " + "dma_rw_overlap_cycles=5 dma_rw_beat_overlap_cycles=2" for window in range(1, 9)) + + +def test_p5_records_keep_old_image_measurements_separate(): + samples = p5.parse_log(p5_log(), {"status": "passed", "exit_code": 0}) + assert len(samples["fabric"]) == 8 + assert len(samples["local"]) == 16 + assert p5.PHASE == "TINY-R2-P5" and p5.previous.PHASE == "TINY-R2-P4" + + +@pytest.mark.parametrize("mutation", ["missing", "duplicate", "negative", "overflow", "overlap", "serialized", "failure"]) +def test_p5_rejects_invalid_concurrency_evidence(mutation): + content = p5_log() + if mutation == "missing": + content = "\n".join(line for line in content.splitlines() if not line.startswith("R2_P5_FABRIC window=8")) + elif mutation == "duplicate": + content += "\n" + content.splitlines()[-1] + elif mutation == "negative": + content = content.replace("active_targets_max=3", "active_targets_max=-1") + elif mutation == "overflow": + content = content.replace("active_targets_max=3", "active_targets_max=24") + elif mutation == "overlap": + content = content.replace("dma_rw_beat_overlap_cycles=2", "dma_rw_beat_overlap_cycles=6") + elif mutation == "serialized": + content = content.replace("active_targets_max=3", "active_targets_max=1") + else: + content += "\nSIM_TEST_FAIL" + with pytest.raises(ValueError): + p5.parse_log(content, {"status": "passed", "exit_code": 0}) + + +@pytest.mark.parametrize("mutation", ["none", "phase", "source", "cycles", "image", "local"]) +def test_p5_reference_is_bound_to_p4_execution(tmp_path, monkeypatch, mutation): + case = {"name": "flat", "image": {"path": "immutable"}, + "samples": {"cases": [{"name": "dma16", "cycles": 17897, "instructions": 10}], + "local": [], "banks": []}} + report = {"phase": "TINY-R2-P4", "status": "measurement_complete", "source_digest": "original", + "executions": {"verilator": {}}, "comparisons": [ + {"name": "flat", "image": {"path": "immutable"}, "local": [], "banks": [], + "workloads": [{"name": "dma16", "candidate_cycles": 17897, "candidate_instructions": 10}]}]} + monkeypatch.setattr(p5.previous, "validate_replay", lambda *args: { + "source_digest": "original", "cases": [case]}) + monkeypatch.setattr(p5.b, "check_artifact", lambda artifact: tmp_path / "record.json") + if mutation == "phase": + report["phase"] = "TINY-R2-P5" + elif mutation == "source": + report["source_digest"] = "other" + elif mutation == "cycles": + report["comparisons"][0]["workloads"][0]["candidate_cycles"] = 1 + elif mutation == "image": + report["comparisons"][0]["image"]["path"] = "different" + elif mutation == "local": + report["comparisons"][0]["local"] = [{}] + path = tmp_path / "report.json" + path.write_text(json.dumps(report)) + if mutation == "none": + assert p5.reference_cases(path, ["flat"]) == [case] + else: + with pytest.raises(ValueError): + p5.reference_cases(path, ["flat"]) From edb6e93eee7e32a2ccf20cf0c56e29d82187361f Mon Sep 17 00:00:00 2001 From: maksyuki Date: Mon, 12 Oct 2026 07:15:32 +0800 Subject: [PATCH 05/12] fix(tiny): preserve AXI transactions across target reset Drain remaining write beats before returning B, retain captured R/B responses, and append missing read error beats through the response spills. Preserve first-error attribution and prevent replay after target reset release. Cover 28 reset scenarios in both simulators, including production DMA drain and APB bridge cases. Extend reset formal checks and publication contract mutation tests. Validation: 101 focused tests passed; full Pytest reported 1685 passed and one optional pypdf skip. TINY ICS55 and IHP130 PR regressions passed. Timing remains observational and unclosed; no performance or physical qualification is claimed. --- .../datasheets/tiny/source-contract.json | 8 +- rtl/tiny/top/tiny_axi4_target.sv | 140 +++-- tests/rtl/tiny_r2_p5_fabric_formal.sv | 36 +- tests/rtl/tiny_r2_p5_reset_tb.sv | 573 +++++++++++++----- tests/test_publication_tiny.py | 18 + tests/test_tiny_r2_p5.py | 9 +- 6 files changed, 545 insertions(+), 239 deletions(-) diff --git a/publications/datasheets/tiny/source-contract.json b/publications/datasheets/tiny/source-contract.json index 04931cf6..ac2214fa 100644 --- a/publications/datasheets/tiny/source-contract.json +++ b/publications/datasheets/tiny/source-contract.json @@ -187,9 +187,13 @@ "file": "rtl/tiny/top/tiny_axi4_target.sv", "text": [ "assign s_accept = (s_req_q.state == Idle) && enable_i && s_valid;", - " assign s_terminal = (s_req_q.state == Response) &&\n (s_req_q.write ? (s_b_valid && s_bready[s_req_q.owner]) :\n (s_r_valid && s_rready[s_req_q.owner] && s_r_data[1]));", + "assign s_terminal = s_resp_active && (s_req_q.write ? (s_b_valid && s_bready[s_req_q.owner]) : (s_r_valid && s_rready[s_req_q.owner] && s_r_data[1]));", "assign fault_valid_o = s_terminal && s_req_q.error_seen;", - "if (!s_req_q.error_seen && (target.rresp != 2'b00)) begin s_req_d.error_seen = 1'b1; s_req_d.error_addr = s_req_q.read_addr; s_req_d.error_resp = target.rresp; end", + "if (!s_req_q.error_seen && (s_rin_data[3:2] != 2'b00)) begin s_req_d.error_seen = 1'b1; s_req_d.error_addr = s_req_q.read_addr; s_req_d.error_resp = s_rin_data[3:2]; end", + "assign s_abort_write_valid = (s_req_q.state == AbortResponse) && s_req_q.write && s_req_q.write_done && !s_req_q.b_captured;", + "assign s_abort_read_valid = (s_req_q.state == AbortResponse) && !s_req_q.write && (s_req_q.read_count <= {1'b0, s_saved_addr.len});", + ".flush_i(1'b0)", + "AbortWrite: begin", "if (s_terminal) s_req_d.state = Idle;" ] } diff --git a/rtl/tiny/top/tiny_axi4_target.sv b/rtl/tiny/top/tiny_axi4_target.sv index cf7cb84c..d53b1051 100644 --- a/rtl/tiny/top/tiny_axi4_target.sv +++ b/rtl/tiny/top/tiny_axi4_target.sv @@ -18,10 +18,11 @@ module tiny_axi4_target ( output logic fault_instruction_o, output logic [ 1:0] fault_resp_o ); - typedef enum logic [1:0] { + typedef enum logic [2:0] { Idle, Address, Response, + AbortWrite, AbortResponse } state_e; typedef struct packed { @@ -44,6 +45,8 @@ module tiny_axi4_target ( addr_t addr; logic [7:0] write_beat; logic write_done; + logic [8:0] read_count; + logic b_captured; logic [31:0] read_addr, error_addr; logic [1:0] error_resp; logic error_seen; @@ -64,6 +67,9 @@ module tiny_axi4_target ( logic [ 3:0] s_b_data; logic s_read_capture, s_write_capture; logic s_abort_read_valid, s_abort_write_valid; + logic s_resp_active, s_rin_valid, s_bin_valid; + logic [36:0] s_rin_data; + logic [ 3:0] s_bin_data; assign s_saved_addr = s_req_q.addr; assign s_req_addr = s_saved_addr.addr; @@ -74,22 +80,34 @@ module tiny_axi4_target ( assign s_selected_addr = s_selected_write ? s_write_addr[s_selected] : s_read_addr[s_selected]; assign s_accept = (s_req_q.state == Idle) && enable_i && s_valid; assign idle_o = (s_req_q.state == Idle) && !s_r_valid && !s_b_valid; - assign s_abort_read_valid = (s_req_q.state == AbortResponse) && !s_req_q.write; - assign s_abort_write_valid = (s_req_q.state == AbortResponse) && s_req_q.write; - assign s_terminal = - (s_req_q.state == Response) ? - (s_req_q.write ? (s_b_valid && s_bready[s_req_q.owner]) : - (s_r_valid && s_rready[s_req_q.owner] && s_r_data[1])) : - (s_req_q.state == AbortResponse) && - (s_req_q.write ? s_bready[s_req_q.owner] : s_rready[s_req_q.owner]); - assign fault_valid_o = s_terminal && (s_req_q.error_seen || s_req_q.state == AbortResponse); + // Target reset cancels downstream work, never an already captured upstream + // response. Append missing error beats through the same spill registers. + assign s_resp_active = (s_req_q.state == Response) || (s_req_q.state == AbortResponse); + assign s_abort_read_valid = (s_req_q.state == AbortResponse) && !s_req_q.write && + (s_req_q.read_count <= {1'b0, s_saved_addr.len}); + assign s_abort_write_valid = (s_req_q.state == AbortResponse) && s_req_q.write && + s_req_q.write_done && !s_req_q.b_captured; + assign s_terminal = s_resp_active && + (s_req_q.write ? (s_b_valid && s_bready[s_req_q.owner]) : + (s_r_valid && s_rready[s_req_q.owner] && s_r_data[1])); + assign fault_valid_o = s_terminal && s_req_q.error_seen; assign fault_addr_o = s_req_q.error_addr; assign fault_master_o = s_req_q.owner; assign fault_write_o = s_req_q.write; assign fault_instruction_o = !s_req_q.write && s_saved_addr.prot[2]; assign fault_resp_o = s_req_q.error_resp; - assign s_read_capture = target.rvalid && target.rready; - assign s_write_capture = target.bvalid && target.bready; + assign s_rin_valid = ((s_req_q.state == Response) && enable_i && + !s_req_q.write && target.rvalid) || s_abort_read_valid; + assign s_rin_data = s_abort_read_valid ? + {s_req_id, 32'd0, 2'b11, + (s_req_q.read_count == {1'b0, s_saved_addr.len}), 1'b0} : + {target.rid, target.rdata, target.rresp, target.rlast, target.ruser}; + assign s_bin_valid = ((s_req_q.state == Response) && enable_i && + s_req_q.write && target.bvalid) || s_abort_write_valid; + assign s_bin_data = s_abort_write_valid ? {s_req_id, 2'b11, 1'b0} : + {target.bid, target.bresp, target.buser}; + assign s_read_capture = s_rin_valid && s_r_ready; + assign s_write_capture = s_bin_valid && s_b_ready; round_robin_arbiter #( .CLIENTS(3) @@ -154,22 +172,17 @@ module tiny_axi4_target ( assign s_bready[owner] = masters[owner].bready; assign masters[owner].arready = s_accept && s_grant[owner] && !s_selected_write; assign masters[owner].awready = s_accept && s_grant[owner] && s_selected_write; - assign masters[owner].wready = (s_req_q.state == Response) && enable_i && s_req_q.write && - !s_req_q.write_done && (s_req_q.owner == 2'(owner)) && target.wready; - assign masters[owner].rvalid = ((s_req_q.state == Response) && enable_i && !s_req_q.write && - (s_req_q.owner == 2'(owner)) && s_r_valid) || - (s_abort_read_valid && (s_req_q.owner == 2'(owner))); - assign masters[owner].rid = s_abort_read_valid ? s_req_id : s_r_data[36]; - assign masters[owner].rdata = s_abort_read_valid ? 32'd0 : s_r_data[35:4]; - assign masters[owner].rresp = s_abort_read_valid ? 2'b11 : s_r_data[3:2]; - assign masters[owner].rlast = s_abort_read_valid ? 1'b1 : s_r_data[1]; - assign masters[owner].ruser = s_abort_read_valid ? 1'b0 : s_r_data[0]; - assign masters[owner].bvalid = ((s_req_q.state == Response) && enable_i && s_req_q.write && - (s_req_q.owner == 2'(owner)) && s_b_valid) || - (s_abort_write_valid && (s_req_q.owner == 2'(owner))); - assign masters[owner].bid = s_abort_write_valid ? s_req_id : s_b_data[3]; - assign masters[owner].bresp = s_abort_write_valid ? 2'b11 : s_b_data[2:1]; - assign masters[owner].buser = s_abort_write_valid ? 1'b0 : s_b_data[0]; + assign masters[owner].wready = ((s_req_q.state == Response) && enable_i && s_req_q.write && + !s_req_q.write_done && (s_req_q.owner == 2'(owner)) && target.wready) || + ((s_req_q.state == AbortWrite) && !s_req_q.write_done && + (s_req_q.owner == 2'(owner))); + assign masters[owner].rvalid = s_resp_active && !s_req_q.write && + (s_req_q.owner == 2'(owner)) && s_r_valid; + assign {masters[owner].rid, masters[owner].rdata, masters[owner].rresp, + masters[owner].rlast, masters[owner].ruser} = s_r_data; + assign masters[owner].bvalid = s_resp_active && s_req_q.write && + (s_req_q.owner == 2'(owner)) && s_b_valid; + assign {masters[owner].bid, masters[owner].bresp, masters[owner].buser} = s_b_data; end assign target.awaddr = s_saved_addr.addr; @@ -209,12 +222,12 @@ module tiny_axi4_target ( ) u_read_response ( .clk_i (clk_i), .rst_n_i(rst_n_i), - .flush_i(!enable_i), - .valid_i((s_req_q.state == Response) && !s_req_q.write && target.rvalid), + .flush_i(1'b0), + .valid_i(s_rin_valid), .ready_o(s_r_ready), - .data_i ({target.rid, target.rdata, target.rresp, target.rlast, target.ruser}), + .data_i (s_rin_data), .valid_o(s_r_valid), - .ready_i((s_req_q.state == Response) && !s_req_q.write && s_rready[s_req_q.owner]), + .ready_i(s_resp_active && !s_req_q.write && s_rready[s_req_q.owner]), .data_o (s_r_data) ); spill_register #( @@ -222,12 +235,12 @@ module tiny_axi4_target ( ) u_write_response ( .clk_i (clk_i), .rst_n_i(rst_n_i), - .flush_i(!enable_i), - .valid_i((s_req_q.state == Response) && s_req_q.write && target.bvalid), + .flush_i(1'b0), + .valid_i(s_bin_valid), .ready_o(s_b_ready), - .data_i ({target.bid, target.bresp, target.buser}), + .data_i (s_bin_data), .valid_o(s_b_valid), - .ready_i((s_req_q.state == Response) && s_req_q.write && s_bready[s_req_q.owner]), + .ready_i(s_resp_active && s_req_q.write && s_bready[s_req_q.owner]), .data_o (s_b_data) ); always_comb begin @@ -244,42 +257,49 @@ module tiny_axi4_target ( end Address: begin if (!enable_i) begin - s_req_d.state = AbortResponse; - s_req_d.error_seen = 1'b1; - s_req_d.error_addr = s_req_addr; - s_req_d.error_resp = 2'b11; + s_req_d.state = s_req_q.write ? AbortWrite : AbortResponse; end else if (s_req_q.write ? target.awready : target.arready) s_req_d.state = Response; end Response: begin if (!enable_i) begin - s_req_d.state = AbortResponse; - s_req_d.error_seen = 1'b1; - s_req_d.error_addr = s_req_addr; - s_req_d.error_resp = 2'b11; + s_req_d.state = (s_req_q.write && !s_req_q.write_done) ? AbortWrite : AbortResponse; end if (target.wvalid && target.wready) begin s_req_d.write_beat = s_req_q.write_beat + 8'd1; if (target.wlast || (s_req_q.write_beat == s_saved_addr.len)) s_req_d.write_done = 1'b1; end - if (s_read_capture) begin - if (s_saved_addr.burst == 2'b01) - s_req_d.read_addr = s_req_q.read_addr + (32'd1 << s_saved_addr.size); - if (!s_req_q.error_seen && (target.rresp != 2'b00)) begin - s_req_d.error_seen = 1'b1; - s_req_d.error_addr = s_req_q.read_addr; - s_req_d.error_resp = target.rresp; + if (s_terminal) s_req_d.state = Idle; + end + AbortWrite: begin + if (s_wvalid[s_req_q.owner] && !s_req_q.write_done) begin + s_req_d.write_beat = s_req_q.write_beat + 8'd1; + if (s_wlast[s_req_q.owner] || (s_req_q.write_beat == s_saved_addr.len)) begin + s_req_d.write_done = 1'b1; + s_req_d.state = AbortResponse; end end - if (s_write_capture && !s_req_q.error_seen && (target.bresp != 2'b00)) begin - s_req_d.error_seen = 1'b1; - s_req_d.error_addr = s_saved_addr.addr; - s_req_d.error_resp = target.bresp; - end - if (s_terminal) s_req_d.state = Idle; end AbortResponse: if (s_terminal) s_req_d.state = Idle; default: s_req_d.state = Idle; endcase + if (s_read_capture) begin + s_req_d.read_count = s_req_q.read_count + 9'd1; + if (s_saved_addr.burst == 2'b01) + s_req_d.read_addr = s_req_q.read_addr + (32'd1 << s_saved_addr.size); + if (!s_req_q.error_seen && (s_rin_data[3:2] != 2'b00)) begin + s_req_d.error_seen = 1'b1; + s_req_d.error_addr = s_req_q.read_addr; + s_req_d.error_resp = s_rin_data[3:2]; + end + end + if (s_write_capture) begin + s_req_d.b_captured = 1'b1; + if (!s_req_q.error_seen && (s_bin_data[2:1] != 2'b00)) begin + s_req_d.error_seen = 1'b1; + s_req_d.error_addr = s_req_addr; + s_req_d.error_resp = s_bin_data[2:1]; + end + end end dffr #( .DATA_WIDTH($bits(request_t)) @@ -334,17 +354,17 @@ module tiny_axi4_target ( end `endif `ifdef HAVE_SVA - assert property (@(posedge clk_i) disable iff (!rst_n_i) + assert property (@(posedge clk_i) disable iff (!rst_n_i || !enable_i) target.wvalid && !target.wready |=> target.wvalid && $stable( {target.wdata, target.wstrb, target.wlast, target.wuser} )); - assert property (@(posedge clk_i) disable iff (!rst_n_i) + assert property (@(posedge clk_i) disable iff (!rst_n_i || !enable_i) target.arvalid && !target.arready |=> target.arvalid && $stable( {target.araddr, target.arlen, target.arid, target.arsize, target.arburst} )); - assert property (@(posedge clk_i) disable iff (!rst_n_i) + assert property (@(posedge clk_i) disable iff (!rst_n_i || !enable_i) target.awvalid && !target.awready |=> target.awvalid && $stable( {target.awaddr, target.awlen, target.awid, target.awsize, target.awburst} diff --git a/tests/rtl/tiny_r2_p5_fabric_formal.sv b/tests/rtl/tiny_r2_p5_fabric_formal.sv index 45d0a53b..998294ab 100644 --- a/tests/rtl/tiny_r2_p5_fabric_formal.sv +++ b/tests/rtl/tiny_r2_p5_fabric_formal.sv @@ -2,9 +2,16 @@ module tiny_r2_p5_fabric_formal ( input logic clk_i, rst_n_i ); +`ifdef TINY_P5_RESET_FORMAL + localparam bit TargetReset = 1'b1; +`else + localparam bit TargetReset = 1'b0; +`endif logic started = 0; (* anyseq *) logic [2:0] arvalid, awvalid, wvalid, rready, bready; - (* anyseq *) logic [2:0][1:0] length; + (* anyseq *)logic [2:0][1:0] length; + (* anyseq *)logic target_reset_n; + wire target_enable = !TargetReset || target_reset_n; axi4_if #( .ADDR_WIDTH(32), .DATA_WIDTH(32), @@ -33,7 +40,7 @@ module tiny_r2_p5_fabric_formal ( tiny_axi4_target u_target ( .clk_i (clk_i), .rst_n_i (rst_n_i), - .enable_i (1'b1), + .enable_i (target_enable), .masters (masters), .target (target), .idle_o (idle), @@ -48,11 +55,13 @@ module tiny_r2_p5_fabric_formal ( .Response(2'b10) ) u_endpoint ( .clk_i (clk_i), - .rst_n_i(rst_n_i), + .rst_n_i(rst_n_i && target_enable), .axi4 (target) ); for (genvar m = 0; m < 3; m++) begin : gen_master logic outstanding = 0; + logic [2:0] remaining = 0; + logic write_data_pending = 0; wire accept=(masters[m].arvalid && masters[m].arready)||(masters[m].awvalid && masters[m].awready); wire done=(masters[m].rvalid && masters[m].rready && masters[m].rlast)|| (masters[m].bvalid && masters[m].bready); @@ -88,15 +97,30 @@ module tiny_r2_p5_fabric_formal ( assign masters[m].rready = rready[m]; assign masters[m].bready = bready[m]; always_ff @(posedge clk_i) begin - if (!rst_n_i) outstanding <= 0; - else begin + if (!rst_n_i) begin + outstanding <= 0; + remaining <= 0; + write_data_pending <= 0; + end else begin + if (masters[m].arvalid && masters[m].arready) remaining <= {1'b0, length[m]} + 3'd1; + if (masters[m].rvalid) begin + assert (remaining != 0); + assert (masters[m].rlast == (remaining == 1)); + if (masters[m].rready) remaining <= remaining - 3'd1; + end + if (masters[m].awvalid && masters[m].awready) write_data_pending <= 1; + if (masters[m].wvalid && masters[m].wready) write_data_pending <= 0; + if (masters[m].bvalid) assert (!write_data_pending); if (accept) outstanding <= 1; if (done) outstanding <= 0; assert (!(accept && outstanding)); assert (!done || outstanding); assert (!(masters[m].arready && masters[m].awready)); if (done) begin - assert (fault && fault_owner == 2'(m) && fault_resp == 2); + assert (fault && fault_owner == 2'(m)); + if (TargetReset) + assert (fault_resp == 2 || fault_resp == 3); + else assert (fault_resp == 2); assert (fault_addr == 32'h30000000 + 32'(m) * 32'h100); end end diff --git a/tests/rtl/tiny_r2_p5_reset_tb.sv b/tests/rtl/tiny_r2_p5_reset_tb.sv index 605a02ab..26b161be 100644 --- a/tests/rtl/tiny_r2_p5_reset_tb.sv +++ b/tests/rtl/tiny_r2_p5_reset_tb.sv @@ -1,11 +1,8 @@ `timescale 1ns / 1ps - module tiny_r2_p5_reset_tb; - logic clk_i = 0; - logic rst_n_i = 0; - logic target_rst_n[23]; + logic clk_i = 0, rst_n_i = 0; always #5 clk_i = !clk_i; - + logic target_rst_n[23]; axi4_if #( .ADDR_WIDTH(32), .DATA_WIDTH(32), @@ -24,29 +21,35 @@ module tiny_r2_p5_reset_tb; .aclk (clk_i), .aresetn(target_rst_n) ); + apb4_if apb ( + .pclk (clk_i), + .presetn(target_rst_n[12]) + ); + logic [22:0] hold_address = '0, hold_read = '0, hold_write = '0, hold_b = '0; + integer error_beat[23]; + integer captures[23], addresses[23], writes[23]; logic [5:0] fault_valid, fault_instruction; logic [ 5:0][31:0] fault_addr; logic [ 5:0][ 1:0] fault_resp; logic [ 2:0] owner_idle; logic [22:0] target_idle; logic [63:0] cpu_wait, dma_wait; - logic hold_address[23]; - logic hold_read [23]; - logic hold_write [23]; - logic hold_b [23]; - logic [2:0] arvalid = '0, rready = '0, awvalid = '0, wvalid = '0, bready = '0; - logic [2:0][31:0] araddr = '0, awaddr = '0, wdata = '0; - logic [2:0][7:0] arlen = '0, awlen = '0; - logic [2:0][2:0] arsize = '0, awsize = '0; - logic [2:0][1:0] arburst = '0, awburst = '0; - logic [2:0] arlock = '0, awlock = '0; - logic [2:0] arid = '0, awid = '0; - logic [2:0][3:0] wstrb = '0; - logic [2:0] wlast = '0; - logic [2:0][2:0] arprot = '0; - logic [2:0] arready, awready, rvalid, bvalid; - logic [2:0][1:0] rresp, bresp; - logic [2:0] rlast; + logic [2:0] arvalid = 0, awvalid = 0, wvalid = 0, rready = 0, bready = 0; + logic [2:0][31:0] araddr = 0, awaddr = 0, wdata = 0; + logic [2:0][7:0] arlen = 0, awlen = 0; + logic [2:0] wlast = 0; + wire [2:0] arready, awready, wready, rvalid, bvalid, rlast, rid, bid; + wire [2:0][1:0] rresp, bresp; + wire [2:0][31:0] rdata; + integer faults [6]; + logic [5:0][31:0] last_fault_addr; + logic [5:0][ 1:0] last_fault_resp; + integer cases = 0; + logic dma_start = 0, dma_valid = 0, dma_start_ready, dma_ready, dma_busy, dma_done; + logic [31:0] dma_addr = 0, dma_data = 0; + logic [4:0] dma_beats = 1; + logic [1:0] dma_resp; + logic dma_id_error; tiny_axi4_fabric u_dut ( .clk_i (clk_i), @@ -66,185 +69,414 @@ module tiny_r2_p5_reset_tb; .dma_wait_o (dma_wait) ); - for (genvar owner = 0; owner < 3; owner++) begin : gen_master - assign masters[owner].araddr = araddr[owner]; - assign masters[owner].arlen = arlen[owner]; - assign masters[owner].arsize = arsize[owner]; - assign masters[owner].arburst = arburst[owner]; - assign masters[owner].arlock = arlock[owner]; - assign masters[owner].arid = arid[owner]; - assign masters[owner].arvalid = arvalid[owner]; - assign masters[owner].arprot = arprot[owner]; - assign masters[owner].awaddr = awaddr[owner]; - assign masters[owner].awlen = awlen[owner]; - assign masters[owner].awsize = awsize[owner]; - assign masters[owner].awburst = awburst[owner]; - assign masters[owner].awlock = awlock[owner]; - assign masters[owner].awid = awid[owner]; - assign masters[owner].awvalid = awvalid[owner]; - assign masters[owner].wdata = wdata[owner]; - assign masters[owner].wstrb = wstrb[owner]; - assign masters[owner].wlast = wlast[owner]; - assign masters[owner].wvalid = wvalid[owner]; - assign masters[owner].rready = rready[owner]; - assign masters[owner].bready = bready[owner]; - assign masters[owner].arcache = '0; - assign masters[owner].arqos = '0; - assign masters[owner].arregion = '0; - assign masters[owner].aruser = '0; - assign masters[owner].awcache = '0; - assign masters[owner].awprot = '0; - assign masters[owner].awqos = '0; - assign masters[owner].awregion = '0; - assign masters[owner].awuser = '0; - assign masters[owner].wuser = '0; - assign arready[owner] = masters[owner].arready; - assign awready[owner] = masters[owner].awready; - assign rvalid[owner] = masters[owner].rvalid; - assign rresp[owner] = masters[owner].rresp; - assign rlast[owner] = masters[owner].rlast; - assign bvalid[owner] = masters[owner].bvalid; - assign bresp[owner] = masters[owner].bresp; + // Production master: BREADY cannot rise until its last W handshake. + dma_axi4_master u_dma ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .clear_i (1'b0), + .read_start_valid_i (1'b0), + .read_start_ready_o (), + .read_addr_i (32'd0), + .read_beats_i (5'd1), + .read_fixed_i (1'b0), + .read_busy_o (), + .read_beat_valid_o (), + .read_beat_ready_i (1'b0), + .read_data_o (), + .read_resp_o (), + .read_last_o (), + .read_expected_last_o(), + .read_id_error_o (), + .read_done_o (), + .write_start_valid_i (dma_start), + .write_start_ready_o (dma_start_ready), + .write_addr_i (dma_addr), + .write_beats_i (dma_beats), + .write_fixed_i (1'b0), + .write_busy_o (dma_busy), + .write_data_valid_i (dma_valid), + .write_data_ready_o (dma_ready), + .write_data_i (dma_data), + .write_strb_i (4'hf), + .write_done_o (dma_done), + .write_resp_o (dma_resp), + .write_id_error_o (dma_id_error), + .axi4 (masters[1]) + ); + + for (genvar m = 0; m < 3; m++) begin : gen_master + if (m != 1) begin : gen_manual + assign masters[m].araddr = araddr[m]; + assign masters[m].arlen = arlen[m]; + assign masters[m].arsize = 2; + assign masters[m].arburst = 1; + assign masters[m].arid = 1; + assign masters[m].arvalid = arvalid[m]; + assign masters[m].arlock = 0; + assign masters[m].arprot = 0; + assign masters[m].arcache = 0; + assign masters[m].arqos = 0; + assign masters[m].arregion = 0; + assign masters[m].aruser = 0; + assign masters[m].awaddr = awaddr[m]; + assign masters[m].awlen = awlen[m]; + assign masters[m].awsize = 2; + assign masters[m].awburst = 1; + assign masters[m].awid = 1; + assign masters[m].awvalid = awvalid[m]; + assign masters[m].awlock = 0; + assign masters[m].awprot = 0; + assign masters[m].awcache = 0; + assign masters[m].awqos = 0; + assign masters[m].awregion = 0; + assign masters[m].awuser = 0; + assign masters[m].wdata = wdata[m]; + assign masters[m].wstrb = 15; + assign masters[m].wvalid = wvalid[m]; + assign masters[m].wlast = wlast[m]; + assign masters[m].wuser = 0; + assign masters[m].rready = rready[m]; + assign masters[m].bready = bready[m]; + end + assign arready[m] = masters[m].arready; + assign awready[m] = masters[m].awready; + assign wready[m] = masters[m].wready; + assign rvalid[m] = masters[m].rvalid; + assign bvalid[m] = masters[m].bvalid; + assign rlast[m] = masters[m].rlast; + assign rid[m] = masters[m].rid; + assign bid[m] = masters[m].bid; + assign rresp[m] = masters[m].rresp; + assign bresp[m] = masters[m].bresp; + assign rdata[m] = masters[m].rdata; + + // Independent upstream scoreboards also run in Icarus without SVA. + integer rleft = 0, wleft = 0, bleft = 0; + logic rheld = 0, bheld = 0; + logic [36:0] saved_r; + logic [ 3:0] saved_b; + always @(posedge clk_i) begin + if (!rst_n_i) begin + rleft = 0; + wleft = 0; + bleft = 0; + rheld = 0; + bheld = 0; + end else begin + if (rheld && (!masters[m].rvalid || + {masters[m].rid, masters[m].rdata, masters[m].rresp, + masters[m].rlast, masters[m].ruser} !== saved_r)) + $fatal(1, "held R response changed"); + if (bheld && (!masters[m].bvalid || + {masters[m].bid, masters[m].bresp, masters[m].buser} !== saved_b)) + $fatal(1, "held B response changed"); + if (masters[m].arvalid && masters[m].arready) begin + if (rleft != 0) $fatal(1, "duplicate read admission"); + rleft = integer'(masters[m].arlen) + 1; + end + if (masters[m].awvalid && masters[m].awready) begin + if (bleft != 0) $fatal(1, "duplicate write admission"); + wleft = integer'(masters[m].awlen) + 1; + bleft = 1; + end + if (masters[m].bvalid && (bleft != 1 || wleft != 0)) + $fatal(1, "B before complete W drain or duplicate B"); + if (masters[m].wvalid && masters[m].wready) begin + if (wleft <= 0 || masters[m].wlast != (wleft == 1)) $fatal(1, "W count/last mismatch"); + wleft--; + end + if (masters[m].rvalid) begin + if (rleft <= 0 || masters[m].rlast != (rleft == 1)) $fatal(1, "R count/last mismatch"); + if (masters[m].rready) rleft--; + end + if (masters[m].bvalid && masters[m].bready) bleft--; + rheld = masters[m].rvalid && !masters[m].rready; + bheld = masters[m].bvalid && !masters[m].bready; + saved_r = { + masters[m].rid, masters[m].rdata, masters[m].rresp, masters[m].rlast, masters[m].ruser + }; + saved_b = {masters[m].bid, masters[m].bresp, masters[m].buser}; + end + end end - for (genvar target = 0; target < 23; target++) begin : gen_target - tiny_r2_p5_reset_target_model u_model ( - .clk_i (clk_i), - .rst_n_i (target_rst_n[target]), - .hold_address_i(hold_address[target]), - .hold_read_i (hold_read[target]), - .hold_write_i (hold_write[target]), - .hold_b_i (hold_b[target]), - .axi4 (targets[target]) - ); + for (genvar t = 0; t < 23; t++) begin : gen_target + if (t == 12) begin : gen_apb + tiny_axi42apb4 u_bridge ( + .clk_i (clk_i), + .rst_n_i(target_rst_n[t]), + .axi4 (targets[t]), + .apb4 (apb) + ); + end else begin : gen_memory + tiny_r2_p5_reset_target_model u_model ( + .clk_i (clk_i), + .rst_n_i (target_rst_n[t]), + .hold_address_i(hold_address[t]), + .hold_read_i (hold_read[t]), + .hold_write_i (hold_write[t]), + .hold_b_i (hold_b[t]), + .error_beat_i (error_beat[t]), + .axi4 (targets[t]) + ); + end + always @(posedge clk_i) begin + if (rst_n_i) begin + if (targets[t].arvalid && targets[t].arready || targets[t].awvalid && targets[t].awready) + addresses[t]++; + if (targets[t].wvalid && targets[t].wready) writes[t]++; + if (targets[t].rvalid && targets[t].rready) captures[t]++; + if (!target_rst_n[t] && (targets[t].arvalid || targets[t].awvalid || + targets[t].wvalid || targets[t].rready || targets[t].bready)) + $fatal(1, "downstream handshake during target reset"); + end + end + end + assign apb.pready = apb.psel && apb.penable && !hold_read[12] && !hold_b[12]; + assign apb.prdata = 32'hcafe_f00d; + assign apb.pslverr = 0; + always @(posedge clk_i) begin + if (rst_n_i) begin + for (integer e = 0; e < 6; e++) begin + if (fault_valid[e]) begin + faults[e]++; + last_fault_addr[e] = fault_addr[e]; + last_fault_resp[e] = fault_resp[e]; + end + end + end end - task automatic reset_read(input logic [31:0] address, input int target); - @(negedge clk_i); - araddr[0] = address; - arlen[0] = 0; - arsize[0] = 2; - arburst[0] = 1; - arid[0] = 0; - arvalid[0] = 1; - do @(posedge clk_i); while (!arready[0]); - @(negedge clk_i); - arvalid[0] = 0; - repeat (2) @(posedge clk_i); - if (owner_idle[0] || target_idle[target]) $fatal(1, "read was not retained"); - target_rst_n[target] = 0; + task automatic quiet; + repeat (4) @(negedge clk_i); + if (owner_idle != 7 || target_idle != 23'h7fffff || rvalid != 0 || bvalid != 0) + $fatal(1, "credit/response not retired"); + endtask + task automatic receive_read(input int index, input int beats, input logic [1:0] expected, + input logic [31:0] data); do @(posedge clk_i); while (!rvalid[0]); - if (rresp[0] != 2'b11 || !rlast[0]) $fatal(1, "reset read did not terminate with DECERR"); + @(negedge clk_i); + repeat (3) @(negedge clk_i); + if (rid[0] !== 1'b1 || rresp[0] !== expected || rdata[0] !== data || + rlast[0] !== (index == beats - 1)) + $fatal( + 1, + "read data/ID/response mismatch index=%0d expected=%0d got=%0d", + index, + expected, + rresp[0] + ); rready[0] = 1; @(posedge clk_i); @(negedge clk_i); - rready[0] = 0; - target_rst_n[target] = 1; - repeat (2) @(posedge clk_i); - if (!owner_idle[0]) $fatal(1, "read credit was not released after reset"); + rready[0] = 0; endtask - - task automatic reset_write(input logic [31:0] address, input int target); + task automatic read_case(input logic [31:0] address, input int t, input int beats, + input bit buffered, input bit before_address, input bit inject_reset); + integer first_capture, prefix, before_faults, before_addresses; + logic [ 1:0] expected; + logic [31:0] data; @(negedge clk_i); - awaddr[1] = address; - awlen[1] = 0; - awsize[1] = 2; - awburst[1] = 1; - awid[1] = 1; - awvalid[1] = 1; - do @(posedge clk_i); while (!awready[1]); - @(negedge clk_i); - awvalid[1] = 0; - repeat (2) @(posedge clk_i); - if (owner_idle[1] || target_idle[target]) $fatal(1, "write was not retained"); - target_rst_n[target] = 0; - do @(posedge clk_i); while (!bvalid[1]); - if (bresp[1] != 2'b11) $fatal(1, "reset write did not terminate with DECERR"); - bready[1] = 1; - @(posedge clk_i); - @(negedge clk_i); - bready[1] = 0; - target_rst_n[target] = 1; - repeat (2) @(posedge clk_i); - if (!owner_idle[1]) $fatal(1, "write credit was not released after reset"); - endtask - - task automatic reset_buffered_read; - @(negedge clk_i); - araddr[0] = 32'h30000000; - arlen[0] = 0; - arsize[0] = 2; - arburst[0] = 1; - arid[0] = 0; - arvalid[0] = 1; + first_capture = captures[t]; + before_faults = faults[0]; + before_addresses = addresses[t]; + hold_address[t] = before_address; + hold_read[t] = !buffered && inject_reset; + araddr[0] = address; + arlen[0] = 8'(beats - 1); + arvalid[0] = 1; do @(posedge clk_i); while (!arready[0]); @(negedge clk_i); arvalid[0] = 0; - do @(posedge clk_i); while (!rvalid[0]); - if (rresp[0] != 2'b00) $fatal(1, "buffered read did not reach response spill"); - target_rst_n[0] = 0; - do @(posedge clk_i); while (!rvalid[0]); - if (rresp[0] != 2'b11 || !rlast[0]) $fatal(1, "buffered response was replayed after reset"); - rready[0] = 1; + if (buffered) begin + wait (rvalid[0]); + repeat (5) @(negedge clk_i); + end else if (!before_address) begin + wait (addresses[t] > before_addresses); + @(negedge clk_i); + end + prefix = captures[t] - first_capture; + if (inject_reset) begin + target_rst_n[t] = 0; + before_addresses = addresses[t]; + repeat (3) @(negedge clk_i); + // Release the downstream reset while the upstream is still stalled. + // The cancelled session must never be issued again after release. + if (t != 0) target_rst_n[t] = 1; + hold_address[t] = 0; + hold_read[t] = 0; + repeat (4) @(negedge clk_i); + if (addresses[t] != before_addresses) $fatal(1, "cancelled AR replayed"); + end + for (integer n = 0; n < beats; n++) begin + expected = inject_reset && n >= prefix ? 3 : (n == error_beat[t] ? 2 : 0); + data = expected == 3 ? 0 : (t == 12 ? 32'hcafe_f00d : (address + 32'(4 * n)) ^ 32'h10000000); + receive_read(n, beats, expected, data); + end + target_rst_n[t] = 1; + quiet(); + if (inject_reset && (prefix < beats || (error_beat[t] >= 0 && error_beat[t] < prefix))) begin + expected = (error_beat[t] >= 0 && error_beat[t] < prefix) ? 2 : 3; + if (faults[0] != before_faults + 1 || last_fault_resp[0] != expected || + last_fault_addr[0] != address + 32'(4 * (expected == 2 ? error_beat[t] : prefix))) + $fatal(1, "first failing read beat metadata/count lost"); + end else if (faults[0] != before_faults) $fatal(1, "spurious read fault"); + cases++; + endtask + task automatic send_word(input int n, input int beats); + @(negedge clk_i); + wdata[2] = 32'ha5000000 + 32'(n); + wlast[2] = n == beats - 1; + wvalid[2] = 1; + do @(posedge clk_i); while (!wready[2]); + @(negedge clk_i); + wvalid[2] = 0; + endtask + task automatic write_case(input logic [31:0] address, input int t, input int beats, + input int prefix, input bit buffered, input bit before_address); + integer before_faults, after_writes; + @(negedge clk_i); + before_faults = faults[5]; + hold_address[t] = before_address; + hold_b[t] = !buffered; + awaddr[2] = address; + awlen[2] = 8'(beats - 1); + awvalid[2] = 1; + do @(posedge clk_i); while (!awready[2]); + @(negedge clk_i); + awvalid[2] = 0; + for (integer n = 0; n < prefix; n++) send_word(n, beats); + if (buffered) begin + wait (bvalid[2]); + repeat (3) @(negedge clk_i); + end + target_rst_n[t] = 0; + after_writes = writes[t]; + repeat (3) @(negedge clk_i); + if (t != 0) target_rst_n[t] = 1; + hold_address[t] = 0; + hold_b[t] = 0; + for (integer n = prefix; n < beats; n++) begin + if (bvalid[2]) $fatal(1, "write response before remaining data"); + send_word(n, beats); + end + do @(posedge clk_i); while (!bvalid[2]); + @(negedge clk_i); + repeat (5) @(negedge clk_i); + if (bresp[2] != (buffered ? 0 : 3) || bid[2] !== 1'b1 || writes[t] != after_writes) + $fatal(1, "write response/ID or downstream session replay"); + bready[2] = 1; @(posedge clk_i); @(negedge clk_i); - rready[0] = 0; - target_rst_n[0] = 1; - repeat (2) @(posedge clk_i); - if (!owner_idle[0]) $fatal(1, "buffered read credit was not released after reset"); + bready[2] = 0; + target_rst_n[t] = 1; + quiet(); + if (faults[5] != before_faults + (buffered ? 0 : 1)) $fatal(1, "write fault count mismatch"); + cases++; + endtask + task automatic dma_case(input logic [31:0] address, input int t, input int beats, + input int prefix); + integer before_faults, after_writes; + @(negedge clk_i); + before_faults = faults[3]; + hold_b[t] = 1; + dma_addr = address; + dma_beats = 5'(beats); + dma_start = 1; + do @(posedge clk_i); while (!dma_start_ready); + @(negedge clk_i); + dma_start = 0; + wait (!owner_idle[1]); + @(negedge clk_i); + for (integer n = 0; n < beats; n++) begin + if (n == prefix) begin + target_rst_n[t] = 0; + after_writes = writes[t]; + repeat (3) @(negedge clk_i); + target_rst_n[t] = 1; + hold_b[t] = 0; + end + dma_data = 32'h5a000000 + 32'(n); + dma_valid = 1; + do @(posedge clk_i); while (!dma_ready); + @(negedge clk_i); + dma_valid = 0; + end + do @(posedge clk_i); while (!dma_done); + if (dma_resp != 3 || dma_id_error) $fatal(1, "production DMA did not terminate with error"); + @(negedge clk_i); + quiet(); + if (dma_busy || writes[t] != after_writes || faults[3] != before_faults + 1) + $fatal(1, "production DMA write drain/credit/count failure"); + cases++; endtask initial begin - for (integer target = 0; target < 23; target++) begin - target_rst_n[target] = 0; - hold_address[target] = 0; - hold_read[target] = 0; - hold_write[target] = 0; - hold_b[target] = 0; + for (integer t = 0; t < 23; t++) begin + target_rst_n[t] = 0; + captures[t] = 0; + addresses[t] = 0; + writes[t] = 0; + error_beat[t] = -1; end - hold_read[0] = 1; - hold_write[12] = 1; + for (integer e = 0; e < 6; e++) faults[e] = 0; repeat (4) @(negedge clk_i); rst_n_i = 1; - for (integer target = 0; target < 23; target++) target_rst_n[target] = 1; - reset_read(32'h30000000, 0); - hold_read[0] = 0; - reset_buffered_read(); - reset_write(32'h10009000, 12); - if (fault_valid != 0) $fatal(1, "fault pulse was not consumed"); + for (integer t = 0; t < 23; t++) target_rst_n[t] = 1; + // SRAM and XPI: address cancellation, missing 16 beats, two captured + // beats under backpressure, and already captured successful completion. + for (integer t = 0; t < 5; t += 4) begin + read_case(t == 0 ? 32'h30000000 : 32'h50000000, t, 16, 0, 1, 1); + read_case(t == 0 ? 32'h30000000 : 32'h50000000, t, 16, 0, 0, 1); + read_case(t == 0 ? 32'h30000000 : 32'h50000000, t, 16, 1, 0, 1); + read_case(t == 0 ? 32'h30000000 : 32'h50000000, t, 1, 1, 0, 1); + read_case(t == 0 ? 32'h30000000 : 32'h50000000, t, 1, 1, 0, 0); + write_case(t == 0 ? 32'h30000000 : 32'h50000000, t, 16, 0, 0, 1); + write_case(t == 0 ? 32'h30000000 : 32'h50000000, t, 16, 2, 0, 0); + write_case(t == 0 ? 32'h30000000 : 32'h50000000, t, 16, 16, 0, 0); + write_case(t == 0 ? 32'h30000000 : 32'h50000000, t, 1, 1, 1, 0); + dma_case(t == 0 ? 32'h30000000 : 32'h50000000, t, 16, 2); + end + error_beat[0] = 1; + read_case(32'h30000000, 0, 16, 1, 0, 1); + error_beat[0] = -1; + // Actual APB bridge, with a withheld peripheral completion. + read_case(32'h10009000, 12, 1, 0, 1, 1); + read_case(32'h10009000, 12, 1, 0, 0, 1); + read_case(32'h10009000, 12, 1, 1, 0, 1); + write_case(32'h10009000, 12, 1, 0, 0, 1); + write_case(32'h10009000, 12, 1, 1, 0, 0); + write_case(32'h10009000, 12, 1, 1, 1, 0); + dma_case(32'h10009000, 12, 1, 0); + $display("P5_RESET cases=%0d production_dma=3 burst_beats=16 held_RB=1 first_fault=1", cases); $display("SIM_TEST_PASS Tiny P5 target reset isolation"); $finish; end initial begin - repeat (2000) @(posedge clk_i); - $fatal(1, "SIM_TEST_TIMEOUT Tiny P5 target reset isolation"); + repeat (20000) @(posedge clk_i); + $fatal(1, "SIM_TEST_TIMEOUT Tiny P5 target reset isolation cases=%0d", cases); end endmodule module tiny_r2_p5_reset_target_model ( input logic clk_i, - input logic rst_n_i, - input logic hold_address_i, - input logic hold_read_i, - input logic hold_write_i, - input logic hold_b_i, + rst_n_i, + hold_address_i, + hold_read_i, + hold_write_i, + hold_b_i, + input integer error_beat_i, axi4_if.slave axi4 ); - logic active = 0; - logic writing = 0; - logic write_done = 0; - logic id = 0; + logic active = 0, writing = 0, write_done = 0, id = 0; logic [31:0] address = 0; - logic [ 7:0] length = 0; - logic [ 7:0] beat = 0; - + logic [7:0] length = 0, beat = 0; assign axi4.arready = !active && !hold_address_i && rst_n_i; assign axi4.awready = !active && !hold_address_i && !axi4.arvalid && rst_n_i; assign axi4.wready = active && writing && !write_done && !hold_write_i && rst_n_i; assign axi4.ruser = 0; assign axi4.buser = 0; - - always @(posedge clk_i) begin + always @(posedge clk_i or negedge rst_n_i) begin if (!rst_n_i) begin active <= 0; writing <= 0; @@ -270,7 +502,6 @@ module tiny_r2_p5_reset_target_model ( active <= 1; writing <= 1; write_done <= 0; - address <= axi4.awaddr; length <= axi4.awlen; beat <= 0; id <= axi4.awid; @@ -279,17 +510,22 @@ module tiny_r2_p5_reset_target_model ( axi4.rvalid <= 1; axi4.rid <= id; axi4.rdata <= 32'h10000000 ^ address; - axi4.rresp <= 0; + axi4.rresp <= integer'(beat) == error_beat_i ? 2 : 0; axi4.rlast <= beat == length; end if (axi4.rvalid && axi4.rready) begin axi4.rvalid <= 0; - active <= 0; + if (axi4.rlast) active <= 0; + else begin + beat <= beat + 1; + address <= address + 4; + end end if (axi4.wvalid && axi4.wready) begin - write_done <= 1; + if (beat == length) write_done <= 1; + else beat <= beat + 1; end - if (active && writing && write_done && !hold_b_i) begin + if (active && writing && write_done && !axi4.bvalid && !hold_b_i) begin axi4.bvalid <= 1; axi4.bid <= id; axi4.bresp <= 0; @@ -297,6 +533,7 @@ module tiny_r2_p5_reset_target_model ( if (axi4.bvalid && axi4.bready) begin axi4.bvalid <= 0; active <= 0; + write_done <= 0; end end end diff --git a/tests/test_publication_tiny.py b/tests/test_publication_tiny.py index 2bbe916d..68e1be4c 100644 --- a/tests/test_publication_tiny.py +++ b/tests/test_publication_tiny.py @@ -54,6 +54,24 @@ def test_common_register_default_remains_mini_compatible(): assert "SOC_SYSCTRL_TEST_STATUS_OFFSET" in constants +@pytest.mark.parametrize("before,after", [ + ("s_req_q.write_done && !s_req_q.b_captured", "1'b1"), + ("s_r_valid && s_rready[s_req_q.owner] && s_r_data[1]", "s_r_valid"), + (".flush_i(1'b0)", ".flush_i(!enable_i)"), +]) +def test_target_reset_contract_rejects_unsafe_completion(tmp_path, before, after): + contract = tiny.read(ROOT / tiny.BOOK / "source-contract.json") + binding = next(item for item in contract["bindings"] + if item["file"] == "rtl/tiny/top/tiny_axi4_target.sv") + path = tmp_path / binding["file"] + path.parent.mkdir(parents=True) + original = (ROOT / binding["file"]).read_text() + assert before in original + path.write_text(original.replace(before, after)) + with pytest.raises(ValueError, match="Tiny behavior binding changed"): + tiny.require_snippets(tmp_path, binding["file"], binding["text"]) + + def test_reset_update_does_not_bypass_reviewed_snapshot(monkeypatch): original = tiny.subprocess.check_output checked = [] diff --git a/tests/test_tiny_r2_p5.py b/tests/test_tiny_r2_p5.py index 1cc99542..51f7e8fd 100644 --- a/tests/test_tiny_r2_p5.py +++ b/tests/test_tiny_r2_p5.py @@ -71,9 +71,11 @@ def test_concurrent_fabric(tmp_path, simulator): @pytest.mark.parametrize("simulator", ["iverilog", "verilator"]) def test_target_reset_isolation(tmp_path, simulator): files, includes = sources(tmp_path) + files.append(ROOT / "rtl/ip/peripheral/dma_axi4_master.sv") files.append(ROOT / "tests/rtl/tiny_r2_p5_reset_tb.sv") log = simulate(tmp_path, simulator, "tiny_r2_p5_reset_tb", files, includes) assert "SIM_TEST_PASS Tiny P5 target reset isolation" in log + assert "P5_RESET cases=28 production_dma=3 burst_beats=16 held_RB=1 first_fault=1" in log @pytest.mark.parametrize("simulator", ["iverilog", "verilator"]) @@ -103,14 +105,15 @@ def test_real_cpu_dma_progress_and_abort(tmp_path, simulator): assert "hart_reset=1 abort_drained=1" in log -@pytest.mark.parametrize("kind", ["fabric", "fault"]) +@pytest.mark.parametrize("kind", ["fabric", "fabric_reset", "fault"]) def test_protocol_proofs(tmp_path, kind): for tool in ("sv2v", "yosys"): assert shutil.which(tool), f"required tool unavailable: {tool}" files, includes = sources(tmp_path) - top = f"tiny_r2_p5_{kind}_formal" + top = f"tiny_r2_p5_{'fabric' if kind == 'fabric_reset' else kind}_formal" files.append(ROOT / f"tests/rtl/{top}.sv") - converted = subprocess.run(["sv2v", "-DFORMAL", "-DSV_ASSRT_DISABLE", *includes, + reset_define = ["-DTINY_P5_RESET_FORMAL"] if kind == "fabric_reset" else [] + converted = subprocess.run(["sv2v", "-DFORMAL", "-DSV_ASSRT_DISABLE", *reset_define, *includes, *(str(p) for p in files)], text=True, capture_output=True, timeout=180) (tmp_path / "sv2v.log").write_text(converted.stderr) assert converted.returncode == 0, converted.stderr From b3b30a72cd6ef5340c28e5044c96cd13258ec5a6 Mon Sep 17 00:00:00 2001 From: maksyuki Date: Mon, 12 Oct 2026 07:15:53 +0800 Subject: [PATCH 06/12] docs(tiny): record P5 reset correction validation Document corrected reset behavior, source-bound evidence, validation commands and both PDK timing observations. Mark the earlier reset repair as superseded while preserving its historical attribution. Retain explicit limits for workload replay, full-function netlist testing and physical qualification. Human phase acceptance and P6 execution remain separate. --- docs/ip/tiny-soc-r2-concurrent-fabric.md | 112 ++++++++++++++++++++++- docs/ip/tiny-soc-verification.md | 43 +++++++++ 2 files changed, 152 insertions(+), 3 deletions(-) diff --git a/docs/ip/tiny-soc-r2-concurrent-fabric.md b/docs/ip/tiny-soc-r2-concurrent-fabric.md index 4d8de63b..68ca60e3 100644 --- a/docs/ip/tiny-soc-r2-concurrent-fabric.md +++ b/docs/ip/tiny-soc-r2-concurrent-fabric.md @@ -5,14 +5,120 @@ This implements the approved P5 preflight on `7e19de49d49efaafacccfa85671e314d53dd15a4`. The default is Tiny/ICS55 SAFE24 with its present PLL parked off; IHP130/no PLL remains compatibility. The [ledger](tiny-soc-verification.md) separates phase evidence from approval. -Functional validation and matched-image measurements are complete for review; -no P6, frequency or physical qualification is implied. +Historical campaigns and their limitations remain below. The P1 correction +has its own source and validation scope; no P6, frequency or physical +qualification is implied. + +## P1 protocol correction (2026-10-11) + +This correction starts from committed `4e6ab1a6fb445521703f0bfa412d3a1e8a38cd69` +and the unchanged frozen specification. The preceding `09-30` campaign's +`final-evidence.json` records two reset protocol failures and stale publication +bindings even though ordinary regressions and matched-image replay passed. +Those results do not qualify this corrected RTL. + +Target reset cancels the downstream session while preserving the live +upstream session. A reset sampled in Address/Response latches the abort path; +releasing the target cannot replay its cancelled address or data. Already +captured R/B responses remain in the Common spills with unchanged VALID, +payload, ID and order. Only fabric/system reset clears those buffers. The +downstream stability assertions follow the target's own reset; upstream +stability assertions remain enabled throughout target-only reset. + +A nine-bit count tracks captured read beats, including buffered and already +retired beats. Missing DECERR beats append through the same spill until the +original ARLEN+1 count is met; RLAST belongs only to the final beat. AbortWrite +accepts/discards remaining upstream W beats before creating one DECERR B. +Captured-B state prevents duplicate responses and preserves an already held +successful completion. Fault metadata is captured from the first erroneous +response beat, preserving earlier SLVERR over a subsequent reset DECERR; +fault counting remains once per terminal transaction. Fully captured successful +responses do not become reset faults. + +The reset fixture drives stimulus away from sampling edges and independently +checks R/B stability, exact beat counts, W-before-B ordering, one completion, +credit release and first-error metadata. Its 28 cases include 16-beat SRAM/XPI +traffic, a production APB bridge, three production DMA-master drain cases, +target reset held through completion and early target release. A separate +formal mode varies target reset arbitrarily and proves response/ownership, +beat-count and W/B ordering properties by induction. The normal formal mode +and its fixed-response expectations remain intact. Publication bindings retain +complete terminal/fault predicates, with negative tests for premature +completion and response flushing; the reviewed publication SHA is unchanged. + +Evidence belongs under +`build/ics55-tiny-2026-10-11-23-35-b4aa59f3e5d9/meta/tiny-r2-p5-p1/`. +Final focused checks precede one full Pytest and one PR execution per Tiny +PDK. No matched-image replay is required to claim this bounded correctness +repair; historical workload measurements retain their source attribution. +Validation completed on 2026-10-12; the ledger retains human review pending. +Runtime peripheral-reset controls/quiescence remain P7, and the existing +five-edge reset leaves remain unchanged. No shared DMA, public ABI, profile, +dependency, warning baseline or metrics policy changes are included. + +The final technical input digest is +`f670789a2379a8d2e6c59f5ecd714ca0237bb6f1cfe749a2f29b0794d356b5f0`. +`source.json` preserves the 2011 inputs and locked tools; `evidence.json` +rechecks their identity and binds all gate/fixture/flow artifacts. Focused +validation passes 101 tests; full Pytest passes 1685 with one optional `pypdf` +skip and no failures. Counts overlap. ICS55/IHP130 PR commands each ran once; +their Tiny nightly dry-run matrices match, so the same gates were not repeated. +All six ordinary simulations satisfy command success, the Tiny success marker +and forbidden-marker checks. The scoped reset fixture and formal checks are +additional protocol evidence, not physical or XPI-device qualification. + +| PDK / balanced recipe | Cells | Library area | Max WNS / TNS (ns) | Min WNS / TNS (ns) | +| --- | ---: | ---: | ---: | ---: | +| ICS55 | 245037 | 1910664.100799 | -70.04 / -1151596.25 | 0 / 0 | +| IHP130 | 259394 | 9911158.202394 | -127.01 / -3120300.50 | 0 / 0 | + +Current physical-flow observations are under +`build/ics55-tiny-2026-10-11-23-35-42d5beccb726/` and +`build/ihp130-tiny-2026-10-11-23-35-bff91e3529ba/`, in `meta/metrics.json` +and `sta/opensta/{timing_metrics.rpt,p4-clock-bindings.rpt,opensta.log}`. +Both mapping checks retain four groups, 32 macros and CPU/main SRAM on SYS. +Timing remains observational and unclosed; library area is not routed die area +and zero observed min violations do not constitute PVT/hold signoff. Lint +signature/count sets are identical to `09-30` (470/414 new against the unchanged +ICS55/IHP130 baselines, zero increased). Successful ABC Liberty-cache fallback +and NFS clock-skew observations remain logged. No baselines were regenerated. + +Commands used the locked activation, `BUILD_TIMESTAMP=2026-10-11-23-35` and +`JOBS=3`. The `run_flow.py` JSON/log pairs in the correction evidence root retain +every exact command: `focused`, `quality`, `pytest-full`, `regress-ics55`, +`regress-ihp130`, `metrics-ics55`, `metrics-ihp130`, four `dry-run-*` records +and `audit-result`. The principal acceptance commands were: + +```sh +source .cache/retrosoc/development/tiny-r2-p1/activate.sh +export BUILD_TIMESTAMP=2026-10-11-23-35 JOBS=3 +make rtl-format-check rtl-style-check-all rtl-readiness-check-all +ruff check . +python3 -m pytest -q --tb=short --basetemp=build/ics55-tiny-2026-10-11-23-35-b4aa59f3e5d9/meta/tiny-r2-p5-p1/tests-full +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55 +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 APP=ci_smoke SYNTH=YOSYS STA=OPENSTA metrics +make CONFIG=configs/ci/ihp130-tiny.mk SOC=TINY PDK=IHP130 APP=ci_smoke SYNTH=YOSYS STA=OPENSTA metrics +git diff --check +``` + +New executions must select a fresh timestamp/evidence directory. New matched +workload replay, compiler experiments, full-function C netlist qualification, +PDF build/visual promotion, CDC/RDC, extracted PVT/MMMC and place/route were +not run. No performance, new operating-frequency or complete-product claim is +made by this correction. Error completion does not roll back downstream writes +or peripheral side effects that committed before reset. ## Target-local reset repair (2026-10-11) +This is the superseded first attempt. Its focused tests passed but omitted +required protocol checks; the later `09-30` campaign and independent monitors +rejected it. The following description records that attempted behavior, not +the corrected implementation above. + The P5 review found that a target reset was previously used only as an admission enable while an accepted controller session remained live. The -fabric now carries the explicit per-target reset leaf into each controller. +attempt carried the explicit per-target reset leaf into each controller. Admission and downstream AXI VALID/W/RREADY are blocked while that leaf is asserted. An accepted Address or Response session changes to an abort response and returns one DECERR RLAST or B response with the saved owner and ID; the diff --git a/docs/ip/tiny-soc-verification.md b/docs/ip/tiny-soc-verification.md index e61dc467..08ffbeee 100644 --- a/docs/ip/tiny-soc-verification.md +++ b/docs/ip/tiny-soc-verification.md @@ -1,7 +1,50 @@ # Tiny MCU Verification Record +## TINY-R2-P5 P1 protocol correction; validated, review pending (2026-10-12) + +The maintainer requested correction of the reset write-drain/read-retention +failures and publication-binding mismatch. The implementation starts from +`4e6ab1a6fb445521703f0bfa412d3a1e8a38cd69`; the specification remains blob +`7e19de49d49efaafacccfa85671e314d53dd15a4`. The [P5 runbook](tiny-soc-r2-concurrent-fabric.md) +describes preserved upstream spill responses, complete read/error bursts, +write drain before B, latched downstream cancellation and first-error capture. +Profiles remain Tiny/ICS55 SAFE24 with parked PLL and Tiny/IHP130 compatibility. + +Current evidence is isolated under +`build/ics55-tiny-2026-10-11-23-35-b4aa59f3e5d9/meta/tiny-r2-p5-p1/`. +The older `09-30` campaign failed overall: 1669 Pytest passes, one skip, +two failures and ten errors (publication), plus independent R stability and +W-before-B violations. Its passing ordinary regressions, PPA and replay are +historical observations, not acceptance of the repair. + +Final focused validation passes 101 tests. Full Pytest passes 1685 tests, +with one optional `pypdf` skip and no failures; these counts overlap. Both +Tiny PDK PR regressions pass, including six strict simulation verdicts, +synthesis, netlist boot and the 32-macro/common-SYS binding check. Each +simulator passes all 28 reset scenarios, including three production DMA +drain cases; normal-target, arbitrary-target-reset and fault-collector +induction/SAT checks pass. Complete terminal/fault publication bindings and +three unsafe-completion mutation tests pass without changing the reviewed +publication SHA. RTL format/style/readiness, Ruff and diff checks pass. + +`source.json` and `evidence.json` bind 2011 technical inputs to digest +`f670789a2379a8d2e6c59f5ecd714ca0237bb6f1cfe749a2f29b0794d356b5f0`, +locked tools, exact command results and retained artifacts. No warning +baseline, metric policy, shared DMA/SDK implementation or public ABI changed. +The lint signatures/counts match the preceding `09-30` campaign; the existing +baseline comparison remains a non-blocking observation. STA remains unclosed: +max WNS is -70.04 ns ICS55 and -127.01 ns IHP130. The runbook records full +area/timing observations and limitations. Matched-image replay, full-function +C netlist qualification, PDF promotion and physical qualification were not +repeated for this bounded correctness repair. P5 human review/acceptance and +all P6+ work remain separate. + ## TINY-R2-P5 target-reset isolation repair; focused validation (2026-10-11) +Superseded attempt: later protocol checks rejected its response flushing and +early B/RLAST behavior. The four focused passes below remain historically +accurate but were insufficient to establish reset isolation. + Following the P5 review, the fabric now receives the explicit 23-entry target reset vector. A reset blocks new admission and downstream handshakes, flushes response spill state, and converts any accepted target session into a held From 952b060a708d3241032cd7df9c0f35a766133eec Mon Sep 17 00:00:00 2001 From: maksyuki Date: Mon, 12 Oct 2026 21:43:30 +0800 Subject: [PATCH 07/12] feat(tiny): integrate P6 shared IP and eight-channel DMA Integrate RNG, CRC, WS2812, Crypto, I2S master and the SDIO private AXI owner on SAFE24. Preserve the 128 KiB main SRAM and six private Crypto macros, add product-specific DMA assignments and reject unsupported SDK operations. Correct APB read strobes, I2S receive assembly and SDIO command timing, backpressure and descriptor retirement. Add pin-level workloads, source-bound evidence checks and publication/commercial consumer compatibility. Validation of the final P6 worktree: 1726 pytest tests passed with one optional pypdf skip (exit 0, 4021.61 seconds; terminal session 36439). Both PDKs passed both RTL simulators, netlist boot and 38-macro binding checks. Shared formal, Mini compatibility and software/source quality gates passed. Evidence remains attributed to the retained dirty-worktree snapshots based on b3b30a72cd6ef5340c28e5044c96cd13258ec5a6 under build/ics55-tiny-2026-10-12-11-50-2eb98c0e1fae/meta/tiny-r2-p6/. Timing is negative and warning observations are not promoted. This commit does not accept P6, qualify physical operation or enable P7. --- Makefile | 2 + app/apps/bringup/tiny.mk | 3 +- app/apps/ci_smoke/mcu_shared_acceptance.h | 9 + app/apps/ci_smoke/tiny.c | 70 +- app/apps/ci_smoke/tiny.mk | 3 +- app/apps/ci_smoke/tiny_r2_p6.c | 801 ++++++++++++++++++ crt/include/retrosoc/hal/dma.h | 9 + crt/include/retrosoc/hal/dma_regs.h | 12 +- crt/src/hal/dma.c | 10 +- crt/src/hal/dma_math.c | 5 +- crt/src/hal/i2c.c | 74 ++ crt/src/hal/sdio.c | 220 ++++- crt/src/hal/sdio_math.c | 4 +- crt/src/hal/uart.c | 11 + physical/commercial/config/products/tiny.mk | 2 +- .../commercial/tcl/common/products/tiny.tcl | 8 +- .../smoke/sta/opensta/tiny_ics55_checks.tcl | 2 +- .../smoke/sta/opensta/tiny_r2_p4_checks.tcl | 27 +- publications/programming_reference.py | 22 +- rtl/ip/serial/i2s_recv.sv | 27 +- rtl/ip/serial/i2s_reg.sv | 2 +- rtl/ip/storage/sdio_command.sv | 5 +- rtl/ip/storage/sdio_core.sv | 17 +- rtl/ip/storage/sdio_data.sv | 7 +- rtl/ip/storage/sdio_dma.sv | 17 +- rtl/mk/software.mk | 9 +- rtl/tiny/Makefile | 12 +- rtl/tiny/address_map/memory_map.json | 72 +- rtl/tiny/dv/retrosoc_tiny_tb.sv | 72 +- rtl/tiny/dv/tiny_p6_devices.svh | 329 +++++++ rtl/tiny/filelist/clusterip.fl | 7 + rtl/tiny/filelist/inc.fl | 3 + rtl/tiny/filelist/ip.fl | 37 + rtl/tiny/filelist/tb.fl | 1 + rtl/tiny/integration/clock_reset_domains.json | 94 +- rtl/tiny/integration/soc_topology.json | 614 ++++++++------ rtl/tiny/mk/config.mk | 2 +- rtl/tiny/pin_map/pin_map.json | 128 +-- rtl/tiny/top/retrosoc_tiny.sv | 209 ++--- rtl/tiny/top/tiny_axi42apb4.sv | 2 +- rtl/tiny/top/tiny_axi4_fabric.sv | 73 +- scripts/generate_tiny.py | 55 +- scripts/setup_tiny.py | 2 +- scripts/tiny_ics55_platform.py | 14 +- scripts/tiny_r2_p6.py | 386 +++++++++ tests/c/test_runtime.c | 105 ++- tests/publication_history.py | 40 + tests/rtl/i2s_tb.sv | 25 + tests/rtl/sdio_axi_memory_responder.sv | 12 +- tests/rtl/sdio_command_tb.sv | 34 +- tests/rtl/sdio_native_model.sv | 175 ++-- tests/rtl/sdio_standalone_tb.sv | 120 ++- tests/rtl/tiny_axi4_fabric_tb.sv | 17 +- tests/rtl/tiny_r2_p5_fabric_tb.sv | 26 +- tests/rtl/tiny_r2_p5_reset_tb.sv | 55 +- tests/rtl/tiny_r2_p5_system_tb.sv | 5 +- tests/rtl/tiny_r2_p6_bridge_tb.sv | 87 ++ tests/rtl/tiny_r2_p6_shared_tb.sv | 153 ++++ tests/test_commercial_flow.py | 4 +- tests/test_publication_programming.py | 15 + tests/test_publication_tiny.py | 218 +---- tests/test_tiny.py | 6 +- tests/test_tiny_ics55_platform.py | 13 +- tests/test_tiny_r2_p2.py | 4 +- tests/test_tiny_r2_p6.py | 544 ++++++++++++ 65 files changed, 4142 insertions(+), 1006 deletions(-) create mode 100644 app/apps/ci_smoke/mcu_shared_acceptance.h create mode 100644 app/apps/ci_smoke/tiny_r2_p6.c create mode 100644 rtl/tiny/dv/tiny_p6_devices.svh create mode 100644 scripts/tiny_r2_p6.py create mode 100644 tests/publication_history.py create mode 100644 tests/rtl/tiny_r2_p6_bridge_tb.sv create mode 100644 tests/rtl/tiny_r2_p6_shared_tb.sv create mode 100644 tests/test_tiny_r2_p6.py diff --git a/Makefile b/Makefile index c3a9dd3c..bc7e8671 100644 --- a/Makefile +++ b/Makefile @@ -420,6 +420,8 @@ endif ifeq ($(HAVE_SVA), NO) DEF_LIST += +define+SV_ASSRT_DISABLE +else ifeq ($(SOC),TINY) + DEF_LIST += +define+HAVE_SVA endif ifeq ($(HAVE_HP), YES) diff --git a/app/apps/bringup/tiny.mk b/app/apps/bringup/tiny.mk index fd133f66..240ff06f 100644 --- a/app/apps/bringup/tiny.mk +++ b/app/apps/bringup/tiny.mk @@ -1,3 +1,4 @@ APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/tiny.c APP_CFLAGS += -Os -APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/mcu_acceptance.S \ No newline at end of file +APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/mcu_acceptance.S +APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/tiny_r2_p6.c \ No newline at end of file diff --git a/app/apps/ci_smoke/mcu_shared_acceptance.h b/app/apps/ci_smoke/mcu_shared_acceptance.h new file mode 100644 index 00000000..873f1abc --- /dev/null +++ b/app/apps/ci_smoke/mcu_shared_acceptance.h @@ -0,0 +1,9 @@ +#ifndef RETROSOC_APP_MCU_SHARED_ACCEPTANCE_H +#define RETROSOC_APP_MCU_SHARED_ACCEPTANCE_H + +#include + +/* Zero means every P6 acceptance case completed; other values identify failure. */ +uint8_t rs_tiny_p6_acceptance(void); + +#endif diff --git a/app/apps/ci_smoke/tiny.c b/app/apps/ci_smoke/tiny.c index 4e7bf321..5553feef 100644 --- a/app/apps/ci_smoke/tiny.c +++ b/app/apps/ci_smoke/tiny.c @@ -20,12 +20,15 @@ #include #include "mcu_acceptance.h" +#include "mcu_shared_acceptance.h" static _Alignas(64) uint32_t rs_mcu_source[80]; static _Alignas(64) uint32_t rs_mcu_destination[80]; static volatile uint32_t rs_mcu_irq_count; static volatile uint32_t rs_mcu_exception_count; static volatile bool rs_mcu_handler_failed; +static volatile bool rs_mcu_probes_active; +static uint8_t rs_mcu_io_code; static void rs_mcu_finish(bool pass, uint8_t code) { printf("Tiny acceptance %s code=%u\n", pass ? "passed" : "failed", code); @@ -39,8 +42,8 @@ static void rs_mcu_finish(bool pass, uint8_t code) { static void rs_mcu_exception(uintptr_t mcause, uintptr_t stack_pointer) { volatile uint32_t *const frame = (volatile uint32_t *)stack_pointer; - if ((mcause != UINT32_C(2)) && (mcause != UINT32_C(5))) { - rs_mcu_handler_failed = true; + if (!rs_mcu_probes_active || ((mcause != UINT32_C(2)) && (mcause != UINT32_C(5)))) { + rs_mcu_finish(false, 11U); } ++rs_mcu_exception_count; /* system_irq.S saves MEPC in slot 11 of the RV32 trap frame. Both test @@ -75,7 +78,7 @@ static bool rs_mcu_dma(void) { rs_mcu_source[index] = UINT32_C(0xa519c300) ^ index; rs_mcu_destination[index] = UINT32_C(0xcccccccc); } - if ((rs_dma_configure(4U, &config) != RS_EINVAL) || + if ((rs_dma_configure(8U, &config) != RS_EINVAL) || (rs_dma_configure(RS_DMA_CHANNEL_BULK, &config) != RS_OK) || (rs_dma_start(RS_DMA_CHANNEL_BULK) != RS_OK) || (rs_dma_wait(RS_DMA_CHANNEL_BULK, RS_TIMEOUT_DEFAULT) != RS_OK)) { @@ -91,7 +94,7 @@ static bool rs_mcu_dma(void) { return false; } config.kind = RS_DMA_KIND_MM_TO_STREAM; - config.request = RS_DMA_REQUEST_I2S_TX; + config.request = RS_DMA_REQUEST_DVP_RX; if (rs_dma_config_validate(0U, &config) == RS_OK) { return false; } @@ -164,9 +167,7 @@ static bool rs_mcu_io(void) { const uint32_t transmit[4] = {UINT32_C(0x54), UINT32_C(0x49), UINT32_C(0x4e), UINT32_C(0x59)}; uint32_t receive[4] = {0U}; rs_gpio_capabilities_t capabilities; - rs_uart_timing_t timing; bool high; - uint32_t timeout = UINT32_C(10000); uint8_t payload = UINT8_C(0x55); const rs_i2c_transfer_t transfer = { .address = UINT16_C(0x50), @@ -175,54 +176,51 @@ static bool rs_mcu_io(void) { }; rs_i2c_status_t status; + rs_mcu_io_code = 40U; if ((rs_gpio_get_capabilities(&capabilities) != RS_OK) || ((capabilities.pad_features & (RS_GPIO_PAD_CAP_PULL_UP | RS_GPIO_PAD_CAP_PULL_DOWN)) != ((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_gpio_configure(27U, &pull_up) != ((RS_SOC_GPIO_HAS_PULLS != 0U) ? RS_OK : RS_ENOTSUP)) || - (rs_gpio_configure(15U, &pull_down) != + (rs_gpio_configure(27U, &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) { + (rs_gpio_configure(27U, &output) != RS_OK) || (rs_gpio_write(27U, true) != RS_OK) || + (rs_gpio_read(27U, &high) != RS_OK) || !high || (rs_gpio_write(27U, false) != RS_OK) || + (rs_gpio_read(27U, &high) != RS_OK) || high) { return false; } + rs_mcu_io_code = 41U; if ((rs_uart_configure(&loopback, RS_TIMEOUT_DEFAULT) != RS_OK) || (rs_uart_write_dma(transmit, 4U, RS_TIMEOUT_DEFAULT) != RS_OK) || (rs_uart_read_dma(receive, 4U, RS_TIMEOUT_DEFAULT) != RS_OK)) { return false; } + rs_mcu_io_code = 42U; for (uint32_t index = 0U; index < 4U; ++index) { if (transmit[index] != receive[index]) { return false; } } + rs_mcu_io_code = 43U; if ((rs_uart_init(RS_CPU_CLOCK_HZ, UART_BPS) != RS_OK) || - (rs_uart_timing_calculate(RS_CPU_CLOCK_HZ, UART_BPS, &timing) != RS_OK)) { - return false; - } - /* UART1 uses the same register ABI; the current shared high-level UART - * API selects UART0. Exercise UART1 PIO and its internal loopback here. */ - RS_SOC_REG32(RS_SOC_APB4_UART1_BASE, UINT32_C(0x00)) = timing.baud_integer; - RS_SOC_REG32(RS_SOC_APB4_UART1_BASE, UINT32_C(0x04)) = timing.baud_fraction; - RS_SOC_REG32(RS_SOC_APB4_UART1_BASE, UINT32_C(0x08)) = UINT32_C(3); - RS_SOC_REG32(RS_SOC_APB4_UART1_BASE, UINT32_C(0x0c)) = UINT32_C(7); - RS_SOC_REG32(RS_SOC_APB4_UART1_BASE, UINT32_C(0x10)) = UINT32_C(0x5a); - while (((RS_SOC_REG32(RS_SOC_APB4_UART1_BASE, UINT32_C(0x18)) & UINT32_C(0x40)) != 0U) && - (timeout != 0U)) { - --timeout; - } - if ((timeout == 0U) || ((RS_SOC_REG32(RS_SOC_APB4_UART1_BASE, UINT32_C(0x14)) & - UINT32_C(0xff)) != UINT32_C(0x5a))) { + (rs_i2c_init(RS_I2C_BUS_1, RS_CPU_CLOCK_HZ, UINT32_C(100000)) != RS_ENOTSUP)) { return false; } /* There is no slave on the pulled-up acceptance board: a real address * transaction must terminate as NACK rather than stick waiting on the bus. */ - for (uint32_t bus = 0U; bus < 2U; ++bus) { + for (uint32_t bus = 0U; bus < 1U; ++bus) { + rs_mcu_io_code = 44U; const rs_i2c_bus_t instance = (bus == 0U) ? RS_I2C_BUS_0 : RS_I2C_BUS_1; - if ((rs_i2c_init(instance, RS_CPU_CLOCK_HZ, UINT32_C(100000)) != RS_OK) || - (rs_i2c_transfer(instance, &transfer, RS_TIMEOUT_DEFAULT) != RS_EIO) || + rs_status_t init_result = rs_i2c_init(instance, RS_CPU_CLOCK_HZ, UINT32_C(100000)); + rs_status_t transfer_result = init_result == RS_OK + ? rs_i2c_transfer(instance, &transfer, RS_TIMEOUT_DEFAULT) + : init_result; + printf("R2_P6_IO i2c_init=%d i2c_transfer=%d\n", (int)init_result, (int)transfer_result); + if (rs_i2c_get_status(instance, &status) == RS_OK) { + printf("R2_P6_IO i2c_errors=%lx\n", (unsigned long)status.errors); + } + if ((init_result != RS_OK) || (transfer_result != RS_EIO) || (rs_i2c_get_status(instance, &status) != RS_OK) || ((status.errors & RS_I2C_ERROR_ADDR_NACK) == 0U)) { return false; @@ -255,6 +253,7 @@ int main(void) { rs_watchdog_status_t watchdog = {0}; uint64_t before; uint64_t after; + uint8_t p6_result; const rs_watchdog_config_t watchdog_config = { .prescale_divider = 1U, .timeout_ticks = UINT32_C(20000), @@ -285,11 +284,14 @@ int main(void) { } if ((rs_mcu_compressed_probe() != 42U) || ((__RV_CSR_READ(CSR_MISA) & UINT32_C(1)) != 0U) || (rs_irq_register_exception(2U, rs_mcu_exception) != RS_OK) || - (rs_irq_register_exception(5U, rs_mcu_exception) != RS_OK)) { + (rs_irq_register_exception(5U, rs_mcu_exception) != RS_OK) || + (rs_irq_register_exception(7U, rs_mcu_exception) != RS_OK)) { rs_mcu_finish(false, 4U); } + rs_mcu_probes_active = true; rs_mcu_atomic_probe(rs_mcu_source); rs_mcu_unmapped_probe(); + rs_mcu_probes_active = false; if ((rs_mcu_exception_count != 2U) || rs_mcu_handler_failed || (rs_sysctrl_get_fault_status(&fault) != RS_OK) || !fault.pending || (fault.address != UINT32_C(0x38000000)) || (rs_sysctrl_clear_fault() != RS_OK)) { @@ -307,7 +309,11 @@ int main(void) { rs_mcu_finish(false, 8U); } if (!rs_mcu_io()) { - rs_mcu_finish(false, 10U); + rs_mcu_finish(false, rs_mcu_io_code); + } + p6_result = rs_tiny_p6_acceptance(); + if (p6_result != 0U) { + rs_mcu_finish(false, p6_result); } printf("Tiny CPU/DMA/IRQ/RTC/I/O passed; exercising watchdog reset\n"); if ((rs_watchdog_configure(&watchdog_config) != RS_OK) || diff --git a/app/apps/ci_smoke/tiny.mk b/app/apps/ci_smoke/tiny.mk index fd133f66..240ff06f 100644 --- a/app/apps/ci_smoke/tiny.mk +++ b/app/apps/ci_smoke/tiny.mk @@ -1,3 +1,4 @@ APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/tiny.c APP_CFLAGS += -Os -APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/mcu_acceptance.S \ No newline at end of file +APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/mcu_acceptance.S +APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/tiny_r2_p6.c \ No newline at end of file diff --git a/app/apps/ci_smoke/tiny_r2_p6.c b/app/apps/ci_smoke/tiny_r2_p6.c new file mode 100644 index 00000000..ad74f328 --- /dev/null +++ b/app/apps/ci_smoke/tiny_r2_p6.c @@ -0,0 +1,801 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mcu_shared_acceptance.h" + +static volatile uint32_t rs_p6_irq_count; +static uint32_t rs_p6_irq_selected; +static volatile bool rs_p6_irq_error; +static _Alignas(64) uint32_t rs_p6_source[256]; +static _Alignas(64) uint32_t rs_p6_destination[258]; +static _Alignas(64) uint32_t rs_p6_audio_tx[768]; +static _Alignas(64) uint32_t rs_p6_audio_rx[562]; +static _Alignas(64) rs_dma_tcd_t rs_p6_descriptors[4]; +static rs_dma_session_t rs_p6_audio_tx_owner; +static rs_dma_session_t rs_p6_audio_rx_owner; +static rs_dma_session_t rs_p6_background_owner; +static uint32_t rs_p6_pixels[65]; + +static void rs_p6_irq_handler(uintptr_t cause, uintptr_t stack) { + (void)cause; + (void)stack; + ++rs_p6_irq_count; + if (rs_irq_disable_external(rs_p6_irq_selected) != RS_OK) { + rs_p6_irq_error = true; + } +} + +static bool rs_p6_irq_arm(uint32_t irq) { + rs_p6_irq_selected = irq; + rs_p6_irq_count = 0U; + rs_p6_irq_error = false; + if ((rs_irq_register_external(irq, rs_p6_irq_handler) != RS_OK) || + (rs_irq_enable_external(irq, rs_p6_irq_handler) != RS_OK)) { + return false; + } + __RV_CSR_SET(CSR_MSTATUS, UINT32_C(8)); + return true; +} + +static bool rs_p6_irq_received(void) { + uint32_t timeout = UINT32_C(100000); + while ((rs_p6_irq_count == 0U) && (timeout != 0U)) { + --timeout; + } + return (rs_p6_irq_count == 1U) && !rs_p6_irq_error; +} + +static bool rs_p6_route(uint32_t pin, bool open_drain) { + const rs_gpio_config_t config = { + .mode = RS_GPIO_MODE_ALT0, + .pull = RS_GPIO_PULL_NONE, + .trigger = RS_GPIO_TRIGGER_NONE, + .open_drain = open_drain, + .filter_enable = false, + }; + return rs_gpio_configure(pin, &config) == RS_OK; +} + +static bool rs_p6_uart(void) { + const uint8_t transmit[4] = {0xA5U, 0x5AU, 0x96U, 0x69U}; + rs_uart_rx_data_t receive[4]; + const rs_uart_config_t unsupported = { + .source_clock_hz = RS_PCLK_CLOCK_HZ, + .baud_rate = UART_BPS, + .data_bits = 8U, + .stop_bits = 1U, + .parity = RS_UART_PARITY_NONE, + .rx_watermark = 1U, + .rx_timeout_bits = 16U, + .tx_enable = true, + .rx_enable = true, + .auto_cts_enable = true, + }; + if ((rs_uart_configure(&unsupported, RS_TIMEOUT_DEFAULT) != RS_ENOTSUP) || + (rs_uart_write(transmit, sizeof(transmit), RS_TIMEOUT_DEFAULT) != RS_OK) || + (rs_uart_read(receive, 4U, RS_TIMEOUT_DEFAULT) != RS_OK)) { + return false; + } + for (uint32_t index = 0U; index < 4U; ++index) { + if (receive[index].data != transmit[index] || receive[index].errors != 0U) { + return false; + } + } + printf("\nR2_P6_UART pads=1 echo_bytes=4 flow_rejected=1\n"); + return true; +} + +static bool rs_p6_rng(void) { + const rs_rng_config_t config = {.fifo_watermark = 1U}; + rs_rng_snapshot_t snapshot; + uint32_t word = 0U; + if ((rs_rng_init(&config) != RS_OK) || + (rs_rng_read_entropy(&word, RS_TIMEOUT_DEFAULT) != RS_ENOTSUP) || + (rs_rng_read_diagnostic(&word, RS_TIMEOUT_DEFAULT) != RS_OK) || + (rs_rng_get_status(&snapshot) != RS_OK) || snapshot.source_qualified || + !rs_p6_irq_arm(RS_SOC_EXT_IRQ_RNG) || + (rs_rng_interrupt_enable(RS_RNG_INTERRUPT_SOURCE_FAULT) != RS_OK) || + (rs_rng_interrupt_test(RS_RNG_INTERRUPT_SOURCE_FAULT) != RS_OK) || !rs_p6_irq_received() || + (rs_rng_interrupt_enable(0U) != RS_OK) || + (rs_rng_interrupt_clear(RS_RNG_INTERRUPT_ALL) != RS_OK)) { + return false; + } + printf("R2_P6_RNG unqualified=1 diagnostic=1 irq=1\n"); + return true; +} + +static bool rs_p6_dma_capabilities(void) { + const uint32_t capability = RS_DMA_REG(RS_DMA_REG_CAPABILITY); + rs_dma_config_t config = { + .kind = RS_DMA_KIND_MM_TO_MM, + .request = RS_DMA_REQUEST_SOFTWARE, + .source = (uintptr_t)rs_p6_source, + .destination = (uintptr_t)rs_p6_destination, + .byte_count = 4U, + .width = RS_DMA_WIDTH_32, + .source_increment = true, + .destination_increment = true, + .burst_beats = 1U, + }; + rs_dma_status_t status; + const uint32_t unsupported[] = {9U, 10U, 11U, 14U, 15U}; + if (((capability >> 8U) & UINT32_C(0xFF)) != 8U) { + return false; + } + for (size_t index = 0U; index < sizeof(unsupported) / sizeof(unsupported[0]); ++index) { + rs_p6_destination[0] = UINT32_C(0x12345678); + if (rs_dma_configure(RS_DMA_CHANNEL_DVP, &config) != RS_OK) { + return false; + } + /* Exercise hardware rejection independently of the SDK's earlier gate. */ + RS_DMA_CH_REG(RS_DMA_CHANNEL_DVP, RS_DMA_CH_REG_REQUEST_SEL) = unsupported[index]; + if ((rs_dma_start(RS_DMA_CHANNEL_DVP) != RS_OK) || + (rs_dma_wait(RS_DMA_CHANNEL_DVP, RS_TIMEOUT_DEFAULT) != RS_EIO) || + (rs_dma_get_status(RS_DMA_CHANNEL_DVP, &status) != RS_OK) || !status.error || + status.busy || (status.bytes_done != 0U) || + (rs_p6_destination[0] != UINT32_C(0x12345678))) { + return false; + } + } + printf("R2_P6_DMA channels=8 rejected_requests=5\n"); + return true; +} + +static uint32_t rs_p6_crc_reference(const uint8_t *data, size_t bytes) { + uint32_t value = UINT32_MAX; + for (size_t index = 0U; index < bytes; ++index) { + value ^= (uint32_t)data[index]; + for (uint32_t bit = 0U; bit < 8U; ++bit) { + value = (value >> 1U) ^ (((value & 1U) != 0U) ? UINT32_C(0xEDB88320) : 0U); + } + } + return ~value; +} + +static bool rs_p6_crc(void) { + rs_crc_config_t crc; + static rs_dma_session_t lease; + uint32_t result; + rs_dma_config_t transfer = { + .kind = RS_DMA_KIND_MM_TO_MM, + .request = RS_DMA_REQUEST_SOFTWARE, + .source = (uintptr_t)rs_p6_source, + .destination = RS_SOC_APB4_CRC_BASE + CRC_DATA_OFFSET, + .byte_count = UINT32_C(64), + .width = RS_DMA_WIDTH_32, + .source_increment = true, + .burst_beats = 1U, + }; + for (uint32_t index = 0U; index < 256U; ++index) { + rs_p6_source[index] = UINT32_C(0x12345678) ^ (index * UINT32_C(0x01020408)); + } + if ((rs_crc_get_profile(RS_CRC_PROFILE_CRC32_ISO_HDLC, &crc) != RS_OK) || + (rs_dma_session_acquire(&lease, RS_DMA_CHANNEL_BULK) != RS_OK)) { + return false; + } + for (size_t tail = 0U; tail < 4U; ++tail) { + if ((rs_crc_init(&crc) != RS_OK) || (rs_crc_start() != RS_OK) || + (rs_dma_session_configure(&lease, &transfer) != RS_OK) || + (rs_dma_session_start(&lease) != RS_OK) || + (rs_dma_wait(RS_DMA_CHANNEL_BULK, RS_TIMEOUT_DEFAULT) != RS_OK) || + (rs_crc_update((const uint8_t *)rs_p6_source + 64U, tail) != RS_OK) || + (rs_crc_finish(&result) != RS_OK) || + (result != rs_p6_crc_reference((const uint8_t *)rs_p6_source, 64U + tail))) { + return false; + } + } + if ((rs_dma_session_release(&lease) != RS_OK) || + (rs_crc_compute(&crc, rs_p6_source, 0U, &result) != RS_OK) || (result != 0U)) { + return false; + } + printf("R2_P6_CRC dma=1 tails=4 empty=1\n"); + return true; +} + +static bool rs_p6_crypto(void) { + static uint32_t modulus[RS_CRYPTO_RSA_WORDS]; + static uint32_t base[RS_CRYPTO_RSA_WORDS]; + static uint32_t exponent[RS_CRYPTO_RSA_WORDS]; + static uint32_t rsa_result[RS_CRYPTO_RSA_WORDS]; + static const uint8_t key[16] = { + 0x00U, 0x01U, 0x02U, 0x03U, 0x04U, 0x05U, 0x06U, 0x07U, + 0x08U, 0x09U, 0x0AU, 0x0BU, 0x0CU, 0x0DU, 0x0EU, 0x0FU, + }; + static const uint32_t input[4] = { + UINT32_C(0x33221100), + UINT32_C(0x77665544), + UINT32_C(0xBBAA9988), + UINT32_C(0xFFEEDDCC), + }; + static const uint32_t expected[4] = { + UINT32_C(0xD8E0C469), + UINT32_C(0x30047B6A), + UINT32_C(0x80B7CDD8), + UINT32_C(0x5AC5B470), + }; + if ((rs_crypto_zeroize_wait(1U) != RS_ETIMEOUT) || + (rs_crypto_zeroize_wait(RS_TIMEOUT_DEFAULT) != RS_OK) || + (rs_crypto_init(2050U) != RS_ETIMEOUT) || + (rs_crypto_selftest(RS_TIMEOUT_DEFAULT) != RS_OK) || + (rs_crypto_aes_set_key(key, sizeof(key), RS_TIMEOUT_DEFAULT) != RS_OK)) { + return false; + } + for (uint32_t index = 0U; index < 16U; ++index) { + rs_p6_source[index] = input[index % 4U]; + } + rs_p6_destination[0] = UINT32_C(0x5A5AA5A5); + rs_p6_destination[17] = UINT32_C(0xA5A55A5A); + if (rs_crypto_aes_crypt_dma(RS_CRYPTO_AES_ECB, false, NULL, rs_p6_source, &rs_p6_destination[1], + 64U, RS_TIMEOUT_DEFAULT) != RS_OK) { + return false; + } + for (uint32_t index = 0U; index < 16U; ++index) { + if (rs_p6_destination[index + 1U] != expected[index % 4U]) { + return false; + } + } + /* A small public-operation integration vector exercises the full 2048-bit + * storage/control path. Full private vectors remain in the shared suite. */ + for (uint32_t index = 0U; index < RS_CRYPTO_RSA_WORDS; ++index) { + modulus[index] = UINT32_MAX; + } + base[0] = 2U; + exponent[0] = 3U; + if ((rs_crypto_rsa_prepare(modulus, RS_TIMEOUT_DEFAULT) != RS_OK) || + (rs_crypto_rsa_modexp(base, exponent, 2U, false, rsa_result, RS_TIMEOUT_DEFAULT) != + RS_OK)) { + return false; + } + for (uint32_t index = 0U; index < RS_CRYPTO_RSA_WORDS; ++index) { + if (rsa_result[index] != ((index == 0U) ? 8U : 0U)) { + return false; + } + } + if ((rs_p6_destination[0] != UINT32_C(0x5A5AA5A5)) || + (rs_p6_destination[17] != UINT32_C(0xA5A55A5A)) || !rs_p6_irq_arm(RS_SOC_EXT_IRQ_CRYPTO)) { + return false; + } + RS_CRYPTO_REG(RS_CRYPTO_REG_IRQ_STATE) = RS_CRYPTO_IRQ_ALL; + RS_CRYPTO_REG(RS_CRYPTO_REG_IRQ_ENABLE) = RS_CRYPTO_IRQ_ZEROIZED; + if ((rs_crypto_zeroize_wait(0U) != RS_ETIMEOUT) || + (rs_crypto_zeroize_wait(RS_TIMEOUT_DEFAULT) != RS_OK) || !rs_p6_irq_received()) { + return false; + } + RS_CRYPTO_REG(RS_CRYPTO_REG_IRQ_ENABLE) = 0U; + RS_CRYPTO_REG(RS_CRYPTO_REG_IRQ_STATE) = RS_CRYPTO_IRQ_ALL; + printf("R2_P6_CRYPTO init=1 aes_sha=1 dma45=1 rsa_public=1 zeroize=1\n"); + return true; +} + +static bool rs_p6_stop_session(rs_dma_session_t *owner) { + rs_dma_status_t status; + uint32_t timeout = UINT32_C(100000); + if ((rs_dma_get_status(owner->channel, &status) != RS_OK) || + (status.busy && (rs_dma_session_abort(owner) != RS_OK))) { + return false; + } + do { + if ((rs_dma_get_status(owner->channel, &status) != RS_OK) || (timeout == 0U)) { + return false; + } + --timeout; + } while (status.busy); + return rs_dma_session_release(owner) == RS_OK; +} + +static uint32_t rs_p6_audio_sample(bool wide, uint32_t index) { + const uint32_t word = rs_p6_audio_rx[1U + (wide ? index : index / 2U)]; + return wide ? word : ((word >> ((index % 2U) * 16U)) & UINT32_C(0xFFFF)); +} + +static bool rs_p6_audio_words(bool wide, uint32_t received_words) { + const uint32_t samples = wide ? received_words : received_words * 2U; + const uint32_t first = wide ? UINT32_C(0x120020) : UINT32_C(0x2020); + uint32_t offset = 0U; + /* Ignore only the bounded PHY startup preamble, never a missing payload. + * Exactly 256 stereo frames must then match consecutive independent samples. */ + while ((offset < 40U) && (rs_p6_audio_sample(wide, offset) != first)) { + ++offset; + } + if ((offset >= 40U) || ((offset + 512U) > samples)) { + return false; + } + for (uint32_t sample = 0U; sample < 512U; ++sample) { + if (rs_p6_audio_sample(wide, offset + sample) != (first + sample)) { + printf("R2_P6_AUDIO mismatch wide=%u sample=%lu got=%lx expected=%lx\n", wide ? 1U : 0U, + (unsigned long)sample, (unsigned long)rs_p6_audio_sample(wide, offset + sample), + (unsigned long)(first + sample)); + return false; + } + } + return (rs_p6_audio_rx[0] == UINT32_C(0x5A5AA5A5)) && + (rs_p6_audio_rx[received_words + 1U] == UINT32_C(0xA5A55A5A)); +} + +static bool rs_p6_audio_ws(bool wide) { + printf("R2_P6_STAGE audio_bits=%u cycles=%lu\n", wide ? 24U : 16U, + (unsigned long)__RV_CSR_READ(CSR_MCYCLE)); + const uint32_t rx_words = wide ? 548U : 280U; + const rs_i2s_config_t audio = { + .stream_tx = true, + .stream_rx = true, + .preset = wide ? RS_I2S_PRESET_24B_48K : RS_I2S_PRESET_16B_48K, + .upbound = 96U, + .lowbound = 32U, + }; + const rs_ws2812_config_t ws = { + .source_clock_hz = RS_PCLK_CLOCK_HZ, + .bit_period_ns = 1250U, + .t0h_ns = 350U, + .t1h_ns = 700U, + .reset_ns = 300000U, + .fifo_watermark = 8U, + }; + rs_dma_config_t tx = { + .kind = RS_DMA_KIND_MM_TO_STREAM, + .request = RS_DMA_REQUEST_I2S_TX, + .source = (uintptr_t)rs_p6_audio_tx, + .byte_count = sizeof(rs_p6_audio_tx), + .width = RS_DMA_WIDTH_32, + .source_increment = true, + .priority = 3U, + .burst_beats = 16U, + }; + rs_dma_config_t rx = { + .kind = RS_DMA_KIND_STREAM_TO_MM, + .request = RS_DMA_REQUEST_I2S_RX, + .destination = (uintptr_t)&rs_p6_audio_rx[1], + .byte_count = rx_words * 4U, + .width = RS_DMA_WIDTH_32, + .destination_increment = true, + .priority = 3U, + .burst_beats = 16U, + }; + rs_dma_config_t background = { + .kind = RS_DMA_KIND_MM_TO_MM, + .request = RS_DMA_REQUEST_SOFTWARE, + .source = (uintptr_t)rs_p6_source, + .destination = (uintptr_t)&rs_p6_destination[1], + .byte_count = 256U, + .width = RS_DMA_WIDTH_32, + .source_increment = true, + .destination_increment = true, + .burst_beats = 16U, + }; + rs_i2s_status_t audio_status; + rs_dma_status_t rx_status, bg_status; + rs_ws2812_dma_status_t ws_status; + uint64_t now, deadline, start; + uint32_t cycles, instructions; + uint32_t frame = 0U, background_jobs = 0U; + for (uint32_t pin = 12U; pin <= 17U; ++pin) { + if (!rs_p6_route(pin, false)) { + return false; + } + } + if (!rs_p6_route(26U, false) || (rs_i2s_disable() != RS_OK) || + (rs_i2s_flush(true, true, RS_TIMEOUT_DEFAULT) != RS_OK) || + (rs_i2s_configure(&audio) != RS_OK) || (rs_ws2812_init(&ws) != RS_OK)) { + return false; + } + printf("R2_P6_STAGE audio_host_ready=1 cycles=%lu\n", (unsigned long)__RV_CSR_READ(CSR_MCYCLE)); + for (uint32_t index = 0U; index < 768U; ++index) { + rs_p6_audio_tx[index] = + wide ? UINT32_C(0x120000) + index + : ((UINT32_C(0x2001) + 2U * index) << 16U) | (UINT32_C(0x2000) + 2U * index); + } + for (uint32_t index = 0U; index < 65U; ++index) { + rs_p6_pixels[index] = index * UINT32_C(0x010203); + } + for (uint32_t index = 0U; index < 256U; ++index) { + rs_p6_source[index] = UINT32_C(0x5A123456) ^ index; + rs_p6_destination[index + 1U] = 0U; + } + rs_p6_destination[0] = UINT32_C(0x5A5AA5A5); + rs_p6_destination[257] = UINT32_C(0xA5A55A5A); + rs_p6_audio_rx[0] = UINT32_C(0x5A5AA5A5); + rs_p6_audio_rx[rx_words + 1U] = UINT32_C(0xA5A55A5A); + for (uint32_t index = 0U; index < 4U; ++index) { + rs_p6_descriptors[index] = (rs_dma_tcd_t){ + .source = (uint32_t)(uintptr_t)&rs_p6_source[index * 64U], + .destination = (uint32_t)(uintptr_t)&rs_p6_destination[1U + index * 64U], + .byte_count = 256U, + .next_ptr = index == 3U ? 0U : (uint32_t)(uintptr_t)&rs_p6_descriptors[index + 1U], + .control = RS_DMA_TCD_VALID | RS_DMA_TCD_SRC_INC | RS_DMA_TCD_DST_INC | + (UINT32_C(16) << RS_DMA_TCD_BURST_SHIFT), + }; + } + if (!rs_p6_irq_arm(RS_SOC_EXT_IRQ_I2S) || + (rs_i2s_interrupt_enable(RS_I2S_INTERRUPT_TX_LOW) != RS_OK) || + (rs_i2s_interrupt_test(RS_I2S_INTERRUPT_TX_LOW) != RS_OK) || !rs_p6_irq_received() || + (rs_i2s_interrupt_enable(0U) != RS_OK) || + (rs_i2s_interrupt_clear(RS_I2S_INTERRUPT_ALL) != RS_OK) || + (rs_dma_session_acquire(&rs_p6_audio_tx_owner, RS_DMA_CHANNEL_I2S_TX) != RS_OK) || + (rs_dma_session_acquire(&rs_p6_audio_rx_owner, RS_DMA_CHANNEL_I2S_RX) != RS_OK) || + (rs_dma_session_acquire(&rs_p6_background_owner, RS_DMA_CHANNEL_DVP) != RS_OK) || + (rs_dma_session_configure(&rs_p6_audio_tx_owner, &tx) != RS_OK) || + (rs_dma_session_configure(&rs_p6_audio_rx_owner, &rx) != RS_OK) || + (rs_dma_session_start(&rs_p6_audio_tx_owner) != RS_OK) || + (rs_dma_session_start(&rs_p6_audio_rx_owner) != RS_OK) || + (rs_clint_get_time(&start) != RS_OK)) { + return false; + } + deadline = start + UINT64_C(50000); + printf("R2_P6_STAGE audio_dma_started=1 cycles=%lu\n", + (unsigned long)__RV_CSR_READ(CSR_MCYCLE)); + do { + if ((rs_i2s_get_status(&audio_status) != RS_OK) || (rs_clint_get_time(&now) != RS_OK) || + (now >= deadline)) { + return false; + } + } while (audio_status.tx_level < 96U); + printf("R2_P6_STAGE audio_prefilled=1 cycles=%lu\n", (unsigned long)__RV_CSR_READ(CSR_MCYCLE)); + cycles = (uint32_t)__RV_CSR_READ(CSR_MCYCLE); + instructions = (uint32_t)__RV_CSR_READ(CSR_MINSTRET); + if (!rs_p6_irq_arm(RS_SOC_EXT_IRQ_WS2812) || + (rs_ws2812_irq_enable(RS_WS2812_INTR_FIFO_LOW | RS_WS2812_INTR_DONE) != RS_OK) || + (rs_i2s_enable(true, true) != RS_OK) || + (rs_ws2812_dma_begin(rs_p6_pixels, 33U, now, deadline) != RS_OK)) { + return false; + } + do { + if ((rs_clint_get_time(&now) != RS_OK) || (now >= deadline) || + (rs_ws2812_dma_service(now) != RS_OK) || (rs_ws2812_dma_status(&ws_status) != RS_OK) || + (rs_dma_get_status(RS_DMA_CHANNEL_I2S_RX, &rx_status) != RS_OK) || + (rs_dma_get_status(RS_DMA_CHANNEL_DVP, &bg_status) != RS_OK) || rx_status.error || + bg_status.error) { + return false; + } + if (!bg_status.busy && (background_jobs < 64U)) { + if (rs_dma_session_configure(&rs_p6_background_owner, &background) != RS_OK) { + return false; + } + /* This application exclusively leases channel 2; the ordinary TCD + * helper is blocking, so publish the same finite hardware chain. */ + RS_DMA_CH_REG(RS_DMA_CHANNEL_DVP, RS_DMA_CH_REG_TCD_HEAD) = + wide ? (uint32_t)(uintptr_t)rs_p6_descriptors : 0U; + RS_DMA_CH_REG(RS_DMA_CHANNEL_DVP, RS_DMA_CH_REG_TCD_COUNT) = wide ? 4U : 0U; + if (rs_dma_session_start(&rs_p6_background_owner) != RS_OK) { + return false; + } + ++background_jobs; + } + if (!ws_status.active && (frame == 0U)) { + if ((ws_status.result != RS_OK) || + (rs_ws2812_dma_begin(rs_p6_pixels, 65U, now, deadline) != RS_OK)) { + return false; + } + frame = 1U; + ws_status.active = true; + } + } while (rx_status.busy || ws_status.active); + cycles = (uint32_t)__RV_CSR_READ(CSR_MCYCLE) - cycles; + instructions = (uint32_t)__RV_CSR_READ(CSR_MINSTRET) - instructions; + if ((rx_status.bytes_done != rx_words * 4U) || !rx_status.done || (ws_status.result != RS_OK) || + (rs_p6_irq_count != 1U) || (rs_i2s_get_status(&audio_status) != RS_OK) || + ((audio_status.interrupt_state & + (RS_I2S_INTERRUPT_TX_UNDERRUN | RS_I2S_INTERRUPT_RX_OVERRUN)) != 0U) || + !rs_p6_stop_session(&rs_p6_audio_tx_owner) || !rs_p6_stop_session(&rs_p6_audio_rx_owner) || + (rs_i2s_disable() != RS_OK) || + (rs_dma_wait(RS_DMA_CHANNEL_DVP, RS_TIMEOUT_DEFAULT) != RS_OK) || + (rs_dma_session_release(&rs_p6_background_owner) != RS_OK) || + (rs_i2s_flush(true, true, RS_TIMEOUT_DEFAULT) != RS_OK) || + (rs_ws2812_irq_enable(0U) != RS_OK) || !rs_p6_audio_words(wide, rx_words)) { + return false; + } + for (uint32_t index = 0U; index < (wide ? 256U : 64U); ++index) { + if (rs_p6_destination[index + 1U] != rs_p6_source[index]) { + return false; + } + } + if ((rs_p6_destination[0] != UINT32_C(0x5A5AA5A5)) || + (rs_p6_destination[257] != UINT32_C(0xA5A55A5A))) { + return false; + } + if (!wide) { + for (uint32_t index = 65U; index <= 256U; ++index) { + if (rs_p6_destination[index] != 0U) { + return false; + } + } + } + printf("R2_P6_AUDIO bits=%u frames=256 ws_frames=2 background_jobs=%lu tcd=%u elapsed_us=%lu " + "cycles=%lu instructions=%lu\n", + wide ? 24U : 16U, (unsigned long)background_jobs, wide ? 1U : 0U, + (unsigned long)(now - start), (unsigned long)cycles, (unsigned long)instructions); + return background_jobs != 0U; +} + +static bool rs_p6_audio_ws_recovery(void) { + const rs_ws2812_config_t config = { + .source_clock_hz = RS_PCLK_CLOCK_HZ, + .bit_period_ns = 1250U, + .t0h_ns = 350U, + .t1h_ns = 700U, + .reset_ns = 300000U, + .fifo_watermark = 8U, + }; + uint64_t now, began; + rs_i2s_status_t audio; + rs_ws2812_status_t ws; + for (uint32_t phase = 0U; phase < 2U; ++phase) { + if ((rs_gpio_write(27U, true) != RS_OK) || (rs_clint_get_time(&began) != RS_OK)) { + return false; + } + do { + if (rs_clint_get_time(&now) != RS_OK) { + return false; + } + } while ((now - began) < 10U); + if ((rs_i2s_flush(true, true, 16U) != RS_ETIMEOUT) || + (rs_i2s_get_status(&audio) != RS_OK)) { + return false; + } + /* A timed-out command remains owned by the controller. Do not issue + * another COMMAND while BUSY: that write is correctly rejected. */ + do { + if ((rs_i2s_get_status(&audio) != RS_OK) || (rs_clint_get_time(&now) != RS_OK) || + ((now - began) > 1000U)) { + return false; + } + } while ((audio.status & (RS_I2S_STATUS_TX_FLUSH_BUSY | RS_I2S_STATUS_RX_FLUSH_BUSY)) != + 0U); + if (rs_gpio_write(27U, false) != RS_OK) { + return false; + } + } + if ((rs_ws2812_init(&config) != RS_OK) || (rs_ws2812_push(0U, RS_TIMEOUT_DEFAULT) != RS_OK) || + (rs_ws2812_start(2U) != RS_OK) || (rs_ws2812_wait(RS_TIMEOUT_DEFAULT) != RS_EIO) || + (rs_ws2812_get_status(&ws) != RS_OK) || + ((ws.error_status & RS_WS2812_ERROR_UNDERFLOW) == 0U) || + (rs_clint_get_time(&began) != RS_OK)) { + return false; + } + do { + if ((rs_ws2812_get_status(&ws) != RS_OK) || (rs_clint_get_time(&now) != RS_OK) || + ((now - began) > 1000U)) { + return false; + } + } while (ws.busy); + if ((rs_ws2812_init(&config) != RS_OK) || (rs_ws2812_push(0U, RS_TIMEOUT_DEFAULT) != RS_OK) || + (rs_ws2812_start(65U) != RS_OK) || (rs_ws2812_abort(RS_TIMEOUT_DEFAULT) != RS_OK)) { + return false; + } + printf("R2_P6_RECOVERY audio_stops=2 ws_underflow=1 ws_abort=1\n"); + return true; +} + +static bool rs_p6_sdio_step(rs_status_t result, uint32_t step) { + rs_sdio_status_t status; + if (result == RS_OK) { + return true; + } + if (rs_sdio_status_get(RS_SDIO_INSTANCE_0, &status) == RS_OK) { + printf("R2_P6_SDIO_ERROR step=%lu result=%ld command=%lx data=%lx dma=%lx errors=%lx\n", + (unsigned long)step, (long)result, (unsigned long)status.command_status, + (unsigned long)status.data_status, (unsigned long)status.dma_status, + (unsigned long)status.error_status); + } + return false; +} + +static bool rs_p6_sdio(void) { + static _Alignas(16) rs_sdio_descriptor_t descriptor; + rs_sd_memory_info_t card; + rs_sdio_clock_t clock; + rs_sdio_response_t response; + rs_dma_config_t background = { + .kind = RS_DMA_KIND_MM_TO_MM, + .request = RS_DMA_REQUEST_SOFTWARE, + .source = (uintptr_t)rs_p6_audio_tx, + .destination = (uintptr_t)&rs_p6_audio_rx[1], + .byte_count = 1024U, + .width = RS_DMA_WIDTH_32, + .source_increment = true, + .destination_increment = true, + .burst_beats = 16U, + }; + rs_sdio_command_t command = { + .index = 17U, + .argument = 1U, + .response = RS_SDIO_RESPONSE_R1, + .crc_check = true, + .index_check = true, + }; + const rs_sdio_data_config_t pio = { + .block_size = 512U, + .block_count = 1U, + .direction = RS_SDIO_DATA_FROM_CARD, + .dma = false, + .block_mode = true, + }; + const rs_sdio_data_config_t dma_write = { + .block_size = 512U, + .block_count = 2U, + .direction = RS_SDIO_DATA_TO_CARD, + .dma = true, + .block_mode = true, + }; + uint32_t id, version, capability; + rs_status_t initialized; + printf("R2_P6_STAGE sdio_begin=1\n"); + for (uint32_t pin = 0U; pin < 6U; ++pin) { + if (!rs_p6_route(pin, false)) { + return false; + } + } + if ((rs_sdio_probe(RS_SDIO_INSTANCE_1, &id, &version, &capability) != RS_ENOTSUP) || + (rs_sdio_controller_selftest(RS_SDIO_INSTANCE_0) != RS_OK)) { + return false; + } + initialized = + rs_sd_memory_initialize(RS_SDIO_INSTANCE_0, RS_CPU_CLOCK_HZ, &card, RS_TIMEOUT_DEFAULT); + printf("R2_P6_STAGE sdio_init=%ld clock=%lu width=%u rca=%u\n", (long)initialized, + (unsigned long)card.actual_clock_hz, (unsigned)card.bus_width, (unsigned)card.rca); + if ((initialized != RS_OK) || (card.actual_clock_hz != UINT32_C(12000000)) || + !card.high_capacity || (card.rca != 1U) || (card.bus_width != RS_SDIO_BUS_WIDTH_4)) { + return false; + } + for (uint32_t index = 0U; index < 256U; ++index) { + rs_p6_source[index] = UINT32_C(0x13579BDF) ^ index; + rs_p6_destination[index + 1U] = 0U; + rs_p6_audio_tx[index] = UINT32_C(0x55AA0000) + index; + rs_p6_audio_rx[index + 1U] = ~rs_p6_audio_tx[index]; + } + rs_p6_destination[0] = UINT32_C(0x5A5AA5A5); + rs_p6_destination[257] = UINT32_C(0xA5A55A5A); + command.index = 25U; + if (!rs_p6_sdio_step(rs_dma_session_acquire(&rs_p6_background_owner, RS_DMA_CHANNEL_DVP), 1U) || + !rs_p6_sdio_step(rs_dma_session_configure(&rs_p6_background_owner, &background), 2U) || + !rs_p6_sdio_step( + rs_sdio_command_execute(RS_SDIO_INSTANCE_0, &command, &response, RS_TIMEOUT_DEFAULT), + 3U) || + !rs_p6_sdio_step(rs_sdio_descriptor_prepare(&descriptor, (uintptr_t)rs_p6_source, 1024U, + (uintptr_t)0U, true, false), + 4U) || + !rs_p6_sdio_step(rs_sdio_dma_setup(RS_SDIO_INSTANCE_0, &descriptor, 1U, 1024U), 5U) || + !rs_p6_sdio_step(rs_sdio_data_configure(RS_SDIO_INSTANCE_0, &dma_write), 6U) || + !rs_p6_sdio_step(rs_sdio_data_start(RS_SDIO_INSTANCE_0), 7U) || + !rs_p6_sdio_step(rs_dma_session_start(&rs_p6_background_owner), 8U) || + !rs_p6_sdio_step(rs_sdio_data_wait(RS_SDIO_INSTANCE_0, RS_TIMEOUT_DEFAULT), 9U) || + !rs_p6_sdio_step(rs_sdio_dma_wait(RS_SDIO_INSTANCE_0, RS_TIMEOUT_DEFAULT), 10U)) { + return false; + } + printf("R2_P6_STAGE sdio_write=1\n"); + command.index = 12U; + command.argument = 0U; + command.response = RS_SDIO_RESPONSE_R1B; + if (!rs_p6_sdio_step( + rs_sdio_command_execute(RS_SDIO_INSTANCE_0, &command, &response, RS_TIMEOUT_DEFAULT), + 11U) || + !rs_p6_sdio_step(rs_sd_memory_read_blocks(RS_SDIO_INSTANCE_0, &card, 1U, 2U, + &rs_p6_destination[1], RS_TIMEOUT_DEFAULT), + 12U) || + !rs_p6_sdio_step(rs_dma_wait(RS_DMA_CHANNEL_DVP, RS_TIMEOUT_DEFAULT), 13U) || + !rs_p6_sdio_step(rs_dma_session_release(&rs_p6_background_owner), 14U)) { + return false; + } + printf("R2_P6_STAGE sdio_read=1\n"); + for (uint32_t index = 0U; index < 256U; ++index) { + if ((rs_p6_destination[index + 1U] != rs_p6_source[index]) || + (rs_p6_audio_rx[index + 1U] != rs_p6_audio_tx[index])) { + return false; + } + } + if ((rs_p6_destination[0] != UINT32_C(0x5A5AA5A5)) || + (rs_p6_destination[257] != UINT32_C(0xA5A55A5A)) || + (rs_sdio_clock_set(RS_SDIO_INSTANCE_0, RS_CPU_CLOCK_HZ, 400000U, &clock) != RS_OK) || + (clock.actual_hz != 400000U)) { + return false; + } + /* Select one-bit card and host consistently before the PIO read. */ + command.index = 55U; + command.argument = (uint32_t)card.rca << 16U; + command.response = RS_SDIO_RESPONSE_R1; + if (rs_sdio_command_execute(RS_SDIO_INSTANCE_0, &command, &response, RS_TIMEOUT_DEFAULT) != + RS_OK) { + return false; + } + command.index = 6U; + command.argument = 0U; + if ((rs_sdio_command_execute(RS_SDIO_INSTANCE_0, &command, &response, RS_TIMEOUT_DEFAULT) != + RS_OK) || + (rs_sdio_bus_width_set(RS_SDIO_INSTANCE_0, RS_SDIO_BUS_WIDTH_1) != RS_OK)) { + return false; + } + command.index = 17U; + command.argument = 1U; + if (!rs_p6_sdio_step( + rs_sdio_command_execute(RS_SDIO_INSTANCE_0, &command, &response, RS_TIMEOUT_DEFAULT), + 15U) || + !rs_p6_sdio_step(rs_sdio_data_configure(RS_SDIO_INSTANCE_0, &pio), 16U) || + !rs_p6_sdio_step(rs_sdio_data_start(RS_SDIO_INSTANCE_0), 17U) || + !rs_p6_sdio_step( + rs_sdio_pio_read(RS_SDIO_INSTANCE_0, &rs_p6_destination[1], 512U, RS_TIMEOUT_DEFAULT), + 18U) || + !rs_p6_sdio_step(rs_sdio_data_wait(RS_SDIO_INSTANCE_0, RS_TIMEOUT_DEFAULT), 19U)) { + return false; + } + printf("R2_P6_STAGE sdio_pio=1\n"); + for (uint32_t index = 0U; index < 128U; ++index) { + if (rs_p6_destination[index + 1U] != rs_p6_source[index]) { + return false; + } + } + if ((rs_sdio_irq_clear(RS_SDIO_INSTANCE_0, RS_SDIO_IRQ_ALL) != RS_OK) || + !rs_p6_irq_arm(RS_SOC_EXT_IRQ_SDIO) || + (rs_sdio_irq_enable(RS_SDIO_INSTANCE_0, RS_SDIO_IRQ_ALL) != RS_OK) || + (rs_sdio_irq_test(RS_SDIO_INSTANCE_0, 1U) != RS_OK) || !rs_p6_irq_received() || + (rs_sdio_irq_enable(RS_SDIO_INSTANCE_0, 0U) != RS_OK) || + (rs_sdio_irq_clear(RS_SDIO_INSTANCE_0, RS_SDIO_IRQ_ALL) != RS_OK) || + (rs_sdio_timeouts_set(RS_SDIO_INSTANCE_0, 24000U, 24000U, 24000U) != RS_OK)) { + return false; + } + /* Verification card injects faults on these documented CMD13 arguments. */ + command.index = 13U; + command.argument = UINT32_C(0xBADC0DE0); + if (rs_sdio_command_execute(RS_SDIO_INSTANCE_0, &command, &response, RS_TIMEOUT_DEFAULT) != + RS_EFORMAT) { + return false; + } + command.argument = UINT32_C(0xBADC0DE1); + if (rs_sdio_command_execute(RS_SDIO_INSTANCE_0, &command, &response, RS_TIMEOUT_DEFAULT) != + RS_ETIMEOUT) { + return false; + } + command.argument = (uint32_t)card.rca << 16U; + if ((rs_sdio_error_clear(RS_SDIO_INSTANCE_0, RS_SDIO_ERROR_ALL) != RS_OK) || + (rs_sdio_command_execute(RS_SDIO_INSTANCE_0, &command, &response, RS_TIMEOUT_DEFAULT) != + RS_OK)) { + return false; + } + printf("R2_P6_SDIO dma_bytes=2048 pio_bytes=512 width1=1 width4=1 irq=1 errors=2\n"); + return true; +} + +uint8_t rs_tiny_p6_acceptance(void) { + if (!rs_p6_uart()) { + return 27U; + } + if (!rs_p6_dma_capabilities()) { + return 19U; + } + if (!rs_p6_rng()) { + return 20U; + } + if (!rs_p6_crc()) { + return 21U; + } + if (!rs_p6_crypto()) { + return 22U; + } + if (!rs_p6_audio_ws(false)) { + return 23U; + } + if (!rs_p6_audio_ws(true)) { + return 24U; + } + if (!rs_p6_audio_ws_recovery()) { + return 26U; + } + if (!rs_p6_sdio()) { + return 25U; + } + return 0U; +} diff --git a/crt/include/retrosoc/hal/dma.h b/crt/include/retrosoc/hal/dma.h index bf35d7ae..9e3cc015 100644 --- a/crt/include/retrosoc/hal/dma.h +++ b/crt/include/retrosoc/hal/dma.h @@ -50,6 +50,15 @@ typedef enum { RS_DMA_CHANNEL_BULK = 3, RS_DMA_CHANNEL_CRYPTO_IN = 4, RS_DMA_CHANNEL_CRYPTO_OUT = 5, +#ifdef RS_SOC_TINY + RS_DMA_CHANNEL_DVP = 2, + RS_DMA_CHANNEL_I2S_TX = 6, + RS_DMA_CHANNEL_I2S_RX = 7, +#else + RS_DMA_CHANNEL_DVP = RS_DMA_CHANNEL_BULK, + RS_DMA_CHANNEL_I2S_TX = RS_DMA_CHANNEL_BULK, + RS_DMA_CHANNEL_I2S_RX = RS_DMA_CHANNEL_BULK, +#endif } rs_dma_channel_t; typedef struct { diff --git a/crt/include/retrosoc/hal/dma_regs.h b/crt/include/retrosoc/hal/dma_regs.h index 596c8e1d..fe70e4f1 100644 --- a/crt/include/retrosoc/hal/dma_regs.h +++ b/crt/include/retrosoc/hal/dma_regs.h @@ -77,15 +77,13 @@ #define RS_DMA_ERROR_READ UINT32_C(0x00000040) #define RS_DMA_ERROR_STREAM_LAST UINT32_C(0x00000100) -#ifdef RS_SOC_TINY -#define RS_DMA_CHANNEL_COUNT 4U -#else -#define RS_DMA_CHANNEL_COUNT 8U -#endif +#define RS_DMA_CHANNEL_COUNT 8U +#ifndef RS_SOC_TINY #define RS_DMA_CHANNEL_HP 6U #define RS_DMA_CHANNEL_RESERVED 7U -#define RS_DMA_MAX_BURST_BEATS 16U -#define RS_DMA_DATA_WIDTH_BITS 32U +#endif +#define RS_DMA_MAX_BURST_BEATS 16U +#define RS_DMA_DATA_WIDTH_BITS 32U #define RS_DMA_CHANNEL_OFFSET(channel, offset) \ (RS_DMA_CH_BASE + ((uint32_t)(channel)*RS_DMA_CH_STRIDE) + (offset)) diff --git a/crt/src/hal/dma.c b/crt/src/hal/dma.c index 455a32ac..9b3832da 100644 --- a/crt/src/hal/dma.c +++ b/crt/src/hal/dma.c @@ -427,9 +427,9 @@ void ip_dma_test(void) { .lowbound = 32U, }) != RS_OK) || (rs_i2s_enable(true, false) != RS_OK) || - (rs_dma_configure(RS_DMA_CHANNEL_BULK, &tx_config) != RS_OK) || + (rs_dma_configure(RS_DMA_CHANNEL_I2S_TX, &tx_config) != RS_OK) || (rs_dma_start(RS_DMA_CHANNEL_BULK) != RS_OK) || - (rs_dma_wait(RS_DMA_CHANNEL_BULK, RS_TIMEOUT_DEFAULT) != RS_OK)) { + (rs_dma_wait(RS_DMA_CHANNEL_I2S_TX, RS_TIMEOUT_DEFAULT) != RS_OK)) { printf("dma transmit failed\n"); return; } @@ -448,9 +448,9 @@ void ip_dma_test(void) { .lowbound = 32U, }) != RS_OK) || (rs_i2s_enable(false, true) != RS_OK) || - (rs_dma_configure(RS_DMA_CHANNEL_BULK, &rx_config) != RS_OK) || - (rs_dma_start(RS_DMA_CHANNEL_BULK) != RS_OK) || - (rs_dma_wait(RS_DMA_CHANNEL_BULK, RS_TIMEOUT_DEFAULT) != RS_OK)) { + (rs_dma_configure(RS_DMA_CHANNEL_I2S_RX, &rx_config) != RS_OK) || + (rs_dma_start(RS_DMA_CHANNEL_I2S_RX) != RS_OK) || + (rs_dma_wait(RS_DMA_CHANNEL_I2S_RX, RS_TIMEOUT_DEFAULT) != RS_OK)) { printf("dma receive failed\n"); (void)rs_i2s_disable(); return; diff --git a/crt/src/hal/dma_math.c b/crt/src/hal/dma_math.c index 99c35ca6..4ebd003a 100644 --- a/crt/src/hal/dma_math.c +++ b/crt/src/hal/dma_math.c @@ -19,7 +19,8 @@ static bool rs_dma_address_valid(uintptr_t address, uint32_t byte_count, bool in static bool rs_dma_request_valid(const rs_dma_config_t *config) { #ifdef RS_SOC_TINY - if (config->kind != RS_DMA_KIND_MM_TO_MM) { + if (((uint32_t)config->request >= 16U) || + ((UINT32_C(0x31FF) & (UINT32_C(1) << (uint32_t)config->request)) == 0U)) { return false; } #endif @@ -97,7 +98,7 @@ rs_status_t rs_dma_tcd_validate(uint32_t channel, const rs_dma_tcd_t *tcd) { request = (tcd->control >> RS_DMA_TCD_REQUEST_SHIFT) & UINT32_C(0xF); burst = (tcd->control >> RS_DMA_TCD_BURST_SHIFT) & UINT32_C(0x1F); #ifdef RS_SOC_TINY - if (kind != (uint32_t)RS_DMA_KIND_MM_TO_MM) { + if ((UINT32_C(0x31FF) & (UINT32_C(1) << request)) == 0U) { return RS_ENOTSUP; } #endif diff --git a/crt/src/hal/i2c.c b/crt/src/hal/i2c.c index 48c59260..dea6ccbc 100644 --- a/crt/src/hal/i2c.c +++ b/crt/src/hal/i2c.c @@ -49,8 +49,12 @@ #define RS_I2C_TIMEOUT_MAX_CYCLES UINT32_C(0xFFFFFF) static uintptr_t rs_i2c_base(rs_i2c_bus_t bus) { +#ifdef RS_SOC_TINY + return (bus == RS_I2C_BUS_0) ? (uintptr_t)RS_SOC_APB4_I2C0_BASE : (uintptr_t)0U; +#else return (bus == RS_I2C_BUS_0) ? (uintptr_t)RS_SOC_APB4_I2C0_BASE : (uintptr_t)RS_SOC_APB4_I2C1_BASE; +#endif } static bool rs_i2c_bus_valid(rs_i2c_bus_t bus) { @@ -92,8 +96,13 @@ static rs_status_t rs_i2c_gpio_configure(rs_i2c_bus_t bus) { .filter_enable = false, .interrupt_enable = false, }; +#ifdef RS_SOC_TINY + const uint32_t scl_pin = UINT32_C(10); + const uint32_t sda_pin = UINT32_C(11); +#else const uint32_t scl_pin = (bus == RS_I2C_BUS_0) ? UINT32_C(7) : UINT32_C(3); const uint32_t sda_pin = (bus == RS_I2C_BUS_0) ? UINT32_C(8) : UINT32_C(4); +#endif rs_status_t status = rs_gpio_configure(scl_pin, &pin_config); if (status == RS_OK) { @@ -228,6 +237,11 @@ static rs_status_t rs_i2c_wait_done(rs_i2c_bus_t bus, rs_timeout_t timeout) { rs_status_t rs_i2c_configure(rs_i2c_bus_t bus, const rs_i2c_config_t *config, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif rs_i2c_timing_t timing; uint32_t stretch_timeout; uint32_t bus_idle_timeout; @@ -287,6 +301,11 @@ rs_status_t rs_i2c_configure(rs_i2c_bus_t bus, const rs_i2c_config_t *config, } rs_status_t rs_i2c_init(rs_i2c_bus_t bus, uint32_t source_clock_hz, uint32_t bus_hz) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif const rs_i2c_config_t config = { .source_clock_hz = source_clock_hz, .bus_hz = bus_hz, @@ -303,6 +322,11 @@ rs_status_t rs_i2c_init(rs_i2c_bus_t bus, uint32_t source_clock_hz, uint32_t bus rs_status_t rs_i2c_transfer(rs_i2c_bus_t bus, const rs_i2c_transfer_t *transfer, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif if (transfer == NULL) { return RS_EINVAL; } @@ -338,6 +362,11 @@ static rs_status_t rs_i2c_register_prefix(const rs_i2c_register_access_t *access rs_status_t rs_i2c_register_write(rs_i2c_bus_t bus, const rs_i2c_register_access_t *access, const uint8_t *data, size_t length, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif uint8_t prefix[2]; size_t prefix_length; rs_status_t status; @@ -355,6 +384,11 @@ rs_status_t rs_i2c_register_write(rs_i2c_bus_t bus, const rs_i2c_register_access rs_status_t rs_i2c_register_read(rs_i2c_bus_t bus, const rs_i2c_register_access_t *access, uint8_t *data, size_t length, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif uint8_t prefix[2]; size_t prefix_length; rs_status_t status; @@ -371,6 +405,11 @@ rs_status_t rs_i2c_register_read(rs_i2c_bus_t bus, const rs_i2c_register_access_ } rs_status_t rs_i2c_abort(rs_i2c_bus_t bus, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif if (!rs_i2c_bus_valid(bus)) { return RS_EINVAL; } @@ -382,6 +421,11 @@ rs_status_t rs_i2c_abort(rs_i2c_bus_t bus, rs_timeout_t timeout) { } rs_status_t rs_i2c_recover(rs_i2c_bus_t bus, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif rs_status_t status; if (!rs_i2c_bus_valid(bus)) { @@ -408,6 +452,11 @@ rs_status_t rs_i2c_recover(rs_i2c_bus_t bus, rs_timeout_t timeout) { } rs_status_t rs_i2c_get_status(rs_i2c_bus_t bus, rs_i2c_status_t *status) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif uint32_t levels; if (!rs_i2c_bus_valid(bus) || (status == NULL)) { @@ -423,6 +472,11 @@ rs_status_t rs_i2c_get_status(rs_i2c_bus_t bus, rs_i2c_status_t *status) { } rs_status_t rs_i2c_irq_enable(rs_i2c_bus_t bus, uint32_t mask) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif if (!rs_i2c_bus_valid(bus) || ((mask & ~RS_I2C_INTR_ALL) != 0U)) { return RS_EINVAL; } @@ -431,6 +485,11 @@ rs_status_t rs_i2c_irq_enable(rs_i2c_bus_t bus, uint32_t mask) { } rs_status_t rs_i2c_irq_ack(rs_i2c_bus_t bus, uint32_t mask) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif if (!rs_i2c_bus_valid(bus) || (mask == 0U) || ((mask & ~RS_I2C_INTR_ALL) != 0U)) { return RS_EINVAL; } @@ -439,6 +498,11 @@ rs_status_t rs_i2c_irq_ack(rs_i2c_bus_t bus, uint32_t mask) { } rs_status_t rs_i2c_irq_test(rs_i2c_bus_t bus, uint32_t mask) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif if (!rs_i2c_bus_valid(bus) || (mask == 0U) || ((mask & ~RS_I2C_INTR_ALL) != 0U)) { return RS_EINVAL; } @@ -449,6 +513,11 @@ rs_status_t rs_i2c_irq_test(rs_i2c_bus_t bus, uint32_t mask) { rs_status_t rs_i2c_write_dma(rs_i2c_bus_t bus, uint16_t address, bool ten_bit_address, const uint8_t *data, size_t length, rs_i2c_dma_workspace_t *workspace, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif rs_status_t status; size_t index; const uint32_t channel = (bus == RS_I2C_BUS_0) ? RS_DMA_CHANNEL_I2C0 : RS_DMA_CHANNEL_I2C1; @@ -494,6 +563,11 @@ rs_status_t rs_i2c_write_dma(rs_i2c_bus_t bus, uint16_t address, bool ten_bit_ad rs_status_t rs_i2c_read_dma(rs_i2c_bus_t bus, uint16_t address, bool ten_bit_address, uint8_t *data, size_t length, rs_i2c_dma_workspace_t *workspace, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif rs_status_t status; size_t index; const uint32_t channel = (bus == RS_I2C_BUS_0) ? RS_DMA_CHANNEL_I2C0 : RS_DMA_CHANNEL_I2C1; diff --git a/crt/src/hal/sdio.c b/crt/src/hal/sdio.c index bf303255..82b7098e 100644 --- a/crt/src/hal/sdio.c +++ b/crt/src/hal/sdio.c @@ -53,6 +53,12 @@ RS_SD_R1_CARD_ECC_FAILED | RS_SD_R1_CC_ERROR | RS_SD_R1_ERROR | RS_SD_R1_CSD_OVERWRITE | \ RS_SD_R1_WP_ERASE_SKIP) +/* Preserve protocol ceilings while supporting slower product input clocks. */ +static uint32_t rs_sdio_transfer_clock(uint32_t source_hz, uint32_t ceiling_hz) { + const uint32_t maximum_hz = source_hz / UINT32_C(2); + return (maximum_hz < ceiling_hz) ? maximum_hz : ceiling_hz; +} + static bool rs_sdio_instance_valid(rs_sdio_instance_t instance) { return (instance == RS_SDIO_INSTANCE_0) || (instance == RS_SDIO_INSTANCE_1); } @@ -61,8 +67,10 @@ static uintptr_t rs_sdio_base(rs_sdio_instance_t instance) { switch (instance) { case RS_SDIO_INSTANCE_0: return (uintptr_t)RS_SOC_APB4_SDIO0_BASE; +#ifndef RS_SOC_TINY case RS_SDIO_INSTANCE_1: return (uintptr_t)RS_SOC_APB4_SDIO1_BASE; +#endif default: return (uintptr_t)0U; } @@ -257,6 +265,11 @@ static rs_status_t rs_sdio_send_app_command(rs_sdio_instance_t instance, uint16_ rs_status_t rs_sdio_probe(rs_sdio_instance_t instance, uint32_t *ip_id, uint32_t *version, uint32_t *capability) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif if (!rs_sdio_instance_valid(instance) || (ip_id == NULL) || (version == NULL) || (capability == NULL) || (rs_sdio_read(instance, RS_SDIO_REG_IP_ID, ip_id) != RS_OK) || (rs_sdio_read(instance, RS_SDIO_REG_IP_VERSION, version) != RS_OK) || @@ -270,6 +283,11 @@ rs_status_t rs_sdio_probe(rs_sdio_instance_t instance, uint32_t *ip_id, uint32_t } rs_status_t rs_sdio_reset(rs_sdio_instance_t instance, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif volatile uint32_t *status_reg; if (!rs_sdio_instance_valid(instance)) { @@ -294,6 +312,11 @@ rs_status_t rs_sdio_reset(rs_sdio_instance_t instance, rs_timeout_t timeout) { rs_status_t rs_sdio_clock_set(rs_sdio_instance_t instance, uint32_t source_clock_hz, uint32_t target_clock_hz, rs_sdio_clock_t *clock) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif rs_sdio_clock_t calculated; uint32_t value; uint32_t enabled; @@ -318,6 +341,11 @@ rs_status_t rs_sdio_clock_set(rs_sdio_instance_t instance, uint32_t source_clock } rs_status_t rs_sdio_bus_width_set(rs_sdio_instance_t instance, rs_sdio_bus_width_t width) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif uint32_t value; if (!rs_sdio_instance_valid(instance) || rs_sdio_busy(instance)) { @@ -338,6 +366,11 @@ rs_status_t rs_sdio_bus_width_set(rs_sdio_instance_t instance, rs_sdio_bus_width rs_status_t rs_sdio_timeouts_set(rs_sdio_instance_t instance, uint32_t command_timeout, uint32_t data_timeout, uint32_t busy_timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif if (!rs_sdio_instance_valid(instance) || rs_sdio_busy(instance) || (command_timeout == 0U) || (data_timeout == 0U) || (busy_timeout == 0U)) { return RS_EINVAL; @@ -351,6 +384,11 @@ rs_status_t rs_sdio_timeouts_set(rs_sdio_instance_t instance, uint32_t command_t } rs_status_t rs_sdio_enable(rs_sdio_instance_t instance, bool enable) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif uint32_t value; if (!rs_sdio_instance_valid(instance) || @@ -366,6 +404,11 @@ rs_status_t rs_sdio_enable(rs_sdio_instance_t instance, bool enable) { } rs_status_t rs_sdio_configure(rs_sdio_instance_t instance, const rs_sdio_config_t *config) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif rs_sdio_clock_t clock; rs_status_t status; @@ -393,6 +436,11 @@ rs_status_t rs_sdio_configure(rs_sdio_instance_t instance, const rs_sdio_config_ } rs_status_t rs_sdio_status_get(rs_sdio_instance_t instance, rs_sdio_status_t *status) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif uint32_t present; if (!rs_sdio_instance_valid(instance) || (status == NULL)) { @@ -421,6 +469,11 @@ rs_status_t rs_sdio_status_get(rs_sdio_instance_t instance, rs_sdio_status_t *st } rs_status_t rs_sdio_command_start(rs_sdio_instance_t instance, const rs_sdio_command_t *command) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif uint32_t cfg; if (!rs_sdio_instance_valid(instance) || (command == NULL) || (command->index > 63U) || @@ -449,6 +502,11 @@ rs_status_t rs_sdio_command_start(rs_sdio_instance_t instance, const rs_sdio_com } rs_status_t rs_sdio_response_get(rs_sdio_instance_t instance, rs_sdio_response_t *response) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif if (!rs_sdio_instance_valid(instance) || (response == NULL) || (rs_sdio_read(instance, RS_SDIO_REG_RESP0, &response->words[0]) != RS_OK) || (rs_sdio_read(instance, RS_SDIO_REG_RESP1, &response->words[1]) != RS_OK) || @@ -462,6 +520,11 @@ rs_status_t rs_sdio_response_get(rs_sdio_instance_t instance, rs_sdio_response_t rs_status_t rs_sdio_command_wait(rs_sdio_instance_t instance, rs_sdio_response_t *response, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif rs_status_t status; if (!rs_sdio_instance_valid(instance) || (response == NULL)) { @@ -485,6 +548,11 @@ rs_status_t rs_sdio_command_wait(rs_sdio_instance_t instance, rs_sdio_response_t rs_status_t rs_sdio_command_execute(rs_sdio_instance_t instance, const rs_sdio_command_t *command, rs_sdio_response_t *response, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif rs_status_t status; status = rs_sdio_command_start(instance, command); @@ -500,6 +568,11 @@ rs_status_t rs_sdio_command_execute(rs_sdio_instance_t instance, const rs_sdio_c rs_status_t rs_sdio_data_configure(rs_sdio_instance_t instance, const rs_sdio_data_config_t *config) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif uint32_t value; if (!rs_sdio_instance_valid(instance) || (config == NULL) || (config->block_size == 0U) || @@ -532,6 +605,11 @@ rs_status_t rs_sdio_data_configure(rs_sdio_instance_t instance, } rs_status_t rs_sdio_data_start(rs_sdio_instance_t instance) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif if (!rs_sdio_instance_valid(instance) || rs_sdio_busy(instance) || (rs_sdio_irq_clear(instance, RS_SDIO_IRQ_DATA_DONE | RS_SDIO_IRQ_DATA_ERROR) != RS_OK) || (rs_sdio_error_clear(instance, RS_SDIO_ERROR_DATA_CRC | RS_SDIO_ERROR_DATA_TIMEOUT | @@ -582,6 +660,11 @@ static rs_status_t rs_sdio_pio_store(rs_sdio_instance_t instance, const uint8_t rs_status_t rs_sdio_pio_write(rs_sdio_instance_t instance, const void *buffer, size_t byte_count, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif const uint8_t *bytes = (const uint8_t *)buffer; rs_status_t status; size_t offset = 0U; @@ -612,6 +695,11 @@ rs_status_t rs_sdio_pio_write(rs_sdio_instance_t instance, const void *buffer, s rs_status_t rs_sdio_pio_read(rs_sdio_instance_t instance, void *buffer, size_t byte_count, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif uint8_t *bytes = (uint8_t *)buffer; size_t offset = 0U; @@ -645,6 +733,11 @@ rs_status_t rs_sdio_pio_read(rs_sdio_instance_t instance, void *buffer, size_t b } rs_status_t rs_sdio_data_wait(rs_sdio_instance_t instance, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif rs_status_t status; if (!rs_sdio_instance_valid(instance)) { @@ -664,6 +757,11 @@ rs_status_t rs_sdio_data_wait(rs_sdio_instance_t instance, rs_timeout_t timeout) rs_status_t rs_sdio_dma_setup(rs_sdio_instance_t instance, rs_sdio_descriptor_t *descriptors, uint16_t count, uint32_t total_bytes) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif uintptr_t address; if (!rs_sdio_instance_valid(instance) || (total_bytes == 0U) || @@ -686,6 +784,11 @@ rs_status_t rs_sdio_dma_setup(rs_sdio_instance_t instance, rs_sdio_descriptor_t } rs_status_t rs_sdio_dma_start(rs_sdio_instance_t instance) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif /* * DATA_START is the coordinated DMA/data-engine launch. Keep this * compatibility entry point as an alias so software cannot start a DMA @@ -695,6 +798,11 @@ rs_status_t rs_sdio_dma_start(rs_sdio_instance_t instance) { } rs_status_t rs_sdio_dma_abort(rs_sdio_instance_t instance) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif if (!rs_sdio_instance_valid(instance)) { return RS_EINVAL; } @@ -702,6 +810,11 @@ rs_status_t rs_sdio_dma_abort(rs_sdio_instance_t instance) { } rs_status_t rs_sdio_dma_status_get(rs_sdio_instance_t instance, rs_sdio_dma_status_t *status) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif uint32_t dma_error; if (!rs_sdio_instance_valid(instance) || (status == NULL) || @@ -720,6 +833,11 @@ rs_status_t rs_sdio_dma_status_get(rs_sdio_instance_t instance, rs_sdio_dma_stat } rs_status_t rs_sdio_dma_wait(rs_sdio_instance_t instance, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif rs_status_t status; if (!rs_sdio_instance_valid(instance)) { @@ -738,6 +856,11 @@ rs_status_t rs_sdio_dma_wait(rs_sdio_instance_t instance, rs_timeout_t timeout) } rs_status_t rs_sdio_irq_enable(rs_sdio_instance_t instance, uint32_t mask) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif if (!rs_sdio_instance_valid(instance) || ((mask & ~RS_SDIO_IRQ_ALL) != 0U)) { return RS_EINVAL; } @@ -745,6 +868,11 @@ rs_status_t rs_sdio_irq_enable(rs_sdio_instance_t instance, uint32_t mask) { } rs_status_t rs_sdio_irq_pending(rs_sdio_instance_t instance, uint32_t *mask) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif if (!rs_sdio_instance_valid(instance) || (mask == NULL)) { return RS_EINVAL; } @@ -752,6 +880,11 @@ rs_status_t rs_sdio_irq_pending(rs_sdio_instance_t instance, uint32_t *mask) { } rs_status_t rs_sdio_irq_clear(rs_sdio_instance_t instance, uint32_t mask) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif if (!rs_sdio_instance_valid(instance) || ((mask & ~RS_SDIO_IRQ_ALL) != 0U)) { return RS_EINVAL; } @@ -759,6 +892,11 @@ rs_status_t rs_sdio_irq_clear(rs_sdio_instance_t instance, uint32_t mask) { } rs_status_t rs_sdio_irq_test(rs_sdio_instance_t instance, uint32_t mask) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif if (!rs_sdio_instance_valid(instance) || ((mask & ~RS_SDIO_IRQ_ALL) != 0U)) { return RS_EINVAL; } @@ -766,6 +904,11 @@ rs_status_t rs_sdio_irq_test(rs_sdio_instance_t instance, uint32_t mask) { } rs_status_t rs_sdio_error_clear(rs_sdio_instance_t instance, uint32_t mask) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif if (!rs_sdio_instance_valid(instance) || ((mask & ~RS_SDIO_ERROR_ALL) != 0U)) { return RS_EINVAL; } @@ -773,6 +916,11 @@ rs_status_t rs_sdio_error_clear(rs_sdio_instance_t instance, uint32_t mask) { } rs_status_t rs_sdio_controller_selftest(rs_sdio_instance_t instance) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif rs_sdio_status_t status; uint32_t ip_id; uint32_t version; @@ -916,7 +1064,9 @@ static rs_status_t rs_sd_memory_try_high_speed(rs_sdio_instance_t instance, return RS_EIO; } if (rs_sdio_clock_set(instance, source_clock_hz, - (source_clock_hz >= 100000000U) ? 50000000U : 36000000U, + rs_sdio_transfer_clock(source_clock_hz, (source_clock_hz >= 100000000U) + ? 50000000U + : 36000000U), &clock) == RS_OK) { info->actual_clock_hz = clock.actual_hz; info->high_speed = true; @@ -928,6 +1078,11 @@ static rs_status_t rs_sd_memory_try_high_speed(rs_sdio_instance_t instance, rs_status_t rs_sd_memory_initialize(rs_sdio_instance_t instance, uint32_t source_clock_hz, rs_sd_memory_info_t *info, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif rs_sdio_response_t response; rs_sdio_clock_t clock; uint32_t payload; @@ -1008,7 +1163,9 @@ rs_status_t rs_sd_memory_initialize(rs_sdio_instance_t instance, uint32_t source if (status != RS_OK) { return status; } - if ((rs_sdio_response_field(&response, 31U, 16U, &payload) != RS_OK) || + /* RESP retains the complete 48-bit frame: the R6 payload is [39:8], + * so its upper sixteen RCA bits are [39:24], above the status/CRC. */ + if ((rs_sdio_response_field(&response, 39U, 24U, &payload) != RS_OK) || (payload > UINT16_MAX)) { return RS_EFORMAT; } @@ -1046,7 +1203,8 @@ rs_status_t rs_sd_memory_initialize(rs_sdio_instance_t instance, uint32_t source info->bus_width_fallback = true; info->bus_width = RS_SDIO_BUS_WIDTH_1; } - status = rs_sdio_clock_set(instance, source_clock_hz, 36000000U, &clock); + status = rs_sdio_clock_set(instance, source_clock_hz, + rs_sdio_transfer_clock(source_clock_hz, 36000000U), &clock); if (status != RS_OK) { return status; } @@ -1150,12 +1308,22 @@ static rs_status_t rs_sd_memory_transfer(rs_sdio_instance_t instance, rs_status_t rs_sd_memory_read_blocks(rs_sdio_instance_t instance, const rs_sd_memory_info_t *info, uint32_t block, uint32_t count, void *buffer, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif return rs_sd_memory_transfer(instance, info, block, count, buffer, false, timeout); } rs_status_t rs_sd_memory_write_blocks(rs_sdio_instance_t instance, const rs_sd_memory_info_t *info, uint32_t block, uint32_t count, const void *buffer, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif return rs_sd_memory_transfer(instance, info, block, count, (void *)buffer, true, timeout); } @@ -1175,6 +1343,11 @@ static rs_status_t rs_sdio_function_command(rs_sdio_instance_t instance, uint8_t rs_status_t rs_sdio_function_cmd52_read(rs_sdio_instance_t instance, uint8_t function, uint32_t address, uint8_t *data, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif rs_sdio_cmd52_t command = { .function = function, .address = address, @@ -1203,6 +1376,11 @@ rs_status_t rs_sdio_function_cmd52_read(rs_sdio_instance_t instance, uint8_t fun rs_status_t rs_sdio_function_cmd52_write(rs_sdio_instance_t instance, uint8_t function, uint32_t address, uint8_t data, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif const rs_sdio_cmd52_t command = { .function = function, .address = address, @@ -1223,6 +1401,11 @@ rs_status_t rs_sdio_function_cmd52_write(rs_sdio_instance_t instance, uint8_t fu rs_status_t rs_sdio_function_initialize(rs_sdio_instance_t instance, uint32_t source_clock_hz, rs_sdio_function_info_t *info, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif rs_sdio_response_t response; rs_sdio_clock_t clock; uint32_t payload; @@ -1345,8 +1528,10 @@ rs_status_t rs_sdio_function_initialize(rs_sdio_instance_t instance, uint32_t so } else { info->high_speed_fallback = true; } - status = rs_sdio_clock_set(instance, source_clock_hz, - high_speed_enabled ? 24000000U : 25000000U, &clock); + status = rs_sdio_clock_set( + instance, source_clock_hz, + rs_sdio_transfer_clock(source_clock_hz, high_speed_enabled ? 24000000U : 25000000U), + &clock); if (status != RS_OK) { return status; } @@ -1356,6 +1541,11 @@ rs_status_t rs_sdio_function_initialize(rs_sdio_instance_t instance, uint32_t so rs_status_t rs_sdio_function_enable(rs_sdio_instance_t instance, rs_sdio_function_info_t *info, uint8_t function, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif uint8_t enable; uint8_t ready; uint32_t attempts; @@ -1394,6 +1584,11 @@ rs_status_t rs_sdio_function_cmd53_transfer(rs_sdio_instance_t instance, uint8_t uint32_t address, void *buffer, size_t byte_count, bool write, bool block_mode, bool fixed_address, uint16_t block_size, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif rs_sdio_cmd53_t command; rs_sdio_data_config_t data_config; rs_sdio_response_t response; @@ -1482,6 +1677,11 @@ rs_status_t rs_sdio_function_cmd53_transfer(rs_sdio_instance_t instance, uint8_t } rs_status_t rs_sdio_function_irq_enable(rs_sdio_instance_t instance, bool enable) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif uint32_t mask; if (!rs_sdio_instance_valid(instance) || @@ -1501,6 +1701,11 @@ rs_status_t rs_sdio_function_irq_enable(rs_sdio_instance_t instance, bool enable } rs_status_t rs_sdio_function_irq_pending(rs_sdio_instance_t instance, bool *pending) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif uint32_t mask; if ((pending == NULL) || (rs_sdio_irq_pending(instance, &mask) != RS_OK)) { @@ -1511,5 +1716,10 @@ rs_status_t rs_sdio_function_irq_pending(rs_sdio_instance_t instance, bool *pend } rs_status_t rs_sdio_function_irq_ack(rs_sdio_instance_t instance) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif return rs_sdio_irq_clear(instance, RS_SDIO_IRQ_DAT1); } diff --git a/crt/src/hal/sdio_math.c b/crt/src/hal/sdio_math.c index 997c63d8..37bf7263 100644 --- a/crt/src/hal/sdio_math.c +++ b/crt/src/hal/sdio_math.c @@ -136,7 +136,9 @@ rs_status_t rs_sd_memory_parse_csd(const rs_sdio_response_t *response, rs_sd_mem return RS_EINVAL; } block_length = UINT32_C(1) << read_bl_len; - capacity_bytes = ((uint64_t)c_size + UINT64_C(1)) * (UINT64_C(1) << (c_size_mult + 2U)) * + /* C_SIZE_MULT is a three-bit CSD field: the shift is at most nine. + * Keep the factor RV32-native; the enclosing product remains 64-bit. */ + capacity_bytes = ((uint64_t)c_size + UINT64_C(1)) * (UINT32_C(1) << (c_size_mult + 2U)) * (uint64_t)block_length; capacity_blocks = capacity_bytes / (uint64_t)RS_SD_MEMORY_BLOCK_SIZE; info->card_type = RS_SD_MEMORY_SDSC; diff --git a/crt/src/hal/uart.c b/crt/src/hal/uart.c index 88f92081..16035267 100644 --- a/crt/src/hal/uart.c +++ b/crt/src/hal/uart.c @@ -84,6 +84,12 @@ static rs_status_t rs_uart_flow_gpio_configure(const rs_uart_config_t *config) { }; rs_status_t status = RS_OK; +#ifdef RS_SOC_TINY + status = rs_gpio_configure(8U, &pin_config); + if (status == RS_OK) { + status = rs_gpio_configure(9U, &pin_config); + } +#endif if (config->auto_cts_enable) { status = rs_gpio_configure(RS_UART_CTS_GPIO, &pin_config); } @@ -106,6 +112,11 @@ rs_status_t rs_uart_configure(const rs_uart_config_t *config, rs_timeout_t timeo return RS_EINVAL; } +#ifdef RS_SOC_TINY + if (config->auto_cts_enable || config->auto_rts_enable) { + return RS_ENOTSUP; + } +#endif RS_UART_CTRL = 0U; while (((RS_UART_STATUS & (RS_UART_STATUS_TX_BUSY | RS_UART_STATUS_RX_BUSY)) != 0U) && (timeout != 0U)) { diff --git a/physical/commercial/config/products/tiny.mk b/physical/commercial/config/products/tiny.mk index a65474de..7c478d5d 100644 --- a/physical/commercial/config/products/tiny.mk +++ b/physical/commercial/config/products/tiny.mk @@ -18,7 +18,7 @@ TOP := retrosoc_tiny_asic # validated against rtl/tiny/integration/clock_reset_domains.json when the # commercial timing contract is generated. Tiny boots SAFE24: the PLL_TOP # macro is present in the netlist but parked off and is not a clock source. -COMMERCIAL_CLOCK_DOMAINS := system jtag +COMMERCIAL_CLOCK_DOMAINS := system jtag audio # Qualified-I/O interface groups and the budget variables each group requires. COMMERCIAL_IO_INTERFACES := JTAG XPI ASYNC diff --git a/physical/commercial/tcl/common/products/tiny.tcl b/physical/commercial/tcl/common/products/tiny.tcl index 247caba5..b4658361 100644 --- a/physical/commercial/tcl/common/products/tiny.tcl +++ b/physical/commercial/tcl/common/products/tiny.tcl @@ -11,15 +11,15 @@ # retroSoC Tiny (retrosoc_tiny_asic) product overlay. # # Clock intent: the canonical contract (rtl/tiny/integration/ -# clock_reset_domains.json) owns two domains, system (SAFE24 external clock) -# and jtag. The ICS55 PLL_TOP macro is present but parked off (EN=0) and is +# clock_reset_domains.json) owns system (SAFE24 external clock), jtag and +# the independent GPIO17 AUDIO reference. The ICS55 PLL_TOP macro is present but parked off (EN=0) and is # not a clock source; it carries no characterized timing arcs yet, so no PLL # overlay clock is created here. See docs/ip/tiny-soc.md (TINY-055). namespace eval product {} proc product::expected_clock_domains {} { - return {system jtag} + return {system jtag audio} } proc product::custom_clock_domains {} { @@ -27,7 +27,7 @@ proc product::custom_clock_domains {} { } proc product::clock_names {} { - return {clk_system clk_jtag} + return {clk_system clk_jtag clk_audio} } proc product::io_interface_groups {} { diff --git a/physical/smoke/sta/opensta/tiny_ics55_checks.tcl b/physical/smoke/sta/opensta/tiny_ics55_checks.tcl index 58642eb7..9c1c671d 100644 --- a/physical/smoke/sta/opensta/tiny_ics55_checks.tcl +++ b/physical/smoke/sta/opensta/tiny_ics55_checks.tcl @@ -4,7 +4,7 @@ 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 $pll] != 1 || [llength $ram] != 38} { 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 diff --git a/physical/smoke/sta/opensta/tiny_r2_p4_checks.tcl b/physical/smoke/sta/opensta/tiny_r2_p4_checks.tcl index 932f5ae2..97d0e23a 100644 --- a/physical/smoke/sta/opensta/tiny_r2_p4_checks.tcl +++ b/physical/smoke/sta/opensta/tiny_r2_p4_checks.tcl @@ -7,7 +7,29 @@ if {[llength $p4_ram] == 0} { set p4_ram [get_cells -hierarchical -filter {ref_name == ics55_ecos_sram_1024x32_m8}] set p4_clock_pin CLK } -if {[llength $p4_ram] != 32} { error "P4 requires 32 main SRAM macros" } +if {[llength $p4_ram] != 38} { error "P6 requires 32 main SRAM and six private Crypto macros" } +set p6_all_ram $p4_ram +set p4_ram {} +set p6_crypto {} +set p6_crypto_expected {} +for {set bank 0} {$bank < 6} {incr bank} { + lappend p6_crypto_expected [format {u_soc.u_crypto.u_storage.gen_bank[%d].gen_macro.u_sram.u_mem} $bank] +} +set p6_crypto_seen {} +foreach cell $p6_all_ram { + set name [string map {/ .} [get_full_name $cell]] + set is_crypto 0 + foreach expected $p6_crypto_expected { + if {[string equal [string range $name end-[expr {[string length $expected]-1}] end] $expected]} { + lappend p6_crypto_seen $expected + set is_crypto 1 + } + } + if {$is_crypto} { lappend p6_crypto $cell } else { lappend p4_ram $cell } +} +if {[llength $p4_ram] != 32 || [lsort $p6_crypto_seen] ne [lsort $p6_crypto_expected]} { + error "P6 private/main SRAM hierarchy mismatch" +} set p4_expected {} for {set group 0} {$group < 4} {incr group} { for {set bank 0} {$bank < 8} {incr bank} { @@ -31,7 +53,7 @@ foreach pin [get_fanout -from $p4_source -flat -pin_levels 1 -trace_arcs all] { dict set p4_direct [get_full_name $pin] 1 } set p4_clock_report [open $p4_dir/p4-clock-bindings.rpt w] -foreach cell $p4_ram { +foreach cell [concat $p4_ram $p6_crypto] { set clocks {} foreach pin [get_pins -of_objects $cell] { if {[string match "*/$p4_clock_pin" [get_full_name $pin]]} { lappend clocks $pin } @@ -52,3 +74,4 @@ puts $p4_clock_report "CPU clock endpoints: [llength $p4_cpu]" puts $p4_clock_report "Frontend clock endpoints: [llength $p4_front]" close $p4_clock_report puts "TINY_R2_P4_BINDING_PASS: four groups, 32 macros, common CPU/SRAM SYS; not timing qualification" +puts "TINY_R2_P6_BINDING_PASS: six private Crypto macros on SAFE24 PCLK; no capacity alias" diff --git a/publications/programming_reference.py b/publications/programming_reference.py index ca4de1e9..afc9685c 100644 --- a/publications/programming_reference.py +++ b/publications/programming_reference.py @@ -125,6 +125,26 @@ def clean_source(text: str) -> str: return re.sub(r"\s+", "", re.sub(r"/\*.*?\*/|//[^\n]*", "", text, flags=re.S)) +def mini_channel_enum(text: str) -> str: + """Select Mini's channel roles, excluding the Tiny product branch. + + Helper aliases share an allocated context; they do not allocate another + channel. Keep their Mini bulk binding checked instead of parsing Tiny's + literal 6/7 values from an inactive preprocessor branch. + """ + match = re.search(r"typedef enum\s*\{([^{}]*)\}\s*rs_dma_channel_t;", text, re.S) + if match is None: + raise ValueError("missing DMA channel enumeration") + body = re.sub(r"#ifdef RS_SOC_TINY\s*\n[^#]*#else\s*\n([^#]*)#endif", r"\1", match[1]) + if "#" in body: + raise ValueError("unreviewed Mini DMA channel selection") + aliases = dict(re.findall(r"(RS_DMA_CHANNEL_\w+)\s*=\s*(RS_DMA_CHANNEL_\w+)\s*,", body)) + expected = {"RS_DMA_CHANNEL_DVP", "RS_DMA_CHANNEL_I2S_TX", "RS_DMA_CHANNEL_I2S_RX"} + if any(name not in expected or value != "RS_DMA_CHANNEL_BULK" for name, value in aliases.items()): + raise ValueError("Mini DMA helper alias binding changed") + return body + + def validate_programming(spec: dict, ids: set[str], root: Path, revision: str) -> None: for relative in dependencies(spec): path = root / relative @@ -193,7 +213,7 @@ def collect_programming(root: Path, spec: dict, ids: set[str], revision: str) -> result = copy.deepcopy(spec) sdk_text = (root / "crt/include/retrosoc/hal/dma.h").read_text() sdk = constants(sdk_text, "RS_DMA_REQUEST_") - channels = constants(sdk_text, "RS_DMA_CHANNEL_") + channels = constants(mini_channel_enum(sdk_text), "RS_DMA_CHANNEL_") # Capacity is selected by product; it is not a named channel allocation. # Keep the Mini publication's role inventory independent of Tiny's count. channels.update(constants((root / "crt/include/retrosoc/hal/dma_regs.h").read_text(), diff --git a/rtl/ip/serial/i2s_recv.sv b/rtl/ip/serial/i2s_recv.sv index 1b18bbeb..b9e4be7a 100644 --- a/rtl/ip/serial/i2s_recv.sv +++ b/rtl/ip/serial/i2s_recv.sv @@ -24,14 +24,14 @@ module i2s_recv ( logic s_lrck_trg; logic [4:0] s_bit_cnt_d, s_bit_cnt_q; logic [4:0] s_bit_num; - logic s_first_bit_d, s_first_bit_q; + logic s_done_d, s_done_q; logic [23:0] s_recv_data_d, s_recv_data_q; logic [23:0] s_data_d, s_data_q; assign s_bit_num = bitmode_i ? 5'd23 : 5'd15; assign data_o = s_data_q; - assign done_o = s_bit_cnt_q == s_bit_num; + assign done_o = s_done_q; assign s_lrck_trg = lrck_i ^ s_lrck_dely_q; @@ -89,26 +89,25 @@ module i2s_recv ( ); - always_comb begin - s_first_bit_d = s_first_bit_q; - if (s_lrck_trg) s_first_bit_d = adcdat_i; - end - dffer #( + // I2S changes LRCK one bit before the next sample's MSB. The bit + // sampled on that transition is the current sample's LSB, not the next + // sample's first bit. Publish completion after data_o has captured it. + assign s_done_d = sclk_pos_i && s_lrck_trg; + dffr #( .DATA_WIDTH(1) - ) u_first_bit_dffer ( + ) u_done_dffr ( .clk_i (clk_i), .rst_n_i(rst_n_i), - .en_i (sclk_pos_i), - .dat_i (s_first_bit_d), - .dat_o (s_first_bit_q) + .dat_i (s_done_d), + .dat_o (s_done_q) ); always_comb begin s_data_d = s_data_q; - if ((!s_lrck_trg) && (s_bit_cnt_q == (s_bit_num - 1'b1))) begin - if (bitmode_i) s_data_d = {s_recv_data_d[22:0], s_first_bit_q}; - else s_data_d = {8'd0, s_recv_data_d[14:0], s_first_bit_q}; + if (s_lrck_trg) begin + if (bitmode_i) s_data_d = {s_recv_data_q[22:0], adcdat_i}; + else s_data_d = {8'd0, s_recv_data_q[14:0], adcdat_i}; end end dffer #( diff --git a/rtl/ip/serial/i2s_reg.sv b/rtl/ip/serial/i2s_reg.sv index 5b246ec4..db6cd72a 100644 --- a/rtl/ip/serial/i2s_reg.sv +++ b/rtl/ip/serial/i2s_reg.sv @@ -92,7 +92,7 @@ module i2s_reg ( endfunction assign s_req = apb4.psel && apb4.penable && !s_ready_q; - assign s_write = |apb4.pstrb; + assign s_write = apb4.pwrite; assign s_accept = s_req; assign s_offset = apb4.paddr[11:0]; assign s_aligned = apb4.paddr[1:0] == 2'b00; diff --git a/rtl/ip/storage/sdio_command.sv b/rtl/ip/storage/sdio_command.sv index 1ad70bb3..bc22b3f2 100644 --- a/rtl/ip/storage/sdio_command.sv +++ b/rtl/ip/storage/sdio_command.sv @@ -88,7 +88,10 @@ module sdio_command #( assign s_frame_crc_error = s_frame_crc != s_resp_shift_next[7:1]; assign s_frame_index_error = s_resp_shift_next[45:40] != s_cmd_index_q; - assign cmd_oe_o = (s_state_q == Send) || (s_tx_final_hold_q && !sample_tick_i); + // Admission can coincide with an SDCLK rising edge. Do not expose the + // start bit until the first scheduled launch, or the card sees it twice. + assign cmd_oe_o = ((s_state_q == Send) && (s_tx_started_q || launch_tick_i)) || + (s_tx_final_hold_q && !sample_tick_i); assign cmd_do_o = ((s_state_q == Send) || s_tx_final_hold_q) ? s_tx_shift_q[47] : 1'b1; assign busy_o = (s_state_q != Idle) && (s_state_q != Complete); assign done_o = s_done_q; diff --git a/rtl/ip/storage/sdio_core.sv b/rtl/ip/storage/sdio_core.sv index dc179701..2e4b9059 100644 --- a/rtl/ip/storage/sdio_core.sv +++ b/rtl/ip/storage/sdio_core.sv @@ -102,7 +102,7 @@ module sdio_core #( logic s_data_tx_last; logic s_data_rx_valid; logic s_data_rx_ready; - logic s_data_rx_stall_q; + logic s_data_rx_stall; logic [ 31:0] s_data_rx_data; logic [ 3:0] s_data_rx_strb; logic s_data_rx_last; @@ -174,8 +174,8 @@ module sdio_core #( assign s_clock_en = host_enable_i && clock_enable_i && (s_cmd_busy || s_data_busy || s_dma_busy || cmd_start_i || data_start_i) && - !s_data_rx_stall_q && - !(s_data_busy && s_data_tx_wait && (s_data_oe == 4'b0000)); + !s_data_rx_stall && + !(s_data_busy && s_data_tx_wait); assign s_total_bytes = {16'd0, block_size_i} * {16'd0, block_count_i}; assign s_abort_request = host_abort_i || dma_abort_i || (s_dma_done && s_dma_err) || @@ -200,6 +200,9 @@ module sdio_core #( assign s_dma_data_in_strb = s_data_rx_strb; assign s_dma_data_in_last = s_data_rx_last; assign s_data_rx_ready = data_dma_enable_i ? s_dma_data_in_ready : !s_pio_valid_q; + // ReadEmit begins on the sampling edge. Stop before the very next launch + // if its word cannot retire; registering this condition loses one card bit. + assign s_data_rx_stall = s_data_rx_valid && !s_data_rx_ready; assign s_core_busy = s_cmd_busy || s_data_busy || s_dma_busy; assign busy_o = s_core_busy; assign s_pio_tx_pop = !data_dma_enable_i && s_data_tx_valid && s_data_tx_ready; @@ -401,7 +404,6 @@ module sdio_core #( s_dat1_meta_q <= 1'b1; s_dat1_sync_q <= 1'b1; s_dat1_seen_q <= 1'b0; - s_data_rx_stall_q <= 1'b0; s_pio_rdata_q <= '0; s_pio_valid_q <= 1'b0; s_pio_tx_rd_q <= '0; @@ -418,10 +420,9 @@ module sdio_core #( s_pio_tx_strb_q[index] <= '0; end end else begin - s_dat1_meta_q <= sdio.dat_di_i[1]; - s_dat1_sync_q <= s_dat1_meta_q; - s_dat1_seen_q <= s_dat1_seen_d; - s_data_rx_stall_q <= s_data_rx_valid && !s_data_rx_ready; + s_dat1_meta_q <= sdio.dat_di_i[1]; + s_dat1_sync_q <= s_dat1_meta_q; + s_dat1_seen_q <= s_dat1_seen_d; if (data_start_i) begin s_pio_tx_rd_q <= '0; diff --git a/rtl/ip/storage/sdio_data.sv b/rtl/ip/storage/sdio_data.sv index f520cf3b..6a6b0972 100644 --- a/rtl/ip/storage/sdio_data.sv +++ b/rtl/ip/storage/sdio_data.sv @@ -160,7 +160,12 @@ module sdio_data #( assign tx_ready_o = (s_state_q == WriteWaitWord) || (s_state_q == WriteNextWord) || ((s_state_q == WritePayload) && launch_tick_i && s_tx_word_final && (s_tx_total_q < s_target_bytes_q)); - assign tx_wait_o = (s_state_q == WriteWaitWord) || (s_state_q == WriteNextWord); + // Stop before launching the next word if memory has not supplied it. + // Waiting only after that launch exposes an idle bit to the card and + // corrupts the next word's MSB when the clock resumes. + assign tx_wait_o = (s_state_q == WriteWaitWord) || (s_state_q == WriteNextWord) || + ((s_state_q == WritePayload) && s_tx_word_final && + (s_tx_total_q < s_target_bytes_q) && !tx_valid_i); assign rx_valid_o = (s_state_q == ReadEmit); assign rx_data_o = s_rx_pending_data_q; assign rx_strb_o = s_rx_pending_strb_q; diff --git a/rtl/ip/storage/sdio_dma.sv b/rtl/ip/storage/sdio_dma.sv index 8cea0c60..e92c4b9d 100644 --- a/rtl/ip/storage/sdio_dma.sv +++ b/rtl/ip/storage/sdio_dma.sv @@ -516,11 +516,18 @@ module sdio_dma #( s_err_addr_d = s_payload_addr + s_burst_offset; end if (s_write_final_sent_q) begin - s_writeback_control_d = (s_desc_control_q & - ~(32'd1 << sdio_pkg::SDIO_DESC_OWN)) | - (s_fault_d ? (32'd1 << sdio_pkg::SDIO_DESC_ERROR) : - (32'd1 << sdio_pkg::SDIO_DESC_DONE)); - s_state_d = WritebackStart; + // A retired AXI burst is not necessarily the whole descriptor. + // Keep ownership while subsequent bursts receive its remaining + // bytes, just as the memory-to-card direction does. + if (s_fault_d || (s_desc_progress_d >= s_desc_count_q)) begin + s_writeback_control_d = (s_desc_control_q & + ~(32'd1 << sdio_pkg::SDIO_DESC_OWN)) | + (s_fault_d ? (32'd1 << sdio_pkg::SDIO_DESC_ERROR) : + (32'd1 << sdio_pkg::SDIO_DESC_DONE)); + s_state_d = WritebackStart; + end else begin + s_state_d = PayloadWriteStart; + end end end end diff --git a/rtl/mk/software.mk b/rtl/mk/software.mk index 6ab91f17..70a877d7 100644 --- a/rtl/mk/software.mk +++ b/rtl/mk/software.mk @@ -179,7 +179,8 @@ CFLAGS += -DRS_SOC_TINY TINY_CRT_NAMES := startup.S clzsi2.c divdi3.c ffssi2.c udivdi3.c umoddi3.c \ stdlib.c string.c console.c printf.c archinfo.c clint.c uart_math.c uart.c \ gpio_math.c gpio.c timer_math.c timer.c pwm.c rtc.c wdg.c watchdog.c i2c_math.c i2c.c \ - dma_math.c dma.c onchip_sram.c xpi.c + dma_math.c dma.c onchip_sram.c xpi.c rng.c crc.c ws2812_math.c ws2812.c \ + crypto.c crypto_lifecycle.c i2s_math.c i2s.c sdio_math.c sdio.c CRT_SRCS := $(foreach source,$(CRT_SRCS),$(if $(filter $(notdir $(source)),$(TINY_CRT_NAMES)),$(source))) CRT_SRCS += $(ROOT_PATH)/crt/src/hal/sysctrl.c endif @@ -282,12 +283,12 @@ asm: $(MPW_VARIANT_DEP) $(MEMORY_MAP_STAMP) cp $(SW_BUILD_DIR)/asm/hello-asm.bin $(SW_BUILD_DIR)/$(ASM_FIRMWARE_NAME).bin cp $(SW_BUILD_DIR)/asm/hello-asm.txt $(SW_BUILD_DIR)/$(ASM_FIRMWARE_NAME)_all.txt +endif + $(CRYPTO_CONSTANTS_HEADER): $(ROOT_PATH)/scripts/crypto_constants.py $(ROOT_PATH)/scripts/data/crypto_constants.json $(PYTHON) $(ROOT_PATH)/scripts/crypto_constants.py --root $(ROOT_PATH) --output $(SW_BUILD_DIR)/include -endif - -CRYPTO_CONSTANTS_DEP := $(if $(filter TINY,$(SOC)),,$(CRYPTO_CONSTANTS_HEADER)) +CRYPTO_CONSTANTS_DEP := $(CRYPTO_CONSTANTS_HEADER) $(FIRMWARE_ELF): $(MPW_VARIANT_DEP) $(MEMORY_MAP_STAMP) $(SOC_TOPOLOGY_STAMP) $(SOC_IRQ_METADATA) $(USER_EXTENSIONS_STAMP) $(ARCHINFO_METADATA_STAMP) $(VERSION_HEADER) $(CRYPTO_CONSTANTS_DEP) $(SRC_PATH) $(SW_HEADERS) $(LDS_PATH) \ $(ROOT_PATH)/rtl/mk/software.mk $(APP_MK) diff --git a/rtl/tiny/Makefile b/rtl/tiny/Makefile index 04c236d9..f0947d58 100644 --- a/rtl/tiny/Makefile +++ b/rtl/tiny/Makefile @@ -60,6 +60,7 @@ OPENSTA_CONFIG := $(SYN_BUILD_ROOT)/out/$(RTL_NAME)_yosys.config NETLIST_PATH := $(OPENSTA_NETLIST) TINY_CONVERTED := $(SIM_BUILD_ROOT)/converted_soc.v TINY_DEPFILE := $(SIM_BUILD_ROOT)/sources.d +TINY_TB_DEPFILE := $(SIM_BUILD_ROOT)/testbench.d TINY_LINT_LOG := $(VARIANT_ROOT)/lint/verilator/lint.log SOC_SIM_TIME ?= 1800 TINY_SIM_TIMEOUT ?= 20000000 @@ -69,7 +70,7 @@ IVERILOG ?= iverilog VVP ?= vvp include $(ROOT_PATH)/rtl/mk/software.mk --include $(TINY_DEPFILE) +-include $(TINY_DEPFILE) $(TINY_TB_DEPFILE) $(TINY_CONVERTED): $(FILELIST_STAMP) @mkdir -p $(@D) @@ -95,6 +96,9 @@ $(SIM_BUILD_ROOT)/simv: $(TINY_CONVERTED) $(wildcard $(RTL_PATH)/dv/*.sv) --log $(SIM_BUILD_ROOT)/compile.log --result $(SIM_BUILD_ROOT)/result-compile.json -- \ $(IVERILOG) -g2012 -gno-specify -s $(RTL_TOP) -o $@ -f $(GENERATED_FL_DIR)/def.fl \ -f $(GENERATED_FL_DIR)/inc.fl -f $(PDK_FILELIST) $(TINY_TB_FLIST) $(TINY_CONVERTED) + $(PYTHON) $(ROOT_PATH)/scripts/rtl/filelist_deps.py -f $(GENERATED_FL_DIR)/def.fl \ + -f $(GENERATED_FL_DIR)/inc.fl $(TINY_TB_FLIST) \ + --target $@ --output $(TINY_TB_DEPFILE) comp: $(SIM_BUILD_ROOT)/simv TINY_RUN := $(VVP) $(SIM_BUILD_ROOT)/simv endif @@ -204,7 +208,7 @@ tiny-r2-p3-report: # P4 preserves old image identity and replays each selected image exactly once. TINY_P4_REFERENCE ?= -TINY_P4_CASES ?= perf-o2-plain-flat,perf-o2-plain-banked +TINY_P4_CASES ?= perf-o2-plain-flat,perf-o2-plain-banked tiny-r2-p4-replay: | manifest $(PYTHON) $(ROOT_PATH)/scripts/tiny_r2_p4.py --root $(ROOT_PATH) --variant-root $(VARIANT_ROOT) \ --reference $(TINY_P4_REFERENCE) --cases $(TINY_P4_CASES) --simulator $(SIM_TOOL_NAME) \ @@ -220,7 +224,7 @@ tiny-r2-p4-report: # P5 compares the same P3 binaries to retained P4 observations, with its own # phase identity and evidence root. The historical P3/P4 commands stay intact. TINY_P5_REFERENCE ?= -TINY_P5_CASES ?= perf-o2-plain-flat,perf-o2-plain-banked +TINY_P5_CASES ?= perf-o2-plain-flat,perf-o2-plain-banked tiny-r2-p5-replay: | manifest $(PYTHON) $(ROOT_PATH)/scripts/tiny_r2_p5.py --root $(ROOT_PATH) --variant-root $(VARIANT_ROOT) \ --reference $(TINY_P5_REFERENCE) --cases $(TINY_P5_CASES) --simulator $(SIM_TOOL_NAME) \ @@ -231,4 +235,4 @@ tiny-r2-p5-replay: | manifest tiny-r2-p5-report: $(PYTHON) $(ROOT_PATH)/scripts/tiny_r2_p5.py --root $(ROOT_PATH) --variant-root $(VARIANT_ROOT) --report -.PHONY: tiny-r2-p5-replay tiny-r2-p5-report +.PHONY: tiny-r2-p5-replay tiny-r2-p5-report \ No newline at end of file diff --git a/rtl/tiny/address_map/memory_map.json b/rtl/tiny/address_map/memory_map.json index 0036f910..51f5aea5 100644 --- a/rtl/tiny/address_map/memory_map.json +++ b/rtl/tiny/address_map/memory_map.json @@ -300,15 +300,6 @@ "public": true, "user_access": "none" }, - { - "symbol": "APB4_I2C1", - "base": "0x10011000", - "size": "0x00001000", - "route": "apb4_periph", - "kind": "active", - "public": true, - "user_access": "rw" - }, { "symbol": "APB4_GPIO_ADMIN", "base": "0x10014000", @@ -327,15 +318,6 @@ "public": true, "user_access": "none" }, - { - "symbol": "APB4_UART1", - "base": "0x10018000", - "size": "0x00001000", - "route": "apb4_periph", - "kind": "active", - "public": true, - "user_access": "rw" - }, { "symbol": "APB4_ARCHINFO", "base": "0x20000000", @@ -399,6 +381,60 @@ "public": true, "user_access": "rw", "burst": "incr16" + }, + { + "symbol": "APB4_RNG", + "base": "0x20001000", + "size": "0x00001000", + "route": "apb4_system", + "kind": "active", + "public": true, + "user_access": "none" + }, + { + "symbol": "APB4_CRC", + "base": "0x20006000", + "size": "0x00001000", + "route": "apb4_system", + "kind": "active", + "public": true, + "user_access": "none" + }, + { + "symbol": "APB4_WS2812", + "base": "0x10008000", + "size": "0x00001000", + "route": "apb4_periph", + "kind": "active", + "public": true, + "user_access": "none" + }, + { + "symbol": "APB4_CRYPTO", + "base": "0x1000C000", + "size": "0x00001000", + "route": "apb4_periph", + "kind": "active", + "public": true, + "user_access": "none" + }, + { + "symbol": "APB4_I2S", + "base": "0x10007000", + "size": "0x00001000", + "route": "apb4_periph", + "kind": "active", + "public": true, + "user_access": "none" + }, + { + "symbol": "APB4_SDIO0", + "base": "0x1000F000", + "size": "0x00001000", + "route": "apb4_periph", + "kind": "active", + "public": true, + "user_access": "none" } ], "legacy_rib": false, diff --git a/rtl/tiny/dv/retrosoc_tiny_tb.sv b/rtl/tiny/dv/retrosoc_tiny_tb.sv index 79e12ba0..9e32acab 100644 --- a/rtl/tiny/dv/retrosoc_tiny_tb.sv +++ b/rtl/tiny/dv/retrosoc_tiny_tb.sv @@ -1,3 +1,4 @@ +`include "tiny_topology.svh" // Copyright (c) 2026 Yuchi Miao // SPDX-License-Identifier: MulanPSL-2.0 `timescale 1ns / 1ps @@ -8,24 +9,27 @@ module retrosoc_tiny_tb; logic s_reset_driver = 1'b0; wire s_rst_n = s_reset_driver; wire s_uart0_tx, s_uart1_tx; - wire s_uart0_rx = 1'b1; - wire s_uart1_rx = 1'b1; - 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; - wire s_xpi_sck; - wire [ 3:0] s_xpi_nss; - tri [ 3:0] s_xpi_data; - integer s_cycles; - integer s_max_cycles = 20000000; - integer s_done_cycles = 0; - logic [ 7:0] s_uart_byte; + wire s_uart0_rx = 1'b1; + wire s_uart1_rx = 1'b1; + 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; + `include "tiny_tb_gpio.svh" + wire s_xpi_sck; + wire [3:0] s_xpi_nss; + tri [3:0] s_xpi_data; + integer s_cycles; +`ifndef RETROSOC_SOC__TINY_NETLIST + `include "tiny_p6_devices.svh" +`endif + 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 @@ -64,24 +68,15 @@ module retrosoc_tiny_tb; if (s_done_cycles != 128) $fatal(1, "SIM_TEST_TIMEOUT Tiny cycles=%0d", s_cycles); end -`ifdef RETROSOC_SOC__TINY_NETLIST +`ifdef SIMU_IVERILOG localparam bit ObserveUartAlways = 1'b1; `else localparam bit ObserveUartAlways = 1'b0; `endif -`ifdef SIMU_IVERILOG - localparam bit ObserveUartBaseline = 1'b1; -`else - localparam bit ObserveUartBaseline = 1'b0; -`endif - // The Verilator RTL already prints accepted TX bytes. The Icarus observer is - // opt-in for measurements; SYSCTRL remains the authoritative verdict. + // The Verilator RTL prints accepted TX bytes. Icarus observes serial output for + // all firmware measurements; SYSCTRL remains the authoritative verdict. initial begin - if (ObserveUartAlways || (ObserveUartBaseline && ($test$plusargs( - "tiny_r2_baseline" - ) || $test$plusargs( - "tiny_r2_p3" - )))) begin + if (ObserveUartAlways) begin forever begin @(negedge s_uart0_tx); #1627.604; @@ -239,10 +234,13 @@ module retrosoc_tiny_tb; // Icarus 13 miscounts the temporary expression $countones(~a_23_bit_vector). // Count declared bits explicitly and reject unknowns rather than correcting // a report afterward. This function changes only opt-in P5 observations. - function automatic integer p5_active_targets(input logic [22:0] idle_i); + function automatic integer p5_active_targets( + input logic [`RETROSOC_TINY__TARGET_COUNT-1:0] idle_i); integer count; count = 0; - for (integer target_index = 0; target_index < 23; target_index++) begin + for ( + integer target_index = 0; target_index < `RETROSOC_TINY__TARGET_COUNT; target_index++ + ) begin if (idle_i[target_index] === 1'b0) count++; else if (idle_i[target_index] !== 1'b1) $fatal(1, "SIM_TEST_FAIL P5 unknown target state"); end @@ -250,11 +248,13 @@ module retrosoc_tiny_tb; endfunction initial begin : p5_monitor_check if ($test$plusargs("tiny_r2_p5_monitor_check")) begin - for (integer active = 0; active <= 23; active++) begin - if (p5_active_targets(23'h7fffff ^ ((23'd1 << active) - 23'd1)) != active) + for (integer active = 0; active <= `RETROSOC_TINY__TARGET_COUNT; active++) begin + if (p5_active_targets( + {`RETROSOC_TINY__TARGET_COUNT{1'b1}} ^ ((`RETROSOC_TINY__TARGET_COUNT'(1) << active) - `RETROSOC_TINY__TARGET_COUNT'(1)) + ) != active) $fatal(1, "SIM_TEST_FAIL P5 monitor population count"); end - $display("SIM_TEST_PASS Tiny P5 monitor-check counts=0..23; instrumentation only"); + $display("SIM_TEST_PASS Tiny P5 monitor-check counts=0..27; instrumentation only"); $finish; end end diff --git a/rtl/tiny/dv/tiny_p6_devices.svh b/rtl/tiny/dv/tiny_p6_devices.svh new file mode 100644 index 00000000..123de212 --- /dev/null +++ b/rtl/tiny/dv/tiny_p6_devices.svh @@ -0,0 +1,329 @@ +// Verification-only external devices on the Gen1 GPIO routes. No DUT state is driven. +logic s_p6_io_trace_started = 0; +always @(negedge s_clk) begin + if ($test$plusargs("p6_debug_io")) begin + if (u_dut.u_soc.u_i2c0.s_cmd_valid && !s_p6_io_trace_started) begin + s_p6_io_trace_started = 1; + $display("P6_IO_DIAGNOSTIC start pads=%b raw=%b%b filtered=%b%b drive=%b%b", s_gpio[11:10], + u_dut.u_soc.u_i2c0_if.sda_i, u_dut.u_soc.u_i2c0_if.scl_i, + u_dut.u_soc.u_i2c0.s_sda_filtered, u_dut.u_soc.u_i2c0.s_scl_filtered, + u_dut.u_soc.u_i2c0.s_sda_pull_low, u_dut.u_soc.u_i2c0.s_scl_pull_low); + $fflush(); + end + if (u_dut.u_soc.u_i2c0.s_addr_nack_event || u_dut.u_soc.u_i2c0.s_bus_timeout_event || + u_dut.u_soc.u_i2c0.s_cmd_timeout_event || u_dut.u_soc.u_i2c0.s_stretch_timeout_event || + u_dut.u_soc.u_i2c0.s_arb_lost_event) begin + $display( + "P6_IO_DIAGNOSTIC nack=%b bus_timeout=%b command_timeout=%b stretch=%b arbitration=%b pads=%b drive=%b%b", + u_dut.u_soc.u_i2c0.s_addr_nack_event, u_dut.u_soc.u_i2c0.s_bus_timeout_event, + u_dut.u_soc.u_i2c0.s_cmd_timeout_event, u_dut.u_soc.u_i2c0.s_stretch_timeout_event, + u_dut.u_soc.u_i2c0.s_arb_lost_event, s_gpio[11:10], u_dut.u_soc.u_i2c0.s_sda_pull_low, + u_dut.u_soc.u_i2c0.s_scl_pull_low); + $finish; // Diagnostic only: deliberately no SIM_TEST_PASS verdict. + end + end +end +`define _P6_WS u_dut.u_soc.u_ws2812 +logic s_p6_uart_rx = 1; +logic [7:0] s_p6_uart_byte; +logic [7:0] s_p6_uart_packet [4]; +integer s_p6_uart_index = 0, s_p6_uart_routes = 0; +assign s_gpio_9 = s_p6_uart_rx; +initial begin + forever begin + @(negedge s_gpio_8); + #1627.604; + for (integer bit_index = 0; bit_index < 8; bit_index++) begin + s_p6_uart_byte[bit_index] = s_gpio_8; + #1085.069; + end + if (s_p6_uart_index != 0 || s_p6_uart_byte == 8'ha5) begin + s_p6_uart_packet[s_p6_uart_index] = s_p6_uart_byte; + s_p6_uart_index++; + if (s_p6_uart_index == 4) begin + if (s_p6_uart_packet[0] != 8'ha5 || s_p6_uart_packet[1] != 8'h5a || + s_p6_uart_packet[2] != 8'h96 || s_p6_uart_packet[3] != 8'h69) + $fatal(1, "SIM_TEST_FAIL P6 UART GPIO8 framing/data"); + for (integer byte_index = 0; byte_index < 4; byte_index++) begin + s_p6_uart_rx = 0; + #1085.069; + for (integer bit_index = 0; bit_index < 8; bit_index++) begin + s_p6_uart_rx = s_p6_uart_packet[byte_index][bit_index]; + #1085.069; + end + s_p6_uart_rx = 1; + #1085.069; + end + s_p6_uart_index = 0; + s_p6_uart_routes++; + end + end + end +end +integer s_p6_frames = 0, s_p6_completed = 0, s_p6_underflows = 0, s_p6_aborts = 0; +longint unsigned s_p6_sdio_overlap = 0, s_p6_overlap_fetches = 0; +always @(posedge s_clk) begin + if (u_dut.u_soc.u_dma.s_busy[2] && u_dut.u_soc.u_sdio.s_dma_status[0]) begin + s_p6_sdio_overlap++; + if (u_dut.u_soc.u_cpu_ahbl_if[0].hready && u_dut.u_soc.u_cpu_ahbl_if[0].htrans[1]) + s_p6_overlap_fetches++; + end +end +integer s_p6_word = 0, s_p6_bit = 0, s_p6_bit_cycle = 0, s_p6_reset_cycles = 0; +integer s_p6_ws_wait = 0, s_p6_ws_wait_max = 0, s_p6_fifo_max = 0; +integer s_p6_clock_stops = 0; +integer s_p6_wire_cases = 0, s_p6_wire_matched = 0, s_p6_wire_examined = 0; +integer s_p6_wire_edges = 0; +logic s_p6_wire_found = 0, s_p6_wire_boundary = 0, s_p6_wire_lrck = 0; +logic [23:0] s_p6_wire_shift = 0; +logic [31:0] s_p6_wire_word; +wire s_p6_audio_enabled = u_dut.u_soc.u_i2s.u_i2s_core.enable_i; +wire s_p6_audio_wide = u_dut.u_soc.u_i2s.u_i2s_core.s_bitmode; +// Decode actual BCLK/LRCLK/DOUT pads, independently of the receive engine. +always @(posedge s_gpio[12] or negedge s_p6_audio_enabled) begin + if (!s_p6_audio_enabled) begin + if (s_p6_wire_found) begin + if (s_p6_wire_matched != 512) $fatal(1, "SIM_TEST_FAIL P6 truncated I2S wire payload"); + s_p6_wire_cases++; + $display("R2_P6_I2S_WIRE bits=%0d samples=%0d", s_p6_audio_wide ? 24 : 16, s_p6_wire_matched); + end + s_p6_wire_matched = 0; + s_p6_wire_examined = 0; + s_p6_wire_edges = 0; + s_p6_wire_found = 0; + s_p6_wire_boundary = 0; + s_p6_wire_lrck = 0; + s_p6_wire_shift = 0; + end else begin + if (s_gpio[14] !== 1'b0 && s_gpio[14] !== 1'b1) $fatal(1, "SIM_TEST_FAIL P6 unknown I2S DOUT"); + s_p6_wire_shift = {s_p6_wire_shift[22:0], s_gpio[14]}; + s_p6_wire_edges++; + if (s_gpio[13] != s_p6_wire_lrck) begin + if (s_p6_wire_boundary && s_p6_wire_edges != (s_p6_audio_wide ? 24 : 16)) + $fatal(1, "SIM_TEST_FAIL P6 I2S slot length"); + s_p6_wire_boundary = 1; + s_p6_wire_edges = 0; + s_p6_wire_word = s_p6_audio_wide ? {8'd0, s_p6_wire_shift} : {16'd0, s_p6_wire_shift[15:0]}; + if (!s_p6_wire_found && s_p6_wire_word == (s_p6_audio_wide ? 32'h120020 : 32'h2020)) + s_p6_wire_found = 1; + if (s_p6_wire_found && s_p6_wire_matched < 512) begin + if (s_p6_wire_word != (s_p6_audio_wide ? 32'h120020 : 32'h2020) + 32'(s_p6_wire_matched)) + $fatal(1, "SIM_TEST_FAIL P6 I2S wire ordering/data index=%0d", s_p6_wire_matched); + s_p6_wire_matched++; + end + s_p6_wire_examined++; + if (!s_p6_wire_found && s_p6_wire_examined > 40) + $fatal(1, "SIM_TEST_FAIL P6 missing I2S wire payload"); + s_p6_wire_lrck = s_gpio[13]; + end + end +end +logic s_p6_busy_previous = 0; +logic [23:0] s_p6_pixel; +logic s_p6_expected_high; +logic s_p6_audio_reference = 1'b0; +logic s_p6_audio_running = 1'b0; +initial begin + repeat (200) @(negedge s_clk); + if (!u_dut.u_soc.s_cpu_ready_rst_n) + $fatal(1, "SIM_TEST_FAIL CPU boot waited for absent AUDIO clock"); + s_p6_audio_running = 1'b1; +end +// GPIO27 is a verification-board control only after the four normal WS frames. +// Stop low and high in separate cases, then restore the source without DUT forcing. +always @(posedge s_gpio[27]) begin + if (s_p6_frames == 4 && s_p6_clock_stops < 2) begin + wait (s_p6_audio_reference == (s_p6_clock_stops != 0)); + s_p6_audio_running = 0; + repeat (4800) @(negedge s_clk); + s_p6_clock_stops++; + s_p6_audio_running = 1; + end +end +always @(negedge s_clk) begin + if (`_P6_WS.s_busy && !s_p6_busy_previous) begin + s_p6_frames++; + s_p6_word = 0; + s_p6_bit = 0; + s_p6_bit_cycle = 0; + s_p6_reset_cycles = 0; + end + if (`_P6_WS.s_underflow) s_p6_underflows++; + if (`_P6_WS.s_aborted) s_p6_aborts++; + if (`_P6_WS.s_busy && !`_P6_WS.s_reset_active && s_p6_frames <= 4) begin + s_p6_pixel = 24'(s_p6_word * 32'h010203); + s_p6_expected_high = s_p6_bit_cycle < (s_p6_pixel[23-s_p6_bit] ? 17 : 8); + if (s_gpio[26] !== s_p6_expected_high) + $fatal( + 1, + "SIM_TEST_FAIL Tiny P6 WS pad waveform frame=%0d word=%0d bit=%0d cycle=%0d", + s_p6_frames, + s_p6_word, + s_p6_bit, + s_p6_bit_cycle + ); + s_p6_bit_cycle++; + if (s_p6_bit_cycle == 30) begin + s_p6_bit_cycle = 0; + s_p6_bit++; + if (s_p6_bit == 24) begin + s_p6_bit = 0; + s_p6_word++; + end + end + if (`_P6_WS.u_ws2812_reg.s_fifo_level > s_p6_fifo_max) + s_p6_fifo_max = integer'(`_P6_WS.u_ws2812_reg.s_fifo_level); + if (`_P6_WS.u_ws2812_reg.s_fifo_low && !`_P6_WS.u_ws2812_reg.s_tx_push) s_p6_ws_wait++; + else s_p6_ws_wait = 0; + if (s_p6_ws_wait > s_p6_ws_wait_max) s_p6_ws_wait_max = s_p6_ws_wait; + if (s_p6_ws_wait > 4800 || s_p6_fifo_max > 16) + $fatal(1, "SIM_TEST_FAIL Tiny P6 WS service budget/occupancy"); + end else s_p6_ws_wait = 0; + if (`_P6_WS.s_reset_active) begin + if (s_gpio[26] !== 1'b0) $fatal(1, "SIM_TEST_FAIL Tiny P6 WS reset-low"); + s_p6_reset_cycles++; + end + if (!`_P6_WS.s_busy && s_p6_busy_previous) begin + if (s_p6_reset_cycles < 7200) $fatal(1, "SIM_TEST_FAIL Tiny P6 WS reset interval"); + if (s_p6_frames <= 4) begin + if (s_p6_word != ((s_p6_frames % 2) ? 33 : 65) || s_p6_bit != 0 || s_p6_bit_cycle != 0) + $fatal(1, "SIM_TEST_FAIL Tiny P6 WS exact frame length"); + s_p6_completed++; + end + end + s_p6_busy_previous = `_P6_WS.s_busy; +end +final begin + if (u_dut.s_test_done && u_dut.s_test_pass && s_p6_frames != 0) begin + if (s_p6_sdio_overlap == 0 || s_p6_overlap_fetches == 0) + $fatal(1, "SIM_TEST_FAIL P6 missing actual SDIO/DMA/CPU contention"); + $display("R2_P6_CONTENTION sdio_dma_cycles=%0d cpu_fetches=%0d", s_p6_sdio_overlap, + s_p6_overlap_fetches); + if (s_p6_completed != 4 || s_p6_underflows != 1 || s_p6_aborts != 1 || + s_p6_clock_stops != 2 || s_p6_wire_cases != 2 || s_p6_uart_routes != 1) + $fatal(1, "SIM_TEST_FAIL Tiny P6 incomplete device coverage"); + $display( + "R2_P6_DEVICES ws_frames=%0d ws_wait_max=%0d fifo_max=%0d underflows=%0d aborts=%0d clock_stops=%0d", + s_p6_completed, s_p6_ws_wait_max, s_p6_fifo_max, s_p6_underflows, s_p6_aborts, + s_p6_clock_stops); + end +end +`undef _P6_WS +// Counts describe observed bus handshakes over the complete acceptance run. +// Read bytes require OKAY; write bytes are accepted W strobes, not rollback claims. +for (genvar owner = 0; owner < 3; owner++) begin : gen_p6_axi_observer + longint unsigned read_bytes = 0, write_bytes = 0, descriptor_bytes = 0; + longint unsigned ar_wait = 0, aw_wait = 0, w_wait = 0, r_wait = 0, b_wait = 0; + longint unsigned ar_max = 0, aw_max = 0, w_max = 0, r_max = 0, b_max = 0; + logic pending_read = 0, pending_write = 0, descriptor = 0; + logic [2:0] read_size = 0; + `define _P6_AXI u_dut.u_soc.u_masters_axi4_if[owner] + always @(posedge s_clk) begin + if (s_rst_n) begin + if (`_P6_AXI.arvalid && `_P6_AXI.arready) begin + if (pending_read) $fatal(1, "SIM_TEST_FAIL P6 duplicate read admission"); + pending_read = 1; + read_size = `_P6_AXI.arsize; + descriptor = owner == 1 && u_dut.u_soc.u_dma.s_tcd_count[64+:32] != 0 && + `_P6_AXI.araddr >= u_dut.u_soc.u_dma.s_tcd_head[64+:32] && + `_P6_AXI.araddr < u_dut.u_soc.u_dma.s_tcd_head[64+:32] + 32'd256; + end + if (`_P6_AXI.rvalid && `_P6_AXI.rready) begin + if (!pending_read) $fatal(1, "SIM_TEST_FAIL P6 unsolicited read response"); + if (`_P6_AXI.rresp == 0) begin + read_bytes += 64'd1 << read_size; + if (descriptor) descriptor_bytes += 64'd1 << read_size; + end + if (`_P6_AXI.rlast) pending_read = 0; + end + if (`_P6_AXI.awvalid && `_P6_AXI.awready) begin + if (pending_write) $fatal(1, "SIM_TEST_FAIL P6 duplicate write admission"); + pending_write = 1; + end + if (`_P6_AXI.wvalid && `_P6_AXI.wready) begin + if (!pending_write) $fatal(1, "SIM_TEST_FAIL P6 W without saved AW"); + for (integer lane = 0; lane < 4; lane++) if (`_P6_AXI.wstrb[lane]) write_bytes++; + end + if (`_P6_AXI.bvalid && `_P6_AXI.bready) begin + if (!pending_write) $fatal(1, "SIM_TEST_FAIL P6 unsolicited write response"); + pending_write = 0; + end + ar_wait = `_P6_AXI.arvalid && !`_P6_AXI.arready ? ar_wait + 1 : 0; + aw_wait = `_P6_AXI.awvalid && !`_P6_AXI.awready ? aw_wait + 1 : 0; + w_wait = `_P6_AXI.wvalid && !`_P6_AXI.wready ? w_wait + 1 : 0; + r_wait = `_P6_AXI.rvalid && !`_P6_AXI.rready ? r_wait + 1 : 0; + b_wait = `_P6_AXI.bvalid && !`_P6_AXI.bready ? b_wait + 1 : 0; + if (ar_wait > ar_max) ar_max = ar_wait; + if (aw_wait > aw_max) aw_max = aw_wait; + if (w_wait > w_max) w_max = w_wait; + if (r_wait > r_max) r_max = r_wait; + if (b_wait > b_max) b_max = b_wait; + end + end + final begin + if (u_dut.s_test_done && u_dut.s_test_pass && s_p6_frames != 0) begin + if (pending_read || pending_write) $fatal(1, "SIM_TEST_FAIL P6 unretired accepted work"); + $display( + "R2_P6_AXI owner=%0d read_bytes=%0d write_bytes=%0d descriptor_bytes=%0d ar_stall_max=%0d aw_stall_max=%0d w_stall_max=%0d r_stall_max=%0d b_stall_max=%0d", + owner, read_bytes, write_bytes, descriptor_bytes, ar_max, aw_max, w_max, r_max, b_max); + end + end + `undef _P6_AXI +end +always #27.126736111 if (s_p6_audio_running) s_p6_audio_reference = !s_p6_audio_reference; +assign s_gpio_17 = s_p6_audio_reference; +// The first integration case is an external codec loopback, across actual pads. +// Independent wire decoding and error injection supplement firmware readback. +assign s_gpio_15 = s_gpio_14; +wire s_p6_card_cmd; +wire [3:0] s_p6_card_data; +wire s_p6_card_irq; +sdio_native_card_model #( + .MemoryInitialization(1'b1), + .BlockCount (1024) +) u_p6_card ( + .rst_n_i (s_rst_n), + .sck_i (s_gpio[0]), + .cmd_oe_i(u_dut.u_soc.u_sdio_if.cmd_oe_o), + .cmd_do_i(s_gpio[1]), + .cmd_do_o(s_p6_card_cmd), + .dat_oe_i(u_dut.u_soc.u_sdio_if.dat_oe_o), + .dat_do_i(s_gpio[5:2]), + .dat_do_o(s_p6_card_data), + .irq_o (s_p6_card_irq) +); +assign s_gpio_1 = u_dut.u_soc.u_sdio_if.cmd_oe_o ? 1'bz : s_p6_card_cmd; +assign s_gpio_2 = u_dut.u_soc.u_sdio_if.dat_oe_o[0] ? 1'bz : s_p6_card_data[0]; +assign s_gpio_3 = u_dut.u_soc.u_sdio_if.dat_oe_o[1] ? 1'bz : s_p6_card_data[1]; +assign s_gpio_4 = u_dut.u_soc.u_sdio_if.dat_oe_o[2] ? 1'bz : s_p6_card_data[2]; +assign s_gpio_5 = u_dut.u_soc.u_sdio_if.dat_oe_o[3] ? 1'bz : s_p6_card_data[3]; +always @(negedge s_clk) begin + // Reuse the standalone card's explicit data-stimulus API. The wire command + // fixes direction/address/length; host DATA_START only schedules admission. + // This is repository controller integration evidence, not card compliance. + if (u_dut.u_soc.u_sdio.s_data_start) begin + if (u_p6_card.s_last_cmd != 17 && u_p6_card.s_last_cmd != 18 && u_p6_card.s_last_cmd != 25) + $fatal(1, "SIM_TEST_FAIL P6 unexpected SDIO data command"); + if (u_dut.u_soc.u_sdio.s_block_size != 512 || + u_dut.u_soc.u_sdio.s_block_count != (u_p6_card.s_last_cmd == 17 ? 1 : 2) || + u_dut.u_soc.u_sdio.s_data_direction != (u_p6_card.s_last_cmd == 25) || + u_dut.u_soc.u_sdio.s_bus_width != (u_p6_card.s_memory_width4 ? 1 : 0)) + $fatal(1, "SIM_TEST_FAIL P6 SDIO command/data configuration disagreement"); + u_p6_card.set_data_delay(1); + if (u_p6_card.s_last_cmd == 25) + u_p6_card.arm_write(integer'(u_p6_card.s_last_arg * 512), 1024, u_p6_card.s_memory_width4, + 1'b0); + else + u_p6_card.arm_read(integer'(u_p6_card.s_last_arg * 512), + u_p6_card.s_last_cmd == 17 ? 512 : 1024, u_p6_card.s_memory_width4, 1'b0); + end + if ($test$plusargs("p6_debug_sdio") && u_dut.u_soc.u_sdio.s_cmd_start) begin + $display("P6_SDIO_DIAGNOSTIC cmd=%0d argument=%h status=%h", u_dut.u_soc.u_sdio.s_cmd_index, + u_dut.u_soc.u_sdio.s_cmd_arg, u_dut.u_soc.u_sdio.s_status); + $fflush(); + end + if (u_dut.u_soc.u_sdio.s_cmd_start && u_dut.u_soc.u_sdio.s_cmd_index == 13) begin + if (u_dut.u_soc.u_sdio.s_cmd_arg == 32'hbadc0de0) u_p6_card.inject_next_crc_error(); + if (u_dut.u_soc.u_sdio.s_cmd_arg == 32'hbadc0de1) u_p6_card.inject_next_timeout(); + end +end diff --git a/rtl/tiny/filelist/clusterip.fl b/rtl/tiny/filelist/clusterip.fl index 33e91aeb..06ca583e 100644 --- a/rtl/tiny/filelist/clusterip.fl +++ b/rtl/tiny/filelist/clusterip.fl @@ -22,3 +22,10 @@ /clusterip/wdg/rtl/wdg_core.sv /clusterip/wdg/rtl/wdg_reg.sv /clusterip/wdg/rtl/apb4_wdg.sv +/clusterip/rng/rtl/rng_core.sv +/clusterip/rng/rtl/rng_reg.sv +/clusterip/rng/rtl/rng_deterministic_source.sv +/clusterip/rng/rtl/apb4_rng.sv +/clusterip/crc/rtl/crc_core.sv +/clusterip/crc/rtl/crc_reg.sv +/clusterip/crc/rtl/apb4_crc.sv diff --git a/rtl/tiny/filelist/inc.fl b/rtl/tiny/filelist/inc.fl index 6bfcb184..8fe3ddaa 100644 --- a/rtl/tiny/filelist/inc.fl +++ b/rtl/tiny/filelist/inc.fl @@ -17,3 +17,6 @@ +incdir+/ip/serial +incdir+/ip/peripheral +incdir+/ip/core ++incdir+/ip/security ++incdir+/clusterip/rng/rtl ++incdir+/clusterip/crc/rtl diff --git a/rtl/tiny/filelist/ip.fl b/rtl/tiny/filelist/ip.fl index 41aeb470..766a200b 100644 --- a/rtl/tiny/filelist/ip.fl +++ b/rtl/tiny/filelist/ip.fl @@ -47,3 +47,40 @@ /ip/core/mgmt_debug_wrapper.sv /ip/core/mgmt_core_wrapper.sv /ip/peripheral/gpio_pad_bridge.sv +/ip/storage/sdio_if.sv +/ip/storage/sdio_pkg.sv +/ip/storage/sdio_define.svh +/ip/storage/sdio_crc7.sv +/ip/storage/sdio_crc16.sv +/ip/storage/sdio_clock.sv +/ip/storage/sdio_command.sv +/ip/storage/sdio_data.sv +/ip/storage/sdio_dma_descriptor.sv +/ip/serial/i2s_pkg.sv +/ip/serial/i2s_reg.sv +/ip/serial/i2s_clkgen.sv +/ip/serial/i2s_recv.sv +/ip/serial/i2s_send.sv +/ip/serial/i2s_core.sv +/ip/serial/apb4_i2s.sv +/ip/serial/ws2812_if.sv +/ip/serial/ws2812_reg.sv +/ip/serial/ws2812_core.sv +/ip/serial/apb4_ws2812.sv +/ip/security/crypto_pkg.sv +/ip/security/crypto_mem_pkg.sv +/ip/security/crypto_define.svh +/ip/security/crypto_sram_store.sv +/ip/security/crypto_scrubber.sv +/ip/security/crypto_clearable_fifo.sv +/ip/security/crypto_mem_ctrl.sv +/ip/security/crypto_aes_sram_core.sv +/ip/security/crypto_aes_sram_engine.sv +/ip/security/crypto_sha2_sram_engine.sv +/ip/security/crypto_montgomery_sram.sv +/ip/security/crypto_rsa_sram_core.sv +/ip/security/apb4_crypto.sv +/ip/storage/sdio_dma.sv +/ip/storage/sdio_reg.sv +/ip/storage/sdio_core.sv +/ip/storage/apb4_sdio.sv diff --git a/rtl/tiny/filelist/tb.fl b/rtl/tiny/filelist/tb.fl index 106d6b51..da7d4e7d 100644 --- a/rtl/tiny/filelist/tb.fl +++ b/rtl/tiny/filelist/tb.fl @@ -1,2 +1,3 @@ /dv/tiny_qspi_nor.sv +/tests/rtl/sdio_native_model.sv /dv/retrosoc_tiny_tb.sv diff --git a/rtl/tiny/integration/clock_reset_domains.json b/rtl/tiny/integration/clock_reset_domains.json index 3f940e6b..af67d80d 100644 --- a/rtl/tiny/integration/clock_reset_domains.json +++ b/rtl/tiny/integration/clock_reset_domains.json @@ -1,10 +1,10 @@ { "schema_version": 2, "external_fabric": { - "phase": "TINY-R2-P5", + "phase": "TINY-R2-P6", "clock_domain": "system", "target_controllers": "u_soc.u_fabric.gen_target[%d].u_controller", - "target_count": 23, + "target_count": 27, "apb_bridges": "u_soc.u__bridge; corresponding existing peripheral reset leaf", "fabric_reset": "fabric leaf; target reset blocks admission and aborts accepted target work with DECERR", "hart_reset": "CPU-only accepted-work drain; DMA and all target responses stay active", @@ -61,9 +61,24 @@ "sta": { "source_port": "jtag_tck_i_pad", "object_type": "port", - "period_ns": 100.0, + "period_ns": 100, "async_group": "jtag" } + }, + { + "name": "audio", + "clock": "s_audio_clk", + "reset": "s_audio_rst_n", + "reset_source": "s_i2s_rst_n", + "reset_primitive": "rst_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", + "instance": "u_audio_rst_sync", + "sta": { + "source_port": "gpio_17_io_pad", + "pin": "u_soc.u_audio_clock_buffer/clk_o", + "period_ns": 54.253472222, + "async_group": "audio" + } } ], "reset_distribution": { @@ -73,8 +88,27 @@ "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" + "fabric", + "gpio", + "uart0", + "timer0", + "timer1", + "i2c0", + "xpi", + "dma", + "sysctrl", + "clint", + "sram", + "archinfo", + "pwm", + "rtc", + "wdg", + "rng", + "crc", + "ws2812", + "crypto", + "i2s", + "sdio" ], "cpu_prerequisite": "all leaf resets released", "hart": "u_soc.u_cpu.u_mgmt_debug_wrapper.u_mgmt_debug_reset.u_core_rst_sync", @@ -82,7 +116,7 @@ "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", + "crypto": "six private macro banks; reset invalidates then scrubs/readbacks; CPU does not wait for READY", "clock_domain": "system" }, "crossings": [ @@ -101,6 +135,54 @@ "primitive": "cdc_sync", "path": "rtl/ip/core/mgmt_debug_wrapper.sv", "instance": "u_dmihardreset_cdc_sync" + }, + { + "name": "i2s_configuration", + "source": "system", + "destination": "audio", + "primitive": "cdc_2phase", + "path": "rtl/ip/serial/apb4_i2s.sv", + "instance": "u_i2s_cfg_cdc" + }, + { + "name": "i2s_commands", + "source": "system", + "destination": "audio", + "primitive": "cdc_2phase", + "path": "rtl/ip/serial/apb4_i2s.sv", + "instance": "u_i2s_cmd_cdc" + }, + { + "name": "i2s_tx_samples", + "source": "system", + "destination": "audio", + "primitive": "cdc_fifo_warm_flush", + "path": "rtl/ip/serial/apb4_i2s.sv", + "instance": "u_tx_cdc_fifo" + }, + { + "name": "i2s_rx_samples", + "source": "audio", + "destination": "system", + "primitive": "cdc_fifo_warm_flush", + "path": "rtl/ip/serial/apb4_i2s.sv", + "instance": "u_rx_cdc_fifo" + }, + { + "name": "i2s_tx_occupancy", + "source": "audio", + "destination": "system", + "primitive": "cdc_2phase", + "path": "rtl/ip/serial/apb4_i2s.sv", + "instance": "u_tx_pop_cnt_cdc" + }, + { + "name": "i2s_rx_occupancy", + "source": "audio", + "destination": "system", + "primitive": "cdc_2phase", + "path": "rtl/ip/serial/apb4_i2s.sv", + "instance": "u_rx_push_cnt_cdc" } ] } diff --git a/rtl/tiny/integration/soc_topology.json b/rtl/tiny/integration/soc_topology.json index 27f27350..9dae7d3d 100644 --- a/rtl/tiny/integration/soc_topology.json +++ b/rtl/tiny/integration/soc_topology.json @@ -1,6 +1,10 @@ { "schema_version": 1, - "cpu": {"module": "hazard3_cpu_2port", "local_ports": 2, "data_outstanding": 1}, + "cpu": { + "module": "hazard3_cpu_2port", + "local_ports": 2, + "data_outstanding": 1 + }, "main_sram": { "groups": 4, "group_bytes": 32768, @@ -11,23 +15,36 @@ "external_attachment": "per-target-P5" }, "fabric": { - "owners": ["cpu_slow", "central_dma", "sdio_reserved"], - "active_owners": [0, 1], + "owners": [ + "cpu_slow", + "central_dma", + "sdio" + ], + "active_owners": [ + 0, + 1, + 2 + ], "cpu_combined_outstanding": 1, "dma_direction_outstanding": 1, "sdio_direction_outstanding": 1, "target_combined_outstanding": 1, - "sram_targets": [0, 1, 2, 3], + "sram_targets": [ + 0, + 1, + 2, + 3 + ], "xpi_target": 4, "apb_target_base": 5, - "decode_error_target": 21, - "protocol_error_target": 22, - "target_count": 23 + "decode_error_target": 25, + "protocol_error_target": 26, + "target_count": 27 }, "gpio_pins": 32, "irq_vector_width": 32, "external_irq_count": 30, - "dma_channels": 4, + "dma_channels": 8, "apb_targets": [ { "name": "gpio", @@ -42,12 +59,6 @@ "APB4_UART0" ] }, - { - "name": "uart1", - "regions": [ - "APB4_UART1" - ] - }, { "name": "timer0", "regions": [ @@ -66,12 +77,6 @@ "APB4_I2C0" ] }, - { - "name": "i2c1", - "regions": [ - "APB4_I2C1" - ] - }, { "name": "xpi", "regions": [ @@ -125,6 +130,42 @@ "regions": [ "APB4_WDG" ] + }, + { + "name": "rng", + "regions": [ + "APB4_RNG" + ] + }, + { + "name": "crc", + "regions": [ + "APB4_CRC" + ] + }, + { + "name": "ws2812", + "regions": [ + "APB4_WS2812" + ] + }, + { + "name": "crypto", + "regions": [ + "APB4_CRYPTO" + ] + }, + { + "name": "i2s", + "regions": [ + "APB4_I2S" + ] + }, + { + "name": "sdio", + "regions": [ + "APB4_SDIO0" + ] } ], "interrupts": [ @@ -158,11 +199,21 @@ "core_bit": 7, "signal": "u_i2c0_if.irq_o" }, + { + "name": "i2s", + "core_bit": 8, + "signal": "u_i2s_if.irq_o" + }, { "name": "xpi", "core_bit": 9, "signal": "s_xpi_irq" }, + { + "name": "sdio", + "core_bit": 10, + "signal": "u_sdio_if.irq_o" + }, { "name": "pwm", "core_bit": 11, @@ -178,36 +229,39 @@ "core_bit": 14, "signal": "u_wdg_if.irq_o" }, + { + "name": "rng", + "core_bit": 16, + "signal": "s_rng_irq" + }, + { + "name": "ws2812", + "core_bit": 17, + "signal": "u_ws2812_if.irq_o" + }, { "name": "gpio", "core_bit": 18, "signal": "u_gpio_if.irq_o" }, - { - "name": "i2c1", - "core_bit": 19, - "signal": "u_i2c1_if.irq_o" - }, { "name": "dma", "core_bit": 20, "signal": "s_dma_irq" }, { - "name": "uart1", - "core_bit": 26, - "signal": "u_uart1_if.irq_o" + "name": "crypto", + "core_bit": 23, + "signal": "s_crypto_irq" } ], "gpio_alt_functions": [ { "pin": 0, "alt0": { - "inputs": [ - "u_uart0_if.cts_n_i" - ], - "do": "1'b0", - "oe": "1'b0" + "inputs": [], + "do": "u_sdio_if.sck_o", + "oe": "1'b1" }, "alt1": { "inputs": [], @@ -218,9 +272,11 @@ { "pin": 1, "alt0": { - "inputs": [], - "do": "u_uart0_if.rts_n_o", - "oe": "1'b1" + "inputs": [ + "u_sdio_if.cmd_di_i" + ], + "do": "u_sdio_if.cmd_do_o", + "oe": "u_sdio_if.cmd_oe_o" }, "alt1": { "inputs": [], @@ -232,10 +288,10 @@ "pin": 2, "alt0": { "inputs": [ - "u_pwm_if.sync_i" + "u_sdio_if.dat_di_i[0]" ], - "do": "1'b0", - "oe": "1'b0" + "do": "u_sdio_if.dat_do_o[0]", + "oe": "u_sdio_if.dat_oe_o[0]" }, "alt1": { "inputs": [], @@ -245,36 +301,243 @@ }, { "pin": 3, + "alt0": { + "inputs": [ + "u_sdio_if.dat_di_i[1]" + ], + "do": "u_sdio_if.dat_do_o[1]", + "oe": "u_sdio_if.dat_oe_o[1]" + }, + "alt1": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + } + }, + { + "pin": 4, + "alt0": { + "inputs": [ + "u_sdio_if.dat_di_i[2]" + ], + "do": "u_sdio_if.dat_do_o[2]", + "oe": "u_sdio_if.dat_oe_o[2]" + }, + "alt1": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + } + }, + { + "pin": 5, + "alt0": { + "inputs": [ + "u_sdio_if.dat_di_i[3]" + ], + "do": "u_sdio_if.dat_do_o[3]", + "oe": "u_sdio_if.dat_oe_o[3]" + }, + "alt1": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + } + }, + { + "pin": 6, + "alt0": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + }, + "alt1": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + } + }, + { + "pin": 7, + "alt0": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + }, + "alt1": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + } + }, + { + "pin": 8, + "alt0": { + "inputs": [], + "do": "u_uart0_if.tx_o", + "oe": "1'b1" + }, + "alt1": { + "inputs": [], + "do": "u_pwm_if.pwm_o[0]", + "oe": "u_pwm_if.oe_o[0]" + } + }, + { + "pin": 9, + "alt0": { + "inputs": [ + "u_uart0_if.rx_i" + ], + "do": "1'b0", + "oe": "1'b0" + }, + "alt1": { + "inputs": [], + "do": "u_pwm_if.pwm_o[1]", + "oe": "u_pwm_if.oe_o[1]" + } + }, + { + "pin": 10, + "alt0": { + "inputs": [ + "u_i2c0_if.scl_i" + ], + "do": "u_i2c0_if.scl_o", + "oe": "u_i2c0_if.scl_oe_o" + }, + "alt1": { + "inputs": [], + "do": "u_pwm_if.pwm_o[2]", + "oe": "u_pwm_if.oe_o[2]" + } + }, + { + "pin": 11, + "alt0": { + "inputs": [ + "u_i2c0_if.sda_i" + ], + "do": "u_i2c0_if.sda_o", + "oe": "u_i2c0_if.sda_oe_o" + }, + "alt1": { + "inputs": [], + "do": "u_pwm_if.pwm_o[3]", + "oe": "u_pwm_if.oe_o[3]" + } + }, + { + "pin": 12, + "alt0": { + "inputs": [], + "do": "u_i2s_if.sclk_o", + "oe": "1'b1" + }, + "alt1": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + } + }, + { + "pin": 13, + "alt0": { + "inputs": [], + "do": "u_i2s_if.lrck_o", + "oe": "1'b1" + }, + "alt1": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + } + }, + { + "pin": 14, + "alt0": { + "inputs": [], + "do": "u_i2s_if.dacdat_o", + "oe": "1'b1" + }, + "alt1": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + } + }, + { + "pin": 15, + "alt0": { + "inputs": [ + "u_i2s_if.adcdat_i" + ], + "do": "1'b0", + "oe": "1'b0" + }, + "alt1": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + } + }, + { + "pin": 16, + "alt0": { + "inputs": [], + "do": "u_i2s_if.mclk_o", + "oe": "1'b1" + }, + "alt1": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + } + }, + { + "pin": 17, + "alt0": { + "inputs": [ + "s_audio_pad_clk" + ], + "do": "1'b0", + "oe": "1'b0" + }, + "alt1": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + } + }, + { + "pin": 18, "alt0": { "inputs": [], "do": "u_pwm_if.pwm_o[0]", "oe": "u_pwm_if.oe_o[0]" }, "alt1": { - "inputs": [ - "u_i2c1_if.scl_i" - ], - "do": "u_i2c1_if.scl_o", - "oe": "u_i2c1_if.scl_oe_o" + "inputs": [], + "do": "1'b0", + "oe": "1'b0" } }, { - "pin": 4, + "pin": 19, "alt0": { "inputs": [], "do": "u_pwm_if.pwm_o[1]", "oe": "u_pwm_if.oe_o[1]" }, "alt1": { - "inputs": [ - "u_i2c1_if.sda_i" - ], - "do": "u_i2c1_if.sda_o", - "oe": "u_i2c1_if.sda_oe_o" + "inputs": [], + "do": "1'b0", + "oe": "1'b0" } }, { - "pin": 5, + "pin": 20, "alt0": { "inputs": [], "do": "u_pwm_if.pwm_o[2]", @@ -287,7 +550,7 @@ } }, { - "pin": 6, + "pin": 21, "alt0": { "inputs": [], "do": "u_pwm_if.pwm_o[3]", @@ -300,37 +563,7 @@ } }, { - "pin": 7, - "alt0": { - "inputs": [ - "u_i2c0_if.scl_i" - ], - "do": "u_i2c0_if.scl_o", - "oe": "u_i2c0_if.scl_oe_o" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 8, - "alt0": { - "inputs": [ - "u_i2c0_if.sda_i" - ], - "do": "u_i2c0_if.sda_o", - "oe": "u_i2c0_if.sda_oe_o" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 9, + "pin": 22, "alt0": { "inputs": [ "u_pwm_if.fault_i" @@ -344,179 +577,12 @@ "oe": "1'b0" } }, - { - "pin": 10, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 11, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 12, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 13, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 14, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 15, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 16, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 17, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 18, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 19, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 20, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 21, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 22, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, { "pin": 23, "alt0": { - "inputs": [], + "inputs": [ + "u_pwm_if.sync_i" + ], "do": "1'b0", "oe": "1'b0" }, @@ -529,7 +595,9 @@ { "pin": 24, "alt0": { - "inputs": [], + "inputs": [ + "u_pwm_if.capture_i[0]" + ], "do": "1'b0", "oe": "1'b0" }, @@ -542,7 +610,9 @@ { "pin": 25, "alt0": { - "inputs": [], + "inputs": [ + "u_pwm_if.capture_i[1]" + ], "do": "1'b0", "oe": "1'b0" }, @@ -556,8 +626,8 @@ "pin": 26, "alt0": { "inputs": [], - "do": "1'b0", - "oe": "1'b0" + "do": "u_ws2812_if.dat_o", + "oe": "1'b1" }, "alt1": { "inputs": [], @@ -595,8 +665,8 @@ "pin": 29, "alt0": { "inputs": [], - "do": "1'b0", - "oe": "1'b0" + "do": "xpi.nss_o[1]", + "oe": "1'b1" }, "alt1": { "inputs": [], @@ -608,13 +678,11 @@ "pin": 30, "alt0": { "inputs": [], - "do": "1'b0", - "oe": "1'b0" + "do": "xpi.nss_o[2]", + "oe": "1'b1" }, "alt1": { - "inputs": [ - "u_pwm_if.capture_i[0]" - ], + "inputs": [], "do": "1'b0", "oe": "1'b0" } @@ -623,16 +691,22 @@ "pin": 31, "alt0": { "inputs": [], - "do": "1'b0", - "oe": "1'b0" + "do": "xpi.nss_o[3]", + "oe": "1'b1" }, "alt1": { - "inputs": [ - "u_pwm_if.capture_i[1]" - ], + "inputs": [], "do": "1'b0", "oe": "1'b0" } } - ] + ], + "dma_request_mask": "0x31ff", + "private_crypto": { + "banks": 6, + "bank_bytes": 4096, + "macro_instance": "u_soc.u_crypto.u_storage.gen_bank[{bank}].gen_macro.u_sram.u_mem", + "clock_domain": "system", + "logical_domain": "PCLK (SAFE24 alias)" + } } diff --git a/rtl/tiny/mk/config.mk b/rtl/tiny/mk/config.mk index 86ee40ca..1b89e8b9 100644 --- a/rtl/tiny/mk/config.mk +++ b/rtl/tiny/mk/config.mk @@ -56,7 +56,7 @@ ifneq ($(ISA),RV32IM) $(error SOC=TINY firmware uses ISA=RV32IM) endif ifneq ($(WS2812_P3_ACCEPTANCE),NO) -$(error SOC=TINY does not yet integrate WS2812) +$(error SOC=TINY uses its integrated P6 acceptance, not the Mini WS2812 P3 image) endif ifneq ($(filter YES,$(APU_ENABLE_P7) $(NPU_P5_ACCEPTANCE) $(NPU_P6_ACCEPTANCE)),) $(error SOC=TINY does not include multimedia accelerators) diff --git a/rtl/tiny/pin_map/pin_map.json b/rtl/tiny/pin_map/pin_map.json index be65c3cb..23a086df 100644 --- a/rtl/tiny/pin_map/pin_map.json +++ b/rtl/tiny/pin_map/pin_map.json @@ -205,38 +205,38 @@ "jtag_tdi_i_pad": "s_jtag_tdi", "jtag_trst_n_i_pad": "s_jtag_trst_n", "jtag_tdo_o_pad": "s_jtag_tdo", - "gpio_0_io_pad": "s_gpio[0]", - "gpio_1_io_pad": "s_gpio[1]", - "gpio_2_io_pad": "s_gpio[2]", - "gpio_3_io_pad": "s_gpio[3]", - "gpio_4_io_pad": "s_gpio[4]", - "gpio_5_io_pad": "s_gpio[5]", - "gpio_6_io_pad": "s_gpio[6]", - "gpio_7_io_pad": "s_gpio[7]", - "gpio_8_io_pad": "s_gpio[8]", - "gpio_9_io_pad": "s_gpio[9]", - "gpio_10_io_pad": "s_gpio[10]", - "gpio_11_io_pad": "s_gpio[11]", - "gpio_12_io_pad": "s_gpio[12]", - "gpio_13_io_pad": "s_gpio[13]", - "gpio_14_io_pad": "s_gpio[14]", - "gpio_15_io_pad": "s_gpio[15]", - "gpio_16_io_pad": "s_gpio[16]", - "gpio_17_io_pad": "s_gpio[17]", - "gpio_18_io_pad": "s_gpio[18]", - "gpio_19_io_pad": "s_gpio[19]", - "gpio_20_io_pad": "s_gpio[20]", - "gpio_21_io_pad": "s_gpio[21]", - "gpio_22_io_pad": "s_gpio[22]", - "gpio_23_io_pad": "s_gpio[23]", - "gpio_24_io_pad": "s_gpio[24]", - "gpio_25_io_pad": "s_gpio[25]", - "gpio_26_io_pad": "s_gpio[26]", - "gpio_27_io_pad": "s_gpio[27]", - "gpio_28_io_pad": "s_gpio[28]", - "gpio_29_io_pad": "s_gpio[29]", - "gpio_30_io_pad": "s_gpio[30]", - "gpio_31_io_pad": "s_gpio[31]", + "gpio_0_io_pad": "s_gpio_0", + "gpio_1_io_pad": "s_gpio_1", + "gpio_2_io_pad": "s_gpio_2", + "gpio_3_io_pad": "s_gpio_3", + "gpio_4_io_pad": "s_gpio_4", + "gpio_5_io_pad": "s_gpio_5", + "gpio_6_io_pad": "s_gpio_6", + "gpio_7_io_pad": "s_gpio_7", + "gpio_8_io_pad": "s_gpio_8", + "gpio_9_io_pad": "s_gpio_9", + "gpio_10_io_pad": "s_gpio_10", + "gpio_11_io_pad": "s_gpio_11", + "gpio_12_io_pad": "s_gpio_12", + "gpio_13_io_pad": "s_gpio_13", + "gpio_14_io_pad": "s_gpio_14", + "gpio_15_io_pad": "s_gpio_15", + "gpio_16_io_pad": "s_gpio_16", + "gpio_17_io_pad": "s_gpio_17", + "gpio_18_io_pad": "s_gpio_18", + "gpio_19_io_pad": "s_gpio_19", + "gpio_20_io_pad": "s_gpio_20", + "gpio_21_io_pad": "s_gpio_21", + "gpio_22_io_pad": "s_gpio_22", + "gpio_23_io_pad": "s_gpio_23", + "gpio_24_io_pad": "s_gpio_24", + "gpio_25_io_pad": "s_gpio_25", + "gpio_26_io_pad": "s_gpio_26", + "gpio_27_io_pad": "s_gpio_27", + "gpio_28_io_pad": "s_gpio_28", + "gpio_29_io_pad": "s_gpio_29", + "gpio_30_io_pad": "s_gpio_30", + "gpio_31_io_pad": "s_gpio_31", "uart0_tx_o_pad": "s_uart0_tx", "uart0_rx_i_pad": "s_uart0_rx", "uart1_tx_o_pad": "s_uart1_tx", @@ -261,38 +261,38 @@ "jtag_tdi_i_pad": "s_jtag_tdi", "jtag_trst_n_i_pad": "s_jtag_trst_n", "jtag_tdo_o_pad": "s_jtag_tdo", - "gpio_0_io_pad": "s_gpio[0]", - "gpio_1_io_pad": "s_gpio[1]", - "gpio_2_io_pad": "s_gpio[2]", - "gpio_3_io_pad": "s_gpio[3]", - "gpio_4_io_pad": "s_gpio[4]", - "gpio_5_io_pad": "s_gpio[5]", - "gpio_6_io_pad": "s_gpio[6]", - "gpio_7_io_pad": "s_gpio[7]", - "gpio_8_io_pad": "s_gpio[8]", - "gpio_9_io_pad": "s_gpio[9]", - "gpio_10_io_pad": "s_gpio[10]", - "gpio_11_io_pad": "s_gpio[11]", - "gpio_12_io_pad": "s_gpio[12]", - "gpio_13_io_pad": "s_gpio[13]", - "gpio_14_io_pad": "s_gpio[14]", - "gpio_15_io_pad": "s_gpio[15]", - "gpio_16_io_pad": "s_gpio[16]", - "gpio_17_io_pad": "s_gpio[17]", - "gpio_18_io_pad": "s_gpio[18]", - "gpio_19_io_pad": "s_gpio[19]", - "gpio_20_io_pad": "s_gpio[20]", - "gpio_21_io_pad": "s_gpio[21]", - "gpio_22_io_pad": "s_gpio[22]", - "gpio_23_io_pad": "s_gpio[23]", - "gpio_24_io_pad": "s_gpio[24]", - "gpio_25_io_pad": "s_gpio[25]", - "gpio_26_io_pad": "s_gpio[26]", - "gpio_27_io_pad": "s_gpio[27]", - "gpio_28_io_pad": "s_gpio[28]", - "gpio_29_io_pad": "s_gpio[29]", - "gpio_30_io_pad": "s_gpio[30]", - "gpio_31_io_pad": "s_gpio[31]", + "gpio_0_io_pad": "s_gpio_0", + "gpio_1_io_pad": "s_gpio_1", + "gpio_2_io_pad": "s_gpio_2", + "gpio_3_io_pad": "s_gpio_3", + "gpio_4_io_pad": "s_gpio_4", + "gpio_5_io_pad": "s_gpio_5", + "gpio_6_io_pad": "s_gpio_6", + "gpio_7_io_pad": "s_gpio_7", + "gpio_8_io_pad": "s_gpio_8", + "gpio_9_io_pad": "s_gpio_9", + "gpio_10_io_pad": "s_gpio_10", + "gpio_11_io_pad": "s_gpio_11", + "gpio_12_io_pad": "s_gpio_12", + "gpio_13_io_pad": "s_gpio_13", + "gpio_14_io_pad": "s_gpio_14", + "gpio_15_io_pad": "s_gpio_15", + "gpio_16_io_pad": "s_gpio_16", + "gpio_17_io_pad": "s_gpio_17", + "gpio_18_io_pad": "s_gpio_18", + "gpio_19_io_pad": "s_gpio_19", + "gpio_20_io_pad": "s_gpio_20", + "gpio_21_io_pad": "s_gpio_21", + "gpio_22_io_pad": "s_gpio_22", + "gpio_23_io_pad": "s_gpio_23", + "gpio_24_io_pad": "s_gpio_24", + "gpio_25_io_pad": "s_gpio_25", + "gpio_26_io_pad": "s_gpio_26", + "gpio_27_io_pad": "s_gpio_27", + "gpio_28_io_pad": "s_gpio_28", + "gpio_29_io_pad": "s_gpio_29", + "gpio_30_io_pad": "s_gpio_30", + "gpio_31_io_pad": "s_gpio_31", "uart0_tx_o_pad": "s_uart0_tx", "uart0_rx_i_pad": "s_uart0_rx", "uart1_tx_o_pad": "s_uart1_tx", diff --git a/rtl/tiny/top/retrosoc_tiny.sv b/rtl/tiny/top/retrosoc_tiny.sv index bb07d932..5677dc40 100644 --- a/rtl/tiny/top/retrosoc_tiny.sv +++ b/rtl/tiny/top/retrosoc_tiny.sv @@ -1,6 +1,7 @@ // Copyright (c) 2026 Yuchi Miao // SPDX-License-Identifier: MulanPSL-2.0 `include "mmap_define.svh" +`include "tiny_topology.svh" module retrosoc_tiny ( input logic clk_i, @@ -21,10 +22,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 [`RETROSOC_TINY__LEAF_COUNT-1:0] s_leaf_rst_n; logic s_fabric_rst_n, s_cpu_ready_rst_n; - logic s_master_rst_n [ 3]; - logic s_target_rst_n [23]; + logic s_master_rst_n [ 3]; + logic s_target_rst_n [`RETROSOC_TINY__TARGET_COUNT]; logic s_debug_halted; logic s_core_rst_n, s_cpu_bus_idle, s_cpu_quiesce; logic [31:0] s_irq; @@ -42,10 +43,14 @@ module retrosoc_tiny ( logic [63:0] s_cpu_wait, s_dma_wait; logic s_timer0_irq, s_timer1_irq, s_xpi_irq, s_dma_irq; logic s_dma_done; - logic [7:0] s_dma_stall; + logic [5:0] s_dma_stall; 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; + logic s_audio_pad_clk, s_audio_clk, s_audio_rst_n; + logic s_rng_en, s_rng_ready, s_rng_valid, s_rng_qualified, s_rng_fault, s_rng_irq; + logic [31:0] s_rng_data; + logic s_crypto_irq, s_crypto_input_ready, s_crypto_output_ready; + logic s_i2s_tx_stall, s_i2s_rx_stall; ahbl_if u_cpu_ahbl_if[2] ( .hclk (clk_i), .hresetn(s_core_rst_n) @@ -62,14 +67,15 @@ module retrosoc_tiny ( ); `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 (); pwm_if u_pwm_if (); clint_if u_clint_if (); gpio_if u_gpio_if (); user_gpio_if u_user_gpio_if (); dma_req_if u_dma_req_if (); + i2s_if u_i2s_if (); + ws2812_if u_ws2812_if (); + sdio_if u_sdio_if (); rtc_if u_rtc_if ( .rtc_clk_i (clk_i), .rtc_rst_n_i(s_rtc_rst_n) @@ -94,7 +100,7 @@ uart_if u_uart0_if (); .DATA_WIDTH(32), .ID_WIDTH (1), .USER_WIDTH(1) - ) u_targets_axi4_if[23] ( + ) u_targets_axi4_if[`RETROSOC_TINY__TARGET_COUNT] ( .aclk (clk_i), .aresetn(s_target_rst_n) ); @@ -128,46 +134,14 @@ uart_if u_uart0_if (); .aclk (clk_i), .aresetn(s_dma_rst_n) ); - assign s_fabric_rst_n = s_leaf_rst_n[0]; - assign s_master_rst_n = '{s_cpu_ready_rst_n, s_dma_rst_n, s_fabric_rst_n}; - assign s_target_rst_n[4] = s_xpi_rst_n; - assign s_target_rst_n[21] = s_fabric_rst_n; - assign s_target_rst_n[22] = s_fabric_rst_n; - // Owner 2 is a reserved native read/write ingress, not an integrated SDIO. - // P6 supplies its real producer; no capability or interrupt is enabled here. - assign u_masters_axi4_if[2].awid = '0; - assign u_masters_axi4_if[2].awaddr = '0; - assign u_masters_axi4_if[2].awlen = '0; - assign u_masters_axi4_if[2].awsize = '0; - assign u_masters_axi4_if[2].awburst = '0; - assign u_masters_axi4_if[2].awlock = 1'b0; - assign u_masters_axi4_if[2].awcache = '0; - assign u_masters_axi4_if[2].awprot = '0; - assign u_masters_axi4_if[2].awqos = '0; - assign u_masters_axi4_if[2].awregion = '0; - assign u_masters_axi4_if[2].awuser = '0; - assign u_masters_axi4_if[2].awvalid = 1'b0; - assign u_masters_axi4_if[2].arid = '0; - assign u_masters_axi4_if[2].araddr = '0; - assign u_masters_axi4_if[2].arlen = '0; - assign u_masters_axi4_if[2].arsize = '0; - assign u_masters_axi4_if[2].arburst = '0; - assign u_masters_axi4_if[2].arlock = 1'b0; - assign u_masters_axi4_if[2].arcache = '0; - assign u_masters_axi4_if[2].arprot = '0; - assign u_masters_axi4_if[2].arqos = '0; - assign u_masters_axi4_if[2].arregion = '0; - assign u_masters_axi4_if[2].aruser = '0; - assign u_masters_axi4_if[2].arvalid = 1'b0; - assign u_masters_axi4_if[2].wdata = '0; - assign u_masters_axi4_if[2].wstrb = '0; - assign u_masters_axi4_if[2].wlast = 1'b0; - assign u_masters_axi4_if[2].wuser = '0; - assign u_masters_axi4_if[2].wvalid = 1'b0; - assign u_masters_axi4_if[2].rready = 1'b0; - assign u_masters_axi4_if[2].bready = 1'b0; - - tiny_reset_tree u_reset_tree ( + assign s_fabric_rst_n = s_leaf_rst_n[0]; + assign s_master_rst_n = '{s_cpu_ready_rst_n, s_dma_rst_n, s_sdio_rst_n}; + assign s_target_rst_n[4] = s_xpi_rst_n; + assign s_target_rst_n[`RETROSOC_TINY__DECODE_ERROR] = s_fabric_rst_n; + assign s_target_rst_n[`RETROSOC_TINY__PROTOCOL_ERROR] = s_fabric_rst_n; + tiny_reset_tree #( + .LeafCount(`RETROSOC_TINY__LEAF_COUNT) + ) u_reset_tree ( .clk_i (clk_i), .rst_n_i (rst_n_i), .watchdog_reset_req_i(u_wdg_if.reset_req_o), @@ -267,14 +241,14 @@ axi4_error_slave #( ) u_decode_error ( .clk_i (clk_i), .rst_n_i(s_fabric_rst_n), - .axi4 (u_targets_axi4_if[21]) + .axi4 (u_targets_axi4_if[`RETROSOC_TINY__DECODE_ERROR]) ); axi4_error_slave #( .Response(2'b10) ) u_protocol_error ( .clk_i (clk_i), .rst_n_i(s_fabric_rst_n), - .axi4 (u_targets_axi4_if[22]) + .axi4 (u_targets_axi4_if[`RETROSOC_TINY__PROTOCOL_ERROR]) ); tiny_fault_collector u_fault_collector ( .local_valid_i (s_local_fault_valid), @@ -331,11 +305,10 @@ axi4_error_slave #( ); assign u_user_gpio_if.do_o = '0; assign u_user_gpio_if.oe_o = '0; - assign u_uart0_if.rx_i = uart0_rx_i; assign uart0_tx_o = u_uart0_if.tx_o; - assign u_uart1_if.rx_i = uart1_rx_i; - assign u_uart1_if.cts_n_i = 1'b0; - assign uart1_tx_o = u_uart1_if.tx_o; + // Legacy ASIC observation pads are retained, not a Gen1 package binding. + assign uart1_tx_o = 1'b1; + assign u_uart0_if.cts_n_i = 1'b0; assign u_wdg_if.debug_halted_i = s_debug_halted; `include "tiny_gpio.svh" `include "tiny_irq.svh" @@ -362,22 +335,6 @@ apb4_uart u_uart0 ( .apb4 (u_timer0_apb4_if), .irq_o (s_timer0_irq) ); - apb4_uart u_uart1 ( - .clk_i (clk_i), - .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), - .dma_rx_stall_o(s_unused_uart1_rx_stall) - ); - apb4_i2c u_i2c1 ( - .clk_i (clk_i), - .rst_n_i (s_i2c1_rst_n), - .apb4 (u_i2c1_apb4_if), - .i2c (u_i2c1_if), - .dma_tx_stall_o(s_dma_stall[6]), - .dma_rx_stall_o(s_dma_stall[7]) - ); apb4_timer u_timer1 ( .clk_i (clk_i), .rst_n_i (s_timer1_rst_n), @@ -413,11 +370,11 @@ apb4_uart u_uart0 ( .wdg (u_wdg_if) ); apb4_dma #( - .NumChannels (4), + .NumChannels (8), .MaxBurstBeats(16), .FifoDepth (32), - .RequestMask (16'h07f9), - .EnableStreams(1'b0) + .RequestMask (16'h31ff), + .EnableStreams(1'b1) ) u_dma ( .clk_i (clk_i), .rst_n_i (s_dma_rst_n), @@ -432,26 +389,18 @@ apb4_uart u_uart0 ( .crypto_in_axis (u_crypto_tx_axis_if), .crypto_out_axis(u_crypto_rx_axis_if) ); - assign u_dma_req_if.i2s_tx_proc = 1'b0; - assign u_dma_req_if.i2s_rx_proc = 1'b0; - assign u_dma_req_if.crypto_in_proc = 1'b0; - assign u_dma_req_if.crypto_out_proc = 1'b0; + assign u_dma_req_if.i2s_tx_proc = !s_i2s_tx_stall; + assign u_dma_req_if.i2s_rx_proc = !s_i2s_rx_stall; + assign u_dma_req_if.crypto_in_proc = s_crypto_input_ready; + assign u_dma_req_if.crypto_out_proc = s_crypto_output_ready; assign u_dma_req_if.qspi_tx_proc = !s_dma_stall[0]; assign u_dma_req_if.qspi_rx_proc = !s_dma_stall[1]; assign u_dma_req_if.uart_tx_proc = !s_dma_stall[2]; assign u_dma_req_if.uart_rx_proc = !s_dma_stall[3]; assign u_dma_req_if.i2c0_tx_proc = !s_dma_stall[4]; assign u_dma_req_if.i2c0_rx_proc = !s_dma_stall[5]; - assign u_dma_req_if.i2c1_tx_proc = !s_dma_stall[6]; - assign u_dma_req_if.i2c1_rx_proc = !s_dma_stall[7]; - assign u_i2s_rx_axis_if.tvalid = '0; - assign u_i2s_rx_axis_if.tdata = '0; - assign u_i2s_rx_axis_if.tstrb = '0; - assign u_i2s_rx_axis_if.tkeep = '0; - assign u_i2s_rx_axis_if.tlast = '0; - assign u_i2s_rx_axis_if.tid = '0; - assign u_i2s_rx_axis_if.tdest = '0; - assign u_i2s_rx_axis_if.tuser = '0; + assign u_dma_req_if.i2c1_tx_proc = 1'b0; + assign u_dma_req_if.i2c1_rx_proc = 1'b0; assign u_dvp_rx_axis_if.tvalid = '0; assign u_dvp_rx_axis_if.tdata = '0; assign u_dvp_rx_axis_if.tstrb = '0; @@ -460,14 +409,80 @@ apb4_uart u_uart0 ( assign u_dvp_rx_axis_if.tid = '0; assign u_dvp_rx_axis_if.tdest = '0; assign u_dvp_rx_axis_if.tuser = '0; - assign u_crypto_rx_axis_if.tvalid = '0; - assign u_crypto_rx_axis_if.tdata = '0; - assign u_crypto_rx_axis_if.tstrb = '0; - assign u_crypto_rx_axis_if.tkeep = '0; - assign u_crypto_rx_axis_if.tlast = '0; - assign u_crypto_rx_axis_if.tid = '0; - assign u_crypto_rx_axis_if.tdest = '0; - assign u_crypto_rx_axis_if.tuser = '0; - assign u_i2s_tx_axis_if.tready = 1'b0; - assign u_crypto_tx_axis_if.tready = 1'b0; + + // Host logic remains SAFE24. Audio reset waits for its own clock, never CPU boot. + tc_clk_buf u_audio_clock_buffer ( + .clk_i(s_audio_pad_clk), + .clk_o(s_audio_clk) + ); + rst_sync #( + .STAGE(5) + ) u_audio_rst_sync ( + .clk_i (s_audio_clk), + .rst_n_i(s_i2s_rst_n), + .rst_n_o(s_audio_rst_n) + ); + apb4_i2s u_i2s ( + .clk_i (clk_i), + .rst_n_i (s_i2s_rst_n), + .clk_aud_i (s_audio_clk), + .rst_aud_n_i (s_audio_rst_n), + .dma_tx_stall_o (s_i2s_tx_stall), + .dma_rx_stall_o (s_i2s_rx_stall), + .tx_underrun_evt_o(), // Sticky status and IRQ retain these events. + .rx_overrun_evt_o (), + .rx_flush_busy_o (), // No APU consumer in Tiny. + .apb4 (u_i2s_apb4_if), + .tx_axis (u_i2s_tx_axis_if), + .rx_axis (u_i2s_rx_axis_if), + .i2s (u_i2s_if) + ); + apb4_sdio #( + .InputClockHz(24_000_000) + ) u_sdio ( + .clk_i (clk_i), + .rst_n_i (s_sdio_rst_n), + .apb4 (u_sdio_apb4_if), + .dma_axi4(u_masters_axi4_if[2]), + .sdio (u_sdio_if) + ); + apb4_ws2812 u_ws2812 ( + .clk_i (clk_i), + .rst_n_i(s_ws2812_rst_n), + .apb4 (u_ws2812_apb4_if), + .ws2812 (u_ws2812_if) + ); + apb4_crypto u_crypto ( + .clk_i (clk_i), + .rst_n_i (s_crypto_rst_n), + .dma_input_proc_o (s_crypto_input_ready), + .dma_output_proc_o(s_crypto_output_ready), + .irq_o (s_crypto_irq), + .apb4 (u_crypto_apb4_if), + .crypto_in_axis (u_crypto_tx_axis_if), + .crypto_out_axis (u_crypto_rx_axis_if) + ); + apb4_crc u_crc (.apb4(u_crc_apb4_if)); + rng_deterministic_source u_rng_source ( + .clk_i (clk_i), + .rst_n_i (s_rng_rst_n), + .enable_i (s_rng_en), + .ready_i (s_rng_ready), + .valid_o (s_rng_valid), + .data_o (s_rng_data), + .qualified_o(s_rng_qualified), + .fault_o (s_rng_fault) + ); + apb4_rng #( + .FIFO_DEPTH(8) + ) u_rng ( + .entropy_enable_o (s_rng_en), + .entropy_ready_o (s_rng_ready), + .entropy_valid_i (s_rng_valid), + .entropy_data_i (s_rng_data), + .entropy_qualified_i(s_rng_qualified), + .entropy_fault_i (s_rng_fault), + .irq_o (s_rng_irq), + .apb4 (u_rng_apb4_if) + ); endmodule diff --git a/rtl/tiny/top/tiny_axi42apb4.sv b/rtl/tiny/top/tiny_axi42apb4.sv index 6d055d14..d0b48b2d 100644 --- a/rtl/tiny/top/tiny_axi42apb4.sv +++ b/rtl/tiny/top/tiny_axi42apb4.sv @@ -57,7 +57,7 @@ module tiny_axi42apb4 ( assign apb4.paddr = s_addr_q; assign apb4.pwrite = s_write_q; assign apb4.pwdata = s_wdata_q; - assign apb4.pstrb = s_wstrb_q; + assign apb4.pstrb = s_write_q ? s_wstrb_q : 4'd0; assign apb4.pprot = 3'd0; assign apb4.psel = s_access; assign apb4.penable = s_enable; diff --git a/rtl/tiny/top/tiny_axi4_fabric.sv b/rtl/tiny/top/tiny_axi4_fabric.sv index 09cd698f..2446037f 100644 --- a/rtl/tiny/top/tiny_axi4_fabric.sv +++ b/rtl/tiny/top/tiny_axi4_fabric.sv @@ -1,47 +1,50 @@ // Copyright (c) 2026 Yuchi Miao // SPDX-License-Identifier: MulanPSL-2.0 `include "mmap_define.svh" +`include "tiny_topology.svh" -// Three reserved owner slots, 23 independently owned targets. CPU admits one +// Three active owners and topology-selected independent targets. CPU admits one // combined operation; DMA and SDIO each admit one read and one write. IDs are // unchanged: saved target ownership, not ID expansion, routes all responses. module tiny_axi4_fabric ( - input logic clk_i, - input logic rst_n_i, - input logic perf_enable_i, - input logic perf_clear_i, - input logic target_rst_n_i [23], - axi4_if.slave masters [ 3], - axi4_if.master targets [23], - output logic [ 5:0] fault_valid_o, - output logic [ 5:0][31:0] fault_addr_o, - output logic [ 5:0][ 1:0] fault_resp_o, - output logic [ 5:0] fault_instruction_o, - output logic [ 2:0] owner_idle_o, - output logic [22:0] target_idle_o, - output logic [63:0] cpu_wait_o, - output logic [63:0] dma_wait_o + input logic clk_i, + input logic rst_n_i, + input logic perf_enable_i, + input logic perf_clear_i, + input logic target_rst_n_i[`RETROSOC_TINY__TARGET_COUNT], + axi4_if.slave masters[3], + axi4_if.master targets[`RETROSOC_TINY__TARGET_COUNT], + output logic [5:0] fault_valid_o, + output logic [5:0][31:0] fault_addr_o, + output logic [5:0][1:0] fault_resp_o, + output logic [5:0] fault_instruction_o, + output logic [2:0] owner_idle_o, + output logic [`RETROSOC_TINY__TARGET_COUNT-1:0] target_idle_o, + output logic [63:0] cpu_wait_o, + output logic [63:0] dma_wait_o ); + localparam logic [4:0] DecodeErrorTarget = 5'(`RETROSOC_TINY__DECODE_ERROR); + localparam logic [4:0] ProtocolErrorTarget = 5'(`RETROSOC_TINY__PROTOCOL_ERROR); logic [5:0] s_busy_d, s_busy_q, s_admit, s_done, s_eligible; logic s_cpu_prefer_write_d, s_cpu_prefer_write_q; logic [2:0][4:0] s_read_target, s_write_target; - logic [22:0][2:0] s_arready, s_awready, s_wready, s_rvalid, s_bvalid; - logic [22:0][ 2:0][36:0] s_rdata; - logic [22:0][ 2:0][ 3:0] s_bdata; - logic [ 2:0][36:0] s_read_return; - logic [ 2:0][ 3:0] s_write_return; + logic [`RETROSOC_TINY__TARGET_COUNT-1:0][2:0] s_arready, s_awready, s_wready, s_rvalid, s_bvalid; + logic [`RETROSOC_TINY__TARGET_COUNT-1:0][ 2:0][36:0] s_rdata; + logic [`RETROSOC_TINY__TARGET_COUNT-1:0][ 2:0][ 3:0] s_bdata; + logic [ 2:0][36:0] s_read_return; + logic [ 2:0][ 3:0] s_write_return; logic [2:0] s_master_arready, s_master_awready, s_master_wready, s_master_rvalid, s_master_bvalid; logic [2:0] s_arvalid, s_awvalid; logic [1:0][63:0] s_wait_d, s_wait_q; - logic [22:0] s_fault_valid, s_fault_write, s_fault_instruction; - logic [22:0][31:0] s_fault_addr; - logic [22:0][1:0] s_fault_master, s_fault_resp; + logic [`RETROSOC_TINY__TARGET_COUNT-1:0] s_fault_valid, s_fault_write, s_fault_instruction; + logic [`RETROSOC_TINY__TARGET_COUNT-1:0][31:0] s_fault_addr; + logic [`RETROSOC_TINY__TARGET_COUNT-1:0][1:0] s_fault_master, s_fault_resp; function automatic logic [4:0] decode_target(input logic [31:0] addr_i); if (`SOC_ADDR_IS_SRAM(addr_i)) return {3'd0, addr_i[16:15]}; if (`SOC_ADDR_IS_FLASH(addr_i) || `SOC_ADDR_IS_XPI(addr_i)) return 5'd4; `include "tiny_fabric_decode.svh" - return 5'd21; + return DecodeErrorTarget; endfunction function automatic logic [4:0] checked_target(input logic [31:0] addr_i, input logic [7:0] len_i, input logic [2:0] size_i, input logic [1:0] burst_i, @@ -56,12 +59,14 @@ module tiny_axi4_fabric ( last_addr[32] || (last_addr[31:12] != addr_i[31:12]) || (decode_target( last_addr[31:0] - ) != decoded) || ((decoded >= 5'd5) && (decoded <= 5'd20) && (len_i != 8'd0))) - return 5'd22; + ) != decoded) || ((decoded >= 5'd5) && (decoded < DecodeErrorTarget) && (len_i != 8'd0))) + return ProtocolErrorTarget; if (instruction_i && !( `SOC_ADDR_IS_FLASH(addr_i) - || ((addr_i >= 32'h5000_0000) && (addr_i < 32'h5400_0000)))) - return ((decoded >= 5'd5) && (decoded <= 5'd20)) ? 5'd22 : 5'd21; + || ((addr_i >= 32'h5000_0000) && (addr_i < 32'h5400_0000)))) begin + if ((decoded >= 5'd5) && (decoded < DecodeErrorTarget)) return ProtocolErrorTarget; + return DecodeErrorTarget; + end return decoded; endfunction @@ -106,7 +111,7 @@ module tiny_axi4_fabric ( fault_addr_o = '0; fault_resp_o = '0; fault_instruction_o = '0; - for (int target_index = 0; target_index < 23; target_index++) begin + for (int target_index = 0; target_index < `RETROSOC_TINY__TARGET_COUNT; target_index++) begin s_master_arready |= s_arready[target_index]; s_master_awready |= s_awready[target_index]; s_master_wready |= s_wready[target_index]; @@ -179,7 +184,9 @@ module tiny_axi4_fabric ( masters[owner].rlast, masters[owner].ruser} = s_read_return[owner]; assign {masters[owner].bid, masters[owner].bresp, masters[owner].buser} = s_write_return[owner]; end - for (genvar target_index = 0; target_index < 23; target_index++) begin : gen_target + for ( + genvar target_index = 0; target_index < `RETROSOC_TINY__TARGET_COUNT; target_index++ + ) begin : gen_target axi4_if #( .ADDR_WIDTH(32), .DATA_WIDTH(32), @@ -260,8 +267,8 @@ module tiny_axi4_fabric ( assert property (@(posedge clk_i) disable iff (!rst_n_i) (s_admit & s_busy_q) == 6'd0); assert property (@(posedge clk_i) disable iff (!rst_n_i) (s_done & ~s_busy_q) == 6'd0); for (genvar owner = 0; owner < 3; owner++) begin : gen_route_assert - logic [22:0] s_reads, s_writes; - for (genvar t = 0; t < 23; t++) begin : gen_column + logic [`RETROSOC_TINY__TARGET_COUNT-1:0] s_reads, s_writes; + for (genvar t = 0; t < `RETROSOC_TINY__TARGET_COUNT; t++) begin : gen_column assign s_reads[t] = s_rvalid[t][owner]; assign s_writes[t] = s_bvalid[t][owner]; end diff --git a/scripts/generate_tiny.py b/scripts/generate_tiny.py index 8fb32b2c..631c0606 100644 --- a/scripts/generate_tiny.py +++ b/scripts/generate_tiny.py @@ -21,8 +21,9 @@ TINY = ROOT / "rtl/tiny" def read_topology(path: Path, memory_map: Path) -> dict: document = json.loads(path.read_text()) regions = {r["symbol"] for r in json.loads(memory_map.read_text())["regions"]} - if document.get("schema_version") != 1 or document.get("dma_channels") != 4: - raise ValueError("Tiny requires topology schema 1 and four DMA channels") + if (document.get("schema_version") != 1 or document.get("dma_channels") != 8 + or document.get("dma_request_mask") != "0x31ff"): + raise ValueError("Tiny P6 requires eight DMA channels and request mask 0x31ff") if document.get("cpu") != {"module": "hazard3_cpu_2port", "local_ports": 2, "data_outstanding": 1}: raise ValueError("Tiny P4 requires independent I/D and one ordered data operation") @@ -35,13 +36,13 @@ def read_topology(path: Path, memory_map: Path) -> dict: }.items()): raise ValueError("Tiny SRAM geometry/P5 attachment differs from its frozen contract") if document.get("fabric") != { - "owners": ["cpu_slow", "central_dma", "sdio_reserved"], "active_owners": [0, 1], + "owners": ["cpu_slow", "central_dma", "sdio"], "active_owners": [0, 1, 2], "cpu_combined_outstanding": 1, "dma_direction_outstanding": 1, "sdio_direction_outstanding": 1, "target_combined_outstanding": 1, "sram_targets": [0, 1, 2, 3], "xpi_target": 4, "apb_target_base": 5, - "decode_error_target": 21, "protocol_error_target": 22, "target_count": 23, + "decode_error_target": 25, "protocol_error_target": 26, "target_count": 27, }: - raise ValueError("Tiny P5 requires three owner slots, two active owners and 23 targets") + raise ValueError("Tiny P6 requires three active owners and 27 targets") if document.get("irq_vector_width") != 32 or document.get("external_irq_count") != 30: raise ValueError("Tiny requires a 32-bit IRQ vector and 30 external inputs") names: set[str] = set() @@ -61,14 +62,24 @@ def read_topology(path: Path, memory_map: Path) -> dict: assigned.add(region) if assigned != {r for r in regions if r.startswith("APB4_")}: raise ValueError("Tiny topology must route every APB region") - if len(document["apb_targets"]) != 16: - raise ValueError("Tiny's fixed APB bridge requires sixteen targets") + if len(document["apb_targets"]) != 20: + raise ValueError("Tiny P6 requires twenty independent APB targets") target_names = [target["name"] for target in document["apb_targets"]] - if len(set(target_names)) != 16 or any(re.fullmatch(r"[a-z][a-z0-9_]*", name) is None + if len(set(target_names)) != 20 or any(re.fullmatch(r"[a-z][a-z0-9_]*", name) is None for name in target_names): raise ValueError("Tiny APB target names must be unique identifiers") if sorted(item["pin"] for item in document["gpio_alt_functions"]) != list(range(32)): raise ValueError("Tiny GPIO map must contain all 32 pins exactly once") + inputs = [signal for pin in document["gpio_alt_functions"] for mode in ("alt0", "alt1") + for signal in pin[mode]["inputs"]] + if len(inputs) != len(set(inputs)): + raise ValueError("Tiny peripheral inputs require one exclusive route") + if document.get("private_crypto") != { + "banks": 6, "bank_bytes": 4096, + "macro_instance": "u_soc.u_crypto.u_storage.gen_bank[{bank}].gen_macro.u_sram.u_mem", + "clock_domain": "system", "logical_domain": "PCLK (SAFE24 alias)", + }: + raise ValueError("Tiny P6 requires six private Crypto banks on SAFE24 PCLK") return document @@ -84,6 +95,22 @@ 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" + constants = [comment, "`ifndef RETROSOC_TINY__TOPOLOGY_SVH", + "`define RETROSOC_TINY__TOPOLOGY_SVH", + f"`define RETROSOC_TINY__TARGET_COUNT {document['fabric']['target_count']}", + f"`define RETROSOC_TINY__LEAF_COUNT {len(document['apb_targets']) + 1}", + f"`define RETROSOC_TINY__DECODE_ERROR {document['fabric']['decode_error_target']}", + f"`define RETROSOC_TINY__PROTOCOL_ERROR {document['fabric']['protocol_error_target']}"] + constants += [f"`define RETROSOC_TINY__TARGET_{target['name'].upper()} {index + 5}" + for index, target in enumerate(document["apb_targets"])] + constants.append("`endif\n") + atomic_write(output / "rtl/tiny_topology.svh", "\n".join(constants)) + # Keep board pulls independently resolved. A packed tri1 with partial + # external drivers loses its weak pulls in the locked Verilator 5.046. + board = [comment, *[f"tri1 s_gpio_{pin};" for pin in range(document["gpio_pins"])]] + board.append("wire [31:0] s_gpio = {" + + ", ".join(f"s_gpio_{pin}" for pin in reversed(range(32))) + "};\n") + atomic_write(output / "rtl/tiny_tb_gpio.svh", "\n".join(board)) irq = [comment, "always_comb begin", " s_irq = '0;"] header = ["#ifndef RETROSOC_GENERATED_IRQ_METADATA_H", "#define RETROSOC_GENERATED_IRQ_METADATA_H", "#include ", @@ -109,7 +136,15 @@ def generate_bindings(document: dict, output: Path, pdk: str = "IHP130", "#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" + "#define RS_SOC_DMA_CHANNELS 8U\n#define RS_SOC_ID 0x54494E59U\n" + "#define RS_SOC_DMA_REQUEST_MASK 0x31FFU\n" + "#define RS_SOC_HAS_I2C1 0U\n#define RS_SOC_HAS_SDIO1 0U\n" + "#define RS_SOC_HAS_UART0 1U\n#define RS_SOC_HAS_UART1 0U\n" + "#define RS_SOC_HAS_I2C0 1U\n" + "#define RS_SOC_HAS_RNG 1U\n#define RS_SOC_HAS_CRC 1U\n" + "#define RS_SOC_HAS_WS2812 1U\n#define RS_SOC_HAS_CRYPTO 1U\n" + "#define RS_SOC_HAS_I2S 1U\n#define RS_SOC_HAS_SDIO0 1U\n" + "#define RS_SOC_HAS_DVP 0U\n#define RS_SOC_RNG_QUALIFIED 0U\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") @@ -149,7 +184,7 @@ def generate_filelists(output: Path, generated: Path, defines: list[str], 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", - "cache": ROOT / ".cache"} + "cache": ROOT / ".cache", "tests": ROOT / "tests"} 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")), diff --git a/scripts/setup_tiny.py b/scripts/setup_tiny.py index 826c9697..1af49294 100644 --- a/scripts/setup_tiny.py +++ b/scripts/setup_tiny.py @@ -15,7 +15,7 @@ from scripts.setup_helpers import ensure_git_repo # noqa: E402 TINY_SOURCES = ( "hazard3", "cluster_common", "cluster_archinfo", "cluster_pwm", - "cluster_rtc", "cluster_wdg", "pdk_ihp130", + "cluster_rtc", "cluster_wdg", "cluster_rng", "cluster_crc", "pdk_ihp130", ) diff --git a/scripts/tiny_ics55_platform.py b/scripts/tiny_ics55_platform.py index c96ce358..6db9dfb6 100644 --- a/scripts/tiny_ics55_platform.py +++ b/scripts/tiny_ics55_platform.py @@ -177,7 +177,18 @@ def inspect_netlist(document: dict) -> dict: 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] + all_ram = [c for c in leaves if c["type"] == RAM] + crypto_names = {f"u_soc.u_crypto.u_storage.gen_bank[{n}].gen_macro.u_sram.u_mem" + for n in range(6)} + crypto = [c for c in all_ram if any(c["path"].replace("/", ".").endswith("." + name) + for name in crypto_names)] + observed_crypto = {name for name in crypto_names for c in crypto + if c["path"].replace("/", ".").endswith("." + name)} + if len(all_ram) != 38 or len(crypto) != 6 or observed_crypto != crypto_names: + raise ValueError("Tiny P6 requires exactly 32 main SRAM and six private Crypto macros") + if any(c["ports"].get("CLK") != [system] for c in crypto): + raise ValueError("private Crypto SRAM does not use SAFE24 PCLK") + ram = [c for c in all_ram if c not in crypto] 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") @@ -219,6 +230,7 @@ def inspect_netlist(document: dict) -> dict: "system_clock_source": {"port": "extclk_i_pad", "net": reference, "path": clock_path}, "cpu_clocked_cells": len(cpu), "sram": [{"path": c["path"], "clock": c["ports"]["CLK"]} for c in ram], + "crypto_sram": [{"path": c["path"], "clock": c["ports"]["CLK"]} for c in crypto], "pll": pll[0], "pll_enable": 0, "qualification": "SAFE24 digital connectivity only"} diff --git a/scripts/tiny_r2_p6.py b/scripts/tiny_r2_p6.py new file mode 100644 index 00000000..1cda97cb --- /dev/null +++ b/scripts/tiny_r2_p6.py @@ -0,0 +1,386 @@ +#!/usr/bin/env python3 +"""Capture and check source-bound Tiny P6 acceptance without promoting a phase.""" +from __future__ import annotations + +import argparse +from datetime import datetime, timezone +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 import tiny_r2_p5 as previous # noqa: E402 +from scripts.check_simulation import DEFAULT_FAILURE # noqa: E402 + +PHASE = "TINY-R2-P6" +MODEL = "tests/rtl/sdio_native_model.sv" +VERIFICATION_UPDATE_PATHS = {MODEL, "scripts/tiny_r2_p6.py", "tests/test_tiny_r2_p6.py"} +CONSUMER_UPDATE_PATHS = { + "publications/programming_reference.py", "tests/test_publication_programming.py", + "physical/commercial/config/products/tiny.mk", "physical/commercial/tcl/common/products/tiny.tcl", + "tests/test_commercial_flow.py", "scripts/tiny_r2_p6.py", "tests/test_tiny_r2_p6.py", +} +COLLECTION_UPDATE_PATHS = {"scripts/tiny_r2_p6.py", "tests/test_tiny_r2_p6.py"} +RECORDS = { + "UART": {"pads": 1, "echo_bytes": 4, "flow_rejected": 1}, + "DMA": {"channels": 8, "rejected_requests": 5}, + "RNG": {"unqualified": 1, "diagnostic": 1, "irq": 1}, + "CRC": {"dma": 1, "tails": 4, "empty": 1}, + "CRYPTO": {"init": 1, "aes_sha": 1, "dma45": 1, "rsa_public": 1, "zeroize": 1}, + "RECOVERY": {"audio_stops": 2, "ws_underflow": 1, "ws_abort": 1}, + "SDIO": {"dma_bytes": 2048, "pio_bytes": 512, "width1": 1, "width4": 1, "irq": 1, "errors": 2}, +} + + +def parse_log(content: str, flow: dict) -> dict: + if (flow.get("status") != "passed" or flow.get("exit_code") != 0 + or not re.search(r"SIM_TEST_PASS Tiny cycles=[0-9]+(?:\r?\n|$)", content) + or DEFAULT_FAILURE.search(content)): + raise ValueError("P6 requires successful command, terminal marker and no forbidden errors") + result = {} + for name, expected in RECORDS.items(): + rows = [b.parse_fields(line, set(expected)) for line in content.splitlines() + if line.startswith("R2_P6_" + name + " ")] + if rows != [expected]: + raise ValueError(f"missing, duplicate or invalid P6 {name} record") + result[name.lower()] = rows[0] + fields = {"bits", "frames", "ws_frames", "background_jobs", "tcd", "elapsed_us", "cycles", "instructions"} + audio = [b.parse_fields(line, fields) for line in content.splitlines() + if line.startswith("R2_P6_AUDIO ")] + if [row["bits"] for row in audio] != [16, 24]: + raise ValueError("P6 needs exactly both audio formats") + for row in audio: + if (row["frames"] != 256 or row["ws_frames"] != 2 or + not 1 <= row["background_jobs"] <= 64 or + row["tcd"] != int(row["bits"] == 24) or not 0 < row["elapsed_us"] < 50000 or + not 0 < row["instructions"] <= row["cycles"]): + raise ValueError("invalid P6 audio/contention observation") + result["audio"] = audio + wires = [b.parse_fields(line, {"bits", "samples"}) for line in content.splitlines() + if line.startswith("R2_P6_I2S_WIRE ")] + if wires != [{"bits": 16, "samples": 512}, {"bits": 24, "samples": 512}]: + raise ValueError("missing or incorrect independent audio wire observations") + result["audio_wires"] = wires + fields = {"owner", "read_bytes", "write_bytes", "descriptor_bytes", "ar_stall_max", + "aw_stall_max", "w_stall_max", "r_stall_max", "b_stall_max"} + bus = sorted([b.parse_fields(line, fields) for line in content.splitlines() + if line.startswith("R2_P6_AXI ")], key=lambda row: row["owner"]) + if [row["owner"] for row in bus] != [0, 1, 2]: + raise ValueError("P6 needs each actual AXI owner observation") + for row in bus: + if (any(v < 0 for v in row.values()) or row["read_bytes"] == 0 or row["write_bytes"] == 0 + or row["descriptor_bytes"] > row["read_bytes"]): + raise ValueError("invalid P6 AXI payload/backpressure observation") + if bus[1]["descriptor_bytes"] == 0: + raise ValueError("no actual background descriptor fetch observed") + result["bus"] = bus + contention = [b.parse_fields(line, {"sdio_dma_cycles", "cpu_fetches"}) + for line in content.splitlines() if line.startswith("R2_P6_CONTENTION ")] + if len(contention) != 1 or any(value <= 0 for value in contention[0].values()): + raise ValueError("missing actual SDIO/DMA overlap and CPU progress") + result["contention"] = contention[0] + fields = {"ws_frames", "ws_wait_max", "fifo_max", "underflows", "aborts", "clock_stops"} + devices = [b.parse_fields(line, fields) for line in content.splitlines() + if line.startswith("R2_P6_DEVICES ")] + if len(devices) != 1: + raise ValueError("missing or duplicate independent device observations") + d = devices[0] + if (d["ws_frames"] != 4 or not 0 <= d["ws_wait_max"] <= 4800 or + not 1 <= d["fifo_max"] <= 16 or d["underflows"] != 1 or + d["aborts"] != 1 or d["clock_stops"] != 2): + raise ValueError("P6 device coverage/service budget failed") + result["devices"] = d + return result + + +def inputs(variants: list[Path]) -> dict: + result = {} + for variant in variants: + config = b.configuration(variant)["configuration"] + if config.get("APP") != "ci_smoke": + raise ValueError("P6 qualification requires the ci_smoke composition") + result.update(previous.inputs(ROOT, variant)) + for name in (".bake.toml", ".clang-format", "docs/misra-c-2012.md", + "quality/misra/deviations.md", "docs/ip/dma.md", "docs/ip/i2s.md", + "docs/ip/sdio.md", "docs/ip/crypto.md", "docs/ip/ws2812.md", + "physical/commercial/config/products/tiny.mk", + "physical/commercial/tcl/common/products/tiny.tcl"): + result[name] = b.sha256(ROOT / name) + return result + + +def current_tools() -> dict: + tools = b.tools_identity(ROOT) + tools["vvp"] = b.vvp_identity("vvp", tools["iverilog"]) + return tools + + +def checked_flow(path: Path, captured_at: str) -> dict: + flow = b.read_json(path) + if flow.get("status") != "passed" or flow.get("exit_code") != 0: + raise ValueError(f"unsuccessful gate: {path}") + if (not flow.get("started_at") or not flow.get("finished_at") + or flow["started_at"] < captured_at or flow["finished_at"] < flow["started_at"]): + raise ValueError(f"gate lacks successful post-capture chronology: {path}") + return flow + + +def check_model_declaration_move(before: str, after: str) -> None: + """Permit only relocation of one unchanged continuous net declaration. + + Verilator/sv2v accept its forward reference; native Icarus needs the integer + first. Every other source byte, including all procedural behavior, must match. + """ + pattern = re.compile(r"^ wire \[3:0\] s_token_lanes = [^;]+;\n", re.M) + old, new = pattern.findall(before), pattern.findall(after) + if (len(old) != 1 or old != new or pattern.sub("", before) != pattern.sub("", after) + or after.index(" integer s_data_bytes;") > after.index(new[0])): + raise ValueError("not the permitted SDIO model declaration-only relocation") + + +def snapshot_model(source: dict) -> str: + with tarfile.open(b.check_artifact(source["snapshot"])) as archive: + return archive.extractfile(MODEL).read().decode() + + +def check_update(directory: Path, saved: dict, update: dict, current: dict) -> dict: + if (update.get("phase") != PHASE or update.get("kind") != "native_model_declaration_order" + or update["original_source"] != b.artifact(directory / "source.json")): + raise ValueError("verification update does not bind the original campaign") + old = b.read_json(b.check_artifact(saved["source"]["inputs"])) + new = b.read_json(b.check_artifact(update["source"]["inputs"])) + changed = {name for name in old.keys() | new.keys() if old.get(name) != new.get(name)} + if (MODEL not in changed or not changed <= VERIFICATION_UPDATE_PATHS + or sorted(changed) != update["changed_inputs"] or new != current): + raise ValueError("verification update changed DUT/firmware/constraints or unapproved inputs") + check_model_declaration_move(snapshot_model(saved["source"]), snapshot_model(update["source"])) + for source in (saved["source"], update["source"]): + for name in ("inputs", "snapshot", "patch"): + b.check_artifact(source[name]) + if update["tools"] != saved["tools"] or update["captured_at"] < saved["captured_at"]: + raise ValueError("verification update changed tools or chronology") + return old + + +def capture_verification_update(directory: Path, variants: list[Path]) -> None: + path = directory / "verification-update.json" + if path.exists(): + raise ValueError("never overwrite a verification update") + saved = b.read_json(directory / "source.json") + current = inputs(variants) + old = b.read_json(b.check_artifact(saved["source"]["inputs"])) + changed = sorted(name for name in old.keys() | current.keys() if old.get(name) != current.get(name)) + if MODEL not in changed or not set(changed) <= VERIFICATION_UPDATE_PATHS: + raise ValueError("only the native model and its collector tests may change") + check_model_declaration_move(snapshot_model(saved["source"]), (ROOT / MODEL).read_text()) + update = {"phase": PHASE, "kind": "native_model_declaration_order", + "original_source": b.artifact(directory / "source.json"), "changed_inputs": changed, + "source": b.snapshot(ROOT, directory / "inputs-native-model", current), + "tools": current_tools(), "captured_at": datetime.now(timezone.utc).isoformat()} + check_update(directory, saved, update, current) + b.dump(path, update) + + +def check_consumer_update(directory: Path, previous_update: dict, update: dict, current: dict) -> None: + """Permit only consumers not used by the running RTL/smoke flows.""" + if (update.get("phase") != PHASE or update.get("kind") != "publication_and_commercial_consumers" + or update["previous_update"] != b.artifact(directory / "verification-update.json")): + raise ValueError("consumer update does not bind the verified model snapshot") + old = b.read_json(b.check_artifact(previous_update["source"]["inputs"])) + new = b.read_json(b.check_artifact(update["source"]["inputs"])) + changed = {name for name in old.keys() | new.keys() if old.get(name) != new.get(name)} + if (not changed or not changed <= CONSUMER_UPDATE_PATHS or + sorted(changed) != update["changed_inputs"] or new != current): + raise ValueError("consumer update changed simulation, firmware, smoke constraints or unrelated inputs") + for field in ("inputs", "snapshot", "patch"): + b.check_artifact(update["source"][field]) + if (update["tools"] != previous_update["tools"] or + update["captured_at"] < previous_update["captured_at"]): + raise ValueError("consumer update changed tools or chronology") + + +def capture_consumer_update(directory: Path, variants: list[Path]) -> None: + path = directory / "consumer-update.json" + if path.exists(): + raise ValueError("never overwrite a consumer update") + previous_update = b.read_json(directory / "verification-update.json") + current = inputs(variants) + old = b.read_json(b.check_artifact(previous_update["source"]["inputs"])) + changed = sorted(name for name in old.keys() | current.keys() if old.get(name) != current.get(name)) + if not changed or not set(changed) <= CONSUMER_UPDATE_PATHS: + raise ValueError("unexpected non-consumer source changes") + update = {"phase": PHASE, "kind": "publication_and_commercial_consumers", + "previous_update": b.artifact(directory / "verification-update.json"), + "changed_inputs": changed, + "source": b.snapshot(ROOT, directory / "inputs-consumers", current), + "tools": current_tools(), "captured_at": datetime.now(timezone.utc).isoformat()} + check_consumer_update(directory, previous_update, update, current) + b.dump(path, update) + + +def check_collection_update(directory: Path, previous_update: dict, update: dict, current: dict) -> None: + if (update.get("phase") != PHASE or update.get("kind") != "terminal_log_collection" + or update["previous_update"] != b.artifact(directory / "consumer-update.json")): + raise ValueError("collection update does not bind the consumer snapshot") + old = b.read_json(b.check_artifact(previous_update["source"]["inputs"])) + new = b.read_json(b.check_artifact(update["source"]["inputs"])) + changed = {name for name in old.keys() | new.keys() if old.get(name) != new.get(name)} + if (not changed or not changed <= COLLECTION_UPDATE_PATHS or + sorted(changed) != update["changed_inputs"] or new != current): + raise ValueError("collection update changed inputs outside the collector/tests") + for field in ("inputs", "snapshot", "patch"): + b.check_artifact(update["source"][field]) + if (update["tools"] != previous_update["tools"] or + update["captured_at"] < previous_update["captured_at"]): + raise ValueError("collection update changed tools or chronology") + + +def capture_collection_update(directory: Path, variants: list[Path]) -> None: + path = directory / "collection-update.json" + if path.exists(): + raise ValueError("never overwrite a collection update") + previous_update = b.read_json(directory / "consumer-update.json") + current = inputs(variants) + old = b.read_json(b.check_artifact(previous_update["source"]["inputs"])) + changed = sorted(name for name in old.keys() | current.keys() if old.get(name) != current.get(name)) + if not changed or not set(changed) <= COLLECTION_UPDATE_PATHS: + raise ValueError("unexpected non-collector source changes") + update = {"phase": PHASE, "kind": "terminal_log_collection", + "previous_update": b.artifact(directory / "consumer-update.json"), + "changed_inputs": changed, + "source": b.snapshot(ROOT, directory / "inputs-collection", current), + "tools": current_tools(), "captured_at": datetime.now(timezone.utc).isoformat()} + check_collection_update(directory, previous_update, update, current) + b.dump(path, update) + + +def capture(directory: Path, variants: list[Path]) -> None: + if (directory / "source.json").exists(): + raise ValueError("capture needs a fresh evidence directory; never overwrite provenance") + if {b.configuration(v)["configuration"]["PDK"] for v in variants} != {"ICS55", "IHP130"}: + raise ValueError("capture both Tiny PDK input sets before validation") + source = b.snapshot(ROOT, directory / "inputs", inputs(variants)) + tools = current_tools() + b.dump(directory / "source.json", {"phase": PHASE, "source": source, "tools": tools, + "captured_at": datetime.now(timezone.utc).isoformat(), + "variants": [str(p.resolve()) for p in variants], + "configurations": [b.configuration(p) for p in variants]}) + + +def report(directory: Path, gates: list[Path], variants: list[Path]) -> None: + saved = b.read_json(directory / "source.json") + if saved.get("phase") != PHASE: + raise ValueError("wrong evidence phase") + if saved["tools"] != current_tools(): + raise ValueError("locked tool identity changed after capture") + selected = [Path(p) for p in saved["variants"]] + current = inputs(selected) + original = b.read_json(b.check_artifact(saved["source"]["inputs"])) + update_path = directory / "verification-update.json" + update = b.read_json(update_path) if update_path.exists() else None + consumer_path = directory / "consumer-update.json" + consumer = b.read_json(consumer_path) if consumer_path.exists() else None + collection_path = directory / "collection-update.json" + collection = b.read_json(collection_path) if collection_path.exists() else None + consumer_inputs = current + if collection: + if not consumer: + raise ValueError("collection update requires the consumer snapshot") + check_collection_update(directory, consumer, collection, current) + consumer_inputs = b.read_json(b.check_artifact(consumer["source"]["inputs"])) + verified_inputs = current + if consumer: + if not update: + raise ValueError("consumer update requires the model snapshot") + check_consumer_update(directory, update, consumer, consumer_inputs) + verified_inputs = b.read_json(b.check_artifact(update["source"]["inputs"])) + if update: + original = check_update(directory, saved, update, verified_inputs) + elif current != original: + raise ValueError("P6 technical inputs changed after capture") + for name in ("snapshot", "patch"): + b.check_artifact(saved["source"][name]) + checked = {} + for path in gates: + gate = checked_flow(path, saved["captured_at"]) + checked[str(path)] = {"result": b.artifact(path), "log": b.artifact(Path(gate["log"]))} + simulations = {} + selections = [(variant, simulator) for variant in variants for simulator in ("iverilog", "verilator") + if (variant / "sim" / simulator / "result-sim.json").is_file()] + for variant, simulator in selections: + config = b.configuration(variant) + if config["configuration"].get("APP") != "ci_smoke": + raise ValueError("simulation must use the complete ci_smoke composition") + pdk = config["configuration"]["PDK"] + key = pdk + "/" + simulator + if key in simulations or simulator not in ("iverilog", "verilator"): + raise ValueError("duplicate or unsupported simulation variant") + folder = variant / "sim" / simulator + model_source = update if update and simulator == "iverilog" else saved + flow = checked_flow(folder / "result-sim.json", model_source["captured_at"]) + compilation = checked_flow(folder / "result-compile.json", model_source["captured_at"]) + if flow["started_at"] < compilation["finished_at"]: + raise ValueError("simulation predates completed model compilation") + check = b.read_json(folder / "result-sim-check.json") + if check.get("status") != "passed": + raise ValueError("missing strict simulation verdict") + samples = parse_log((folder / "sim.log").read_text(), flow) + paths = [folder / name for name in ("result-sim.json", "result-sim-check.json", "sim.log", + "result-compile.json", "compile.log")] + paths += [folder / ("emu" if simulator == "verilator" else "simv"), + variant / "sw/firmware", variant / "sw/retrosoc_fw.hex", variant / "sw/retrosoc_fw.map", + variant / "sw/retrosoc_fw.bin", variant / "sw/ld2_all_sram.lds", + variant / "sw/memory_regions.ld", + variant / "meta/manifest.json"] + paths += sorted(path for path in (variant / "sw/include").rglob("*") if path.is_file()) + runtime = b.runtime_inputs(ROOT, variant, simulator) + if update and simulator == "verilator": + if runtime.get(MODEL) != current[MODEL]: + raise ValueError("missing native card model runtime input") + runtime[MODEL] = original[MODEL] + simulations[key] = {"configuration": config, "simulator": simulator, "samples": samples, + "source": model_source["source"], "runtime_inputs": runtime, + "artifacts": [b.artifact(path) for path in paths]} + if set(simulations) != {p + "/" + s for p in ("ICS55", "IHP130") for s in ("iverilog", "verilator")}: + raise ValueError("P6 needs both simulators on both Tiny PDKs") + # Physical/quality reports remain independent gates; this status never + # promotes functional collection into phase or physical qualification. + b.dump(directory / "report.json", {"phase": PHASE, "status": "functional_evidence_collected", + "tools": saved["tools"], + "verification_update": b.artifact(update_path) if update else None, + "consumer_update": b.artifact(consumer_path) if consumer else None, + "collection_update": b.artifact(collection_path) if collection else None, + "source": saved["source"], "source_inputs_rechecked": len(current), "gates": checked, + "simulations": simulations, "limitations": ["human review pending", "timing observational", + "no QFN64/package, routed/PVT/CDC/RDC or publication qualification"]}) + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("action", choices=("capture", "capture-verification-update", "capture-consumer-update", + "capture-collection-update", "report")) + parser.add_argument("--evidence-root", type=Path, required=True) + parser.add_argument("--variant-root", action="append", type=Path, required=True) + parser.add_argument("--gate", action="append", type=Path, default=[]) + args = parser.parse_args() + directory = args.evidence_root.resolve() + if not directory.is_relative_to(ROOT / "build"): + parser.error("evidence must stay under the repository build root") + if args.action == "capture": + capture(directory, args.variant_root) + elif args.action == "capture-verification-update": + capture_verification_update(directory, args.variant_root) + elif args.action == "capture-consumer-update": + capture_consumer_update(directory, args.variant_root) + elif args.action == "capture-collection-update": + capture_collection_update(directory, args.variant_root) + else: + report(directory, args.gate, args.variant_root) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/c/test_runtime.c b/tests/c/test_runtime.c index e09337f9..985bb920 100644 --- a/tests/c/test_runtime.c +++ b/tests/c/test_runtime.c @@ -92,9 +92,13 @@ void rs_dma_test_irq_restore(uint32_t saved) { dma_irq_result = rs_dma_session_acquire(&dma_irq_owner, 3U); if (dma_irq_result == RS_OK) { const rs_dma_config_t config = {.kind = RS_DMA_KIND_MM_TO_MM, - .source = 0x3000U, .destination = 0x4000U, .byte_count = 16U, - .width = RS_DMA_WIDTH_32, .source_increment = true, - .destination_increment = true, .burst_beats = 1U}; + .source = 0x3000U, + .destination = 0x4000U, + .byte_count = 16U, + .width = RS_DMA_WIDTH_32, + .source_increment = true, + .destination_increment = true, + .burst_beats = 1U}; dma_irq_result = rs_dma_session_configure(&dma_irq_owner, &config); } } @@ -147,18 +151,21 @@ static void ws_test_reset(void) { } static int test_dma_tcd_preemption(void) { - static _Alignas(64) rs_dma_tcd_t tcd = { - .source = 0x1000U, .destination = 0x2000U, .byte_count = 32U, .y_count = 1U, - .control = RS_DMA_TCD_VALID | RS_DMA_TCD_SRC_INC | RS_DMA_TCD_DST_INC | - (UINT32_C(1) << RS_DMA_TCD_BURST_SHIFT)}; + static _Alignas(64) + rs_dma_tcd_t tcd = {.source = 0x1000U, + .destination = 0x2000U, + .byte_count = 32U, + .y_count = 1U, + .control = RS_DMA_TCD_VALID | RS_DMA_TCD_SRC_INC | RS_DMA_TCD_DST_INC | + (UINT32_C(1) << RS_DMA_TCD_BURST_SHIFT)}; rs_status_t result; bool valid; ws_test_reset(); dma_irq_pending = true; dma_irq_result = RS_OK; result = rs_dma_submit_tcd(3U, &tcd, 1U); - valid = (result == RS_ETIMEOUT) && (dma_irq_result == RS_EIO) && - !dma_irq_pending && (dma_irq_enabled == 8U) && + valid = (result == RS_ETIMEOUT) && (dma_irq_result == RS_EIO) && !dma_irq_pending && + (dma_irq_enabled == 8U) && (RS_DMA_CH_REG(3U, RS_DMA_CH_REG_TCD_HEAD) == (uint32_t)(uintptr_t)&tcd) && (RS_DMA_CH_REG(3U, RS_DMA_CH_REG_TCD_COUNT) == 1U) && (RS_DMA_CH_REG(3U, RS_DMA_CH_REG_SRC_ADDR) == tcd.source); @@ -211,8 +218,7 @@ static int test_ws2812_completion_race(void) { return 4; } (void)rs_ws2812_dma_status(&transfer); - if (transfer.active || (transfer.result != RS_OK) || - (ws_registers[0x24U / 4U] != 3U)) { + if (transfer.active || (transfer.result != RS_OK) || (ws_registers[0x24U / 4U] != 3U)) { return 5; } } @@ -248,9 +254,14 @@ static int test_ws2812_completion_race(void) { static int test_dma_sessions(void) { rs_dma_session_t first = {0}, second = {0}; - rs_dma_config_t config = {.kind = RS_DMA_KIND_MM_TO_MM, .source = 0x1000U, - .destination = 0x2000U, .byte_count = 32U, .width = RS_DMA_WIDTH_32, - .source_increment = true, .destination_increment = true, .burst_beats = 1U}; + rs_dma_config_t config = {.kind = RS_DMA_KIND_MM_TO_MM, + .source = 0x1000U, + .destination = 0x2000U, + .byte_count = 32U, + .width = RS_DMA_WIDTH_32, + .source_increment = true, + .destination_increment = true, + .burst_beats = 1U}; ws_test_reset(); if ((rs_dma_session_acquire(&first, 3U) != RS_OK) || (rs_dma_session_acquire(&first, 2U) != RS_EIO) || @@ -262,13 +273,11 @@ static int test_dma_sessions(void) { return 1; } RS_DMA_CH_REG(3U, RS_DMA_CH_REG_STATUS) = RS_DMA_STATUS_BUSY; - if ((rs_dma_session_release(&first) != RS_EIO) || - (rs_dma_session_abort(&first) != RS_OK)) { + if ((rs_dma_session_release(&first) != RS_EIO) || (rs_dma_session_abort(&first) != RS_OK)) { return 2; } RS_DMA_CH_REG(3U, RS_DMA_CH_REG_STATUS) = RS_DMA_STATUS_ABORTED; - if ((rs_dma_session_release(&first) != RS_OK) || - (rs_dma_session_release(&first) != RS_EIO) || + if ((rs_dma_session_release(&first) != RS_OK) || (rs_dma_session_release(&first) != RS_EIO) || (rs_dma_session_acquire(&second, 3U) != RS_OK) || (rs_dma_session_release(&second) != RS_OK)) { return 3; @@ -301,15 +310,13 @@ static int test_ws2812_refill(void) { ws_pixels[32] = 0U; if ((rs_ws2812_dma_begin(ws_pixels, 33U, 0U, 100U) != RS_OK) || (ws_pushed != 16U) || (ws_registers[0x24U / 4U] != 8U) || (rs_ws2812_push(0U, 1U) != RS_EIO) || - (rs_ws2812_start(1U) != RS_EIO) || - (rs_dma_session_acquire(&competitor, 3U) != RS_EIO)) { + (rs_ws2812_start(1U) != RS_EIO) || (rs_dma_session_acquire(&competitor, 3U) != RS_EIO)) { return 4; } ws_registers[0x30U / 4U] = RS_WS2812_INTR_FIFO_LOW; (void)rs_ws2812_dma_service(1U); (void)rs_ws2812_dma_status(&transfer); - if ((transfer.batches != 0U) || - ((ws_registers[0x30U / 4U] & RS_WS2812_INTR_FIFO_LOW) != 0U)) { + if ((transfer.batches != 0U) || ((ws_registers[0x30U / 4U] & RS_WS2812_INTR_FIFO_LOW) != 0U)) { return 5; } /* Hardware REMAINING includes queued words: it must never size the batch. */ @@ -345,8 +352,7 @@ static int test_ws2812_refill(void) { ws_registers[0x30U / 4U] = RS_WS2812_INTR_DONE; if ((rs_ws2812_dma_service(15U) != RS_OK) || (rs_dma_session_acquire(&competitor, 3U) != RS_OK) || - (rs_dma_session_release(&competitor) != RS_OK) || - (ws_registers[0x24U / 4U] != 3U)) { + (rs_dma_session_release(&competitor) != RS_OK) || (ws_registers[0x24U / 4U] != 3U)) { return 11; } /* Wire DONE may precede a delayed final B. Retain the lease until both @@ -907,7 +913,11 @@ static int test_sdio_helpers(void) { rs_sd_memory_info_t info = {0}; uint32_t value; - if ((rs_sdio_clock_calculate(72000000U, 400000U, &clock) != RS_OK) || + if ((rs_sdio_clock_calculate(24000000U, 400000U, &clock) != RS_OK) || + (clock.half_period != 30U) || (clock.actual_hz != 400000U) || + (rs_sdio_clock_calculate(24000000U, 12000000U, &clock) != RS_OK) || + (clock.half_period != 1U) || (clock.actual_hz != 12000000U) || + (rs_sdio_clock_calculate(72000000U, 400000U, &clock) != RS_OK) || (clock.half_period != 90U) || (clock.actual_hz != 400000U) || (rs_sdio_clock_calculate(100000000U, 50000000U, &clock) != RS_OK) || (clock.half_period != 1U) || (clock.actual_hz != 50000000U)) { @@ -1075,32 +1085,48 @@ rs_status_t rs_tiny_dma_tcd_validate(uint32_t channel, const rs_dma_tcd_t *tcd); static int test_tiny_dma_config_validation(void) { rs_dma_config_t config = { - .kind = RS_DMA_KIND_MM_TO_MM, .request = RS_DMA_REQUEST_SOFTWARE, + .kind = RS_DMA_KIND_MM_TO_MM, + .request = RS_DMA_REQUEST_SOFTWARE, .source = (uintptr_t)UINT32_C(0x30000000), - .destination = (uintptr_t)UINT32_C(0x30001000), .byte_count = 259U, - .width = RS_DMA_WIDTH_32, .source_increment = true, .destination_increment = true, + .destination = (uintptr_t)UINT32_C(0x30001000), + .byte_count = 259U, + .width = RS_DMA_WIDTH_32, + .source_increment = true, + .destination_increment = true, .burst_beats = 16U, }; static _Alignas(64) rs_dma_tcd_t descriptor; if ((rs_tiny_dma_config_validate(3U, &config) != RS_OK) || - (rs_tiny_dma_config_validate(4U, &config) != RS_EINVAL)) { + (rs_tiny_dma_config_validate(7U, &config) != RS_OK) || + (rs_tiny_dma_config_validate(8U, &config) != RS_EINVAL)) { return 1; } config.kind = RS_DMA_KIND_MM_TO_STREAM; config.request = RS_DMA_REQUEST_I2S_TX; config.byte_count = 256U; - if (rs_tiny_dma_config_validate(0U, &config) != RS_EINVAL) { + if (rs_tiny_dma_config_validate(6U, &config) != RS_OK) { return 2; } descriptor.source = UINT32_C(0x30000000); descriptor.byte_count = 256U; descriptor.y_count = 1U; descriptor.control = RS_DMA_TCD_VALID | RS_DMA_TCD_SRC_INC | - ((uint32_t)RS_DMA_KIND_MM_TO_STREAM << RS_DMA_TCD_KIND_SHIFT) | - ((uint32_t)RS_DMA_REQUEST_I2S_TX << RS_DMA_TCD_REQUEST_SHIFT); - if (rs_tiny_dma_tcd_validate(0U, &descriptor) != RS_ENOTSUP) { + ((uint32_t)RS_DMA_KIND_MM_TO_STREAM << RS_DMA_TCD_KIND_SHIFT) | + ((uint32_t)RS_DMA_REQUEST_I2S_TX << RS_DMA_TCD_REQUEST_SHIFT); + if (rs_tiny_dma_tcd_validate(6U, &descriptor) != RS_OK) { return 3; } + config.kind = RS_DMA_KIND_STREAM_TO_MM; + config.request = RS_DMA_REQUEST_DVP_RX; + if (rs_tiny_dma_config_validate(2U, &config) != RS_EINVAL) { + return 4; + } + descriptor.control = RS_DMA_TCD_VALID | RS_DMA_TCD_DST_INC | + ((uint32_t)RS_DMA_KIND_STREAM_TO_MM << RS_DMA_TCD_KIND_SHIFT) | + ((uint32_t)RS_DMA_REQUEST_DVP_RX << RS_DMA_TCD_REQUEST_SHIFT); + if (rs_tiny_dma_tcd_validate(2U, &descriptor) != RS_ENOTSUP) { + return 5; + } return 0; } @@ -2992,9 +3018,12 @@ static int test_npu_p6_reference_edge_ops(void) { uint8_t depthwise_weights[72] = {0U}; int32_t depthwise_parameters[4] = {1, INT32_C(1073741824), 1, 0}; rs_npu_p6_memory_t depthwise_memory = { - .arena = {.base = UINT32_C(0x1000), .bytes = sizeof(depthwise_arena), .data = depthwise_arena}, - .weights = - {.base = UINT32_C(0x3000), .bytes = sizeof(depthwise_weights), .data = depthwise_weights}, + .arena = {.base = UINT32_C(0x1000), + .bytes = sizeof(depthwise_arena), + .data = depthwise_arena}, + .weights = {.base = UINT32_C(0x3000), + .bytes = sizeof(depthwise_weights), + .data = depthwise_weights}, .params = { .base = UINT32_C(0x2000), @@ -3030,7 +3059,9 @@ static int test_npu_p6_reference_edge_ops(void) { { uint8_t average_arena[16] = {1U, 2U, 3U, 4U}; rs_npu_p6_memory_t average_memory = { - .arena = {.base = UINT32_C(0x4000), .bytes = sizeof(average_arena), .data = average_arena}, + .arena = {.base = UINT32_C(0x4000), + .bytes = sizeof(average_arena), + .data = average_arena}, .weights = {.base = UINT32_C(0x5000), .bytes = 1U, .data = average_arena}, .params = {.base = UINT32_C(0x6000), .bytes = 1U, .data = average_arena}, }; diff --git a/tests/publication_history.py b/tests/publication_history.py new file mode 100644 index 00000000..fd45f9ed --- /dev/null +++ b/tests/publication_history.py @@ -0,0 +1,40 @@ +"""Materialize immutable, locally available publication test inputs. + +This is a test fixture, never a publication promotion or a current-RTL verdict. +Each exported tree keeps its own adapter, technical contract, tests and lock. +""" +from __future__ import annotations + +import io +import json +import subprocess +import tarfile +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +ORIGINAL_BOOK = "2805ea91833a6bede32f3a82df3d17d20eb5ec77" +P5_CONTRACT = "b3b30a72cd6ef5340c28e5044c96cd13258ec5a6" + + +def export(repository: Path, revision: str, destination: Path, paths: list[str] = ()) -> None: + data = subprocess.check_output(["git", "-C", str(repository), "archive", revision, *paths]) + destination.mkdir(parents=True, exist_ok=True) + with tarfile.open(fileobj=io.BytesIO(data)) as archive: + for member in archive.getmembers(): + target = (destination / member.name).resolve() + if not target.is_relative_to(destination.resolve()) or member.issym() or member.islnk(): + raise ValueError("unsafe publication fixture archive member") + archive.extractall(destination) + + +def snapshot(destination: Path, revision: str) -> Path: + export(ROOT, revision, destination, ["publications", "scripts", "rtl", "crt", "app", + "configs", "docs", "dependencies", "LICENSE", + "Makefile", "README.md", "tests"]) + lock = json.loads((destination / "dependencies/dependencies.lock.json").read_text()) + for name in ("cluster_common", "cluster_archinfo", "cluster_pwm", "cluster_rtc", + "cluster_wdg", "hazard3"): + source = lock["sources"][name] + export(ROOT / source["destination"], source["revision"], destination / source["destination"], + ["hdl"] if name == "hazard3" else []) + return destination diff --git a/tests/rtl/i2s_tb.sv b/tests/rtl/i2s_tb.sv index 80a2cb20..4ac05c0c 100644 --- a/tests/rtl/i2s_tb.sv +++ b/tests/rtl/i2s_tb.sv @@ -98,6 +98,28 @@ module i2s_tb; end endtask + task automatic zero_strobe_rx_write; + logic [31:0] before_status, after_status; + read(32'h8, before_status); + if (before_status[2]) $fatal(1, "missing RX data for direction test"); + @(negedge clk_i); + apb4.paddr = 32'h20; + apb4.pwdata = 0; + apb4.pstrb = 0; + apb4.pwrite = 1; + apb4.psel = 1; + apb4.penable = 0; + @(negedge clk_i); + apb4.penable = 1; + while (!apb4.pready) @(negedge clk_i); + if (!apb4.pslverr) $fatal(1, "zero-strobe write became an RX read"); + apb4.psel = 0; + apb4.penable = 0; + apb4.pwrite = 0; + read(32'h8, after_status); + if (before_status[23:16] != after_status[23:16]) $fatal(1, "invalid write popped RX FIFO"); + endtask + always @(posedge i2s.sclk_o) sclk_edges = sclk_edges + 1; initial begin @@ -132,6 +154,9 @@ module i2s_tb; repeat (4000) @(posedge clk_aud_i); if (sclk_edges == 0) $fatal(1, "I2S SCLK did not toggle"); read(32'h0000_0008, value); + write(32'h0000_0000, 32'h0000_0000); + repeat (64) @(negedge clk_i); + zero_strobe_rx_write(); write(32'h0000_0000, 32'h0000_0008); write(32'h0000_0000, 32'h0000_0009); repeat (2000) @(posedge clk_aud_i); diff --git a/tests/rtl/sdio_axi_memory_responder.sv b/tests/rtl/sdio_axi_memory_responder.sv index 89e9dbe3..8cb9c6da 100644 --- a/tests/rtl/sdio_axi_memory_responder.sv +++ b/tests/rtl/sdio_axi_memory_responder.sv @@ -141,7 +141,17 @@ module sdio_axi_memory_responder #( assign axi4.rresp = (s_read_error_q) ? `AXI4_RESP_SLAVE_ERROR : `AXI4_RESP_OKAY; assign axi4.rlast = s_read_bad_last_q || ((s_read_index_q + 1'b1) >= (s_read_len_q + 1'b1)); assign axi4.ruser = '0; - assign axi4.rvalid = s_read_active_q; + integer s_read_gap = 0; + integer s_read_gap_count = 0; + task automatic set_read_gap(input integer cycles_i); + s_read_gap = cycles_i < 0 ? 0 : cycles_i; + endtask + always @(posedge clk_i or negedge rst_n_i) begin + if (!rst_n_i) s_read_gap_count <= 0; + else if (axi4.rvalid && axi4.rready) s_read_gap_count <= s_read_gap; + else if (s_read_gap_count != 0) s_read_gap_count <= s_read_gap_count - 1; + end + assign axi4.rvalid = s_read_active_q && (s_read_gap_count == 0); assign axi4.awready = !s_write_active_q && !axi4.bvalid && !s_aw_stall_q; assign axi4.wready = s_write_active_q && !s_w_stall_q; diff --git a/tests/rtl/sdio_command_tb.sv b/tests/rtl/sdio_command_tb.sv index f8bb2f1d..ddc0115a 100644 --- a/tests/rtl/sdio_command_tb.sv +++ b/tests/rtl/sdio_command_tb.sv @@ -32,6 +32,8 @@ module sdio_command_tb; integer launch_count; integer captured_bits; logic [ 47:0] captured_command; + tri1 command_pad; + assign command_pad = cmd_oe_o ? cmd_do_o : 1'bz; always #1 clk_i = ~clk_i; @@ -77,8 +79,8 @@ module sdio_command_tb; end always @(posedge sck) begin - if (cmd_oe_o) begin - captured_command = {captured_command[46:0], cmd_do_o}; + if (captured_bits < 48 && (captured_bits != 0 || command_pad == 1'b0)) begin + captured_command = {captured_command[46:0], command_pad}; captured_bits = captured_bits + 1; end end @@ -95,6 +97,7 @@ module sdio_command_tb; start_i = 1'b0; clock_enable_i = 1'b1; wait (done_o); + @(negedge clk_i); if (error_o || (launch_count != 48) || (captured_bits != 48) || (captured_command[47:46] != 2'b01) || (captured_command[0] != 1'b1) || (captured_command[7:1] != sdio_pkg::sdio_crc7_calc( @@ -104,6 +107,33 @@ module sdio_command_tb; error_o, launch_count, captured_bits, captured_command, cmd_oe_o); end + // A running SDCLK can accept a command on either half-cycle. The + // stopped-clock first command above does not exercise this boundary. + for (integer phase = 0; phase < 16; phase++) begin + repeat (phase % 3 + 3) @(negedge clk_i); + captured_bits = 0; + captured_command = '0; + cmd_index_i = 6'd8; + cmd_arg_i = 32'h1aa ^ 32'(phase); + start_i = 1'b1; + @(negedge clk_i); + start_i = 1'b0; + wait (done_o); + @(negedge clk_i); + if (error_o || captured_bits != 48 || captured_command[47:40] != 8'h48 || + captured_command[39:8] != cmd_arg_i || captured_command[0] != 1'b1 || + captured_command[7:1] != sdio_pkg::sdio_crc7_calc( + captured_command[47:8] + )) + $fatal( + 1, + "SDIO running-clock frame phase=%0d bits=%0d frame=%h", + phase, + captured_bits, + captured_command + ); + end + @(posedge clk_i); resp_type_i = sdio_pkg::SdioRespR1; timeout_cycles_i = 32'd4; diff --git a/tests/rtl/sdio_native_model.sv b/tests/rtl/sdio_native_model.sv index 58ffad58..fe68aa2a 100644 --- a/tests/rtl/sdio_native_model.sv +++ b/tests/rtl/sdio_native_model.sv @@ -14,13 +14,14 @@ // implements only public command/data behavior exercised by this repository; // it is not a normative-specification replacement. module sdio_native_card_model #( - parameter bit SdioOnly = 1'b0, - parameter bit HighCapacity = 1'b1, - parameter int BlockBytes = 512, - parameter int BlockCount = 128, - parameter int FunctionCount = 1, - parameter int FunctionBytes = 4096, - parameter int BusyCycles = 2 + parameter bit MemoryInitialization = 1'b0, + parameter bit SdioOnly = 1'b0, + parameter bit HighCapacity = 1'b1, + parameter int BlockBytes = 512, + parameter int BlockCount = 128, + parameter int FunctionCount = 1, + parameter int FunctionBytes = 4096, + parameter int BusyCycles = 2 ) ( input logic rst_n_i, input logic sck_i, @@ -48,60 +49,63 @@ module sdio_native_card_model #( WriteBusy } data_state_e; - logic [ 7:0] s_memory [ 0:MemoryBytes-1]; - logic [ 7:0] s_function_memory [0:FunctionStorageBytes-1]; + logic [7:0] s_memory[0:MemoryBytes-1]; + logic [7:0] s_function_memory[0:FunctionStorageBytes-1]; - logic [ 47:0] s_cmd_shift; - integer s_cmd_bits; - logic s_cmd_response_active; - logic [135:0] s_cmd_response; - integer s_cmd_response_length; - integer s_cmd_response_index; - integer s_cmd_response_delay; - integer s_response_delay; - integer s_data_delay_cfg; - logic s_inject_crc_error; - logic s_inject_timeout; - logic s_inject_write_error; - logic s_next_cmd_crc_error; + logic [47:0] s_cmd_shift; + integer s_cmd_bits; + logic s_cmd_response_active; + logic [135:0] s_cmd_response; + integer s_cmd_response_length; + integer s_cmd_response_index; + integer s_cmd_response_delay; + integer s_response_delay; + integer s_data_delay_cfg; + logic s_inject_crc_error; + logic s_inject_timeout; + logic s_inject_write_error; + logic s_next_cmd_crc_error; - logic [ 5:0] s_last_cmd; - logic [ 31:0] s_last_arg; - logic s_last_cmd_write; - logic s_last_cmd_data; - logic s_pending_busy; - integer s_busy_count; + logic [5:0] s_last_cmd; + logic [31:0] s_last_arg; + logic s_last_cmd_write; + logic s_last_cmd_data; + logic s_memory_width4; + logic s_pending_busy; + integer s_busy_count; - data_state_e s_data_state; - logic s_data_width4; - logic s_data_fixed; - integer s_data_address; - integer s_data_bytes; - integer s_data_byte_index; - integer s_data_bit_index; - integer s_data_nibble_index; - integer s_data_crc_index; - integer s_data_delay; - logic [ 15:0] s_read_crc; - logic [ 15:0] s_read_crc_lane [ 0:3]; - logic [ 15:0] s_write_crc; - logic [ 15:0] s_write_crc_lane [ 0:3]; - logic [ 15:0] s_received_crc; - logic [ 15:0] s_received_crc_lane [ 0:3]; - logic [ 7:0] s_write_byte; - logic [ 7:0] s_write_byte_lane [ 0:3]; - integer s_write_bit_count; - integer s_write_nibble_count; - logic [ 4:0] s_write_response_token; - integer s_write_response_index; - logic s_write_response_started; - logic s_data_crc_error; - logic s_data_timeout; - logic s_irq_pending; - logic [ 2:0] s_cmd_function; - logic [ 31:0] s_cmd_address; - logic s_cmd_fixed_address; - integer s_cmd_count; + data_state_e s_data_state; + logic s_data_width4; + logic s_data_fixed; + integer s_data_address; + integer s_data_bytes; + wire [3:0] s_token_lanes = !s_data_width4 ? 4'b0001 : + (s_data_bytes >= 4 ? 4'b1111 : (4'b0001 << s_data_bytes) - 1'b1); + integer s_data_byte_index; + integer s_data_bit_index; + integer s_data_nibble_index; + integer s_data_crc_index; + integer s_data_delay; + logic [15:0] s_read_crc; + logic [15:0] s_read_crc_lane[0:3]; + logic [15:0] s_write_crc; + logic [15:0] s_write_crc_lane[0:3]; + logic [15:0] s_received_crc; + logic [15:0] s_received_crc_lane[0:3]; + logic [7:0] s_write_byte; + logic [7:0] s_write_byte_lane[0:3]; + integer s_write_bit_count; + integer s_write_nibble_count; + logic [4:0] s_write_response_token; + integer s_write_response_index; + logic s_write_response_started; + logic s_data_crc_error; + logic s_data_timeout; + logic s_irq_pending; + logic [2:0] s_cmd_function; + logic [31:0] s_cmd_address; + logic s_cmd_fixed_address; + integer s_cmd_count; function automatic logic [6:0] crc7_40(input logic [39:0] data_i); logic [6:0] crc; @@ -404,17 +408,20 @@ module sdio_native_card_model #( endtask task automatic command_received(input logic [47:0] frame_i); - logic [ 5:0] index; - logic [31:0] argument; - logic [ 6:0] expected_crc; - logic [39:0] body; - logic [31:0] response_payload; - integer address; + logic [ 5:0] index; + logic [ 31:0] argument; + logic [ 6:0] expected_crc; + logic [ 39:0] body; + logic [ 31:0] response_payload; + logic [127:0] memory_csd_body; + logic application_command; + integer address; begin index = frame_i[45:40]; argument = frame_i[39:8]; body = frame_i[47:8]; expected_crc = crc7_40(body); + application_command = s_last_cmd == 6'd55; s_last_cmd = index; s_last_arg = argument; s_last_cmd_data = 1'b0; @@ -471,7 +478,15 @@ module sdio_native_card_model #( cmd_do_o = 1'b1; end 6'd2, 6'd9: begin - make_r2_response(index == 6'd9 ? {HighCapacity ? 1'b0 : 1'b0, 127'd0} : 128'd0); + memory_csd_body = '0; + if (MemoryInitialization && index == 6'd9) begin + // The 136-bit wire frame holds the CSD at [127:0], including + // its CRC/end byte. make_r2_response appends that final byte. + memory_csd_body[119:118] = 2'd1; + memory_csd_body[75:72] = 4'd9; + memory_csd_body[61:40] = 22'(BlockCount / 1024 - 1); + end + make_r2_response(memory_csd_body); s_cmd_response_active = 1'b1; s_cmd_response_index = 0; s_cmd_response_delay = s_response_delay; @@ -492,6 +507,15 @@ module sdio_native_card_model #( s_busy_count = BusyCycles; end 6'd16, 6'd55, 6'd41, 6'd6, 6'd13: begin + if (MemoryInitialization) begin + if (index == 6'd41 && application_command) response_payload = 32'hc0ff8000; + if (index == 6'd6) begin + if (application_command) s_memory_width4 = argument == 2; + // This deterministic card supports default speed only. + else + response_payload = 32'h00400000; + end + end queue_response(index, response_payload, 1'b0, s_inject_crc_error); s_inject_crc_error = 1'b0; end @@ -654,9 +678,10 @@ module sdio_native_card_model #( s_last_arg = 32'd0; s_last_cmd_write = 1'b0; s_last_cmd_data = 1'b0; + s_memory_width4 = 1'b0; s_pending_busy = 1'b0; s_busy_count = 0; - end else if (cmd_oe_i) begin + end else if ((s_cmd_bits != 0) || (cmd_oe_i && cmd_do_i == 1'b0)) begin if (s_cmd_bits == 0) begin s_cmd_shift = {{47{1'b0}}, cmd_do_i}; s_cmd_bits = 1; @@ -717,6 +742,7 @@ module sdio_native_card_model #( s_last_arg = 32'd0; s_last_cmd_write = 1'b0; s_last_cmd_data = 1'b0; + s_memory_width4 = 1'b0; s_pending_busy = 1'b0; s_busy_count = 0; s_data_state = DataIdle; @@ -828,7 +854,7 @@ module sdio_native_card_model #( dat_do_o = 4'b1111; for (integer lane = 0; lane < 4; lane++) begin if (lane < s_data_bytes) begin - dat_do_o[lane] = s_read_crc_lane[lane][15-(s_data_crc_index+1)]; + dat_do_o[lane] = s_read_crc_lane[lane][15-s_data_crc_index]; end end end @@ -838,7 +864,7 @@ module sdio_native_card_model #( end else begin s_data_crc_index = s_data_crc_index + 1; dat_do_o = 4'b1111; - dat_do_o[0] = s_read_crc[15-(s_data_crc_index+1)]; + dat_do_o[0] = s_read_crc[15-s_data_crc_index]; end end WriteResponse: begin @@ -850,7 +876,9 @@ module sdio_native_card_model #( s_write_response_index = s_write_response_index + 1; dat_do_o[0] = s_write_response_token[4-s_write_response_index]; end else begin - s_data_state = (s_write_response_token == 5'b00101) ? WriteBusy : DataIdle; + // Keep the enum assignment portable to Icarus's native SV parser. + if (s_write_response_token == 5'b00101) s_data_state = WriteBusy; + else s_data_state = DataIdle; s_busy_count = BusyCycles; dat_do_o[0] = (s_write_response_token == 5'b00101) ? 1'b0 : 1'b1; end @@ -879,7 +907,7 @@ module sdio_native_card_model #( end else begin case (s_data_state) WriteWait: begin - if ((dat_oe_i != 4'b0000) && (dat_do_i == 4'b0000)) begin + if ((dat_oe_i != 4'b0000) && ((dat_do_i & s_token_lanes) == 4'b0000)) begin s_data_state = WritePayload; s_write_bit_count = 0; s_write_nibble_count = 0; @@ -903,7 +931,8 @@ module sdio_native_card_model #( s_data_crc_error = 1'b0; if (s_data_width4) begin for (integer lane = 0; lane < 4; lane++) begin - if (s_received_crc_lane[lane] != s_write_crc_lane[lane]) begin + if ((s_data_bytes % 4 == 0 || lane < s_data_bytes % 4) && + s_received_crc_lane[lane] != s_write_crc_lane[lane]) begin s_data_crc_error = 1'b1; end end @@ -938,6 +967,9 @@ module sdio_native_card_model #( end initial begin + if (MemoryInitialization && (!HighCapacity || SdioOnly || BlockBytes != 512 || + BlockCount < 1024 || BlockCount % 1024 != 0)) + $fatal(1, "SDIO enumeration profile requires an exactly representable SDHC capacity"); if ((BlockBytes < 1) || (BlockCount < 1) || (FunctionCount < 1) || (FunctionBytes < 256)) begin $fatal(1, "sdio_native_card_model: invalid storage geometry"); end @@ -955,6 +987,7 @@ module sdio_native_card_model #( s_last_arg = 32'd0; s_last_cmd_write = 1'b0; s_last_cmd_data = 1'b0; + s_memory_width4 = 1'b0; s_pending_busy = 1'b0; s_busy_count = 0; s_data_state = DataIdle; diff --git a/tests/rtl/sdio_standalone_tb.sv b/tests/rtl/sdio_standalone_tb.sv index 6d452204..1fe49df2 100644 --- a/tests/rtl/sdio_standalone_tb.sv +++ b/tests/rtl/sdio_standalone_tb.sv @@ -12,7 +12,9 @@ module sdio_standalone_tb #( parameter bit CardHighCapacity = 1'b1, - parameter bit RunFourBit = 1'b1 + parameter bit RunFourBit = 1'b1, + parameter bit PadLoopback = 1'b0, + parameter int CardBusyCycles = 8 ); localparam logic [31:0] HostCtrl = 32'h00C; localparam logic [31:0] ClockCtrl = 32'h010; @@ -65,8 +67,21 @@ module sdio_standalone_tb #( integer timeout_count_before; integer crc_count_before; - assign sdio.cmd_di_i = sdio.cmd_oe_o ? sdio.cmd_do_o : card_cmd_do; - assign sdio.dat_di_i = (sdio.dat_oe_o & sdio.dat_do_o) | (~sdio.dat_oe_o & card_dat_do); + tri1 command_pad, data0_pad, data1_pad, data2_pad, data3_pad; + wire [3:0] data_pads = {data3_pad, data2_pad, data1_pad, data0_pad}; + assign command_pad = sdio.cmd_oe_o ? sdio.cmd_do_o : 1'bz; + assign command_pad = sdio.cmd_oe_o ? 1'bz : card_cmd_do; + assign data0_pad = sdio.dat_oe_o[0] ? sdio.dat_do_o[0] : 1'bz; + assign data1_pad = sdio.dat_oe_o[1] ? sdio.dat_do_o[1] : 1'bz; + assign data2_pad = sdio.dat_oe_o[2] ? sdio.dat_do_o[2] : 1'bz; + assign data3_pad = sdio.dat_oe_o[3] ? sdio.dat_do_o[3] : 1'bz; + assign data0_pad = sdio.dat_oe_o[0] ? 1'bz : card_dat_do[0]; + assign data1_pad = sdio.dat_oe_o[1] ? 1'bz : card_dat_do[1]; + assign data2_pad = sdio.dat_oe_o[2] ? 1'bz : card_dat_do[2]; + assign data3_pad = sdio.dat_oe_o[3] ? 1'bz : card_dat_do[3]; + assign sdio.cmd_di_i = PadLoopback ? command_pad : (sdio.cmd_oe_o ? sdio.cmd_do_o : card_cmd_do); + assign sdio.dat_di_i = PadLoopback ? data_pads : + ((sdio.dat_oe_o & sdio.dat_do_o) | (~sdio.dat_oe_o & card_dat_do)); always #5 clk_i = ~clk_i; @@ -87,15 +102,15 @@ module sdio_standalone_tb #( .HighCapacity(CardHighCapacity), .BlockBytes (512), .BlockCount (128), - .BusyCycles (8) + .BusyCycles (CardBusyCycles) ) u_card ( .rst_n_i (rst_n_i), .sck_i (sdio.sck_o), .cmd_oe_i(sdio.cmd_oe_o), - .cmd_do_i(sdio.cmd_do_o), + .cmd_do_i(PadLoopback ? command_pad : sdio.cmd_do_o), .cmd_do_o(card_cmd_do), .dat_oe_i(sdio.dat_oe_o), - .dat_do_i(sdio.dat_do_o), + .dat_do_i(PadLoopback ? data_pads : sdio.dat_do_o), .dat_do_o(card_dat_do), .irq_o (card_irq) ); @@ -204,7 +219,13 @@ module sdio_standalone_tb #( end end if (timeout_guard >= 200000) begin - $fatal(1, "data path did not drain"); + $fatal( + 1, + "data path did not drain: data_state=%0d dma_state=%0d rx=%b%b dma_bytes=%0d card_state=%0d card_bytes=%0d clk=%b", + u_apb4_sdio.u_sdio_core.u_sdio_data.s_state_q, + u_apb4_sdio.u_sdio_core.u_sdio_dma.s_state_q, u_apb4_sdio.u_sdio_core.s_data_rx_valid, + u_apb4_sdio.u_sdio_core.s_data_rx_ready, u_apb4_sdio.s_bytes_done, + u_card.u_model.s_data_state, u_card.u_model.s_data_byte_index, sdio.sck_o); end end endtask @@ -552,6 +573,84 @@ module sdio_standalone_tb #( $fatal(1, "memory-to-card DMA end-to-end direction failed"); end + // P6 exercises a full four-lane DMA payload at divider 1, with memory + // arbitration delays. Short one-lane tails do not cover its word pauses. + if (RunFourBit) begin + u_axi_memory.clear_memory(); + u_axi_memory.set_read_gap(24); + u_axi_memory.write_word(32'h0, 32'h200); + u_axi_memory.write_word(32'h4, 32'd1024); + u_axi_memory.write_word(32'h8, 32'd0); + u_axi_memory.write_word(32'hc, 32'h5); + for (integer word_index = 0; word_index < 256; word_index++) + u_axi_memory.write_word(32'h200 + 32'(word_index * 4), 32'h13579bdf ^ 32'(word_index)); + configure_dma_data(1'b1, 1'b1, 16'd512); + apb_write(BlockCount, 32'd2, 4'hf); + u_card.arm_write(24000, 1024, 1'b1, 1'b0); + apb_write(DataStart, 32'd1, 4'h1); + wait_data(); + apb_read(DataStatus, apb_value); + for (integer byte_index = 0; byte_index < 1024; byte_index++) begin + if (u_card.read_backing( + 24000 + byte_index + ) != 8'((32'h13579bdf ^ 32'(byte_index / 4)) >> ((byte_index % 4) * 8))) + $fatal( + 1, + "SDIO four-lane DMA payload byte=%0d got=%h expected=%h", + byte_index, + u_card.read_backing( + 24000 + byte_index + ), + 8'((32'h13579bdf ^ 32'(byte_index / 4)) >> ((byte_index % 4) * 8)) + ); + end + if ((apb_value & 32'h1e) != 0) + $fatal( + 1, + "SDIO four-lane DMA block status=%h card_crc=%b", + apb_value, + u_card.u_model.s_data_crc_error + ); + if (u_axi_memory.read_word(32'hc) != 32'h00010004) + $fatal(1, "SDIO four-lane DMA descriptor did not retire"); + // Delay descriptor reads while the card sends its first receive word. + u_axi_memory.clear_memory(); + u_axi_memory.write_word(32'h0, 32'h800); + u_axi_memory.write_word(32'h4, 32'd1024); + u_axi_memory.write_word(32'h8, 32'd0); + u_axi_memory.write_word(32'hc, 32'h5); + configure_dma_data(1'b1, 1'b0, 16'd512); + apb_write(BlockCount, 32'd2, 4'hf); + u_card.arm_read(24000, 1024, 1'b1, 1'b0); + apb_write(DataStart, 32'd1, 4'h1); + wait_data(); + apb_read(DataStatus, apb_value); + for (integer word_index = 0; word_index < 256; word_index++) begin + if (u_axi_memory.read_word( + 32'h800 + 32'(word_index * 4) + ) != (32'h13579bdf ^ 32'(word_index))) + $fatal( + 1, + "SDIO delayed-descriptor receive word=%0d got=%h expected=%h", + word_index, + u_axi_memory.read_word( + 32'h800 + 32'(word_index * 4) + ), + 32'h13579bdf ^ 32'(word_index) + ); + end + if ((apb_value & 32'h1e) != 0 || u_axi_memory.read_word(32'hc) != 32'h00010004) + $fatal( + 1, + "SDIO delayed-descriptor receive status=%h descriptor=%h", + apb_value, + u_axi_memory.read_word( + 32'hc + ) + ); + u_axi_memory.set_read_gap(0); + end + apb_read(AxiCount, apb_value); timeout_count_before = apb_value; u_axi_memory.clear_memory(); @@ -675,3 +774,10 @@ endmodule module sdio_onebit_tb; sdio_standalone_tb #(.RunFourBit(1'b0)) u_tb (); endmodule + +module tiny_p6_sdio_pads_tb; + sdio_standalone_tb #( + .PadLoopback (1'b1), + .CardBusyCycles(2) + ) u_tb (); +endmodule diff --git a/tests/rtl/tiny_axi4_fabric_tb.sv b/tests/rtl/tiny_axi4_fabric_tb.sv index 0371854c..85c499b4 100644 --- a/tests/rtl/tiny_axi4_fabric_tb.sv +++ b/tests/rtl/tiny_axi4_fabric_tb.sv @@ -1,8 +1,9 @@ +`include "tiny_topology.svh" `timescale 1ns / 1ps module tiny_axi4_fabric_tb; logic clk_i = 0; logic rst_n_i = 0; - logic target_rst_n[23]; + logic target_rst_n[`RETROSOC_TINY__TARGET_COUNT]; always #5 clk_i = !clk_i; axi4_if #( .ADDR_WIDTH(32), @@ -18,7 +19,7 @@ module tiny_axi4_fabric_tb; .DATA_WIDTH(32), .ID_WIDTH (1), .USER_WIDTH(1) - ) targets[23] ( + ) targets[`RETROSOC_TINY__TARGET_COUNT] ( .aclk (clk_i), .aresetn(rst_n_i) ); @@ -27,8 +28,8 @@ module tiny_axi4_fabric_tb; logic [5:0][ 1:0] fault_resp; logic [63:0] cpu_wait, dma_wait; integer faults = 0; - integer completions [ 3]; - integer target_requests[23]; + integer completions [ 3]; + integer target_requests[`RETROSOC_TINY__TARGET_COUNT]; logic [2:0][31:0] awaddr; logic [2:0][ 7:0] awlen; logic [2:0][ 2:0] awsize; @@ -78,7 +79,7 @@ module tiny_axi4_fabric_tb; .cpu_wait_o (cpu_wait), .dma_wait_o (dma_wait) ); - for (genvar target = 0; target < 23; target++) begin : gen_target_reset + for (genvar target = 0; target < `RETROSOC_TINY__TARGET_COUNT; target++) begin : gen_target_reset assign target_rst_n[target] = rst_n_i; end always @(posedge clk_i) faults <= faults + $countones(fault_valid); @@ -126,11 +127,11 @@ module tiny_axi4_fabric_tb; assign masters[m].aruser = '0; assign masters[m].wuser = '0; end - for (genvar t = 0; t < 23; t++) begin : gen_target + for (genvar t = 0; t < `RETROSOC_TINY__TARGET_COUNT; t++) begin : gen_target // Deterministic terminal responder allows independent checks of routing, // burst length and ID preservation without sharing the fabric decoder. axi4_error_slave #( - .Response(t == 21 ? 2'b11 : (t == 22 ? 2'b10 : 2'b00)) + .Response(t == `RETROSOC_TINY__DECODE_ERROR ? 2'b11 : (t == `RETROSOC_TINY__PROTOCOL_ERROR ? 2'b10 : 2'b00)) ) u_response ( .clk_i (clk_i), .rst_n_i(rst_n_i), @@ -273,7 +274,7 @@ module tiny_axi4_fabric_tb; join if (completions[0]!=7 || completions[1]!=6 || faults!=7 || target_requests[0]!=2 || target_requests[3]!=1 || target_requests[4]!=1 || - target_requests[6]!=2 || target_requests[21]!=3 || target_requests[22]!=4) + target_requests[6]!=2 || target_requests[`RETROSOC_TINY__DECODE_ERROR]!=3 || target_requests[`RETROSOC_TINY__PROTOCOL_ERROR]!=4) $fatal(1, "accounting/fairness mismatch faults=%0d", faults); read_request(0, 32'h30000000, 1, 2, 1, 0); arprot[0] = 3'b100; diff --git a/tests/rtl/tiny_r2_p5_fabric_tb.sv b/tests/rtl/tiny_r2_p5_fabric_tb.sv index 8b67df48..f8ac5184 100644 --- a/tests/rtl/tiny_r2_p5_fabric_tb.sv +++ b/tests/rtl/tiny_r2_p5_fabric_tb.sv @@ -1,8 +1,9 @@ +`include "tiny_topology.svh" `timescale 1ns / 1ps module tiny_r2_p5_fabric_tb; logic clk_i = 0; logic rst_n_i = 0; - logic target_rst_n[23]; + logic target_rst_n[`RETROSOC_TINY__TARGET_COUNT]; always #5 clk_i = !clk_i; axi4_if #( .ADDR_WIDTH(32), @@ -18,7 +19,7 @@ module tiny_r2_p5_fabric_tb; .DATA_WIDTH(32), .ID_WIDTH (1), .USER_WIDTH(1) - ) targets[23] ( + ) targets[`RETROSOC_TINY__TARGET_COUNT] ( .aclk (clk_i), .aresetn(rst_n_i) ); @@ -27,14 +28,15 @@ module tiny_r2_p5_fabric_tb; logic [5:0][ 1:0] fault_resp; logic [63:0] cpu_wait, dma_wait; integer faults = 0; - logic [22:0] hold_address = '0, hold_read = '0, hold_write = '0, hold_b = '0; - integer error_beat [23]; + logic [`RETROSOC_TINY__TARGET_COUNT-1:0] + hold_address = '0, hold_read = '0, hold_write = '0, hold_b = '0; + integer error_beat [`RETROSOC_TINY__TARGET_COUNT]; logic [ 2:0] error_last_wait = '0; logic release_error = 0; integer simultaneous_faults = 0; logic [31:0] seed = 32'h12345678; - integer completions [ 3]; - integer target_requests [23]; + integer completions [ 3]; + integer target_requests [`RETROSOC_TINY__TARGET_COUNT]; logic [ 2:0][31:0] awaddr; logic [ 2:0][ 7:0] awlen; logic [ 2:0][ 2:0] awsize; @@ -84,7 +86,7 @@ module tiny_r2_p5_fabric_tb; .cpu_wait_o (cpu_wait), .dma_wait_o (dma_wait) ); - for (genvar target = 0; target < 23; target++) begin : gen_target_reset + for (genvar target = 0; target < `RETROSOC_TINY__TARGET_COUNT; target++) begin : gen_target_reset assign target_rst_n[target] = rst_n_i; end always @(posedge clk_i) begin @@ -140,9 +142,9 @@ module tiny_r2_p5_fabric_tb; assign masters[m].aruser = '0; assign masters[m].wuser = '0; end - for (genvar t = 0; t < 23; t++) begin : gen_target + for (genvar t = 0; t < `RETROSOC_TINY__TARGET_COUNT; t++) begin : gen_target tiny_p5_target_model #( - .ErrorResponse(t == 21 ? 3 : (t == 22 ? 2 : 0)) + .ErrorResponse(t == `RETROSOC_TINY__DECODE_ERROR ? 3 : (t == `RETROSOC_TINY__PROTOCOL_ERROR ? 2 : 0)) ) u_response ( .clk_i (clk_i), .rst_n_i (rst_n_i), @@ -346,13 +348,13 @@ module tiny_r2_p5_fabric_tb; end join // A waiting APB FIFO branch cannot block independent control admission. - hold_read[12] = 1; + hold_read[`RETROSOC_TINY__TARGET_XPI] = 1; fork read_request(1, 32'h10009000, 1, 2, 1, 0); begin - wait (!u_dut.target_idle_o[12]); + wait (!u_dut.target_idle_o[`RETROSOC_TINY__TARGET_XPI]); read_request(0, 32'h1000a000, 1, 2, 1, 0); - hold_read[12] = 0; + hold_read[`RETROSOC_TINY__TARGET_XPI] = 0; end join fork diff --git a/tests/rtl/tiny_r2_p5_reset_tb.sv b/tests/rtl/tiny_r2_p5_reset_tb.sv index 26b161be..5290e2ff 100644 --- a/tests/rtl/tiny_r2_p5_reset_tb.sv +++ b/tests/rtl/tiny_r2_p5_reset_tb.sv @@ -1,8 +1,9 @@ +`include "tiny_topology.svh" `timescale 1ns / 1ps module tiny_r2_p5_reset_tb; logic clk_i = 0, rst_n_i = 0; always #5 clk_i = !clk_i; - logic target_rst_n[23]; + logic target_rst_n[`RETROSOC_TINY__TARGET_COUNT]; axi4_if #( .ADDR_WIDTH(32), .DATA_WIDTH(32), @@ -17,22 +18,26 @@ module tiny_r2_p5_reset_tb; .DATA_WIDTH(32), .ID_WIDTH (1), .USER_WIDTH(1) - ) targets[23] ( + ) targets[`RETROSOC_TINY__TARGET_COUNT] ( .aclk (clk_i), .aresetn(target_rst_n) ); apb4_if apb ( .pclk (clk_i), - .presetn(target_rst_n[12]) + .presetn(target_rst_n[`RETROSOC_TINY__TARGET_XPI]) ); - logic [22:0] hold_address = '0, hold_read = '0, hold_write = '0, hold_b = '0; - integer error_beat[23]; - integer captures[23], addresses[23], writes[23]; + logic [`RETROSOC_TINY__TARGET_COUNT-1:0] + hold_address = '0, hold_read = '0, hold_write = '0, hold_b = '0; + integer error_beat[`RETROSOC_TINY__TARGET_COUNT]; + integer + captures[`RETROSOC_TINY__TARGET_COUNT], + addresses[`RETROSOC_TINY__TARGET_COUNT], + writes[`RETROSOC_TINY__TARGET_COUNT]; logic [5:0] fault_valid, fault_instruction; - logic [ 5:0][31:0] fault_addr; - logic [ 5:0][ 1:0] fault_resp; - logic [ 2:0] owner_idle; - logic [22:0] target_idle; + logic [ 5:0][31:0] fault_addr; + logic [ 5:0][ 1:0] fault_resp; + logic [ 2:0] owner_idle; + logic [`RETROSOC_TINY__TARGET_COUNT-1:0] target_idle; logic [63:0] cpu_wait, dma_wait; logic [2:0] arvalid = 0, awvalid = 0, wvalid = 0, rready = 0, bready = 0; logic [2:0][31:0] araddr = 0, awaddr = 0, wdata = 0; @@ -200,8 +205,8 @@ module tiny_r2_p5_reset_tb; end end - for (genvar t = 0; t < 23; t++) begin : gen_target - if (t == 12) begin : gen_apb + for (genvar t = 0; t < `RETROSOC_TINY__TARGET_COUNT; t++) begin : gen_target + if (t == `RETROSOC_TINY__TARGET_XPI) begin : gen_apb tiny_axi42apb4 u_bridge ( .clk_i (clk_i), .rst_n_i(target_rst_n[t]), @@ -232,8 +237,8 @@ module tiny_r2_p5_reset_tb; end end end - assign apb.pready = apb.psel && apb.penable && !hold_read[12] && !hold_b[12]; - assign apb.prdata = 32'hcafe_f00d; + assign apb.pready = apb.psel && apb.penable && !hold_read[`RETROSOC_TINY__TARGET_XPI] && !hold_b[`RETROSOC_TINY__TARGET_XPI]; + assign apb.prdata = 32'hcafe_f00d; assign apb.pslverr = 0; always @(posedge clk_i) begin if (rst_n_i) begin @@ -249,7 +254,7 @@ module tiny_r2_p5_reset_tb; task automatic quiet; repeat (4) @(negedge clk_i); - if (owner_idle != 7 || target_idle != 23'h7fffff || rvalid != 0 || bvalid != 0) + if (owner_idle != 7 || target_idle != {`RETROSOC_TINY__TARGET_COUNT{1'b1}} || rvalid != 0 || bvalid != 0) $fatal(1, "credit/response not retired"); endtask task automatic receive_read(input int index, input int beats, input logic [1:0] expected, @@ -310,7 +315,7 @@ module tiny_r2_p5_reset_tb; end for (integer n = 0; n < beats; n++) begin expected = inject_reset && n >= prefix ? 3 : (n == error_beat[t] ? 2 : 0); - data = expected == 3 ? 0 : (t == 12 ? 32'hcafe_f00d : (address + 32'(4 * n)) ^ 32'h10000000); + data = expected == 3 ? 0 : (t == `RETROSOC_TINY__TARGET_XPI ? 32'hcafe_f00d : (address + 32'(4 * n)) ^ 32'h10000000); receive_read(n, beats, expected, data); end target_rst_n[t] = 1; @@ -412,7 +417,7 @@ module tiny_r2_p5_reset_tb; endtask initial begin - for (integer t = 0; t < 23; t++) begin + for (integer t = 0; t < `RETROSOC_TINY__TARGET_COUNT; t++) begin target_rst_n[t] = 0; captures[t] = 0; addresses[t] = 0; @@ -422,7 +427,7 @@ module tiny_r2_p5_reset_tb; for (integer e = 0; e < 6; e++) faults[e] = 0; repeat (4) @(negedge clk_i); rst_n_i = 1; - for (integer t = 0; t < 23; t++) target_rst_n[t] = 1; + for (integer t = 0; t < `RETROSOC_TINY__TARGET_COUNT; t++) target_rst_n[t] = 1; // SRAM and XPI: address cancellation, missing 16 beats, two captured // beats under backpressure, and already captured successful completion. for (integer t = 0; t < 5; t += 4) begin @@ -441,13 +446,13 @@ module tiny_r2_p5_reset_tb; read_case(32'h30000000, 0, 16, 1, 0, 1); error_beat[0] = -1; // Actual APB bridge, with a withheld peripheral completion. - read_case(32'h10009000, 12, 1, 0, 1, 1); - read_case(32'h10009000, 12, 1, 0, 0, 1); - read_case(32'h10009000, 12, 1, 1, 0, 1); - write_case(32'h10009000, 12, 1, 0, 0, 1); - write_case(32'h10009000, 12, 1, 1, 0, 0); - write_case(32'h10009000, 12, 1, 1, 1, 0); - dma_case(32'h10009000, 12, 1, 0); + read_case(32'h10009000, `RETROSOC_TINY__TARGET_XPI, 1, 0, 1, 1); + read_case(32'h10009000, `RETROSOC_TINY__TARGET_XPI, 1, 0, 0, 1); + read_case(32'h10009000, `RETROSOC_TINY__TARGET_XPI, 1, 1, 0, 1); + write_case(32'h10009000, `RETROSOC_TINY__TARGET_XPI, 1, 0, 0, 1); + write_case(32'h10009000, `RETROSOC_TINY__TARGET_XPI, 1, 1, 0, 0); + write_case(32'h10009000, `RETROSOC_TINY__TARGET_XPI, 1, 1, 1, 0); + dma_case(32'h10009000, `RETROSOC_TINY__TARGET_XPI, 1, 0); $display("P5_RESET cases=%0d production_dma=3 burst_beats=16 held_RB=1 first_fault=1", cases); $display("SIM_TEST_PASS Tiny P5 target reset isolation"); $finish; diff --git a/tests/rtl/tiny_r2_p5_system_tb.sv b/tests/rtl/tiny_r2_p5_system_tb.sv index 454f32d0..37a89c3b 100644 --- a/tests/rtl/tiny_r2_p5_system_tb.sv +++ b/tests/rtl/tiny_r2_p5_system_tb.sv @@ -1,3 +1,4 @@ +`include "tiny_topology.svh" `timescale 1ns / 1ps module tiny_r2_p5_system_tb; logic clk_i = 0, rst_n_i = 0; @@ -38,7 +39,7 @@ retrosoc_tiny u_dut ( u_dut.u_masters_axi4_if[1].wvalid && u_dut.u_masters_axi4_if[1].wready) overlap_cycles++; if((u_dut.u_fabric.s_busy_q[2] || u_dut.u_fabric.s_busy_q[3]) && - (!u_dut.u_fabric.target_idle_o[4] || !u_dut.u_fabric.target_idle_o[12]) && + (!u_dut.u_fabric.target_idle_o[4] || !u_dut.u_fabric.target_idle_o[`RETROSOC_TINY__TARGET_XPI]) && u_dut.u_sysctrl_apb4_if.psel && u_dut.u_sysctrl_apb4_if.penable && u_dut.u_sysctrl_apb4_if.pready) controls++; if(u_dut.u_cpu_local_if[1].req_valid && u_dut.u_cpu_local_if[1].req_ready && @@ -76,7 +77,7 @@ retrosoc_tiny u_dut ( jtag_dmi(1, 7'h10, 3, status); $display("P5_SYSTEM_STAGE reset-request pending=%b", u_dut.u_cpu.s_reset_pending); wait (!u_dut.s_core_rst_n); - if (!u_dut.u_fabric.s_busy_q[3] || u_dut.u_fabric.target_idle_o[12]) + if (!u_dut.u_fabric.s_busy_q[3] || u_dut.u_fabric.target_idle_o[`RETROSOC_TINY__TARGET_XPI]) $fatal(1, "hart reset cancelled unrelated DMA"); hart_reset_observed = 1; $display("P5_SYSTEM_STAGE hart-reset"); diff --git a/tests/rtl/tiny_r2_p6_bridge_tb.sv b/tests/rtl/tiny_r2_p6_bridge_tb.sv new file mode 100644 index 00000000..8797d53a --- /dev/null +++ b/tests/rtl/tiny_r2_p6_bridge_tb.sv @@ -0,0 +1,87 @@ +`timescale 1ns / 1ps +module tiny_r2_p6_bridge_tb; + logic clk_i = 0, rst_n_i = 0; + always #5 clk_i = !clk_i; + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) axi ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + apb4_if apb ( + .pclk (clk_i), + .presetn(rst_n_i) + ); + tiny_axi42apb4 u_dut ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .axi4 (axi), + .apb4 (apb) + ); + assign apb.pready = apb.psel && apb.penable; + assign apb.prdata = 32'h13579bdf; + // A strict APB4 receiver rejects nonzero read strobes independently of PWRITE. + assign apb.pslverr = apb.psel && apb.penable && !apb.pwrite && apb.pstrb != 0; + task automatic write_read(input logic [3:0] mask); + @(negedge clk_i); + axi.awvalid = 1; + do @(posedge clk_i); while (!axi.awready); + @(negedge clk_i); + axi.awvalid = 0; + axi.wvalid = 1; + axi.wstrb = mask; + do @(posedge clk_i); while (!axi.wready); + @(negedge clk_i); + axi.wvalid = 0; + do @(posedge clk_i); while (!apb.penable); + if (!apb.pwrite || apb.pstrb !== mask) $fatal(1, "SIM_TEST_FAIL lost write byte lanes"); + do @(posedge clk_i); while (!axi.bvalid); + if (axi.bresp != 0) $fatal(1, "SIM_TEST_FAIL APB write response"); + @(negedge clk_i); + axi.arvalid = 1; + do @(posedge clk_i); while (!axi.arready); + @(negedge clk_i); + axi.arvalid = 0; + do @(posedge clk_i); while (!axi.rvalid); + if (axi.rresp != 0 || axi.rdata !== 32'h13579bdf || !axi.rlast) + $fatal(1, "SIM_TEST_FAIL stale write strobes corrupted APB read"); + @(negedge clk_i); + endtask + initial begin + axi.awvalid = 0; + axi.awaddr = 'h10007000; + axi.awlen = 0; + axi.awsize = 2; + axi.awburst = 1; + axi.awlock = 0; + axi.awid = 0; + axi.wvalid = 0; + axi.wdata = 'h12345678; + axi.wstrb = 0; + axi.wlast = 1; + axi.bready = 1; + axi.arvalid = 0; + axi.araddr = 'h10007008; + axi.arlen = 0; + axi.arsize = 2; + axi.arburst = 1; + axi.arlock = 0; + axi.arid = 0; + axi.rready = 1; + repeat (4) @(negedge clk_i); + rst_n_i = 1; + write_read(15); + write_read(1); + write_read(3); + write_read(10); + $display("SIM_TEST_PASS Tiny P6 APB read strobes"); + $finish; + end + initial begin + #100000; + $fatal(1, "SIM_TEST_TIMEOUT P6 APB strobes"); + end +endmodule diff --git a/tests/rtl/tiny_r2_p6_shared_tb.sv b/tests/rtl/tiny_r2_p6_shared_tb.sv new file mode 100644 index 00000000..4fbbf809 --- /dev/null +++ b/tests/rtl/tiny_r2_p6_shared_tb.sv @@ -0,0 +1,153 @@ +`timescale 1ns / 1ps +module tiny_r2_p6_shared_tb; + logic clk_i = 0, clk_aud_i = 0, rst_n_i = 0, rst_aud_n_i = 0; + always #20.833333 clk_i = !clk_i; + always #27.126736111 clk_aud_i = !clk_aud_i; + apb4_if apb4 ( + .pclk (clk_i), + .presetn(rst_n_i) + ); + axi4_stream_if #( + .DATA_WIDTH(32) + ) tx ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + axi4_stream_if #( + .DATA_WIDTH(32) + ) rx ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + i2s_if i2s (); + logic streaming = 0, wide = 0; + integer sent = 0, received = 0, matched = 0, examined = 0; + logic found = 0; + logic [31:0] value; + assign i2s.adcdat_i = i2s.dacdat_o; + assign tx.tvalid = streaming; + assign tx.tdata = wide ? 32'h120000 + 32'(sent) : + {16'(16'h2001 + 2*sent), 16'(16'h2000 + 2*sent)}; + assign tx.tstrb = 4'hf; + assign tx.tkeep = 4'hf; + assign tx.tlast = 0; + assign tx.tuser = 0; + assign tx.tid = 0; + assign tx.tdest = 0; + assign rx.tready = 1; + apb4_i2s u_dut ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .clk_aud_i (clk_aud_i), + .rst_aud_n_i (rst_aud_n_i), + .dma_tx_stall_o (), + .dma_rx_stall_o (), + .tx_underrun_evt_o(), + .rx_overrun_evt_o (), + .rx_flush_busy_o (), + .apb4 (apb4), + .tx_axis (tx), + .rx_axis (rx), + .i2s (i2s) + ); + task automatic write(input logic [31:0] address, input logic [31:0] data); + @(negedge clk_i); + apb4.paddr = address; + apb4.pwdata = data; + apb4.pstrb = 15; + apb4.pwrite = 1; + apb4.psel = 1; + apb4.penable = 0; + @(negedge clk_i); + apb4.penable = 1; + do @(posedge clk_i); while (!apb4.pready); + if (apb4.pslverr) $fatal(1, "SIM_TEST_FAIL P6 I2S APB write %h", address); + @(negedge clk_i); + apb4.psel = 0; + apb4.penable = 0; + endtask + task automatic sample (input logic [31:0] observed); + logic [31:0] first; + first = wide ? 32'h120020 : 32'h2020; + if (!found && observed == first) found = 1; + if (found && matched < 512) begin + if (observed !== first + 32'(matched)) + $fatal( + 1, + "SIM_TEST_FAIL P6 I2S sample bits=%0d index=%0d got=%h expected=%h", + wide ? 24 : 16, + matched, + observed, + first + 32'(matched) + ); + matched++; + end + examined++; + if (!found && examined > 40) + $fatal( + 1, + "SIM_TEST_FAIL P6 I2S startup/payload missing bits=%0d last=%h", + wide ? 24 : 16, + observed + ); + endtask + always @(posedge clk_i) begin + if (rst_n_i) begin + if (tx.tvalid && tx.tready) sent <= sent + 1; + if (rx.tvalid && rx.tready) begin + if (rx.tkeep !== 4'hf || rx.tstrb !== 4'hf) + $fatal(1, "SIM_TEST_FAIL P6 I2S receive qualifiers"); + if (wide) sample (rx.tdata); + else begin + sample ({16'd0, rx.tdata[15:0]}); + sample ({16'd0, rx.tdata[31:16]}); + end + received++; + end + end + end + initial begin + apb4.psel = 0; + apb4.penable = 0; + apb4.pwrite = 0; + apb4.paddr = 0; + apb4.pwdata = 0; + apb4.pstrb = 0; + apb4.pprot = 0; + for (integer mode = 0; mode < 2; mode++) begin + @(negedge clk_i); + rst_n_i = 0; + rst_aud_n_i = 0; + streaming = 0; + sent = 0; + received = 0; + matched = 0; + examined = 0; + found = 0; + wide = mode != 0; + repeat (8) @(negedge clk_i); + rst_n_i = 1; + repeat (8) @(negedge clk_aud_i); + rst_aud_n_i = 1; + repeat (32) @(negedge clk_i); + write(4, 3); + repeat (256) @(negedge clk_i); + if (u_dut.s_tx_flush_busy || u_dut.s_rx_flush_busy) + $fatal(1, "SIM_TEST_FAIL P6 initial disabled audio flush"); + write('h10, wide ? 2 : 0); + write('h18, 'h00200060); + write('hc, 3); + streaming = 1; + wait (sent >= 96); + write(0, 7); + wait (matched == 512); + $display("P6_I2S bits=%0d samples=%0d", wide ? 24 : 16, matched); + end + $display("SIM_TEST_PASS Tiny P6 I2S payload"); + $finish; + end + initial begin + #30000000; + $fatal(1, "SIM_TEST_TIMEOUT P6 I2S payload"); + end +endmodule diff --git a/tests/test_commercial_flow.py b/tests/test_commercial_flow.py index d001e7be..177d2148 100644 --- a/tests/test_commercial_flow.py +++ b/tests/test_commercial_flow.py @@ -421,7 +421,7 @@ def test_commercial_timing_contract_supports_tiny(tmp_path: Path) -> None: check=True, ) assert result.stdout.splitlines() == [ - "jtag,system", + "audio,jtag,system", "ext_rst_n_i_pad,jtag_trst_n_i_pad", ] assert "observation {u_clock_buffer/clk_o}" in output.read_text( @@ -660,7 +660,7 @@ CONSTRAINT_SMOKE_CASES = { "TINY": { "domains": "rtl/tiny/integration/clock_reset_domains.json", "pin_map": "rtl/tiny/pin_map/pin_map.json", - "masters": "clk_system clk_jtag", + "masters": "clk_system clk_jtag clk_audio", "generated": "", "groups": "1", }, diff --git a/tests/test_publication_programming.py b/tests/test_publication_programming.py index ff91ba4b..b1a032f4 100644 --- a/tests/test_publication_programming.py +++ b/tests/test_publication_programming.py @@ -96,6 +96,21 @@ def test_channel_coverage_cannot_drop_the_boot_channel(spec): pr.collect_programming(ROOT, spec, IDS, REFERENCE["source_revision"]) +def test_mini_channel_selection_excludes_inactive_tiny_roles(): + header = (ROOT / "crt/include/retrosoc/hal/dma.h").read_text() + selected = pr.mini_channel_enum(header) + channels = pr.constants(selected, "RS_DMA_CHANNEL_") + assert sorted(channels.values()) == list(range(6)) + assert "RS_DMA_CHANNEL_I2S_TX = RS_DMA_CHANNEL_BULK" in selected + # A Tiny allocation change must not become a Mini allocation or alias. + assert pr.mini_channel_enum(header.replace("RS_DMA_CHANNEL_I2S_TX = 6", "RS_DMA_CHANNEL_I2S_TX = 7")) == selected + with pytest.raises(ValueError, match="alias binding"): + pr.mini_channel_enum(header.replace("RS_DMA_CHANNEL_I2S_TX = RS_DMA_CHANNEL_BULK", + "RS_DMA_CHANNEL_I2S_TX = RS_DMA_CHANNEL_CRYPTO_IN")) + with pytest.raises(ValueError, match="unreviewed"): + pr.mini_channel_enum(header.replace("#ifdef RS_SOC_TINY", "#ifdef UNKNOWN_PRODUCT")) + + def test_credit_parser_fails_closed_on_unreviewed_shape(): with pytest.raises(ValueError, match="unsupported credit function"): pr.credit_values("function automatic int limits(input int master); return compute(master); endfunction", "limits", 8) diff --git a/tests/test_publication_tiny.py b/tests/test_publication_tiny.py index 68e1be4c..c2d6a647 100644 --- a/tests/test_publication_tiny.py +++ b/tests/test_publication_tiny.py @@ -1,57 +1,38 @@ -"""Tiny publication is parameter-bound, complete and isolated from Mini inputs.""" +"""Historical Tiny publication contracts remain separate from P6 source support.""" from __future__ import annotations -import copy -import json -import re +import os from pathlib import Path -import shutil +import subprocess +import sys import pytest from publications import tiny_reference as tiny -from publications.register_reference import parse_definitions +from publication_history import ORIGINAL_BOOK, P5_CONTRACT, snapshot ROOT = Path(__file__).resolve().parents[1] -@pytest.fixture(scope="module") -def data(): - return tiny.collect(tiny.read(ROOT / tiny.BOOK / "tiny.json"), check_snapshot=False) +@pytest.mark.parametrize("revision", [ORIGINAL_BOOK, P5_CONTRACT]) +def test_historical_publication_contracts(tmp_path, revision): + historical = snapshot(tmp_path / "source", revision) + result = subprocess.run( + [sys.executable, "-m", "pytest", "-q", "tests/test_publication_tiny.py", + "--basetemp=" + str(tmp_path / "tests"), "--tb=short"], + cwd=historical, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + env={**os.environ, "PYTHONDONTWRITEBYTECODE": "1"}, timeout=180, + ) + (tmp_path / "historical-tests.log").write_text(result.stdout) + assert result.returncode == 0, result.stdout + assert " passed" in result.stdout and " skipped" not in result.stdout -def test_tiny_configuration_and_inventory_are_derived(data): - assert (data["facts"]["hardware_isa"], data["facts"]["firmware_isa"]) == ("RV32IMC", "RV32IM") - assert data["facts"]["sram"] == {"CapacityKiB": 128, "DataWidth": 32, "IdWidth": 1} - 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 data["facts"]["fabric"]["target_count"] == 23 - assert data["facts"]["fabric"]["active_owners"] == [0, 1] - 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"]) - assert "mpw" not in {s["destination"].split("/")[-1] for s in data["managed_sources"]} - - -def test_tiny_collection_does_not_open_mini_files(monkeypatch): - original = Path.read_text - def read(path, *args, **kwargs): - name = path.as_posix() - if "/rtl/mini/" in name or ("/publications/datasheets/" in name and "/tiny/" not in name): - raise AssertionError("Tiny opened a Mini product input: " + name) - return original(path, *args, **kwargs) - monkeypatch.setattr(Path, "read_text", read) - tiny.collect(tiny.read(ROOT / tiny.BOOK / "tiny.json"), check_snapshot=False) - - -def test_common_register_default_remains_mini_compatible(): - paths = ["rtl/ip/serial/apb4_uart_define.svh"] - assert parse_definitions(paths, {}) == parse_definitions(paths, {}, "rtl/mini/address_map/memory_map.json") - _, constants = parse_definitions(paths, {}, tiny.MAP) - assert "SOC_SYSCTRL_TEST_STATUS_OFFSET" in constants +def test_current_source_cannot_relabel_historical_publication(): + config = tiny.read(ROOT / tiny.BOOK / "tiny.json") + assert config["source_revision"] == "9c706ba0ff1da1028c299cfff573010a86dfd7ce" + with pytest.raises(ValueError, match="DMA parameters changed"): + tiny.collect(config) @pytest.mark.parametrize("before,after", [ @@ -59,7 +40,7 @@ def test_common_register_default_remains_mini_compatible(): ("s_r_valid && s_rready[s_req_q.owner] && s_r_data[1]", "s_r_valid"), (".flush_i(1'b0)", ".flush_i(!enable_i)"), ]) -def test_target_reset_contract_rejects_unsafe_completion(tmp_path, before, after): +def test_current_reset_binding_still_rejects_unsafe_completion(tmp_path, before, after): contract = tiny.read(ROOT / tiny.BOOK / "source-contract.json") binding = next(item for item in contract["bindings"] if item["file"] == "rtl/tiny/top/tiny_axi4_target.sv") @@ -70,156 +51,3 @@ def test_target_reset_contract_rejects_unsafe_completion(tmp_path, before, after path.write_text(original.replace(before, after)) with pytest.raises(ValueError, match="Tiny behavior binding changed"): tiny.require_snippets(tmp_path, binding["file"], binding["text"]) - - -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.RESET_TREE, tiny.ARCHINFO, tiny.SYSCTRL, - tiny.CPU, tiny.CPU_MEM, tiny.SRAM, "configs/ci/ihp130-tiny.mk", - tiny.MAP, tiny.TOPOLOGY, (tiny.BOOK / "source-contract.json").as_posix()] - for relative in sources: - target = tmp_path / relative - target.parent.mkdir(parents=True, exist_ok=True) - shutil.copyfile(ROOT / relative, target) - return tmp_path - - -@pytest.mark.parametrize("path,before,after", [ - (tiny.SRAM, ".CapacityKiB(128)", ".CapacityKiB(32)"), - (tiny.SRAM, "group < 4", "group < 2"), - (tiny.SRAM, ".DataBytes(4)", ".DataBytes(8)"), - (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]"), -]) -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") - # 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) - - -@pytest.mark.parametrize("change", ["irq_missing", "irq_number", "region_address", "region_missing"]) -def test_missing_or_wrong_maps_fail(source_tree, change): - path = source_tree / (tiny.TOPOLOGY if change.startswith("irq") else tiny.MAP) - document = tiny.read(path) - if change == "irq_missing": - document["interrupts"].pop() - elif change == "irq_number": - document["interrupts"][-1]["core_bit"] = 27 - elif change == "region_address": - next(r for r in document["regions"] if r["symbol"] == "SRAM")["base"] = "0x31000000" - else: - document["regions"].pop() - path.write_text(json.dumps(document), encoding="utf-8") - with pytest.raises(ValueError): - tiny.validate_maps(source_tree, tiny.facts(source_tree)) - - -def test_tiny_sysctrl_and_discovery_do_not_inherit_mini_controls(data): - control = data["registers"]["sysctrl"]["registers"] - assert {r["name"] for r in control} == tiny.SUPPORTED_SYSCTRL - assert not any(r["name"] in {"HP_CTRL", "PLL_CFG", "DEBUG_SELECT"} for r in control) - test = next(r for r in control if r["name"] == "TEST_STATUS") - assert [(f["name"], f["lsb"], f["msb"]) for f in test["fields"] if f["name"] != "Reserved"] == [ - ("PASS", 0, 0), ("CODE", 8, 15), ("VALID", 31, 31)] - info = data["registers"]["archinfo"]["registers"] - assert next(r for r in info if r["name"] == "SOC_ID")["reset"] == "0x54494E59" - assert next(r for r in info if r["name"] == "SRAM_BYTES")["reset"] == "0x00020000" - assert data["registers"]["dma"]["groups"][1]["count"] == 4 - - -def test_tcd_layout_is_complete_and_bounded(data): - assert len(data["tcd"]) == 17 - assert sum(f["bytes"] for f in data["tcd"]) == 64 - assert data["tcd"][-1]["offset"] == 60 - - -def test_local_capabilities_keep_fabric_and_encoding_restrictions(data): - capability = next(r for r in data["registers"]["dma"]["registers"] if r["name"] == "CAPABILITY") - assert capability["reset"] == "0x00200401" - burst = next(f for f in capability["fields"] if f["name"] == "MAX_BURST_FIELD") - assert (burst["lsb"], burst["msb"], burst["reset"]) == (24, 27, "0x0") - assert "encoding limitation" in burst["description"] - sram = next(r for r in data["registers"]["sram"]["registers"] if r["name"] == "CAPABILITY") - assert "rejects WRAP" in next(f for f in sram["fields"] if f["name"] == "WRAP")["description"] - - -@pytest.mark.parametrize("key,value", [("document_id", "RS-MINI-DS"), ("profile", "configs/ci/ihp130.mk"), ("status", "RELEASE")]) -def test_wrong_product_configuration_cannot_enter_tiny_pipeline(key, value): - config = tiny.read(ROOT / tiny.BOOK / "tiny.json") - config[key] = value - with pytest.raises(ValueError, match="configuration"): - tiny.validate_configuration(config) - - -def test_existing_mini_output_is_never_overwritten(tmp_path, monkeypatch): - from publications import build_tiny_datasheet as builder - folder = tmp_path / "build/mini-existing" - folder.mkdir(parents=True) - manifest = folder / "manifest.json" - manifest.write_text('{"document":{"document_id":"RS-MINI-DS"}}') - before = manifest.read_bytes() - monkeypatch.setattr(builder, "ROOT", tmp_path) - with pytest.raises(ValueError, match="another product"): - builder.validate_output(folder, {"document_id": "RS-TINY-DS"}) - assert manifest.read_bytes() == before - - -@pytest.mark.parametrize("change", ["missing", "offset"]) -def test_register_inventory_cannot_silently_drop_or_move_a_definition(data, change): - refs = copy.deepcopy(data["registers"]) - if change == "missing": - refs["uart"]["registers"].pop() - else: - refs["uart"]["registers"][0]["offset"] += 4 - with pytest.raises(ValueError, match="register inventory"): - tiny.validate_register_inventory(refs, tiny.read(ROOT / tiny.BOOK / "register-contract.json")) - - -@pytest.mark.parametrize("change", ["missing", "duplicate"]) -def test_pdf_must_render_each_extracted_register_once(data, change): - from publications.build_tiny_datasheet import validate_rendered_registers - items = [dict(kind="tiny-register", family=family, key=r["key"], page=1) - for family, value in data["registers"].items() for r in value["registers"]] - assert validate_rendered_registers(data, items) == 337 - if change == "missing": - items.pop() - else: - items.append(items[0]) - with pytest.raises(ValueError, match="omitted or duplicated"): - validate_rendered_registers(data, items) - - -@pytest.mark.parametrize("key,value", [("status", "qualified"), ("current_commit_reports", [{"passed": True}])]) -def test_unmatched_qualification_claim_is_rejected(data, key, value): - evidence = copy.deepcopy(data["evidence"]) - evidence[key] = value - with pytest.raises(ValueError, match="qualification"): - tiny.validate_evidence(evidence) diff --git a/tests/test_tiny.py b/tests/test_tiny.py index 67019395..3c35ed21 100644 --- a/tests/test_tiny.py +++ b/tests/test_tiny.py @@ -51,7 +51,9 @@ def test_tiny_sources_and_abi_are_independent(tmp_path: Path) -> None: assert "RS_SOC_PSRAM_BASE" not in sdk irq = (output / "include/retrosoc/generated/irq_metadata.h").read_text() assert "RS_SOC_EXTERNAL_IRQ_COUNT UINT32_C(30)" in irq - assert "RS_SOC_EXT_IRQ_UART1 UINT32_C(24)" in irq + assert "RS_SOC_EXT_IRQ_UART1" not in irq + assert "RS_SOC_EXT_IRQ_I2C1" not in irq + assert "RS_SOC_EXT_IRQ_SDIO UINT32_C(8)" in irq assert "RS_SOC_EXT_IRQ_DMA UINT32_C(18)" in irq assert "RS_SOC_EXT_IRQ_NPU" not in irq # A consumer of public SDK headers must see Tiny capabilities without @@ -59,7 +61,7 @@ def test_tiny_sources_and_abi_are_independent(tmp_path: Path) -> None: if shutil.which("cc"): probe = tmp_path / "consumer.c" probe.write_text('#include \n' - '_Static_assert(RS_DMA_CHANNEL_COUNT == 4, "wrong product");\n') + '_Static_assert(RS_DMA_CHANNEL_COUNT == 8, "wrong product");\n') run(["cc", "-std=c11", "-Werror", "-I" + str(ROOT / "crt/include"), "-I" + str(output / "memory_map/include"), "-I" + str(output / "include"), "-c", str(probe), "-o", str(tmp_path / "consumer.o")]) diff --git a/tests/test_tiny_ics55_platform.py b/tests/test_tiny_ics55_platform.py index 6aee5dd7..f43d2311 100644 --- a/tests/test_tiny_ics55_platform.py +++ b/tests/test_tiny_ics55_platform.py @@ -114,6 +114,8 @@ def mapped_netlist(): "low": cell("TIELOH7R", Z=3)} cells.update({f"u_soc.u_sram.gen_group[{i // 8}].u_group.gen_bank[{i % 8}].u_ram.u_mem": cell(RAM, CLK=2) for i in range(32)}) + cells.update({f"u_soc.u_crypto.u_storage.gen_bank[{i}].gen_macro.u_sram.u_mem": + cell(RAM, CLK=2) for i in range(6)}) return {"modules": { "retrosoc_tiny_asic": {"ports": {"extclk_i_pad": {"direction": "inout", "bits": [1]}, "jtag_tck_i_pad": {"direction": "inout", "bits": [11]}}, @@ -126,6 +128,7 @@ def mapped_netlist(): def test_safe24_structural_binding(mapped_netlist): result = inspect_netlist(mapped_netlist) assert len(result["sram"]) == 32 and result["cpu_clocked_cells"] == 1 + assert len(result["crypto_sram"]) == 6 assert result["pll_enable"] == 0 assert result["system_clock_source"]["port"] == "extclk_i_pad" assert [c["type"] for c in result["system_clock_source"]["path"]] == [ @@ -199,10 +202,12 @@ def test_hierarchical_constant_output_is_resolved(mapped_netlist): @pytest.mark.parametrize("mutation", ["missing_pll", "missing_ram", "live_pll", "unknown_enable", - "divided_ram", "wrong_cpu", "pll_sys"]) + "divided_ram", "wrong_cpu", "pll_sys", + "missing_crypto", "misplaced_crypto", "divided_crypto"]) def test_unsafe_or_incomplete_mapping_is_rejected(mapped_netlist, mutation): cells = mapped_netlist["modules"]["retrosoc_tiny_asic"]["cells"] first_ram = "u_soc.u_sram.gen_group[0].u_group.gen_bank[0].u_ram.u_mem" + first_crypto = "u_soc.u_crypto.u_storage.gen_bank[0].gen_macro.u_sram.u_mem" if mutation == "missing_pll": del cells["pll"] elif mutation == "missing_ram": @@ -215,6 +220,12 @@ def test_unsafe_or_incomplete_mapping_is_rejected(mapped_netlist, mutation): cells[first_ram]["connections"]["CLK"] = [99] elif mutation == "wrong_cpu": cells["u_soc.u_cpu.u_hazard3_cpu_2port.core.pc_reg"]["connections"]["CK"] = [99] + elif mutation == "missing_crypto": + del cells[first_crypto] + elif mutation == "misplaced_crypto": + cells["unexpected_private_memory"] = cells.pop(first_crypto) + elif mutation == "divided_crypto": + cells[first_crypto]["connections"]["CLK"] = [99] else: cells["pll"]["connections"]["CKOUT1"] = [2] with pytest.raises(ValueError): diff --git a/tests/test_tiny_r2_p2.py b/tests/test_tiny_r2_p2.py index 96760beb..601c30ef 100644 --- a/tests/test_tiny_r2_p2.py +++ b/tests/test_tiny_r2_p2.py @@ -59,8 +59,8 @@ def test_reset_inventory_has_one_clock_and_all_existing_targets() -> None: 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 len(reset["leaf_names"]) == 21 + assert {domain["name"] for domain in inventory["domains"]} == {"system", "jtag", "audio"} assert reset["clock_domain"] == "system" and reset["main_sram_payload_reset"] == "none" diff --git a/tests/test_tiny_r2_p6.py b/tests/test_tiny_r2_p6.py new file mode 100644 index 00000000..9e94a242 --- /dev/null +++ b/tests/test_tiny_r2_p6.py @@ -0,0 +1,544 @@ +"""P6 product integration and fail-closed SDK/macro contracts.""" +from __future__ import annotations + +import json +import hashlib +import io +from pathlib import Path +import re +import shutil +import subprocess +import tarfile + +import pytest + +from scripts.generate_tiny import generate_bindings, read_topology +from scripts.rtl.generate_memory_map import generate as generate_memory_map +from scripts.check_clock_reset_domains import validate as validate_clocks +from test_tiny_r2_p4 import run +from test_sdio import SDIO_FULL_SOURCES, SDIO_FULL_INCDIRS +from scripts.check_simulation import DEFAULT_FAILURE +from scripts import tiny_r2_p6 as evidence + +ROOT = Path(__file__).resolve().parents[1] +TINY = ROOT / "rtl/tiny" + + +def test_native_card_model_elaborates_without_sv2v(tmp_path): + run(["iverilog", "-g2012", "-s", "sdio_native_card_model", "-o", str(tmp_path / "sim"), + str(ROOT / evidence.MODEL)], tmp_path, "compile") + + +@pytest.mark.parametrize("mutation", ["none", "expression", "body", "still_forward"]) +def test_only_continuous_declaration_relocation_is_equivalent(mutation): + after = (ROOT / evidence.MODEL).read_text() + declaration = re.search(r"^ wire \[3:0\] s_token_lanes = [^;]+;\n", after, re.M).group() + before = after.replace(declaration, "").replace(" integer s_data_bytes;\n", + declaration + " integer s_data_bytes;\n") + if mutation == "expression": + after = after.replace("s_data_bytes >= 4", "s_data_bytes >= 3") + elif mutation == "body": + after = after.replace("s_data_bytes = (bytes_i < 1) ? 1 : bytes_i;", + "s_data_bytes = 1;") + assert after != (ROOT / evidence.MODEL).read_text() + elif mutation == "still_forward": + after = before + if mutation == "none": + evidence.check_model_declaration_move(before, after) + else: + with pytest.raises(ValueError): + evidence.check_model_declaration_move(before, after) + + +@pytest.mark.parametrize("mutation", ["none", "dut", "model", "tool"]) +def test_verification_update_cannot_relabel_dut_changes(tmp_path, mutation): + after = (ROOT / evidence.MODEL).read_text() + declaration = re.search(r"^ wire \[3:0\] s_token_lanes = [^;]+;\n", after, re.M).group() + before = after.replace(declaration, "").replace(" integer s_data_bytes;\n", + declaration + " integer s_data_bytes;\n") + if mutation == "model": + after = after.replace("s_data_bytes >= 4", "s_data_bytes >= 3") + dut = "rtl/tiny/top/retrosoc_tiny.sv" + documents = [{evidence.MODEL: before, dut: "unchanged DUT"}, + {evidence.MODEL: after, dut: "changed DUT" if mutation == "dut" else "unchanged DUT"}] + snapshots = [] + maps = [] + for index, documents_by_name in enumerate(documents): + folder = tmp_path / str(index) + folder.mkdir() + hashes = {name: hashlib.sha256(text.encode()).hexdigest() for name, text in documents_by_name.items()} + maps.append(hashes) + evidence.b.dump(folder / "inputs.json", hashes) + (folder / "patch").write_text("unit-test snapshot") + with tarfile.open(folder / "source.tar", "w") as archive: + for name, text in documents_by_name.items(): + info = tarfile.TarInfo(name) + info.size = len(text.encode()) + archive.addfile(info, io.BytesIO(text.encode())) + snapshots.append({key: evidence.b.artifact(folder / name) for key, name in + (("inputs", "inputs.json"), ("patch", "patch"), ("snapshot", "source.tar"))}) + saved = {"source": snapshots[0], "tools": {"unit-test": 1}, "captured_at": "2026-10-12T00:00:00+00:00"} + evidence.b.dump(tmp_path / "source.json", saved) + changed = sorted(name for name in maps[0] if maps[0][name] != maps[1][name]) + update = {"phase": evidence.PHASE, "kind": "native_model_declaration_order", + "original_source": evidence.b.artifact(tmp_path / "source.json"), "source": snapshots[1], + "tools": {"unit-test": 2 if mutation == "tool" else 1}, "changed_inputs": changed, + "captured_at": "2026-10-12T00:01:00+00:00"} + if mutation == "none": + assert evidence.check_update(tmp_path, saved, update, maps[1]) == maps[0] + else: + with pytest.raises(ValueError): + evidence.check_update(tmp_path, saved, update, maps[1]) + + +@pytest.mark.parametrize("scope", ["consumer", "collection"]) +@pytest.mark.parametrize("mutation", ["none", "dut", "firmware", "smoke", "model", "clock_contract", "other_consumer", "tool"]) +def test_consumer_snapshot_cannot_change_running_flow_inputs(tmp_path, mutation, scope): + consumer = "publications/programming_reference.py" if scope == "consumer" else "scripts/tiny_r2_p6.py" + forbidden = { + "dut": "rtl/tiny/top/retrosoc_tiny.sv", "firmware": "app/apps/ci_smoke/tiny.c", + "smoke": "physical/smoke/sta/opensta/generate_sdc.py", "model": evidence.MODEL, + "clock_contract": "rtl/tiny/integration/clock_reset_domains.json", + "other_consumer": "scripts/regress.py" if scope == "consumer" else "publications/programming_reference.py", + } + old = {consumer: "old", **{name: "same" for name in forbidden.values()}} + new = dict(old, **{consumer: "new"}) + if mutation in forbidden: + new[forbidden[mutation]] = "changed" + snapshots = [] + for index, values in enumerate((old, new)): + folder = tmp_path / str(index) + folder.mkdir() + evidence.b.dump(folder / "inputs", values) + for name in ("snapshot", "patch"): + (folder / name).write_text("unit-test consumer artifact") + snapshots.append({name: evidence.b.artifact(folder / name) for name in ("inputs", "snapshot", "patch")}) + previous = {"source": snapshots[0], "tools": {"unit-test": 1}, "captured_at": "2026-10-12T00:00:00+00:00"} + prior = "verification-update.json" if scope == "consumer" else "consumer-update.json" + kind = "publication_and_commercial_consumers" if scope == "consumer" else "terminal_log_collection" + check = evidence.check_consumer_update if scope == "consumer" else evidence.check_collection_update + evidence.b.dump(tmp_path / prior, previous) + update = {"phase": evidence.PHASE, "kind": kind, + "previous_update": evidence.b.artifact(tmp_path / prior), + "source": snapshots[1], "tools": {"unit-test": 2 if mutation == "tool" else 1}, + "changed_inputs": sorted(name for name in old if old[name] != new[name]), + "captured_at": "2026-10-12T00:01:00+00:00"} + if mutation == "none": + check(tmp_path, previous, update, new) + else: + with pytest.raises(ValueError): + check(tmp_path, previous, update, new) + + +@pytest.mark.parametrize("simulator,top", [("verilator", "sdio_standalone_tb"), + ("verilator", "tiny_p6_sdio_pads_tb"), + ("iverilog", "tiny_p6_sdio_pads_tb")]) +def test_shared_sdio_native_data(tmp_path, simulator, top): + sources = [ROOT / name for name in (*SDIO_FULL_SOURCES, "tests/rtl/sdio_standalone_tb.sv")] + output = tmp_path / "sim" + includes = ["-I" + str(ROOT / p) for p in SDIO_FULL_INCDIRS] + if simulator == "iverilog": + converted = subprocess.run(["sv2v", *includes, *map(str, sources)], capture_output=True, + text=True, timeout=180) + assert converted.returncode == 0, converted.stderr + (tmp_path / "converted.v").write_text(converted.stdout) + run([simulator, "-g2012", "-s", top, "-o", str(output), str(tmp_path / "converted.v")], + tmp_path, "compile") + launch = ["vvp", str(output)] + else: + run([simulator, "--binary", "--timing", "--assert", "-Wno-fatal", "-j", "3", + "--top-module", top, *includes, *map(str, sources), + "--Mdir", str(tmp_path / "obj"), "-o", str(output)], tmp_path, "compile", timeout=180) + launch = [str(output)] + assert "SDIO standalone native SD model test passed" in run(launch, tmp_path, "sim") + + +@pytest.mark.parametrize("simulator", ["iverilog", "verilator"]) +def test_shared_sdio_running_clock_commands(tmp_path, simulator): + sources = [ROOT / "rtl/ip/storage" / name for name in + ("sdio_pkg.sv", "sdio_clock.sv", "sdio_command.sv")] + sources += [ROOT / "tests/rtl/sdio_command_tb.sv"] + output = tmp_path / "sim" + if simulator == "iverilog": + converted = subprocess.run(["sv2v", *map(str, sources)], capture_output=True, + text=True, timeout=60) + assert converted.returncode == 0, converted.stderr + (tmp_path / "converted.v").write_text(converted.stdout) + run([simulator, "-g2012", "-s", "sdio_command_tb", "-o", str(output), + str(tmp_path / "converted.v")], tmp_path, "compile") + launch = ["vvp", str(output)] + else: + run([simulator, "--binary", "--timing", "-Wno-fatal", "--top-module", "sdio_command_tb", + *map(str, sources), "--Mdir", str(tmp_path / "obj"), "-o", str(output)], + tmp_path, "compile", timeout=120) + launch = [str(output)] + assert "SDIO command engine test passed" in run(launch, tmp_path, "sim") + + +@pytest.mark.parametrize("simulator", ["iverilog", "verilator"]) +def test_apb_read_strobes_after_writes(tmp_path, simulator): + common = ROOT / "rtl/managed/clusterip/common/rtl" + generate_memory_map(TINY / "address_map/memory_map.json", tmp_path / "map", "YES", 128) + sources = [common / "utils/register.sv", common / "interface/axi4_if.sv", + common / "interface/apb4_if.sv", TINY / "top/tiny_axi42apb4.sv", + ROOT / "tests/rtl/tiny_r2_p6_bridge_tb.sv"] + flags = ["-DSV_ASSRT_DISABLE", "-I" + str(common), "-I" + str(common / "interface"), + "-I" + str(tmp_path / "map/rtl")] + top = "tiny_r2_p6_bridge_tb" + output = tmp_path / "sim" + if simulator == "iverilog": + converted = subprocess.run(["sv2v", *flags, *map(str, sources)], capture_output=True, + text=True, timeout=60) + assert converted.returncode == 0, converted.stderr + (tmp_path / "converted.v").write_text(converted.stdout) + run([simulator, "-g2012", "-s", top, "-o", str(output), str(tmp_path / "converted.v")], + tmp_path, "compile") + launch = ["vvp", str(output)] + else: + run([simulator, "--binary", "--timing", "--assert", "-Wno-fatal", "--top-module", top, + *flags, *map(str, sources), "--Mdir", str(tmp_path / "obj"), "-o", str(output)], + tmp_path, "compile", timeout=120) + launch = [str(output)] + assert "SIM_TEST_PASS Tiny P6 APB read strobes" in run(launch, tmp_path, "sim") + + +def p6_log(): + rows = ["R2_P6_" + name + " " + " ".join(f"{k}={v}" for k, v in fields.items()) + for name, fields in evidence.RECORDS.items()] + rows += [f"R2_P6_AUDIO bits={bits} frames=256 ws_frames=2 background_jobs=8 " + f"tcd={int(bits == 24)} elapsed_us=7000 cycles=168000 instructions=40000" + for bits in (16, 24)] + rows += [f"R2_P6_I2S_WIRE bits={bits} samples=512" for bits in (16, 24)] + rows += [f"R2_P6_AXI owner={owner} read_bytes=1024 write_bytes=1024 " + f"descriptor_bytes={256 if owner == 1 else 0} ar_stall_max=3 aw_stall_max=3 " + "w_stall_max=3 r_stall_max=3 b_stall_max=3" for owner in range(3)] + rows += ["R2_P6_CONTENTION sdio_dma_cycles=100 cpu_fetches=10"] + return "\n".join([*rows, "R2_P6_DEVICES ws_frames=4 ws_wait_max=100 fifo_max=16 " + "underflows=1 aborts=1 clock_stops=2", "SIM_TEST_PASS Tiny cycles=200000"]) + + +def test_terminal_verdict_survives_partial_uart_line(): + flow = {"status": "passed", "exit_code": 0} + content = p6_log().replace("SIM_TEST_PASS Tiny cycles=", "Tiny acceptance passed coSIM_TEST_PASS Tiny cycles=") + assert evidence.parse_log(content, flow)["uart"]["echo_bytes"] == 4 + for replacement in ("SIM_TEST_PASS Tiny JTAG transport halt/resume", "Hello retroSoC!", + "SIM_TEST_PASS Mini cycles=200000", "SIM_TEST_PASS Tiny cycles=unknown"): + with pytest.raises(ValueError): + evidence.parse_log(content.replace("SIM_TEST_PASS Tiny cycles=200000", replacement), flow) + + +@pytest.mark.parametrize("mutation", ["none", "failed_command", "missing", "duplicate", "budget", + "overflow", "false_qualification", "error", "no_terminal"]) +def test_p6_evidence_is_fail_closed(mutation): + log = p6_log() + flow = {"status": "passed", "exit_code": 0} + if mutation == "failed_command": + flow["exit_code"] = 1 + elif mutation == "missing": + log = "\n".join(line for line in log.splitlines() if not line.startswith("R2_P6_RNG")) + elif mutation == "duplicate": + log += "\n" + log.splitlines()[0] + elif mutation == "budget": + log = log.replace("ws_wait_max=100", "ws_wait_max=4801") + elif mutation == "overflow": + log = log.replace("fifo_max=16", "fifo_max=17") + elif mutation == "false_qualification": + log = log.replace("unqualified=1", "unqualified=0") + elif mutation == "error": + log += "\nSIM_TEST_FAIL" + elif mutation == "no_terminal": + log = log.replace("SIM_TEST_PASS", "Hello retroSoC!") + if mutation == "none": + assert len(evidence.parse_log(log, flow)["audio"]) == 2 + else: + with pytest.raises(ValueError): + evidence.parse_log(log, flow) + + +@pytest.mark.parametrize("mutation", ["none", "failed", "old", "unfinished", "reversed"]) +def test_p6_gate_chronology(tmp_path, mutation): + flow = {"status": "passed", "exit_code": 0, + "started_at": "2026-10-12T01:00:00+00:00", "finished_at": "2026-10-12T01:01:00+00:00"} + if mutation == "failed": + flow["exit_code"] = 1 + elif mutation == "old": + flow["started_at"] = "2026-10-11T01:00:00+00:00" + elif mutation == "unfinished": + del flow["finished_at"] + elif mutation == "reversed": + flow["finished_at"] = "2026-10-12T00:30:00+00:00" + path = tmp_path / "gate.json" + path.write_text(json.dumps(flow)) + if mutation == "none": + assert evidence.checked_flow(path, "2026-10-12T00:59:00+00:00") == flow + else: + with pytest.raises(ValueError): + evidence.checked_flow(path, "2026-10-12T00:59:00+00:00") + + +@pytest.mark.parametrize("mutation", ["none", "source", "tool", "old_model", "missing_image"]) +def test_p6_complete_collection_requires_current_inputs(tmp_path, monkeypatch, mutation): + """Exercise real artifact retention with synthetic, explicitly unit-test flows.""" + directory = tmp_path / "evidence" + directory.mkdir() + source = tmp_path / "source.sv" + source.write_text("unit-test source\n") + source_inputs = {"source.sv": evidence.b.sha256(source)} + evidence.b.dump(directory / "inputs.json", source_inputs) + for name in ("patch", "snapshot"): + (directory / name).write_text("unit-test artifact\n") + saved_source = {name: evidence.b.artifact(directory / filename) for name, filename in + (("inputs", "inputs.json"), ("patch", "patch"), ("snapshot", "snapshot"))} + variants = [tmp_path / pdk for pdk in ("ICS55", "IHP130")] + captured = "2026-10-12T00:00:00+00:00" + evidence.b.dump(directory / "source.json", {"phase": evidence.PHASE, "source": saved_source, + "tools": {"unit-test": 1}, "captured_at": captured, "variants": list(map(str, variants))}) + monkeypatch.setattr(evidence, "ROOT", tmp_path) + monkeypatch.setattr(evidence, "inputs", lambda _: {"source.sv": evidence.b.sha256(source)}) + monkeypatch.setattr(evidence, "current_tools", lambda: {"unit-test": 2 if mutation == "tool" else 1}) + monkeypatch.setattr(evidence.b, "configuration", lambda p: { + "configuration": {"PDK": p.name, "APP": "ci_smoke"}}) + monkeypatch.setattr(evidence.b, "runtime_inputs", lambda *_: {"unit-test": "explicit fixture"}) + for variant in variants: + for relative in ("sw/firmware", "sw/retrosoc_fw.hex", "sw/retrosoc_fw.map", "sw/retrosoc_fw.bin", + "sw/ld2_all_sram.lds", "sw/memory_regions.ld", "meta/manifest.json"): + path = variant / relative + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("unit-test artifact\n") + for simulator in ("iverilog", "verilator"): + folder = variant / "sim" / simulator + folder.mkdir(parents=True) + for name in ("compile.log", "simv" if simulator == "iverilog" else "emu"): + (folder / name).write_text("unit-test artifact\n") + (folder / "sim.log").write_text(p6_log()) + evidence.b.dump(folder / "result-sim-check.json", {"status": "passed"}) + for label, start, end in (("compile", "01", "02"), ("sim", "03", "04")): + evidence.b.dump(folder / f"result-{label}.json", {"status": "passed", "exit_code": 0, + "started_at": f"2026-10-12T00:{start}:00+00:00", + "finished_at": f"2026-10-12T00:{end}:00+00:00"}) + if mutation == "source": + source.write_text("changed unit-test source\n") + elif mutation == "old_model": + path = variants[0] / "sim/iverilog/result-compile.json" + flow = evidence.b.read_json(path) + flow["started_at"] = "2026-10-11T00:00:00+00:00" + evidence.b.dump(path, flow) + elif mutation == "missing_image": + (variants[0] / "sw/retrosoc_fw.hex").unlink() + if mutation == "none": + evidence.report(directory, [], variants) + report = evidence.b.read_json(directory / "report.json") + assert report["status"] == "functional_evidence_collected" + assert len(report["simulations"]) == 4 + for simulation in report["simulations"].values(): + for artifact in simulation["artifacts"]: + evidence.b.check_artifact(artifact) + else: + with pytest.raises((ValueError, FileNotFoundError)): + evidence.report(directory, [], variants) + assert not (directory / "report.json").exists() + + +@pytest.mark.parametrize("name", ["rng", "crc"]) +def test_locked_controller_contracts(tmp_path, name): + for tool in ("iverilog", "vvp", "verilator", "cc"): + assert shutil.which(tool), f"required tool unavailable: {tool}" + source = ROOT / "rtl/managed/clusterip" / name + result = subprocess.run(["make", "-C", str(source), f"BUILD_DIR={tmp_path}", + "register-check", "test"], text=True, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, timeout=180) + (tmp_path / "build.log").write_text(result.stdout) + assert result.returncode == 0, result.stdout[-12000:] + for simulator, marker in (("iverilog", f"{name.upper()}_TEST_PASS"), + ("verilator", f"APB4_{name.upper()}_TEST_PASS")): + log = (tmp_path / simulator / "test.log").read_text() + assert marker in log and not DEFAULT_FAILURE.search(log), log + + +@pytest.mark.parametrize("simulator", ["iverilog", "verilator"]) +def test_shared_i2s_payload(tmp_path, simulator): + common = ROOT / "rtl/managed/clusterip/common/rtl" + serial = ROOT / "rtl/ip/serial" + for tool in (simulator, "sv2v", "vvp"): + assert shutil.which(tool), f"required tool unavailable: {tool}" + sources = [common / name for name in ( + "interface/apb4_if.sv", "interface/axi4_stream_if.sv", "utils/register.sv", + "utils/bin2gray.sv", "utils/gray2bin.sv", "utils/spill_register.sv", + "cdc/cdc_sync.sv", "cdc/cdc_rst_ctrlr.sv", "cdc/async_reqack.sv", "cdc/cdc_fifo.sv", + "cdc/cdc_2phase.sv", "cdc/cdc_warm_flush.sv", "utils/edge_det.sv", "clkrst/rst_sync.sv")] + sources += [ROOT / "rtl/tech/tc_clk.sv"] + sources += [serial / name for name in ("i2s_pkg.sv", "i2s_reg.sv", "i2s_clkgen.sv", + "i2s_recv.sv", "i2s_send.sv", "i2s_core.sv", "apb4_i2s.sv")] + sources += [ROOT / "tests/rtl/tiny_r2_p6_shared_tb.sv"] + flags = ["-DPDK_BEHAV", "-DSV_ASSRT_DISABLE", *[f"-I{p}" for p in ( + common, common / "interface", common / "utils", common / "cdc", common / "clkrst", serial)]] + top = "tiny_r2_p6_shared_tb" + output = tmp_path / "sim" + if simulator == "iverilog": + converted = subprocess.run(["sv2v", *flags, *map(str, sources)], capture_output=True, + text=True, timeout=180) + assert converted.returncode == 0, converted.stderr + (tmp_path / "converted.v").write_text(converted.stdout) + run([simulator, "-g2012", "-s", top, "-o", str(output), str(tmp_path / "converted.v")], + tmp_path, "compile", timeout=180) + launch = ["vvp", str(output)] + else: + run([simulator, "--binary", "--timing", "--assert", "-Wno-fatal", "-j", "3", + "--top-module", top, *flags, *map(str, sources), "--Mdir", str(tmp_path / "obj"), + "-o", str(output)], tmp_path, "compile", timeout=180) + launch = [str(output)] + content = run(launch, tmp_path, "sim", timeout=180) + assert "SIM_TEST_PASS Tiny P6 I2S payload" in content + for bits in (16, 24): + assert f"P6_I2S bits={bits} samples=512" in content + + +def test_p6_map_irq_request_and_pin_contract(): + topology = read_topology(TINY / "integration/soc_topology.json", TINY / "address_map/memory_map.json") + regions = {r["symbol"]: int(r["base"], 0) for r in json.loads( + (TINY / "address_map/memory_map.json").read_text())["regions"]} + for name, base in {"RNG": 0x20001000, "CRC": 0x20006000, "WS2812": 0x10008000, + "CRYPTO": 0x1000C000, "I2S": 0x10007000, "SDIO0": 0x1000F000}.items(): + assert regions["APB4_" + name] == base + assert not {"APB4_UART1", "APB4_I2C1", "APB4_DVP", "APB4_SPI0", "APB4_PIOLITE"} & regions.keys() + irqs = {i["name"]: i["core_bit"] for i in topology["interrupts"]} + assert {k: irqs[k] for k in ("rng", "ws2812", "crypto", "i2s", "sdio")} == { + "rng": 16, "ws2812": 17, "crypto": 23, "i2s": 8, "sdio": 10} + assert {15, 19, 24, 25, 26, 27, 31}.isdisjoint(irqs.values()) + pins = topology["gpio_alt_functions"] + assert pins[9]["alt0"]["inputs"] == ["u_uart0_if.rx_i"] + assert pins[10]["alt0"]["inputs"] == ["u_i2c0_if.scl_i"] + assert pins[11]["alt0"]["inputs"] == ["u_i2c0_if.sda_i"] + assert pins[26]["alt0"]["do"] == "u_ws2812_if.dat_o" + for pin in (24, 25): + assert pins[pin]["alt0"]["inputs"] == [f"u_pwm_if.capture_i[{pin - 24}]"] + assert pins[pin]["alt1"]["inputs"] == [] + for pin in range(12, 24): + assert pins[pin]["alt1"]["oe"] == "1'b0" + validate_clocks(TINY / "integration/clock_reset_domains.json", ROOT) + + +def test_duplicate_input_routes_are_rejected(tmp_path): + topology = json.loads((TINY / "integration/soc_topology.json").read_text()) + topology["gpio_alt_functions"][25]["alt1"]["inputs"] = ["u_uart0_if.rx_i"] + path = tmp_path / "topology.json" + path.write_text(json.dumps(topology)) + with pytest.raises(ValueError, match="exclusive route"): + read_topology(path, TINY / "address_map/memory_map.json") + + +@pytest.mark.parametrize("enabled", ["YES", "NO"]) +def test_tiny_sva_build_selection_reaches_owned_assertions(enabled): + result = subprocess.run(["make", "-n", "CONFIG=configs/ci/ics55-tiny.mk", "SOC=TINY", + "PDK=ICS55", "SIMU=VERILATOR", f"HAVE_SVA={enabled}", + "BUILD_TIMESTAMP=2099-01-01-00-00", "comp"], cwd=ROOT, + capture_output=True, text=True, check=True) + assert ("+define+HAVE_SVA" in result.stdout) == (enabled == "YES") + assert ("+define+SV_ASSRT_DISABLE" in result.stdout) == (enabled == "NO") + + +@pytest.mark.parametrize("simulator", ["iverilog", "verilator"]) +def test_board_pullups_survive_partial_external_drivers(tmp_path, simulator): + assert shutil.which(simulator), f"required simulator unavailable: {simulator}" + generate_bindings(read_topology(TINY / "integration/soc_topology.json", + TINY / "address_map/memory_map.json"), tmp_path) + source = tmp_path / "board.sv" + drivers = "\n".join(f"assign s_gpio_{n}=oe[{n}] ? data[{n}] : 1'bz;" for n in range(32)) + source.write_text('module board;\n`include "tiny_tb_gpio.svh"\n' + + "logic clk=0; logic [31:0] oe=0, data=0; always #5 clk=!clk;\n" + drivers + + "\nassign s_gpio_17=clk; assign s_gpio_15=s_gpio_14;\n" + r''' +initial begin + #21; + if (s_gpio[11:10] !== 2'b11) $fatal(1, "lost board pullups"); + oe[10]=1; + #10; + if (s_gpio[11:10] !== 2'b10) $fatal(1, "open-drain drive/readback"); + oe[10]=0; + #10; + if (s_gpio[11:10] !== 2'b11) $fatal(1, "open-drain release/readback"); + $display("SIM_TEST_PASS Tiny P6 board pulls"); + $finish; +end +endmodule +''') + output = tmp_path / "sim" + flags = ["-I" + str(tmp_path / "rtl")] + if simulator == "iverilog": + run([simulator, "-g2012", "-s", "board", *flags, "-o", str(output), str(source)], + tmp_path, "compile") + launch = ["vvp", str(output)] + else: + run([simulator, "--binary", "--timing", "-Wno-fatal", "--top-module", "board", + *flags, str(source), "--Mdir", str(tmp_path / "obj"), "-o", str(output)], + tmp_path, "compile", timeout=180) + launch = [str(output)] + assert "SIM_TEST_PASS Tiny P6 board pulls" in run(launch, tmp_path, "sim") + + +@pytest.mark.parametrize("tiny", [True, False]) +def test_dma_sdk_request_matrix(tmp_path, tiny): + assert shutil.which("cc"), "host compiler required" + if tiny: + generate_bindings(read_topology(TINY / "integration/soc_topology.json", + TINY / "address_map/memory_map.json"), tmp_path) + else: + extension = tmp_path / "include/retrosoc/generated/user_extensions.h" + extension.parent.mkdir(parents=True) + extension.write_text("") + generate_memory_map(ROOT / ("rtl/tiny" if tiny else "rtl/mini") / "address_map/memory_map.json", + tmp_path / "map", "YES", 128 if tiny else 32) + probe = tmp_path / "probe.c" + probe.write_text(r''' +#include +#include +int main(void) { + _Alignas(64) rs_dma_tcd_t tcd = {0}; + rs_dma_config_t c = {.kind=RS_DMA_KIND_MM_TO_MM, .source=0x30000000U, + .destination=0x30010000U, .byte_count=64U, .width=RS_DMA_WIDTH_32, + .source_increment=true, .destination_increment=true, .burst_beats=16U}; + for (unsigned channel=0; channel<8; ++channel) { + for (unsigned request=0; request<16; ++request) { + c.request=(rs_dma_request_t)request; + c.kind=(request==1 || request==12) ? RS_DMA_KIND_MM_TO_STREAM : + ((request==2 || request==11 || request==13) ? RS_DMA_KIND_STREAM_TO_MM : + RS_DMA_KIND_MM_TO_MM); +#ifdef RS_SOC_TINY + bool supported=(0x31ffU & (1U << request)) != 0U; +#else + bool supported=request < 14U; +#endif + assert((rs_dma_config_validate(channel, &c)==RS_OK)==supported); + tcd.source=c.source; tcd.destination=c.destination; tcd.byte_count=64U; + tcd.control=RS_DMA_TCD_VALID | RS_DMA_TCD_SRC_INC | RS_DMA_TCD_DST_INC | + ((unsigned)c.kind << RS_DMA_TCD_KIND_SHIFT) | + (request << RS_DMA_TCD_REQUEST_SHIFT) | (16U << RS_DMA_TCD_BURST_SHIFT); + assert((rs_dma_tcd_validate(channel, &tcd)==RS_OK)==supported); + } + } + assert(rs_dma_config_validate(8, &c)!=RS_OK); + c.request=(rs_dma_request_t)16; + assert(rs_dma_config_validate(0, &c)!=RS_OK); + c.request=(rs_dma_request_t)17; + assert(rs_dma_config_validate(0, &c)!=RS_OK); + c.request=(rs_dma_request_t)255; + assert(rs_dma_config_validate(0, &c)!=RS_OK); + return 0; +} +''') + binary = tmp_path / "probe" + command = ["cc", "-std=c11", "-Wall", "-Wextra", "-Werror", + "-I" + str(ROOT / "crt/include"), "-I" + str(tmp_path / "include"), + "-I" + str(tmp_path / "map/include"), str(probe), + str(ROOT / "crt/src/hal/dma_math.c"), "-o", str(binary)] + subprocess.run(command, check=True, capture_output=True, text=True) + subprocess.run([str(binary)], check=True, capture_output=True, text=True) + + +def test_p6_reset_and_macro_policy_remains_explicit(): + text = (TINY / "top/retrosoc_tiny.sv").read_text() + assert ".clk_aud_i" in text and "s_audio_rst_n" in text + assert ".EnableStreams" in text and "16'h31ff" in text + assert "apb4_crypto u_crypto" in text + tree = (TINY / "top/tiny_reset_tree.sv").read_text() + assert "s_audio" not in tree and "crypto" not in tree + assert ".STAGE(5)" in tree From 43a3c8d453b6d41df3536719ebc6d1f4521bc7cd Mon Sep 17 00:00:00 2001 From: maksyuki Date: Mon, 12 Oct 2026 21:44:28 +0800 Subject: [PATCH 08/12] docs(tiny): document P6 integration and validation boundaries Record the shared-IP and eight-channel DMA integration, common I2S/SDIO correctness fixes, deterministic workloads, snapshot update scopes and both PDKs' functional and smoke-flow evidence. Keep original failed attempts, publication history and source attribution intact. The later final full Pytest passed 1726 tests with one optional pypdf skip in 4021.61 seconds, exit 0, in terminal session 36439; it has no standalone verdict log or JSON. Existing campaign evidence is not rewritten by this documentation commit. Retain explicit negative timing, warning observations, legacy package/floorplan limits and pending physical qualification. Human phase acceptance and any P7 work remain separate. --- app/README.md | 8 +- configs/README.md | 7 +- crt/README.md | 7 +- docs/ip/i2s.md | 9 + docs/ip/sdio.md | 44 ++++ docs/ip/tiny-soc-r2-shared-integration.md | 253 ++++++++++++++++++++++ docs/ip/tiny-soc-verification.md | 34 +++ physical/commercial/README.md | 9 +- rtl/tiny/README.md | 10 +- 9 files changed, 368 insertions(+), 13 deletions(-) create mode 100644 docs/ip/tiny-soc-r2-shared-integration.md diff --git a/app/README.md b/app/README.md index fae63e58..4e3d7573 100644 --- a/app/README.md +++ b/app/README.md @@ -169,7 +169,11 @@ configurations, and build artifact locations. For `SOC=TINY`, `bringup` and `ci_smoke` use the MCU acceptance composition. It boots through XPI into SRAM, checks CPU C/A behavior and bus exceptions, DMA -(including UART requests), interrupts, RTC, GPIO, UART1 and I2C error completion, -then verifies watchdog reset by completing after the next NOR boot. It uses +(including UART requests), interrupts, RTC, GPIO and I2C0 error completion. +The P6 composition additionally checks RNG qualification rejection, CRC DMA, +Crypto initialization/vectors/erasure, audio plus finite WS refill and background +DMA, and SDIO transfers/recovery with verification-board models. Missing external +devices fail boundedly; they do not hold CPU boot reset. The image then verifies +watchdog reset by completing after the next NOR boot. It uses SYSCTRL terminal status. The small `asm/tiny_boot.S` image separately qualifies the synthesized SRAM/pad path; it is not a replacement for the full RTL test. diff --git a/configs/README.md b/configs/README.md index c0f19a12..b84ec217 100644 --- a/configs/README.md +++ b/configs/README.md @@ -82,8 +82,9 @@ 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. +128 KiB SRAM and AXI32/APB4. The P6 candidate adds eight-channel DMA and the +frozen shared-peripheral integration; source presence is not phase acceptance. The profile uses 24 MHz, `HAVE_HP=NO`, `MINI_MODE=NONE`, CSR-enabled RV32IM firmware and `ld2_all_sram`. Both `bringup` and `ci_smoke` select Tiny acceptance -firmware. Icarus and Verilator use the same pin-level Flash model. Other PDKs, -wireless, external RAM and HP applications are rejected by the Tiny config gate. +firmware. Icarus and Verilator use the same pin-level Flash model. PDKs other +than IHP130/ICS55, external-RAM boot layouts and HP applications remain rejected. diff --git a/crt/README.md b/crt/README.md index 8c5809eb..82fa9125 100644 --- a/crt/README.md +++ b/crt/README.md @@ -239,11 +239,16 @@ For the complete firmware, simulation, synthesis, and timing flow, see the Tiny uses the same `rs_` SDK and selects its generated memory, IRQ and capability headers through the committed profile. The generated memory-map header includes -Tiny capability metadata, so public DMA headers report four channels without +Tiny capability metadata, so public DMA headers report eight channels without requiring an application to reproduce private compiler flags. Unavailable IP drivers are omitted; Tiny SYSCTRL rejects unsupported lifecycle/PLL operations before MMIO. Startup skips external-RAM initialization and loads into SRAM. +P6 selects the existing RNG/CRC/WS2812/Crypto/I2S/SDIO drivers. Crypto uses 4/5, +audio uses 6/7 and channel 3 remains exclusively leased for a WS frame. +I2C1, SDIO1, DVP and future DMA endpoints remain unsupported on Tiny. Shared +Mini defaults are unchanged; Tiny does not acquire Mini's HP channel reservation. + `` provides `rs_watchdog_configure`, `rs_watchdog_start`, `rs_watchdog_service`, `rs_watchdog_get_status` and `rs_watchdog_clear_reset_cause`, with SDK status values and bounded command waits. These wrappers preserve the diff --git a/docs/ip/i2s.md b/docs/ip/i2s.md index cf3dbbcc..25d3758d 100644 --- a/docs/ip/i2s.md +++ b/docs/ip/i2s.md @@ -105,6 +105,15 @@ change. in `[23:0]`. `TKEEP` and `TSTRB` are `4'hF`. I2S RX does not assert `TLAST`. DMA asserts `TLAST` on the final programmed TX word. +The P6 integration regression uses alternating sample least-significant bits +in both 16- and 24-bit pin-level loopback. It exposed a receiver defect that +combined the preceding sample's last bit with the next sample. The corrected +receiver appends the bit sampled at the LRCK transition to the preceding +sample's shifted bits and registers completion after that value is available. +This preserves the host register/stream ABI and does not add slave mode. +The register frontend uses PWRITE for transfer direction. In particular, a +write with zero byte strobes cannot be reinterpreted as a side-effecting RX read. + ## HAL Sequence Mini applications should use `rs_i2s_configure()`, `rs_i2s_enable()`, and either diff --git a/docs/ip/sdio.md b/docs/ip/sdio.md index 0365806b..208b0d36 100644 --- a/docs/ip/sdio.md +++ b/docs/ip/sdio.md @@ -180,3 +180,47 @@ The standalone IP does not allocate memory, perform cache maintenance, implement an IOMMU/firewall, or provide a hosted/runtime C API. The SoC integration only supplies the two native AXI/APB/IRQ/pad paths; the SDK driver is maintained by a separate workstream. + +## Tiny SAFE24 integration + +Tiny R2-P6 connects instance 0 to external AXI owner 2, its independent APB +branch and IRQ10, and GPIO0..5. Instance 1 remains absent and its public SDK +operations return `RS_ENOTSUP` before MMIO. These bindings do not replace the +Mini mappings above. The 24 MHz input permits at most 12 MHz SDCLK; enumeration +still starts at 400 kHz and the SDK caps later protocol requests at input/2. + +R6 is retained as the complete 48-bit frame in RESP. RCA therefore occupies +bits 39:24, above the payload status and CRC/end bits. The shared initialization +helper uses this field rather than bits 31:16; no register or public API changes. + +The command sender enables its first output bit at its scheduled launch. A +command admitted on an already running SDCLK rising edge must not expose an +extra start bit. Directed pad sampling covers repeated command admission at +both half-cycle phases in Icarus and Verilator. The card model collects the +complete 48-bit frame after its start bit, including the pulled-high end bit +after host output release; host output-enable is not the frame length oracle. + +The transmit wait indication also covers the last bit of a word when its +successor is unavailable. The core pauses SDCLK before the next word's launch, +while retaining the current driven bit and draining the high half-cycle. This +prevents an idle bit from becoming the next word's MSB under memory backpressure. +A 1024-byte four-lane DMA case inserts 24 system cycles between read beats and +checks every card byte, CRC status and descriptor retirement through real +three-state lines. Inactive lanes stay pulled high in the card's token/tail checks. + +Card-to-memory DMA retains descriptor ownership across every AXI burst, rather +than publishing DONE after the first 64 bytes. Receive backpressure stops the +next SDCLK launch as soon as a complete word is held; a registered stop loses +the next card bit. The roundtrip fixture delays descriptor fetch as well as +payload supply, checks all 1024 received bytes and successful final writeback. +Its card emits all sixteen CRC bits in order; earlier manual-model tests did +not reject the skipped CRC bit. These fixes preserve the native one-read/one-write +capacity, descriptor layout, register ABI and interrupt/error lifecycle. + +The existing verification card keeps its manually scheduled standalone mode. +Tiny selects its optional SDHC enumeration responses with 1024 512-byte blocks +and default-speed-only capability. Its board fixture checks the observed wire +command, direction, width and length, then calls the existing data-stimulus API +at DATA_START. All payload and response bits cross the real pads. This checks +the repository controller/SDK contract; it does not qualify arbitrary card +response timing, SD protocol compliance, a physical card or an electrical rate. diff --git a/docs/ip/tiny-soc-r2-shared-integration.md b/docs/ip/tiny-soc-r2-shared-integration.md new file mode 100644 index 00000000..9bff33b9 --- /dev/null +++ b/docs/ip/tiny-soc-r2-shared-integration.md @@ -0,0 +1,253 @@ +# Tiny R2-P6 shared integration + +Implementation in progress from `b3b30a72cd6ef5340c28e5044c96cd13258ec5a6`, +against specification blob `7e19de49d49efaafacccfa85671e314d53dd15a4`. +The maintainer approved the single-phase P6 preflight. No phase acceptance, +new operating-frequency claim, commit or publication promotion follows. + +Default `configs/ci/ics55-tiny.mk` remains SAFE24 with its PLL parked off; +`configs/ci/ihp130-tiny.mk` remains no-PLL compatibility. CPU and all 32 main +SRAM macros share SYS. Crypto adds its six private 4 KiB macros on the current +SAFE24 PCLK alias, without reducing the 128 KiB main aperture. + +## Product boundary + +The eight-channel DMA enables requests 0-8 and 12/13 (`0x31ff`). I2C1 requests +9/10, DVP11 and future requests stay unsupported. Default owners are UART0=0, +I2C0=1, camera reservation/explicit memory borrowing=2, serialized bulk=3, +Crypto=4/5 and audio=6/7. The application lease on channel 3 persists across +all WS2812 refill batches; CRC and XPI must acquire it only after release. +SPI16/17 remain outside the existing V2.0 selector ABI and are rejected by +the SDK. This does not claim hardware rejection of arbitrary raw extended +selector writes: the common DMA specification explicitly records their legacy +truncation and requires V2.2 identity/capability guards before SPI integration. + +The 20 independent APB branches occupy fabric targets 5-24. SRAM remains +0-3, XPI memory 4, and decode/protocol errors are 25/26. The original three +external owners remain; owner 2 is now the actual SDIO private DMA producer. +P5 response retention, write drain, fault routing and ownership are preserved. +The 21 system reset leaves exclude the independently released AUDIO reset; +neither an absent audio reference nor Crypto initialization holds CPU reset. + +Gen1 default GPIO routes are SDIO0-5, UART8/9, I2C10/11, audio12-17, +PWM18-25 (plus its frozen ALT1 outputs), WS26 and XPI chip selects29-31. +Unfrozen UART/I2C alternate inputs, camera, SPI and CLKOUT remain inactive. +The historical ASIC wrapper still has legacy dedicated UART/XPI-CS ports and +its old power-pad inventory. It is not a QFN64 binding. This explicitly +approved compatibility boundary leaves the complete package/power wrapper +to final physical implementation, without changing the frozen package table. + +## Shared correctness and software + +Tiny's shared DMA validator now accepts integrated streams and eight channels. +Known absent I2C1/SDIO1 and UART flow-control operations fail before MMIO. +SD/SDIO enumeration caps a protocol clock request at half the actual input +frequency; the existing low-level clock calculator still rejects invalid +requests. The bounded SDSC CSD multiplier uses a 32-bit shift factor inside +the unchanged 64-bit capacity calculation, avoiding an absent RV32 runtime +helper without replacing the locked GNU toolchain. +The SD Memory RCA extraction uses raw R6 frame bits 39:24; the preceding +31:16 selection incorrectly shifted the assigned card address by eight bits. + +The independent P6 I2S payload fixture reproduced an existing receive-bit +assembly defect in both simulators. Completion now follows capture of the +current sample's LSB at the LRCK boundary. Both widths preserve the existing +register ABI, FIFO/CDC topology and master-only integration. Shared Mini/APU +checks remain required; this is not an I2S slave extension. + +Adding partial external GPIO drivers exposed a locked-Verilator simulation +limitation: a packed `tri1` bus loses pulls on its otherwise undriven bits. +The original I2C test consequently saw released SCL/SDA low and timed out. +A minimal reproduction fails in Verilator and passes in Icarus; explicit +independent scalar board nets retain both weak pulls and driven readback in +both tools. Tiny's generated testbench bindings use those scalar nets and an +observation-only packed view. ASIC ports, pad cells, power counts and I2C RTL +are unchanged by this verification repair. + +The Tiny `HAVE_SVA=YES` flow now also emits `+define+HAVE_SVA`, which the +owned P4/P5 assertions use. Previously it only omitted the Common +`SV_ASSRT_DISABLE` define; focused fixtures explicitly enabled the owned +assertions but ordinary firmware flows did not. Mini flag behavior is unchanged. + +The first full integration reached Crypto but timed out before audio completion. +A smaller actual-SoC diagnostic isolated an I2S status load fault: Tiny's +AXI-to-APB bridge retained the preceding write strobes on reads. Read PSTRB is +now zero as required by APB4, while writes retain their byte lanes. A directed +write/read sequence checks full/partial writes with a strict receiver. +The Tiny firmware limits exception skipping to its explicit CPU probes; +unexpected later load/store faults fail immediately instead of being masked. + +Audio shutdown keeps the consumer running while aborting/draining its leased +TX DMA session. A held stream beat cannot retire into an already stopped/full +audio FIFO. Only after DMA retirement does the acceptance image disable I2S +and flush. After a missing-clock flush timeout it polls the outstanding command +through clock recovery instead of issuing another protected COMMAND write. + +SDIO's reused card model originally required manual enumeration/data stimulus; +its default ACMD41 never reports READY. The command trace confirmed the +initialization loop. An opt-in SDHC response profile and the existing explicit +data API now support the Tiny board fixture while preserving standalone defaults. +The fixture checks wire-command/data-policy agreement and drives only external +pins. Card timing/protocol/electrical qualification remains excluded. The native +Icarus parser also needs an equivalent explicit enum branch in this model. +Tiny Icarus builds now retain separate testbench/header dependencies, including +the shared card model and P6 board include. Its serial observer emits measurement +records on ordinary runs; TEST_STATUS and strict error checks remain authoritative. + +The 12 MHz command case exposed a running-clock start-bit duplication in the +shared SDIO sender. Output admission now waits for its first scheduled launch; +the independent pad sampler covers both admission phases. The card sampler +retains the complete frame across host OE release, including the pulled-high +end bit. This preserves the command/register ABI and does not extend card modes. + +The full four-lane SDIO block exposed a separate transmit pause defect: when +memory delayed the next word, a late clock stop corrupted its first bit +(`0x57` became `0xD7`) and caused CRC rejection. The shared data engine now +requests the pause before that launch; the clock wrapper honors it while +draining the current high half-cycle. A full 1024-byte fixture with read-beat +gaps checks payload, CRC and descriptor retirement. Its scalar three-state +variant also caught card-model assumptions about unused lanes being zero; +token and partial-tail checks now account for their pulled-high state. + +The receive roundtrip isolated premature descriptor completion after one +64-byte write burst. SDIO private DMA retains ownership until every payload +burst retires or an error is reported. A separately reproduced receive stop +delay shifted the next word (`0x13579BDE` to `0x26AE37BD`); immediate stop +qualification prevents the extra launch. The card's read-CRC sequencer also +skipped one bit and is corrected independently. Delayed descriptor/payload +tests check full readback, CRC, descriptor retirement and both simulators. +These repairs add no masters, channels, descriptor fields or operating modes. + +## Validation and historical publication + +Workloads keep the ordinary Tiny boot, SRAM/DMA tail, trap, interrupt, RTC, +I2C NACK, JTAG and watchdog-reset checks. P6 adds real GPIO8/9 UART echo; +eight-channel/unsupported-request checks; RNG qualification rejection and IRQ; +CRC DMA, empty input and byte tails; Crypto initialization, AES/SHA/public RSA, +channels 4/5 and zeroization. Audio uses the fixed 18.432 MHz external reference +and 48 kHz master presets, checks 256 stereo frames at both 16 and 24 bits, +and drives paced 33/65-word WS frames in each case. Channel 2 concurrently runs +bounded 256-byte jobs, then four-descriptor chains; channels 6/7 own audio. +The board independently decodes I2S/WS pins, checks FIFO/service bounds and +stops AUDIO low/high for recovery tests. SDIO uses 400 kHz enumeration/one-bit +PIO and 12 MHz four-bit DMA, transfers 1024 bytes in each DMA direction and +512 PIO bytes, overlaps a separate 1024-byte central-DMA job and checks IRQ, +CRC-error, timeout and recovery behavior. + +MCYCLE/MINSTRET measure the audio/WS service loop after prefill; elapsed CLINT +time also includes its bounded preparation. The independent AXI observers count +handshakes over the complete firmware run, including boot/control accesses; +they are not payload-only bandwidth measurements. Write counts mean accepted +strobes, not rollback guarantees. Descriptor fetch bytes, per-owner AR/AW/W/R/B +backpressure, WS service gaps and actual SDIO/DMA overlap with CPU progress +remain explicit. `scripts/tiny_r2_p6.py` retains ELF/hex/bin/map, linker/generated +software inputs, simulator binaries/filelists, manifests, logs and their hashes. +Both simulators on both PDKs must have successful commands, Tiny's terminal +`SIM_TEST_PASS`, all workload/independent-observer records and no forbidden +error marker. Component diagnostics never satisfy that collector. + +The current Tiny PR and nightly dry-run command matrices are identical for +each PDK. After focused/quality checks, execute full Pytest once and one full +PR per PDK in independent build directories; these broader gates may run in +parallel but must all finish before acceptance. Collect warnings/metrics with explicit `APP=ci_smoke`, +`SYNTH=YOSYS`, `STA=OPENSTA` and the same build timestamp. Do not rerun an +identical nightly matrix. Shared I2S and SDIO standalone/formal checks plus the +affected Mini firmware path remain separate compatibility evidence. Core STA +must execute and bind all 38 exact macros; its slack remains observational. +The legacy IHP130 LibreLane floorplan generator still places the 32 main macros +only. It has not placed/connected the six private Crypto macros or implemented +the final QFN64 wrapper. Full-chip APR/PDN/package work remains outside this +campaign; neither its old floorplan nor smoke STA establishes P6 physical closure. + +Development artifacts begin at the manually selected `2026-10-12-12-00` +build label, with subsequent diagnostic labels recorded in each command. +Those labels are not wall-clock measurements; JSON start/end times are. +Some diagnostic firmware directories were reused while finding defects and +must not be rebound to later binaries. The `11-42` snapshot's Make-format gate +failed before long validation. The fresh `2026-10-12-11-50` campaign retains +its input snapshot under `build/ics55-tiny-2026-10-12-11-50-2eb98c0e1fae/meta/tiny-r2-p6/`. +Failed attempts retain their own verdicts and are not acceptance evidence. + +Both original PR attempts passed complete Verilator/JTAG acceptance and then +stopped at native Icarus elaboration: `s_token_lanes` referenced `s_data_bytes` +before its declaration. The declaration relocation changes no expression or +other model byte. `verification-update.json` retains the second snapshot; +the collector checks that exact equivalence and rejects any DUT, firmware, +constraint, tool or other model-behavior change. Only the card model and its +collector/tests differ. Original Verilator and Mini results retain their +original source identity. Native Icarus and the remaining physical smoke stages +resume from the failed step; the original failed PR verdicts are not rewritten. +Full Pytest retains its original collection, with the equivalent model edit +during execution; the updated 48-case P6 suite separately covers the native +parser and fail-closed provenance checks. Counts overlap and must not be added. + +The one full Pytest attempt reports 1634 passed, one optional `pypdf` skip, +five failures and 60 fixture errors. These reduce to two source-consumer issues: +Mini's publication parser counted inactive Tiny enum branches as Mini channel +allocations, and the commercial Tiny overlay retained its pre-P6 two-domain +allowlist. The parser now selects and checks Mini's actual bulk aliases; no book +identity, chapter, PDF or historical evidence changes. The commercial policy +and Tcl inventory now include the canonical AUDIO root; no licensed flow or +qualified I/O run is implied. `consumer-update.json` binds this separate scope +and rejects changes to RTL, firmware, models or smoke constraints. + +Every one of the 65 original failed/error nodes passes in the explicit repair +selection. The complete affected publication/commercial suite reports 512 passed +and one optional PDF-library skip; 35 selected provenance tests also pass. +These counts overlap the full attempt and each other. There is no claimed clean +full-suite rerun: unaffected long tests were retained with their original scope. + +### Completed Verilator observations (partial campaign) + +`verilator-observations.json` and `report-functional-before-physical.json` bind +the following values to successful command/log artifacts. Each row verifies 256 +stereo frames and two WS frames, with 64 completed background jobs. These values +are functional observations, not phase acceptance or timing closure. + +| PDK | Audio bits | Service-loop cycles | Retired instructions | Elapsed including preparation (us) | +| --- | ---: | ---: | ---: | ---: | +| ICS55 | 16 | 141917 | 41351 | 8044 | +| ICS55 | 24 | 138353 | 40283 | 7892 | +| IHP130 | 16 | 141917 | 41351 | 8065 | +| IHP130 | 24 | 138350 | 40283 | 7903 | + +The independent WS service-gap maxima are 2375/2374 SYS cycles +(98.96/98.92 us), below the 4800-cycle budget; FIFO peak occupancy is 15/16. +Both runs observe 4124 cycles of actual SDIO/private-DMA versus central-DMA +overlap, with 1185 CPU instruction-fetch handshakes in that interval. SDIO's +external owner reads 1056 bytes and accepts 1032 write bytes, including its +descriptor fetch/writeback. Central-DMA descriptor fetches total 16384 bytes. +No cross-architecture speedup or physical rate is inferred from these samples. + +Both complete Icarus runs subsequently pass: ICS55 at 16036387 cycles and +IHP130 at 16035743 cycles, with the +same workload, WS, contention and bus observations as its Verilator run. +Its UART observer leaves a partial human-readable line before the testbench's +terminal `SIM_TEST_PASS Tiny cycles=...` record. `collection-update.json` +records a collector/test-only correction to accept that exact terminal token +without requiring a fresh line; JTAG-only/Mini markers, malformed cycle values +and forbidden errors remain rejected. Forty focused collection/provenance +checks pass. Raw logs, RTL, firmware, models and constraints are unchanged. + +Both netlist boot checks pass at 30030 cycles. Yosys synthesis and OpenSTA run +for both PDKs and report the exact 32 main plus 6 private Crypto macro bindings. +Observed top-cell/area values are ICS55 412515/2461020.464702 and IHP130 +433861/13320370.152627. Setup/hold remain negative: ICS55 WNSmax/WNSmin +−84.93/−0.31 ns, IHP130 −165.41/−0.51 ns. These are smoke-flow observations; +the frozen phase does not use synthesis slack as an intermediate blocking gate. +Warning checks retain new Yosys/Verilator/RTL-lint and IHP130 Icarus signatures +as non-promoted observations under the existing policy; no baseline was edited. + +The old Tiny publication remains frozen at its advertised technical revision +`9c706ba0ff1da1028c299cfff573010a86dfd7ce`. Tests execute the original +publication adapter/test snapshot `2805ea91833a6bede32f3a82df3d17d20eb5ec77` +and, separately, the later P5 contract snapshot `b3b30a72cd6ef5340c28e5044c96cd13258ec5a6`. +Neither snapshot becomes P6 publication evidence. Current P6 source must be +rejected by the historical collector. The reset mutation checks still run +against the current target RTL. No frozen chapter, reviewed SHA or old PDF is +rewritten; unavailable PDF/media inputs remain a separate publication gap. + +P7 RCU/variable clocks, PWM dependency upgrades, alternate-input ABI, slave +audio, PSRAM, DVP and later extensions remain outside this phase. Timing is +observational before complete-product qualification; exact macro/clock/source +binding and successful functional verdicts remain mandatory. diff --git a/docs/ip/tiny-soc-verification.md b/docs/ip/tiny-soc-verification.md index 08ffbeee..0f496663 100644 --- a/docs/ip/tiny-soc-verification.md +++ b/docs/ip/tiny-soc-verification.md @@ -1,5 +1,39 @@ # Tiny MCU Verification Record +## TINY-R2-P6 implementation complete, human review pending (2026-10-12) + +The maintainer approved the P6 preflight on +`b3b30a72cd6ef5340c28e5044c96cd13258ec5a6`, against unchanged specification blob +`7e19de49d49efaafacccfa85671e314d53dd15a4`. The +[P6 runbook](tiny-soc-r2-shared-integration.md) records eight-channel/shared-IP +integration, the explicitly retained historical ASIC wrapper and publication, +and the separate source-bound validation scope. Development diagnostics are +not final acceptance; no P7 or physical-frequency qualification is implied. + +Initial integration exposed a shared I2S sample-LSB assembly defect and a +Verilator packed-tri1 board-model limitation. The runbook records their narrow +corrections. Directed SDIO integration additionally exposed command admission, +transmit/receive pause and multi-burst descriptor-retirement defects, plus +manual card-model gaps; their independent reproductions are recorded there. +Both Tiny PDKs have complete Verilator and Icarus functional verdicts and +netlist boots. Yosys synthesis and OpenSTA run for both PDKs with the exact +32-main plus 6-private-Crypto macro bindings. The final audit is +`build/ics55-tiny-2026-10-12-11-50-2eb98c0e1fae/meta/tiny-r2-p6/audit-result-v3.json`; +the source-bound evidence is `evidence.json` in the same directory. The +functional report retains four simulator records, all workload/AXI/device +observations and the Mini compatibility result. + +Observed setup/hold slack remains negative: ICS55 WNSmax/WNSmin −84.93/−0.31 ns +and IHP130 −165.41/−0.51 ns. Warning checks report new signatures from the +existing policy's observation flows; no baseline or metrics policy was edited. +The full Pytest attempt remains recorded as 1634 passed, one optional `pypdf` +skip, five failures and 60 fixture errors; all 65 failed/error nodes pass in +the explicit repair selection, and the affected consumer suite passes 512 with +one optional skip. These overlapping counts are not a clean full-suite rerun. +Final human review, complete-product timing closure, package/QFN64 binding and +physical signoff remain pending. Earlier P5 results and failed P6 attempts +retain their original attribution. + ## TINY-R2-P5 P1 protocol correction; validated, review pending (2026-10-12) The maintainer requested correction of the reset write-drain/read-retention diff --git a/physical/commercial/README.md b/physical/commercial/README.md index ba2bf84d..ffa77929 100644 --- a/physical/commercial/README.md +++ b/physical/commercial/README.md @@ -158,11 +158,14 @@ false-paths the canonical reset ports. Product overlays Qualified I/O covers JTAG, DVP (GPIO10-20), ULPI, SDRAM, SDIO, XPI, and asynchronous pads (UART0/1, remaining GPIO); a reviewed `TIMING_IO_MODE_HOOK` is mandatory in qualified mode. -- Tiny (`tiny.tcl`): SAFE24 boot — `clk_system` (external 24 MHz) and - `clk_jtag` only. The PLL_TOP macro is parked (EN=0), is not a clock source, +- Tiny (`tiny.tcl`): SAFE24 boot — `clk_system` (external 24 MHz), + `clk_jtag`, and P6's independent `clk_audio` (GPIO17, 18.432 MHz). + The PLL_TOP macro is parked (EN=0), is not a clock source, and has no characterized timing arcs yet (see `docs/ip/tiny-soc.md` TINY-055); its DB/LEF/GDS/CDL views are still required for link, placement, - and LVS. Qualified I/O covers JTAG, XPI, and asynchronous pads. + and LVS. The legacy I/O template covers JTAG, XPI, and asynchronous pads; + this domain inventory update does not qualify P6 I2S/SDIO board timing or + the final QFN64 wrapper. ## Legacy-flow parity diff --git a/rtl/tiny/README.md b/rtl/tiny/README.md index 89d6f9fc..55b9239f 100644 --- a/rtl/tiny/README.md +++ b/rtl/tiny/README.md @@ -14,7 +14,9 @@ official dual-port CPU to four independently serviced 32 KiB local SRAM groups. The 32 physical macros and register ABI are unchanged. Only non-SRAM CPU work uses the tagged slow path. The [P5 implementation](../../docs/ip/tiny-soc-r2-concurrent-fabric.md) replaces external serialization with independent target ownership and APB -branches; the third, SDIO ingress remains reserved and inactive until P6. +branches. The [P6 candidate](../../docs/ip/tiny-soc-r2-shared-integration.md) +activates the SDIO ingress, eight-channel DMA, RNG/CRC/WS2812/Crypto and the +existing I2S master path. Its validation and acceptance remain in the ledger. Source presence alone is not completed phase or physical qualification. `top` owns product integration, `address_map`, `pin_map` and `integration` own @@ -51,7 +53,7 @@ final timing closure is mandatory. Software/DMA scheduling is `TINY-R2-P3`, loca `TINY-R2-P9`, system qualification `TINY-R2-P10` and complete-product physical qualification `TINY-R2-P11` (historical IHP130 title; now default ICS55). Legacy `TINY-P0` through `TINY-P12` remain in -the contract history. Executable profiles retain SAFE24 and four DMA channels; +the contract history. The P6 candidate retains SAFE24 with eight DMA channels; ICS55's present PLL stays off. No faster CPU/SRAM or PLL timing support follows from this freeze or the `HAVE_PLL` selector alone. @@ -96,8 +98,8 @@ FIFOs, APB4 `0x1001D000..0x1001DFFF`, IRQ25 and RCU target16. DMA V2.2 adds paced fixed-MMIO requests `SPI_TX=16` / `SPI_RX=17`; payload MMIO retains single-beat transactions. TX borrows channel3 and RX channel2 only after prior owners fully drain and release them. No extra AXI master, DMA channel, -SRAM capacity or pad is added. None of these paths exists in the current -24 MHz/no-PLL/four-channel RTL/profile. +SRAM capacity or pad is added. SPI and its requests remain absent from the +SAFE24 P6 candidate. The frozen Gen1 routes add only GPIO27 ALT0 SCK, GPIO28 ALT1 MOSI, GPIO30 ALT1 MISO and GPIO31 ALT1 CS_N. Write-only display mode uses ordinary GPIO30 From a14fe127247c9b3dc7dba12aa38578e6b01d3ce9 Mon Sep 17 00:00:00 2001 From: maksyuki Date: Wed, 14 Oct 2026 16:54:43 +0800 Subject: [PATCH 09/12] build(deps): pin PWM runtime clock reporting for Tiny P7 Advance the approved PWM dependency to 38efaf5e20f084a00148920087c60cadab61839b so Tiny RCU can publish the committed PCLK rate through the existing PWM register interface. Keep the static-clock default for existing consumers. Required by TINY-R2-P7 in docs/ip/tiny-soc.md. The locked checkout and P7 validation use this exact revision; dependency-lock validation passes. --- dependencies/dependencies.lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies/dependencies.lock.json b/dependencies/dependencies.lock.json index 0997b658..d6460be4 100644 --- a/dependencies/dependencies.lock.json +++ b/dependencies/dependencies.lock.json @@ -150,7 +150,7 @@ "cluster_common": {"url": "https://github.com/retroSoC/common.git", "revision": "964a54d70cb78e394ba9a25c05f2aa2b02941fb7", "destination": "rtl/managed/clusterip/common", "license": "NOASSERTION"}, "cluster_crc": {"url": "https://github.com/retroSoC/crc.git", "revision": "8c1a5664c4d23d005c1afad432372a13ca967cc8", "destination": "rtl/managed/clusterip/crc", "license": "MulanPSL-2.0"}, "cluster_ps2": {"url": "https://github.com/retroSoC/ps2.git", "revision": "2097a0d41b07573ff0453d65ba9a1670b3df7b9c", "destination": "rtl/managed/clusterip/ps2", "license": "MulanPSL-2.0"}, - "cluster_pwm": {"url": "https://github.com/retroSoC/pwm.git", "revision": "79ae32f725e848a1e7f6134847b3fb032ed64006", "destination": "rtl/managed/clusterip/pwm", "license": "MulanPSL-2.0"}, + "cluster_pwm": {"url": "https://github.com/retroSoC/pwm.git", "revision": "38efaf5e20f084a00148920087c60cadab61839b", "destination": "rtl/managed/clusterip/pwm", "license": "MulanPSL-2.0"}, "cluster_rng": {"url": "https://github.com/retroSoC/rng.git", "revision": "36f3e0774d8f3c4b597d3db8a96ca3992532b61e", "destination": "rtl/managed/clusterip/rng", "license": "MulanPSL-2.0"}, "cluster_rtc": {"url": "https://github.com/retroSoC/rtc.git", "revision": "32548c5cbcdf39e09d0880fb611f9dd47940ea88", "destination": "rtl/managed/clusterip/rtc", "license": "NOASSERTION"}, "cluster_wdg": {"url": "https://github.com/retroSoC/wdg.git", "revision": "f5d39fa246054d678d34195824d943367dfcce44", "destination": "rtl/managed/clusterip/wdg", "license": "MulanPSL-2.0"} From 6fbc6ec521d32fe27c31648b16c1443607f6620b Mon Sep 17 00:00:00 2001 From: maksyuki Date: Wed, 14 Oct 2026 16:55:12 +0800 Subject: [PATCH 10/12] fix(flow): retain terminal evidence across interrupted runs Record parent and child process IDs for diagnostics, persist interruption before cleanup, and ignore repeated termination signals during cleanup. Close unexpected failures with a terminal result and retain the child's exit status without treating namespace-local PID absence as completion. Require a successful terminal run_flow result when checking simulation logs with an associated result file. Cover repeated cancellation and reject running, failed or inconsistent terminal records despite PASS text. This supports the TINY-R2-P7 campaign. Interrupted and timed-out runs remain non-acceptance evidence; UART output alone cannot qualify a Tiny simulation. --- scripts/check_simulation.py | 15 ++++++++++ scripts/run_flow.py | 51 ++++++++++++++++++++++---------- tests/test_script_tools.py | 58 +++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 16 deletions(-) diff --git a/scripts/check_simulation.py b/scripts/check_simulation.py index 2197685e..0a4a5012 100644 --- a/scripts/check_simulation.py +++ b/scripts/check_simulation.py @@ -39,6 +39,19 @@ def main() -> int: if default_failure: rejected.append(default_failure.group(0)) + flow_result_path = args.result.with_name("result-sim.json") + flow_status = None + if flow_result_path.is_file(): + try: + flow_result = json.loads(flow_result_path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError): + flow_result = {} + flow_status = flow_result.get("status", "invalid") + # PIDs are namespace-local and can be reused. A durable terminal + # verdict, not a liveness guess from this process, establishes success. + if flow_status != "passed" or flow_result.get("exit_code") != 0: + missing.append("successful terminal run_flow result") + passed = not missing and not rejected report = { "schema_version": 1, @@ -48,6 +61,8 @@ def main() -> int: "missing_markers": missing, "rejected_matches": sorted(set(rejected)), } + if flow_status is not None: + report["flow_status"] = flow_status atomic_write(args.result, json.dumps(report, indent=2, sort_keys=True) + "\n") print(f"simulation check: {report['status']} ({args.result.resolve()})") return int(not passed) diff --git a/scripts/run_flow.py b/scripts/run_flow.py index 26dcddc5..91ba1867 100644 --- a/scripts/run_flow.py +++ b/scripts/run_flow.py @@ -124,24 +124,22 @@ def main() -> int: args.result.parent.mkdir(parents=True, exist_ok=True) started = datetime.now(timezone.utc) start_clock = time.monotonic() - atomic_write( - args.result, - json.dumps( - { - "schema_version": 1, - "tool": args.tool, - "command": command, - "started_at": started.isoformat(), - "status": "running", - "log": str(args.log.resolve()), - }, - indent=2, - sort_keys=True, - ) - + "\n", - ) + running_result = { + "schema_version": 1, + "tool": args.tool, + "command": command, + "started_at": started.isoformat(), + "status": "running", + "parent_pid": os.getpid(), + "log": str(args.log.resolve()), + } + atomic_write(args.result, json.dumps(running_result, indent=2, sort_keys=True) + "\n") def interrupt_flow(_signum: int, _frame: object) -> None: + # A launcher can deliver SIGINT followed by SIGTERM. Do not let a + # second signal interrupt process-group cleanup or result persistence. + signal.signal(signal.SIGINT, signal.SIG_IGN) + signal.signal(signal.SIGTERM, signal.SIG_IGN) raise KeyboardInterrupt signal.signal(signal.SIGINT, interrupt_flow) @@ -169,6 +167,8 @@ def main() -> int: pass_fds=jobserver_fds(), start_new_session=True, ) + running_result["child_pid"] = process.pid + atomic_write(args.result, json.dumps(running_result, indent=2, sort_keys=True) + "\n") assert process.stdout is not None if args.stream_bytes: while chunk := os.read(process.stdout.fileno(), 4096): @@ -193,11 +193,25 @@ def main() -> int: returncode = process.wait() except KeyboardInterrupt: error_message = "interrupted" + # Persist cancellation before the bounded cleanup wait. If the outer + # executor subsequently kills this wrapper, the file must not imply + # that the job is still running or that its child finished normally. + cancelled = dict(running_result, status="interrupted", exit_code=130, + interrupted_at=datetime.now(timezone.utc).isoformat(), + child_cleanup_pending=process is not None) + atomic_write(args.result, json.dumps(cancelled, indent=2, sort_keys=True) + "\n") if process is not None: terminate_process_group(process) returncode = 130 except (OSError, subprocess.SubprocessError) as error: error_message = str(error) + if process is not None: + terminate_process_group(process) + except BaseException as error: + error_message = f"unexpected {type(error).__name__}: {error}" + if process is not None: + terminate_process_group(process) + returncode = 1 finished = datetime.now(timezone.utc) result = { @@ -211,8 +225,13 @@ def main() -> int: "peak_rss_kib": resource.getrusage(resource.RUSAGE_CHILDREN).ru_maxrss, "exit_code": returncode, "status": "interrupted" if returncode == 130 else ("passed" if returncode == 0 else "failed"), + "parent_pid": os.getpid(), "log": str(args.log.resolve()), } + if process is not None: + result["child_pid"] = process.pid + result["child_exit_code"] = process.returncode + result["child_cleanup_pending"] = process.returncode is None if error_message: result["error"] = error_message if args.success_marker: diff --git a/tests/test_script_tools.py b/tests/test_script_tools.py index 98884b6f..ca9c2206 100644 --- a/tests/test_script_tools.py +++ b/tests/test_script_tools.py @@ -1969,6 +1969,8 @@ def test_run_flow_writes_structured_result(tmp_path: Path) -> None: assert data["status"] == "passed" assert data["exit_code"] == 0 assert data["duration_seconds"] >= 0 + assert isinstance(data["parent_pid"], int) + assert isinstance(data["child_pid"], int) assert isinstance(data["peak_rss_kib"], int) assert data["peak_rss_kib"] > 0 assert log.read_text(encoding="utf-8") == "flow output\n" @@ -2120,6 +2122,41 @@ def test_run_flow_streams_bytes_without_waiting_for_newline(tmp_path: Path) -> N assert json.loads(result.read_text(encoding="utf-8"))["status"] == "passed" +def test_run_flow_persists_cancel_before_cleanup_and_ignores_second_signal(tmp_path: Path) -> None: + result = tmp_path / "cancelled.json" + process = subprocess.Popen([ + sys.executable, str(ROOT / "scripts/run_flow.py"), "--tool", "unit", + "--log", str(tmp_path / "cancelled.log"), "--result", str(result), "--", + sys.executable, "-u", "-c", + "import signal,time; signal.signal(signal.SIGTERM,signal.SIG_IGN); " + "print('READY',flush=True); time.sleep(30)", + ], text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + try: + assert process.stdout is not None + assert process.stdout.readline().strip() == "READY" + process.send_signal(signal.SIGINT) + for _ in range(100): + checkpoint = json.loads(result.read_text()) + if checkpoint["status"] == "interrupted": + break + time.sleep(0.01) + assert checkpoint["status"] == "interrupted" + assert checkpoint["child_cleanup_pending"] is True + assert "finished_at" not in checkpoint + process.send_signal(signal.SIGTERM) + _, stderr = process.communicate(timeout=10) + assert process.returncode == 130, stderr + final = json.loads(result.read_text()) + assert final["status"] == "interrupted" + assert final["child_cleanup_pending"] is False + assert final["child_exit_code"] == -signal.SIGKILL + assert final["finished_at"] >= final["started_at"] + finally: + if process.poll() is None: + process.kill() + process.wait(timeout=5) + + def test_run_flow_records_interruption(tmp_path: Path) -> None: log = tmp_path / "interrupted.log" result = tmp_path / "interrupted.json" @@ -2192,6 +2229,27 @@ def test_simulation_success_marker_and_failure_detection(tmp_path: Path) -> None assert json.loads(result.read_text(encoding="utf-8"))["status"] == "failed" +@pytest.mark.parametrize("status,exit_code", [("running", None), ("failed", 1), + ("passed", 124), ("invalid", None)]) +def test_check_simulation_requires_terminal_flow(tmp_path: Path, status: str, + exit_code: int | None) -> None: + log = tmp_path / "sim.log" + result = tmp_path / "result-sim-check.json" + log.write_text("SIM_TEST_PASS code=0\n", encoding="utf-8") + (tmp_path / "result-sim.json").write_text( + json.dumps({"status": status, "exit_code": exit_code}), + encoding="utf-8", + ) + checked = subprocess.run( + [sys.executable, str(ROOT / "scripts/check_simulation.py"), "--log", str(log), + "--result", str(result)], text=True, capture_output=True, + ) + report = json.loads(result.read_text(encoding="utf-8")) + assert checked.returncode != 0 + assert report["status"] == "failed" + assert report["flow_status"] == status + + def test_warning_baseline_rejects_new_signature(tmp_path: Path) -> None: profile = "unit-profile" log = tmp_path / "variant/sim/verilator/verilating.log" From bc48cec74a8f3cec731c4853ac0380f9ae32c35b Mon Sep 17 00:00:00 2001 From: maksyuki Date: Wed, 14 Oct 2026 16:56:01 +0800 Subject: [PATCH 11/12] feat(tiny): implement R2-P7 clock and reset lifecycle Integrate the Tiny RCU, normalized REF24 control, external-input profiles, single PLL backend, SYS/MEM/PCLK clocks, CPU-only WFI gating, peripheral lifecycle control and retained debug behavior. Keep CPU and all 32 main SRAM macros on SYS and the six private Crypto macros on PCLK. Add the handwritten RCU register/HAL contract, RS_EBUSY, bounded SDK operations and focused firmware. Preserve shared-IP defaults and the ICS55/PLL SAFE24 boot and explicit IHP130/no-PLL compatibility profiles. Use APB/AXI domain bridges and source-owned response holders to preserve backpressured responses, complete aborted bursts and prevent stale replay. Verify production DMA source-reset drain and common XPI endpoint reset with extracted integration bindings and an independent scoreboard. Add source-bound macro/clock audits, SAFE24 observational STA constraints and commercial-adapter host checks. No P8 functionality or operating-point qualification is included. See TINY-R2-P7 in docs/ip/tiny-soc.md. Validation: 54 P7 directed tests pass, including both simulators, ten source-reset ratio runs and two expected-negative drain mutations. RTL format/style, clock/reset inventory, Ruff and both Tiny PDK PR dry-runs pass. Retained 06-00 synthesis and strict netlist boots pass for both PDKs; STA remains negative and observational. Source comparison confirms only the follow-up tests changed after that backend campaign. Evidence: build/ics55-tiny-2026-10-14-06-00-ff4b5cef6eb5/meta/ tiny-r2-p7-source-reset/summary.json. Earlier firmware, HAL and compatibility results and the non-clean full-suite attempt retain their original attribution. Full Icarus firmware is deferred to final Tiny qualification; this commit does not constitute human phase acceptance or physical signoff. --- Makefile | 14 +- app/apps/ci_smoke/tiny.c | 19 +- app/apps/ci_smoke/tiny.mk | 5 +- app/apps/ci_smoke/tiny_r2_p7.c | 171 ++++ configs/ci/ihp130-tiny-xin48.mk | 19 + configs/ci/ihp130-tiny-xin96.mk | 19 + crt/include/retrosoc/core/status.h | 1 + crt/include/retrosoc/hal/rcu.h | 42 + crt/include/retrosoc/hal/rcu_regs.h | 46 + crt/src/hal/rcu.c | 261 +++++ physical/commercial/config/products/tiny.mk | 8 +- .../commercial/tcl/common/products/tiny.tcl | 36 +- physical/smoke/sta/opensta/generate_sdc.py | 49 +- physical/smoke/sta/opensta/opensta.mk | 10 +- physical/smoke/sta/opensta/opensta.tcl | 6 +- .../smoke/sta/opensta/tiny_r2_p7_checks.tcl | 22 + physical/smoke/syn/yosys/script/synth.tcl | 2 +- physical/smoke/syn/yosys/yosys.mk | 8 +- publications/datasheets/system-reference.json | 6 +- rtl/ip/core/mgmt_debug_wrapper.sv | 8 +- rtl/ip/peripheral/apb4_dma.sv | 4 + rtl/ip/peripheral/apb4_timer.sv | 2 + rtl/ip/security/apb4_crypto.sv | 11 +- rtl/ip/serial/apb4_i2c.sv | 11 +- rtl/ip/serial/apb4_i2s.sv | 34 +- rtl/ip/serial/apb4_uart.sv | 7 +- rtl/ip/serial/apb4_ws2812.sv | 7 +- rtl/ip/storage/apb4_sdio.sv | 27 +- rtl/ip/storage/apb4_xpi.sv | 6 + rtl/ip/storage/sdio_core.sv | 14 +- rtl/mk/software.mk | 3 +- rtl/rtl_style_audit.json | 12 +- rtl/tech/tc_pll.sv | 15 +- rtl/tiny/dv/retrosoc_tiny_tb.sv | 15 +- rtl/tiny/dv/tiny_p6_devices.svh | 19 +- rtl/tiny/dv/tiny_p7_observers.svh | 65 ++ rtl/tiny/filelist/commonip.fl | 3 + rtl/tiny/filelist/top.fl | 9 + rtl/tiny/integration/clock_reset_domains.json | 285 +++++- rtl/tiny/integration/soc_topology.json | 17 +- rtl/tiny/mk/config.mk | 20 +- rtl/tiny/top/retrosoc_tiny.sv | 910 +++++++++++++++--- rtl/tiny/top/retrosoc_tiny_asic.sv | 16 - rtl/tiny/top/tiny_apb_async_bridge.sv | 156 +++ rtl/tiny/top/tiny_axi4_admission.sv | 153 +++ rtl/tiny/top/tiny_axi4_async_bridge.sv | 432 +++++++++ rtl/tiny/top/tiny_clock_gate.sv | 21 + rtl/tiny/top/tiny_clock_output.sv | 66 ++ rtl/tiny/top/tiny_clock_reset_subsystem.sv | 407 ++++++++ rtl/tiny/top/tiny_cpu_mem.sv | 26 +- rtl/tiny/top/tiny_cpu_wrapper.sv | 58 +- rtl/tiny/top/tiny_managed_lifecycle.sv | 53 + rtl/tiny/top/tiny_peripheral_control.sv | 133 +++ rtl/tiny/top/tiny_peripheral_leaf.sv | 55 ++ rtl/tiny/top/tiny_rcu_regs.svh | 30 + rtl/tiny/top/tiny_reset_tree.sv | 18 +- rtl/tiny/top/tiny_sysctrl.sv | 601 +++++++++++- scripts/check_clock_reset_domains.py | 17 + scripts/generate_tiny.py | 59 +- scripts/run_c_tests.py | 2 + scripts/tiny_ics55_platform.py | 13 +- scripts/tiny_r2_p7.py | 187 ++++ tests/c/test_runtime.c | 59 ++ tests/rtl/tiny_apb_async_bridge_tb.sv | 87 ++ tests/rtl/tiny_axi4_admission_tb.sv | 135 +++ tests/rtl/tiny_axi4_async_bridge_tb.sv | 432 +++++++++ tests/rtl/tiny_clock_external_tb.sv | 159 +++ tests/rtl/tiny_clock_output_tb.sv | 56 ++ tests/rtl/tiny_clock_reset_pll_tb.sv | 175 ++++ tests/rtl/tiny_clock_reset_subsystem_tb.sv | 87 ++ tests/rtl/tiny_p7_source_reset_tb.sv | 415 ++++++++ tests/rtl/tiny_peripheral_control_tb.sv | 122 +++ tests/rtl/tiny_r2_p4_cpu.S | 10 + tests/rtl/tiny_r2_p4_cpu_tb.sv | 70 +- tests/rtl/tiny_r2_p5_system_tb.sv | 2 + tests/rtl/tiny_sysctrl_tb.sv | 170 +++- tests/rtl/tiny_watchdog_reset_tb.sv | 188 ++++ tests/test_commercial_flow.py | 14 +- tests/test_tiny.py | 15 +- tests/test_tiny_r2_p2.py | 8 +- tests/test_tiny_r2_p4.py | 12 +- tests/test_tiny_r2_p5.py | 5 +- tests/test_tiny_r2_p6.py | 7 +- tests/test_tiny_r2_p7.py | 294 ++++++ 84 files changed, 6913 insertions(+), 360 deletions(-) create mode 100644 app/apps/ci_smoke/tiny_r2_p7.c create mode 100644 configs/ci/ihp130-tiny-xin48.mk create mode 100644 configs/ci/ihp130-tiny-xin96.mk create mode 100644 crt/include/retrosoc/hal/rcu.h create mode 100644 crt/include/retrosoc/hal/rcu_regs.h create mode 100644 crt/src/hal/rcu.c create mode 100644 physical/smoke/sta/opensta/tiny_r2_p7_checks.tcl create mode 100644 rtl/tiny/dv/tiny_p7_observers.svh create mode 100644 rtl/tiny/top/tiny_apb_async_bridge.sv create mode 100644 rtl/tiny/top/tiny_axi4_admission.sv create mode 100644 rtl/tiny/top/tiny_axi4_async_bridge.sv create mode 100644 rtl/tiny/top/tiny_clock_gate.sv create mode 100644 rtl/tiny/top/tiny_clock_output.sv create mode 100644 rtl/tiny/top/tiny_clock_reset_subsystem.sv create mode 100644 rtl/tiny/top/tiny_managed_lifecycle.sv create mode 100644 rtl/tiny/top/tiny_peripheral_control.sv create mode 100644 rtl/tiny/top/tiny_peripheral_leaf.sv create mode 100644 rtl/tiny/top/tiny_rcu_regs.svh create mode 100644 scripts/tiny_r2_p7.py create mode 100644 tests/rtl/tiny_apb_async_bridge_tb.sv create mode 100644 tests/rtl/tiny_axi4_admission_tb.sv create mode 100644 tests/rtl/tiny_axi4_async_bridge_tb.sv create mode 100644 tests/rtl/tiny_clock_external_tb.sv create mode 100644 tests/rtl/tiny_clock_output_tb.sv create mode 100644 tests/rtl/tiny_clock_reset_pll_tb.sv create mode 100644 tests/rtl/tiny_clock_reset_subsystem_tb.sv create mode 100644 tests/rtl/tiny_p7_source_reset_tb.sv create mode 100644 tests/rtl/tiny_peripheral_control_tb.sv create mode 100644 tests/rtl/tiny_watchdog_reset_tb.sv create mode 100644 tests/test_tiny_r2_p7.py diff --git a/Makefile b/Makefile index bc7e8671..c8248c45 100644 --- a/Makefile +++ b/Makefile @@ -57,7 +57,7 @@ JTAG_IDCODE ?= DEADBEEF EXT_CLK_HZ ?= 72000000 AUD_CLK_HZ ?= 18432000 CLINT_TIMEBASE_HZ ?= 1000000 -MGMT_CPU_CLK_HZ := $(if $(filter PRODUCT,$(MINI_MODE)),24000000,$(EXT_CLK_HZ)) +MGMT_CPU_CLK_HZ := $(if $(filter TINY,$(SOC)),24000000,$(if $(filter PRODUCT,$(MINI_MODE)),24000000,$(EXT_CLK_HZ))) # Reset-state peripheral clock: the PCLK divider in the clock/reset subsystem # resets to passthrough on the LP root clock, so PCLK starts at the management # CPU frequency until software programs a different divider. @@ -85,6 +85,7 @@ SW_ISA_PROFILE ?= COMPAT SW_OPT ?= APP SW_LTO ?= NO WS2812_P3_ACCEPTANCE ?= NO +TINY_P7_ACCEPTANCE ?= NO COREMARK_MODE ?= quick NPU_P5_ACCEPTANCE ?= NO NPU_P6_ACCEPTANCE ?= NO @@ -127,6 +128,9 @@ endif ifeq ($(WS2812_P3_ACCEPTANCE),YES) CONFIG_KEY_VARS += WS2812_P3_ACCEPTANCE endif +ifeq ($(TINY_P7_ACCEPTANCE),YES) +CONFIG_KEY_VARS += TINY_P7_ACCEPTANCE +endif VARIANT_ID := $(strip $(shell $(VCS_SHELL_PYTHON) $(ROOT_PATH)/scripts/config_key.py \ --lock $(LOCK_FILE) --profile $(PROFILE_NAME) --timestamp $(BUILD_TIMESTAMP) \ $(foreach var,$(CONFIG_KEY_VARS),--value $(var)=$($(var))) | tail -n 1)) @@ -287,6 +291,12 @@ $(call validate_value,SW_ISA_PROFILE,COMPAT TINY_PERF) $(call validate_value,SW_OPT,APP O2 O3 Os) $(call validate_value,SW_LTO,$(VALID_BOOL)) $(call validate_value,WS2812_P3_ACCEPTANCE,$(VALID_BOOL)) +$(call validate_value,TINY_P7_ACCEPTANCE,$(VALID_BOOL)) +ifeq ($(TINY_P7_ACCEPTANCE),YES) +ifneq ($(SOC)/$(APP)/$(HAVE_CSR),TINY/ci_smoke/YES) +$(error TINY_P7_ACCEPTANCE requires SOC=TINY APP=ci_smoke HAVE_CSR=YES) +endif +endif ifeq ($(WS2812_P3_ACCEPTANCE),YES) ifneq ($(SOC)/$(APP)/$(HAVE_CSR),MINI/ci_smoke/YES) $(error WS2812_P3_ACCEPTANCE requires SOC=MINI APP=ci_smoke HAVE_CSR=YES) @@ -383,7 +393,7 @@ endif ifeq ($(STA),OPENSTA) ifeq ($(HAVE_PLL),YES) -ifneq ($(SOC)/$(PDK)/$(TINY_SAFE24_PLL_OFF),TINY/ICS55/YES) +ifneq ($(SOC)/$(PDK)/$(TINY_R2_P7),TINY/ICS55/YES) $(error STA=OPENSTA requires a qualified PDK PLL timing profile; HAVE_PLL=YES is unsupported) endif endif diff --git a/app/apps/ci_smoke/tiny.c b/app/apps/ci_smoke/tiny.c index 5553feef..902a6b81 100644 --- a/app/apps/ci_smoke/tiny.c +++ b/app/apps/ci_smoke/tiny.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,7 @@ static volatile uint32_t rs_mcu_exception_count; static volatile bool rs_mcu_handler_failed; static volatile bool rs_mcu_probes_active; static uint8_t rs_mcu_io_code; +static volatile uint32_t rs_mcu_watchdog_cookie __attribute__((section(".noinit.rs_mcu_wdg"))); static void rs_mcu_finish(bool pass, uint8_t code) { printf("Tiny acceptance %s code=%u\n", pass ? "passed" : "failed", code); @@ -251,6 +253,7 @@ int main(void) { rs_onchip_sram_info_t memory; rs_sysctrl_fault_status_t fault; rs_watchdog_status_t watchdog = {0}; + rs_tiny_rcu_snapshot_t reset_snapshot; uint64_t before; uint64_t after; uint8_t p6_result; @@ -268,12 +271,21 @@ int main(void) { rs_mcu_finish(false, 1U); } printf("Hello retroSoC! Tiny MCU\n"); - if (rs_watchdog_get_status(&watchdog, RS_TIMEOUT_DEFAULT) != RS_OK) { + if (rs_watchdog_get_status(&watchdog, RS_TIMEOUT_DEFAULT) != RS_OK || + rs_tiny_rcu_get_snapshot(&reset_snapshot) != RS_OK) { rs_mcu_finish(false, 2U); } - if (watchdog.reset_count != 0U) { - rs_mcu_finish(true, 0U); + if ((reset_snapshot.reset_cause & UINT32_C(4)) != 0U) { + /* P7 resets WDG functional state. The AON RCU owns the retained cause; + * the SRAM cookie proves all pre-reset acceptance work completed. */ + const bool reset_valid = + rs_mcu_watchdog_cookie == UINT32_C(0x57375037) && watchdog.reset_count == 0U && + reset_snapshot.current_profile == 0U && reset_snapshot.sys_hz == UINT32_C(24000000) && + reset_snapshot.gate_status == 0U; + rs_mcu_watchdog_cookie = 0U; + rs_mcu_finish(reset_valid, reset_valid ? 0U : 2U); } + rs_mcu_watchdog_cookie = 0U; 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) || @@ -316,6 +328,7 @@ int main(void) { rs_mcu_finish(false, p6_result); } printf("Tiny CPU/DMA/IRQ/RTC/I/O passed; exercising watchdog reset\n"); + rs_mcu_watchdog_cookie = UINT32_C(0x57375037); if ((rs_watchdog_configure(&watchdog_config) != RS_OK) || (rs_watchdog_start(RS_TIMEOUT_DEFAULT) != RS_OK)) { rs_mcu_finish(false, 9U); diff --git a/app/apps/ci_smoke/tiny.mk b/app/apps/ci_smoke/tiny.mk index 240ff06f..4478e0e2 100644 --- a/app/apps/ci_smoke/tiny.mk +++ b/app/apps/ci_smoke/tiny.mk @@ -1,4 +1,7 @@ APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/tiny.c APP_CFLAGS += -Os APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/mcu_acceptance.S -APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/tiny_r2_p6.c \ No newline at end of file +APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/tiny_r2_p6.c +ifeq ($(TINY_P7_ACCEPTANCE),YES) +APP_SRCS := $(ROOT_PATH)/app/apps/ci_smoke/tiny_r2_p7.c +endif \ No newline at end of file diff --git a/app/apps/ci_smoke/tiny_r2_p7.c b/app/apps/ci_smoke/tiny_r2_p7.c new file mode 100644 index 00000000..edc785c2 --- /dev/null +++ b/app/apps/ci_smoke/tiny_r2_p7.c @@ -0,0 +1,171 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Short P7 firmware: execute in real main SRAM and use real SYSCTRL/APB + * crossings. The full peripheral firmware remains a separate regression. */ +static volatile uint32_t rs_p7_words[128]; +static _Alignas(64) uint32_t rs_p7_source[1024]; +static _Alignas(64) uint32_t rs_p7_destination[1024]; +static volatile uint32_t rs_p7_retained __attribute__((section(".noinit.rs_p7"))); + +static bool rs_p7_peripheral_rate(uint32_t pclk_hz) { + const rs_timer_config_t config = {.mode = RS_TIMER_MODE_FREE_RUNNING, + .direction = RS_TIMER_DIRECTION_UP}; + const volatile uint32_t *pwm_rate = + (const volatile uint32_t *)(RS_SOC_APB4_PWM_BASE + PWM_CLOCK_HZ_OFFSET); + uint32_t first, last; + uint64_t start, end; + uint64_t expected; + rs_timeout_t budget = RS_TIMEOUT_DEFAULT; + if (*pwm_rate != pclk_hz || rs_timer_configure(RS_TIMER_0, &config) != RS_OK || + rs_timer_start(RS_TIMER_0) != RS_OK || rs_clint_get_time(&start) != RS_OK || + rs_timer_get_value(RS_TIMER_0, &first) != RS_OK) { + return false; + } + do { + if (rs_clint_get_time(&end) != RS_OK || budget-- == 0U) { + return false; + } + } while (end - start < 200U); + if (rs_timer_get_value(RS_TIMER_0, &last) != RS_OK || rs_timer_stop(RS_TIMER_0) != RS_OK) { + return false; + } + expected = (end - start) * (pclk_hz / 1000000U); + /* Independent CLINT reference; allow the bounded APB/software read skew. + * A wrong /2 or /4 domain cannot fit this +/-25% interval. */ + return (uint64_t)(last - first) > expected * 3U / 4U && + (uint64_t)(last - first) < expected * 5U / 4U; +} + +static bool rs_p7_sleep(void) { + rs_dma_session_t session; + rs_dma_config_t config = {.kind = RS_DMA_KIND_MM_TO_MM, + .request = RS_DMA_REQUEST_SOFTWARE, + .source = (uintptr_t)rs_p7_source, + .destination = (uintptr_t)rs_p7_destination, + .byte_count = sizeof(rs_p7_source), + .width = RS_DMA_WIDTH_32, + .source_increment = true, + .destination_increment = true, + .burst_beats = 16U}; + uint64_t now; + uint32_t index; + __disable_irq(); + for (index = 0U; index < 1024U; index++) { + rs_p7_source[index] = index ^ UINT32_C(0x13572468); + rs_p7_destination[index] = 0U; + } + if (rs_tiny_rcu_apply_gate(1U, RS_TIMEOUT_DEFAULT) != RS_OK || + rs_dma_session_acquire(&session, RS_DMA_CHANNEL_BULK) != RS_OK || + rs_dma_session_configure(&session, &config) != RS_OK || rs_clint_get_time(&now) != RS_OK || + rs_clint_set_compare(0U, now + 3000U) != RS_OK || rs_dma_session_start(&session) != RS_OK) { + return false; + } + __RV_CSR_SET(CSR_MIE, MIE_MTIE); + __WFI(); + __RV_CSR_CLEAR(CSR_MIE, MIE_MTIE); + if (rs_clint_set_compare(0U, UINT64_MAX) != RS_OK || + rs_dma_wait(RS_DMA_CHANNEL_BULK, RS_TIMEOUT_DEFAULT) != RS_OK || + rs_dma_session_release(&session) != RS_OK || + rs_tiny_rcu_apply_gate(0U, RS_TIMEOUT_DEFAULT) != RS_OK) { + return false; + } + for (index = 0U; index < 1024U; index++) { + if (rs_p7_destination[index] != rs_p7_source[index]) { + return false; + } + } + return true; +} + +static bool rs_p7_profile(rs_tiny_rcu_profile_t profile, uint32_t sys_hz, uint32_t mem_hz, + uint32_t pclk_hz) { + rs_tiny_rcu_snapshot_t snapshot; + uint32_t index; + uint64_t before, after; + + if (rs_clint_get_time(&before) != RS_OK || + rs_tiny_rcu_request_profile(profile, RS_TIMEOUT_DEFAULT) != RS_OK || + rs_clint_get_time(&after) != RS_OK || after <= before) { + return false; + } + if (rs_tiny_rcu_get_snapshot(&snapshot) != RS_OK || snapshot.current_profile != profile || + snapshot.sys_hz != sys_hz || snapshot.mem_hz != mem_hz || snapshot.pclk_hz != pclk_hz) { + return false; + } + for (index = 0U; index < 128U; index++) { + if (rs_p7_words[index] != (UINT32_C(0xABCDEF00) ^ index)) { + return false; + } + } + return rs_p7_peripheral_rate(pclk_hz); +} + +int main(void) { + rs_tiny_rcu_snapshot_t snapshot; + uint32_t index; + uint8_t code = 0U; + + if (rs_tiny_rcu_get_snapshot(&snapshot) == RS_OK && (snapshot.reset_cause & 2U) != 0U) { + code = (rs_p7_retained == UINT32_C(0x7037CAFE) && snapshot.current_profile == 0U && + snapshot.sys_hz == 24000000U && snapshot.gate_status == 0U) + ? 0U + : 11U; + rs_sysctrl_write_test_status(code == 0U, code); + for (;;) { + } + } + + for (index = 0U; index < 128U; index++) { + rs_p7_words[index] = UINT32_C(0xABCDEF00) ^ index; + } + if (rs_tiny_rcu_get_snapshot(&snapshot) != RS_OK || snapshot.sys_hz != 24000000U || + snapshot.target_capability != UINT32_C(0x3FFF)) { + code = 1U; + } else if ((snapshot.capability & UINT32_C(0xC)) == UINT32_C(0xC)) { + if (!rs_p7_profile(RS_TINY_RCU_PROFILE_PLL192, 192000000U, 96000000U, 48000000U)) { + code = 2U; + } else if (!rs_p7_profile(RS_TINY_RCU_PROFILE_PLL240, 240000000U, 120000000U, 60000000U)) { + code = 3U; + } + } else if (!rs_p7_profile(RS_TINY_RCU_PROFILE_XIN, snapshot.xin_hz, snapshot.xin_hz, + snapshot.xin_hz > 60000000U ? snapshot.xin_hz / 2U + : snapshot.xin_hz)) { + code = 4U; + } else { + /* No-PLL compatibility has no PLL profile bit. */ + } + if (code == 0U && !rs_p7_profile(RS_TINY_RCU_PROFILE_SAFE24, 24000000U, 24000000U, 24000000U)) { + code = 5U; + } + if (code == 0U && rs_tiny_rcu_apply_gate(UINT32_C(1) << 4U, RS_TIMEOUT_DEFAULT) != RS_OK) { + code = 6U; + } + if (code == 0U && rs_tiny_rcu_apply_gate(0U, RS_TIMEOUT_DEFAULT) != RS_OK) { + code = 7U; + } + if (code == 0U && rs_tiny_rcu_apply_reset(UINT32_C(1) << 4U, RS_TIMEOUT_DEFAULT) != RS_OK) { + code = 8U; + } + if (code == 0U && !rs_p7_sleep()) { + code = 9U; + } + if (code == 0U) { + rs_p7_retained = UINT32_C(0x7037CAFE); + (void)rs_tiny_rcu_system_reset(RS_TIMEOUT_DEFAULT); + code = 10U; + } + rs_sysctrl_write_test_status(code == 0U, code); + for (;;) { + } +} diff --git a/configs/ci/ihp130-tiny-xin48.mk b/configs/ci/ihp130-tiny-xin48.mk new file mode 100644 index 00000000..ce972c70 --- /dev/null +++ b/configs/ci/ihp130-tiny-xin48.mk @@ -0,0 +1,19 @@ +# Functional external-input profile; physical qualification is pending. +SOC := TINY +MINI_MODE := NONE +PDK := IHP130 +SIMU := VERILATOR +HAVE_PLL := NO +HAVE_SRAM_IF := YES +HAVE_SRAM_MACRO := YES +SRAM_SIZE_KIB := 128 +HAVE_HP := NO +HAVE_SVA := NO +EXT_CLK_HZ := 48000000 +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 \ No newline at end of file diff --git a/configs/ci/ihp130-tiny-xin96.mk b/configs/ci/ihp130-tiny-xin96.mk new file mode 100644 index 00000000..1e84419b --- /dev/null +++ b/configs/ci/ihp130-tiny-xin96.mk @@ -0,0 +1,19 @@ +# Functional external-input profile; physical qualification is pending. +SOC := TINY +MINI_MODE := NONE +PDK := IHP130 +SIMU := VERILATOR +HAVE_PLL := NO +HAVE_SRAM_IF := YES +HAVE_SRAM_MACRO := YES +SRAM_SIZE_KIB := 128 +HAVE_HP := NO +HAVE_SVA := NO +EXT_CLK_HZ := 96000000 +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 \ No newline at end of file diff --git a/crt/include/retrosoc/core/status.h b/crt/include/retrosoc/core/status.h index 5cee6728..52f43034 100644 --- a/crt/include/retrosoc/core/status.h +++ b/crt/include/retrosoc/core/status.h @@ -12,6 +12,7 @@ typedef enum { RS_EIO = -5, RS_ENOTSUP = -6, RS_EFORMAT = -7, + RS_EBUSY = -8, } rs_status_t; typedef uint32_t rs_timeout_t; diff --git a/crt/include/retrosoc/hal/rcu.h b/crt/include/retrosoc/hal/rcu.h new file mode 100644 index 00000000..0965c017 --- /dev/null +++ b/crt/include/retrosoc/hal/rcu.h @@ -0,0 +1,42 @@ +#ifndef RETROSOC_HAL_RCU_H +#define RETROSOC_HAL_RCU_H + +#include +#include + +#include + +typedef enum { + RS_TINY_RCU_PROFILE_SAFE24 = 0, + RS_TINY_RCU_PROFILE_XIN = 1, + RS_TINY_RCU_PROFILE_PLL192 = 2, + RS_TINY_RCU_PROFILE_PLL240 = 3, +} rs_tiny_rcu_profile_t; + +typedef struct { + uint32_t capability; + uint32_t target_capability; + uint32_t status; + uint32_t fault; + uint32_t reset_cause; + uint32_t gate_status; + uint32_t xin_hz; + uint32_t ref_hz; + uint32_t sys_hz; + uint32_t mem_hz; + uint32_t pclk_hz; + uint32_t clint_hz; + rs_tiny_rcu_profile_t current_profile; +} rs_tiny_rcu_snapshot_t; + +rs_status_t rs_tiny_rcu_get_snapshot(rs_tiny_rcu_snapshot_t *snapshot); +rs_status_t rs_tiny_rcu_profile_hz(rs_tiny_rcu_profile_t profile, uint32_t *hz); +rs_status_t rs_tiny_rcu_request_profile(rs_tiny_rcu_profile_t profile, rs_timeout_t timeout); +rs_status_t rs_tiny_rcu_apply_gate(uint32_t target_mask, rs_timeout_t timeout); +rs_status_t rs_tiny_rcu_apply_reset(uint32_t target_mask, rs_timeout_t timeout); +rs_status_t rs_tiny_rcu_clear_fault(uint32_t mask); +rs_status_t rs_tiny_rcu_clear_reset_cause(uint32_t mask); +rs_status_t rs_tiny_rcu_system_reset(rs_timeout_t timeout); +rs_status_t rs_tiny_rcu_set_clkout(uint32_t source, uint16_t half_period); + +#endif diff --git a/crt/include/retrosoc/hal/rcu_regs.h b/crt/include/retrosoc/hal/rcu_regs.h new file mode 100644 index 00000000..f2dfc126 --- /dev/null +++ b/crt/include/retrosoc/hal/rcu_regs.h @@ -0,0 +1,46 @@ +#ifndef RETROSOC_HAL_RCU_REGS_H +#define RETROSOC_HAL_RCU_REGS_H + +#include + +#define RS_TINY_RCU_ID_OFFSET UINT32_C(0x100) +#define RS_TINY_RCU_VERSION_OFFSET UINT32_C(0x104) +#define RS_TINY_RCU_CAPABILITY_OFFSET UINT32_C(0x108) +#define RS_TINY_RCU_CLOCK_REQUEST_OFFSET UINT32_C(0x10C) +#define RS_TINY_RCU_CLOCK_CURRENT_OFFSET UINT32_C(0x110) +#define RS_TINY_RCU_COMMAND_OFFSET UINT32_C(0x114) +#define RS_TINY_RCU_STATUS_OFFSET UINT32_C(0x118) +#define RS_TINY_RCU_TIMEOUT_OFFSET UINT32_C(0x11C) +#define RS_TINY_RCU_GATE_REQUEST_OFFSET UINT32_C(0x120) +#define RS_TINY_RCU_GATE_STATUS_OFFSET UINT32_C(0x124) +#define RS_TINY_RCU_RESET_REQUEST_OFFSET UINT32_C(0x128) +#define RS_TINY_RCU_RESET_DONE_OFFSET UINT32_C(0x12C) +#define RS_TINY_RCU_RESET_CAUSE_OFFSET UINT32_C(0x130) +#define RS_TINY_RCU_FAULT_OFFSET UINT32_C(0x134) +#define RS_TINY_RCU_IRQ_STATE_OFFSET UINT32_C(0x138) +#define RS_TINY_RCU_IRQ_ENABLE_OFFSET UINT32_C(0x13C) +#define RS_TINY_RCU_XIN_HZ_OFFSET UINT32_C(0x140) +#define RS_TINY_RCU_REF_HZ_OFFSET UINT32_C(0x144) +#define RS_TINY_RCU_SYS_HZ_OFFSET UINT32_C(0x148) +#define RS_TINY_RCU_MEM_HZ_OFFSET UINT32_C(0x14C) +#define RS_TINY_RCU_PCLK_HZ_OFFSET UINT32_C(0x150) +#define RS_TINY_RCU_CLINT_HZ_OFFSET UINT32_C(0x154) +#define RS_TINY_RCU_TARGET_CAP_OFFSET UINT32_C(0x158) +#define RS_TINY_RCU_CLKOUT_OFFSET UINT32_C(0x15C) + +#define RS_TINY_RCU_COMMAND_CLOCK_APPLY UINT32_C(1U << 0) +#define RS_TINY_RCU_COMMAND_GATE_APPLY UINT32_C(1U << 1) +#define RS_TINY_RCU_COMMAND_RESET_APPLY UINT32_C(1U << 2) +#define RS_TINY_RCU_COMMAND_SYSTEM_RESET UINT32_C(1U << 3) +#define RS_TINY_RCU_STATUS_BUSY UINT32_C(1U << 0) +#define RS_TINY_RCU_STATUS_SAFE_SOURCE UINT32_C(1U << 1) +#define RS_TINY_RCU_STATUS_PLL_LOCKED UINT32_C(1U << 2) +#define RS_TINY_RCU_STATUS_PLL_SELECTED UINT32_C(1U << 3) +#define RS_TINY_RCU_STATUS_FAULT UINT32_C(1U << 4) +#define RS_TINY_RCU_CAP_PLL_PRESENT UINT32_C(1U << 8) +#define RS_TINY_RCU_CAP_TARGET_MASK UINT32_C(0x0003FFFF) +#define RS_TINY_RCU_FAULT_MASK UINT32_C(0x0000001F) +#define RS_TINY_RCU_RESET_CAUSE_MASK UINT32_C(0x0000003F) +#define RS_TINY_RCU_CSR_MSLEEP 0xBF0 + +#endif diff --git a/crt/src/hal/rcu.c b/crt/src/hal/rcu.c new file mode 100644 index 00000000..4dddf99f --- /dev/null +++ b/crt/src/hal/rcu.c @@ -0,0 +1,261 @@ +#include + +#include +#include +#include +#if defined(__riscv) && defined(RS_SOC_TINY) +#include +#endif + +static volatile uint32_t *rs_tiny_rcu_register(uint32_t offset) { +#if defined(RS_TINY_RCU_TEST_MMIO) + extern volatile uint32_t rs_tiny_rcu_test_mmio[1024]; + return &rs_tiny_rcu_test_mmio[offset / 4U]; +#else + return (volatile uint32_t *)(RS_SOC_APB4_SYSCTRL_BASE + (uintptr_t)offset); +#endif +} + +static rs_status_t rs_tiny_rcu_fault_status(void) { + const uint32_t fault = *rs_tiny_rcu_register(RS_TINY_RCU_FAULT_OFFSET); + if ((fault & UINT32_C(0x09)) != 0U) { + return RS_EIO; + } + return (fault != 0U) ? RS_ETIMEOUT : RS_OK; +} + +static rs_status_t rs_tiny_rcu_prepare(rs_timeout_t timeout) { + if (timeout == 0U) { + return RS_EINVAL; + } + if ((*rs_tiny_rcu_register(RS_TINY_RCU_STATUS_OFFSET) & RS_TINY_RCU_STATUS_BUSY) != 0U) { + return RS_EBUSY; + } + /* The caller owns clearing sticky faults. Do not start an operation whose + * result could be confused with a previous failure. */ + return rs_tiny_rcu_fault_status(); +} + +static rs_status_t rs_tiny_rcu_wait(rs_timeout_t timeout, uint32_t *status) { + uint32_t value = 0U; + + while (timeout-- != 0U) { + value = *rs_tiny_rcu_register(RS_TINY_RCU_STATUS_OFFSET); + if ((value & RS_TINY_RCU_STATUS_BUSY) == 0U) { + if (status != NULL) { + *status = value; + } + return RS_OK; + } + } + return RS_ETIMEOUT; +} + +rs_status_t rs_tiny_rcu_get_snapshot(rs_tiny_rcu_snapshot_t *snapshot) { + uint32_t initial_profile; + if (snapshot == NULL) { + return RS_EINVAL; + } + if ((*rs_tiny_rcu_register(RS_TINY_RCU_STATUS_OFFSET) & RS_TINY_RCU_STATUS_BUSY) != 0U) { + return RS_EBUSY; + } + initial_profile = *rs_tiny_rcu_register(RS_TINY_RCU_CLOCK_CURRENT_OFFSET); + snapshot->capability = *rs_tiny_rcu_register(RS_TINY_RCU_CAPABILITY_OFFSET); + snapshot->target_capability = *rs_tiny_rcu_register(RS_TINY_RCU_TARGET_CAP_OFFSET); + snapshot->status = *rs_tiny_rcu_register(RS_TINY_RCU_STATUS_OFFSET); + snapshot->fault = *rs_tiny_rcu_register(RS_TINY_RCU_FAULT_OFFSET); + snapshot->reset_cause = *rs_tiny_rcu_register(RS_TINY_RCU_RESET_CAUSE_OFFSET); + snapshot->gate_status = *rs_tiny_rcu_register(RS_TINY_RCU_GATE_STATUS_OFFSET); + snapshot->xin_hz = *rs_tiny_rcu_register(RS_TINY_RCU_XIN_HZ_OFFSET); + snapshot->ref_hz = *rs_tiny_rcu_register(RS_TINY_RCU_REF_HZ_OFFSET); + snapshot->sys_hz = *rs_tiny_rcu_register(RS_TINY_RCU_SYS_HZ_OFFSET); + snapshot->mem_hz = *rs_tiny_rcu_register(RS_TINY_RCU_MEM_HZ_OFFSET); + snapshot->pclk_hz = *rs_tiny_rcu_register(RS_TINY_RCU_PCLK_HZ_OFFSET); + snapshot->clint_hz = *rs_tiny_rcu_register(RS_TINY_RCU_CLINT_HZ_OFFSET); + snapshot->current_profile = + (rs_tiny_rcu_profile_t)(*rs_tiny_rcu_register(RS_TINY_RCU_CLOCK_CURRENT_OFFSET) & + UINT32_C(3)); + if (((*rs_tiny_rcu_register(RS_TINY_RCU_STATUS_OFFSET) & RS_TINY_RCU_STATUS_BUSY) != 0U) || + ((uint32_t)snapshot->current_profile != initial_profile)) { + return RS_EBUSY; + } + return RS_OK; +} + +rs_status_t rs_tiny_rcu_profile_hz(rs_tiny_rcu_profile_t profile, uint32_t *hz) { + if (hz == NULL) { + return RS_EINVAL; + } + switch (profile) { + case RS_TINY_RCU_PROFILE_SAFE24: + *hz = UINT32_C(24000000); + break; + case RS_TINY_RCU_PROFILE_XIN: + *hz = *rs_tiny_rcu_register(RS_TINY_RCU_XIN_HZ_OFFSET); + break; + case RS_TINY_RCU_PROFILE_PLL192: + *hz = UINT32_C(192000000); + break; + case RS_TINY_RCU_PROFILE_PLL240: + *hz = UINT32_C(240000000); + break; + default: + return RS_EINVAL; + } + return RS_OK; +} + +rs_status_t rs_tiny_rcu_request_profile(rs_tiny_rcu_profile_t profile, rs_timeout_t timeout) { + uint32_t capability; + uint32_t status; + uint32_t hz; + rs_status_t result; + + if (timeout == 0U || rs_tiny_rcu_profile_hz(profile, &hz) != RS_OK) { + return RS_EINVAL; + } + (void)hz; + capability = *rs_tiny_rcu_register(RS_TINY_RCU_CAPABILITY_OFFSET); + if ((capability & (UINT32_C(1) << (uint32_t)profile)) == 0U) { + return RS_ENOTSUP; + } + result = rs_tiny_rcu_prepare(timeout); + if (result != RS_OK) { + return result; + } + *rs_tiny_rcu_register(RS_TINY_RCU_CLOCK_REQUEST_OFFSET) = (uint32_t)profile; + *rs_tiny_rcu_register(RS_TINY_RCU_COMMAND_OFFSET) = RS_TINY_RCU_COMMAND_CLOCK_APPLY; + if (rs_tiny_rcu_wait(timeout, &status) != RS_OK) { + return RS_ETIMEOUT; + } + if ((status & RS_TINY_RCU_STATUS_FAULT) != 0U) { + return rs_tiny_rcu_fault_status(); + } + if (*rs_tiny_rcu_register(RS_TINY_RCU_CLOCK_CURRENT_OFFSET) != (uint32_t)profile) { + return RS_EIO; + } + return RS_OK; +} + +rs_status_t rs_tiny_rcu_apply_gate(uint32_t target_mask, rs_timeout_t timeout) { + uint32_t status; + rs_status_t result; + + if (timeout == 0U || (target_mask & ~RS_TINY_RCU_CAP_TARGET_MASK) != 0U) { + return RS_EINVAL; + } + if ((target_mask & ~(*rs_tiny_rcu_register(RS_TINY_RCU_TARGET_CAP_OFFSET))) != 0U) { + return RS_ENOTSUP; + } + result = rs_tiny_rcu_prepare(timeout); + if (result != RS_OK) { + return result; + } + *rs_tiny_rcu_register(RS_TINY_RCU_GATE_REQUEST_OFFSET) = target_mask; + *rs_tiny_rcu_register(RS_TINY_RCU_COMMAND_OFFSET) = RS_TINY_RCU_COMMAND_GATE_APPLY; + if (rs_tiny_rcu_wait(timeout, &status) != RS_OK) { + return RS_ETIMEOUT; + } + result = rs_tiny_rcu_fault_status(); + if (result != RS_OK) { + return result; + } +#if defined(__riscv) && defined(RS_SOC_TINY) + /* Hazard3's existing sleep permission complements the RCU leaf gate. + * Keep power-down clear: the fabric, SRAM and DMA remain available. */ + if ((target_mask & 1U) != 0U) { + __RV_CSR_CLEAR(RS_TINY_RCU_CSR_MSLEEP, 2U); + __RV_CSR_SET(RS_TINY_RCU_CSR_MSLEEP, 1U); + } else { + __RV_CSR_CLEAR(RS_TINY_RCU_CSR_MSLEEP, 1U); + } +#endif + return RS_OK; +} + +rs_status_t rs_tiny_rcu_apply_reset(uint32_t target_mask, rs_timeout_t timeout) { + uint32_t status; + rs_status_t result; + + if (timeout == 0U || target_mask == 0U || (target_mask & ~RS_TINY_RCU_CAP_TARGET_MASK) != 0U || + (target_mask & 1U) != 0U) { + return RS_EINVAL; + } + if ((target_mask & ~(*rs_tiny_rcu_register(RS_TINY_RCU_TARGET_CAP_OFFSET))) != 0U) { + return RS_ENOTSUP; + } + result = rs_tiny_rcu_prepare(timeout); + if (result != RS_OK) { + return result; + } + if ((*rs_tiny_rcu_register(RS_TINY_RCU_GATE_STATUS_OFFSET) & target_mask) != 0U) { + return RS_EBUSY; + } + *rs_tiny_rcu_register(RS_TINY_RCU_RESET_REQUEST_OFFSET) = target_mask; + *rs_tiny_rcu_register(RS_TINY_RCU_COMMAND_OFFSET) = RS_TINY_RCU_COMMAND_RESET_APPLY; + if (rs_tiny_rcu_wait(timeout, &status) != RS_OK) { + return RS_ETIMEOUT; + } + return rs_tiny_rcu_fault_status(); +} + +rs_status_t rs_tiny_rcu_clear_fault(uint32_t mask) { + if ((mask & ~RS_TINY_RCU_FAULT_MASK) != 0U) { + return RS_EINVAL; + } + *rs_tiny_rcu_register(RS_TINY_RCU_FAULT_OFFSET) = mask; + return RS_OK; +} + +rs_status_t rs_tiny_rcu_clear_reset_cause(uint32_t mask) { + if ((mask & ~RS_TINY_RCU_RESET_CAUSE_MASK) != 0U) { + return RS_EINVAL; + } + *rs_tiny_rcu_register(RS_TINY_RCU_RESET_CAUSE_OFFSET) = mask; + return RS_OK; +} + +rs_status_t rs_tiny_rcu_system_reset(rs_timeout_t timeout) { + uint32_t status; + if (timeout == 0U) { + return RS_EINVAL; + } + status = *rs_tiny_rcu_register(RS_TINY_RCU_STATUS_OFFSET); + if ((status & RS_TINY_RCU_STATUS_BUSY) != 0U) { + return RS_EBUSY; + } + *rs_tiny_rcu_register(RS_TINY_RCU_COMMAND_OFFSET) = RS_TINY_RCU_COMMAND_SYSTEM_RESET; + /* Success restarts execution at the reset vector. Returning means failure. */ + while (timeout-- != 0U) { + status = *rs_tiny_rcu_register(RS_TINY_RCU_STATUS_OFFSET); + if ((status & RS_TINY_RCU_STATUS_FAULT) != 0U) { + return RS_EIO; + } + } + return RS_ETIMEOUT; +} + +rs_status_t rs_tiny_rcu_set_clkout(uint32_t source, uint16_t half_period) { + rs_tiny_rcu_snapshot_t snapshot; + rs_status_t result; + uint32_t hz; + if (source > 4U || (source != 0U && half_period == 0U)) { + return RS_EINVAL; + } + result = rs_tiny_rcu_get_snapshot(&snapshot); + if (result != RS_OK) { + return result; + } + hz = source == 2U ? snapshot.sys_hz + : source == 3U ? snapshot.mem_hz + : source == 4U ? snapshot.pclk_hz + : snapshot.ref_hz; + if (source != 0U && (uint64_t)hz > (UINT64_C(48000000) * (uint64_t)half_period)) { + return RS_EINVAL; + } + if (source != 0U && (*rs_tiny_rcu_register(RS_TINY_RCU_CLKOUT_OFFSET) & 7U) != 0U) { + return RS_EBUSY; + } + *rs_tiny_rcu_register(RS_TINY_RCU_CLKOUT_OFFSET) = source | ((uint32_t)half_period << 8U); + return (*rs_tiny_rcu_register(RS_TINY_RCU_FAULT_OFFSET) != 0U) ? RS_EIO : RS_OK; +} diff --git a/physical/commercial/config/products/tiny.mk b/physical/commercial/config/products/tiny.mk index 7c478d5d..598e6a60 100644 --- a/physical/commercial/config/products/tiny.mk +++ b/physical/commercial/config/products/tiny.mk @@ -16,9 +16,9 @@ TOP := retrosoc_tiny_asic # Canonical clock domains, mirrored by tcl/common/products/tiny.tcl and # validated against rtl/tiny/integration/clock_reset_domains.json when the -# commercial timing contract is generated. Tiny boots SAFE24: the PLL_TOP -# macro is present in the netlist but parked off and is not a clock source. -COMMERCIAL_CLOCK_DOMAINS := system jtag audio +# commercial timing contract is generated. This adapter analyzes the SAFE24 +# boot scenario, explicitly constraining RCU mux selections and PLL enable. +COMMERCIAL_CLOCK_DOMAINS := aon system memory peripheral jtag audio # Qualified-I/O interface groups and the budget variables each group requires. COMMERCIAL_IO_INTERFACES := JTAG XPI ASYNC @@ -38,7 +38,7 @@ COMMERCIAL_IO_BUDGET_VARIABLES := \ # Tiny has no bidirectional pad-mode selection requirement. PRODUCT_REQUIRES_IO_MODE_HOOK := NO -# The Tiny PLL_TOP macro is parked (EN=0) and has no characterized timing +# In this SAFE24 scenario PLL_TOP is parked (EN=0) and has no characterized timing # arcs; doctor skips the qualified PLL-mode checks for this product. See # docs/ip/tiny-soc.md (TINY-055) for the qualification boundary. PRODUCT_PLL_MODE := parked diff --git a/physical/commercial/tcl/common/products/tiny.tcl b/physical/commercial/tcl/common/products/tiny.tcl index b4658361..7fbd90dd 100644 --- a/physical/commercial/tcl/common/products/tiny.tcl +++ b/physical/commercial/tcl/common/products/tiny.tcl @@ -11,15 +11,14 @@ # retroSoC Tiny (retrosoc_tiny_asic) product overlay. # # Clock intent: the canonical contract (rtl/tiny/integration/ -# clock_reset_domains.json) owns system (SAFE24 external clock), jtag and -# the independent GPIO17 AUDIO reference. The ICS55 PLL_TOP macro is present but parked off (EN=0) and is -# not a clock source; it carries no characterized timing arcs yet, so no PLL -# overlay clock is created here. See docs/ip/tiny-soc.md (TINY-055). +# clock_reset_domains.json) owns REF24, derived SYS/MEM/PCLK, JTAG and GPIO17 +# AUDIO. This adapter covers SAFE24 boot only. RCU can enable the physical PLL +# in functional operation, but its active timing remains unqualified. namespace eval product {} proc product::expected_clock_domains {} { - return {system jtag audio} + return {aon system memory peripheral jtag audio} } proc product::custom_clock_domains {} { @@ -27,7 +26,7 @@ proc product::custom_clock_domains {} { } proc product::clock_names {} { - return {clk_system clk_jtag clk_audio} + return {clk_aon clk_system clk_memory clk_peripheral clk_jtag clk_audio clk_sys_div2 clk_sys_div4 clk_clkout_source} } proc product::io_interface_groups {} { @@ -35,6 +34,31 @@ proc product::io_interface_groups {} { } proc product::apply_clock_overlays {} { + foreach divisor {2 4} { + create_generated_clock -name clk_sys_div$divisor \ + -source [flow::domain_object system] -divide_by $divisor \ + [flow::required_net_driver_pin "Tiny SYS /$divisor" \ + "u_soc/u_clock_backend/s_div${divisor}_clk"] + flow::register_clock clk_sys_div$divisor core + } + foreach mux {u_sys_mux u_mem_mux u_pclk_div2_mux u_pclk_mux} { + set_case_analysis 0 [flow::required_pins "Tiny SAFE24 mux $mux" \ + "u_soc/u_clock_backend/$mux/select_i"] + foreach {side enabled} {0 1 1 0} { + set_case_analysis $enabled [flow::required_net_driver_pin "Tiny settled clock gate" \ + "u_soc/u_clock_backend/$mux/r_enable$side"] + } + } + set_case_analysis 0 [flow::required_pins "Tiny SAFE24 PLL enable" \ + "u_soc/u_clock_backend/u_pll/gen_ecos/u_backend/u_PLL_TOP/EN"] + foreach bit {1 2} { + set_case_analysis 0 [flow::required_net_driver_pin "Tiny boot CLKOUT source" \ + "u_soc/u_clock_output/s_cfg_q\[$bit\]"] + } + create_generated_clock -name clk_clkout_source -source [flow::domain_object aon] \ + -divide_by 1 [flow::required_net_driver_pin "Tiny boot CLKOUT source clock" \ + "u_soc/u_clock_output/s_src_clk"] + flow::register_clock clk_clkout_source core } proc product::apply_io_constraints {} { diff --git a/physical/smoke/sta/opensta/generate_sdc.py b/physical/smoke/sta/opensta/generate_sdc.py index b7b1eb42..329e17ea 100644 --- a/physical/smoke/sta/opensta/generate_sdc.py +++ b/physical/smoke/sta/opensta/generate_sdc.py @@ -119,9 +119,12 @@ def render(domains: list[dict[str, Any]], reset_ports: list[str]) -> str: if source_domain is None: lines.append(f"create_clock -name clk_{name} -period {period} ${pin_variable}") else: + divide_by = sta.get("divide_by", 1) + if not isinstance(divide_by, int) or isinstance(divide_by, bool) or divide_by < 1: + raise ValueError("generated clock division must be a positive integer") lines.append( f"create_generated_clock -name clk_{name} -source ${pin_variables[source_domain]} " - f"-divide_by 1 ${pin_variable}" + f"-divide_by {divide_by} ${pin_variable}" ) lines.append("") @@ -175,10 +178,52 @@ def main() -> int: parser.add_argument("--domains", type=Path, required=True) parser.add_argument("--pin-map", type=Path, required=True) parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--xin-hz", type=int, default=24000000) args = parser.parse_args() try: domains, reset_ports = parse_document(args.domains, args.pin_map) - atomic_write(args.output, render(domains, reset_ports)) + document = json.loads(args.domains.read_text()) + p7 = document.get("clock_contract") == "TINY-R2-P7" + if p7 and args.xin_hz not in (24000000, 48000000, 96000000): + raise ValueError("unsupported Tiny external clock") + if p7 and args.xin_hz != 24000000: + aon = next(domain for domain in domains if domain["name"] == "aon") + external = {**aon, "name": "external", "sta": {**aon["sta"], + "period_ns": 1e9 / args.xin_hz}} + aon["sta"] = {"source_domain": "external", "period_ns": 1e9 / 24000000, + "divide_by": args.xin_hz // 24000000, "async_group": "core", + "pin": "u_soc.u_clock_backend.gen_ref_divided.u_ref_div/clk_o"} + domains.insert(0, external) + if p7: + # These sources run even while the muxes select their undivided + # inputs. Their handover synchronizers still require real clocks. + for divisor in (2, 4): + domains.append({"name": f"sys_div{divisor}", "sta": { + "source_domain": "system", "period_ns": 1e9 * divisor / 24000000, + "divide_by": divisor, "async_group": "core", "object_type": "net_driver", + "net": f"u_soc.u_clock_backend.s_div{divisor}_clk"}}) + domains.append({"name": "clkout_source", "sta": { + "source_domain": "aon", "period_ns": 1e9 / 24000000, + "divide_by": 1, "async_group": "core", "object_type": "net_driver", + "net": "u_soc.u_clock_output.s_src_clk"}}) + result = render(domains, reset_ports) + if p7: + result += "\n# P7 SAFE24 boot scenario; this does not qualify active PLL timing.\n" + for mux in ("u_sys_mux", "u_mem_mux", "u_pclk_div2_mux", "u_pclk_mux"): + result += (f"set_case_analysis 0 [require_pins {{P7 SAFE24 selection}} " + f"{{u_soc.u_clock_backend.{mux}/select_i}}]\n") + # select_i passes through sequential handshake state. A static + # mode must also select its settled gate state; setting only + # the request cannot prune the other live clock in STA. + for side, enabled in ((0, 1), (1, 0)): + result += (f"set_case_analysis {enabled} [require_net_driver_pin " + f"{{P7 SAFE24 settled gate}} " + f"{{u_soc.u_clock_backend.{mux}/r_enable{side}}}]\n") + result += "# CLKOUT retains its reset REF24 source in this boot scenario.\n" + for bit in (1, 2): + result += (f"set_case_analysis 0 [require_net_driver_pin {{P7 CLKOUT source}} " + f"{{u_soc.u_clock_output.s_cfg_q_{bit}_}}]\n") + atomic_write(args.output, result) except (OSError, ValueError, json.JSONDecodeError) as error: parser.error(str(error)) return 0 diff --git a/physical/smoke/sta/opensta/opensta.mk b/physical/smoke/sta/opensta/opensta.mk index e6d89433..9182bddc 100644 --- a/physical/smoke/sta/opensta/opensta.mk +++ b/physical/smoke/sta/opensta/opensta.mk @@ -16,14 +16,14 @@ include $(ROOT_PATH)/physical/smoke/sta/opensta/pdk_timing.mk $(OPENSTA_SDC): $(OPENSTA_SDC_GEN) $(OPENSTA_DOMAIN_MAP) $(OPENSTA_PIN_MAP) | manifest @mkdir -p $(dir $@) - python3 $(OPENSTA_SDC_GEN) --domains $(OPENSTA_DOMAIN_MAP) --pin-map $(OPENSTA_PIN_MAP) --output $@ + python3 $(OPENSTA_SDC_GEN) --domains $(OPENSTA_DOMAIN_MAP) --pin-map $(OPENSTA_PIN_MAP) --output $@ --xin-hz $(EXT_CLK_HZ) 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 \ +ifeq ($(SOC),TINY) + python3 $(ROOT_PATH)/scripts/tiny_r2_p7.py --variant-root $(VARIANT_ROOT) verify-netlist \ --netlist $(OPENSTA_NETLIST) --config $(OPENSTA_CONFIG) \ - --output $(SYN_BUILD_ROOT)/tiny-ics55-netlist.json + --output $(SYN_BUILD_ROOT)/tiny-r2-p7-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; }; \ @@ -45,7 +45,7 @@ endif --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_TINY_ICS55=$(TINY_SAFE24_PLL_OFF) -- \ + --env OPENSTA_METRICS=$(OPENSTA_METRICS) --env OPENSTA_TINY_ICS55=$(TINY_SAFE24_PLL_OFF) --env OPENSTA_TINY_P7=$(TINY_R2_P7) -- \ $(OPENSTA) -no_init -exit -threads $(OPENSTA_THREADS) $(ROOT_PATH)/physical/smoke/sta/opensta/opensta.tcl .PHONY: sta \ No newline at end of file diff --git a/physical/smoke/sta/opensta/opensta.tcl b/physical/smoke/sta/opensta/opensta.tcl index a8e84504..baa5d755 100644 --- a/physical/smoke/sta/opensta/opensta.tcl +++ b/physical/smoke/sta/opensta/opensta.tcl @@ -21,7 +21,11 @@ if {[info exists ::env(OPENSTA_TOP)] && $::env(OPENSTA_TOP) ne ""} { link_design $top_design read_sdc $::env(OPENSTA_SDC) if {$top_design eq "retrosoc_tiny_asic"} { - source [file join [file dirname [info script]] tiny_r2_p4_checks.tcl] + if {[info exists ::env(OPENSTA_TINY_P7)] && $::env(OPENSTA_TINY_P7) eq "YES"} { + source [file join [file dirname [info script]] tiny_r2_p7_checks.tcl] + } else { + source [file join [file dirname [info script]] tiny_r2_p4_checks.tcl] + } } if {[info exists ::env(OPENSTA_TINY_ICS55)] && $::env(OPENSTA_TINY_ICS55) eq "YES"} { source [file join [file dirname [info script]] tiny_ics55_checks.tcl] diff --git a/physical/smoke/sta/opensta/tiny_r2_p7_checks.tcl b/physical/smoke/sta/opensta/tiny_r2_p7_checks.tcl new file mode 100644 index 00000000..ada232fc --- /dev/null +++ b/physical/smoke/sta/opensta/tiny_r2_p7_checks.tcl @@ -0,0 +1,22 @@ +# P7 uses a dedicated, provenance-checked Python macro/clock audit. Historical +# P1/P4 checks retain their direct-SYS/parked-PLL assumptions unchanged. +set p7_dir [file dirname $::env(OPENSTA_REPORT)] +foreach clock {clk_aon clk_system clk_memory clk_peripheral clk_audio clk_jtag clk_sys_div2 clk_sys_div4 clk_clkout_source} { + if {[llength [get_clocks -quiet $clock]] != 1} { error "P7 clock missing: $clock" } +} +set p7_pll [get_cells -hierarchical -quiet -filter {ref_name == PLL_TOP}] +if {[llength $p7_pll] > 1} { error "P7 contains multiple PLL macros" } +if {[llength $p7_pll] == 1} { + # The analysis is explicitly the cold SAFE24 boot operating state. + set p7_enable [get_pins -quiet "[get_full_name [lindex $p7_pll 0]]/EN"] + if {[llength $p7_enable] != 1} { error "P7 PLL enable pin missing" } + set_case_analysis 0 $p7_enable +} +report_clock_properties > $p7_dir/clocks.rpt +check_setup -verbose > $p7_dir/coverage.rpt +report_check_types -recovery -removal -min_pulse_width -min_period -max_slew -max_capacitance -max_fanout -violators -verbose > $p7_dir/constraint-violations.rpt +foreach domain {system memory peripheral aon} { + report_checks -path_group clk_$domain -path_delay max -group_path_count 20 -fields {capacitance slew fanout} > $p7_dir/$domain-setup.rpt + report_checks -path_group clk_$domain -path_delay min -group_path_count 20 -fields {capacitance slew fanout} > $p7_dir/$domain-hold.rpt +} +puts "TINY_R2_P7_STA_OBSERVATION: SAFE24 boot only; not active PLL, routed or PVT qualification" diff --git a/physical/smoke/syn/yosys/script/synth.tcl b/physical/smoke/syn/yosys/script/synth.tcl index 568ca6d9..8f6c9b8a 100644 --- a/physical/smoke/syn/yosys/script/synth.tcl +++ b/physical/smoke/syn/yosys/script/synth.tcl @@ -165,7 +165,7 @@ 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"} { +if {$soc == "TINY"} { yosys write_json ${netlist}.json } diff --git a/physical/smoke/syn/yosys/yosys.mk b/physical/smoke/syn/yosys/yosys.mk index c1f49bc3..304fc307 100644 --- a/physical/smoke/syn/yosys/yosys.mk +++ b/physical/smoke/syn/yosys/yosys.mk @@ -59,7 +59,7 @@ gen_synth_filelist: $(SV_FLIST) ## Synthesize netlist using Yosys synth: $(NETLIST) | manifest -$(NETLIST): $(SV_FLIST) $(YOSYS_SCRIPTS) +$(NETLIST): $(SV_FLIST) $(YOSYS_SCRIPTS) | manifest @mkdir -p $(YOSYS_OUT) @mkdir -p $(YOSYS_TMP) @mkdir -p $(YOSYS_RPT) @@ -74,9 +74,9 @@ $(NETLIST): $(SV_FLIST) $(YOSYS_SCRIPTS) --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 +ifeq ($(SOC),TINY) + python3 $(ROOT_PATH)/scripts/tiny_r2_p7.py --variant-root $(VARIANT_ROOT) audit-netlist \ + --netlist $(NETLIST) --config $(NETLIST_CONFIG) --output $(YOSYS_BUILD)/tiny-r2-p7-netlist.json endif $(NETLIST_DEBUG): $(NETLIST) diff --git a/publications/datasheets/system-reference.json b/publications/datasheets/system-reference.json index daf01a88..e69bd544 100644 --- a/publications/datasheets/system-reference.json +++ b/publications/datasheets/system-reference.json @@ -4500,7 +4500,8 @@ "RS_ETIMEOUT": -4, "RS_EIO": -5, "RS_ENOTSUP": -6, - "RS_EFORMAT": -7 + "RS_EFORMAT": -7, + "RS_EBUSY": -8 }, "meanings": { "RS_OK": "Operation completed under that API contract.", @@ -4510,7 +4511,8 @@ "RS_ETIMEOUT": "Bounded operation timed out.", "RS_EIO": "I/O operation failed.", "RS_ENOTSUP": "Operation unsupported.", - "RS_EFORMAT": "Input format rejected." + "RS_EFORMAT": "Input format rejected.", + "RS_EBUSY": "Resource or lifecycle operation is busy; retry after the owning operation completes." }, "observation": "Signed API return value. Exact triggering conditions depend on the called function; the enum definition does not make every code reachable from every API.", "handling": "Check the return before using output values. No hardware W1C applies to a C return value; preserve the relevant peripheral status if diagnosis requires it.", diff --git a/rtl/ip/core/mgmt_debug_wrapper.sv b/rtl/ip/core/mgmt_debug_wrapper.sv index 91ba858f..6c4faa68 100644 --- a/rtl/ip/core/mgmt_debug_wrapper.sv +++ b/rtl/ip/core/mgmt_debug_wrapper.sv @@ -11,11 +11,13 @@ // 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 int ResetSyncStages = 3 + parameter logic [31:0] JtagIdcode = 32'hDEAD_BEEF, + parameter int ResetSyncStages = 3, + parameter bit SeparateSystemReset = 1'b0 ) ( input logic clk_i, input logic rst_n_i, + input logic system_rst_n_i, input logic bridge_idle_i, input logic jtag_tck_i, input logic jtag_tms_i, @@ -156,7 +158,7 @@ module mgmt_debug_wrapper #( .ResetSyncStages(ResetSyncStages) ) u_mgmt_debug_reset ( .clk_i (clk_i), - .rst_n_i (rst_n_i), + .rst_n_i (SeparateSystemReset ? system_rst_n_i : rst_n_i), .reset_req_i (s_sys_reset_req || s_hart_reset_req), .bridge_idle_i (bridge_idle_i), .core_rst_n_o (core_rst_n_o), diff --git a/rtl/ip/peripheral/apb4_dma.sv b/rtl/ip/peripheral/apb4_dma.sv index 9cc49bdd..b5b5a68b 100644 --- a/rtl/ip/peripheral/apb4_dma.sv +++ b/rtl/ip/peripheral/apb4_dma.sv @@ -18,6 +18,7 @@ module apb4_dma #( parameter bit EnableStreams = 1'b1 ) ( // verilog_format: off -- integration ports retain established peripheral endpoint names. + output logic lifecycle_idle_o, input logic clk_i, input logic rst_n_i, output logic dma_xfer_done_o, @@ -179,4 +180,7 @@ module apb4_dma #( .crypto_in_axis (crypto_in_axis), .crypto_out_axis (crypto_out_axis) ); + logic s_bus_busy; + assign s_bus_busy = axi4.awvalid || axi4.arvalid || axi4.wvalid; + assign lifecycle_idle_o = !(|s_busy) && !(|s_start) && !s_bus_busy; endmodule diff --git a/rtl/ip/peripheral/apb4_timer.sv b/rtl/ip/peripheral/apb4_timer.sv index 5db235bd..6fca13af 100644 --- a/rtl/ip/peripheral/apb4_timer.sv +++ b/rtl/ip/peripheral/apb4_timer.sv @@ -10,6 +10,7 @@ module apb4_timer ( // verilog_format: off -- preserve reviewed column alignment + output logic lifecycle_idle_o, input logic clk_i, input logic rst_n_i, input logic debug_halted_i, @@ -89,4 +90,5 @@ module apb4_timer ( .one_shot_done_o (s_one_shot_done) ); + assign lifecycle_idle_o = !s_en; endmodule diff --git a/rtl/ip/security/apb4_crypto.sv b/rtl/ip/security/apb4_crypto.sv index f766b5a8..8580dbcc 100644 --- a/rtl/ip/security/apb4_crypto.sv +++ b/rtl/ip/security/apb4_crypto.sv @@ -7,6 +7,7 @@ module apb4_crypto #( parameter logic [5:0] DropResponseMask = 6'd0, parameter bit DuplicateResponse = 1'b0 ) ( + output logic lifecycle_idle_o, input logic clk_i, rst_n_i, output logic dma_input_proc_o, @@ -43,9 +44,13 @@ module apb4_crypto #( logic [11:0] s_offset; logic [31:0] s_wdata; logic [ 3:0] s_strb; - assign s_offset = s_regs_q.offset; - assign s_wdata = s_regs_q.data; - assign s_strb = s_regs_q.mask; + logic s_transport_busy; + assign s_transport_busy = (|s_fifo_busy) || (|s_engine_pending_q) || s_aes_key_busy; + assign lifecycle_idle_o = !(|s_busy) && !s_maintenance_busy && !s_transport_busy; + + assign s_offset = s_regs_q.offset; + assign s_wdata = s_regs_q.data; + assign s_strb = s_regs_q.mask; crypto_mem_req_t [5:0] s_engine_req, s_maintenance_req, s_store_req; crypto_mem_resp_t [5:0] s_store_resp, s_engine_resp, s_maintenance_resp; logic [ 6:0] s_mem_stat; diff --git a/rtl/ip/serial/apb4_i2c.sv b/rtl/ip/serial/apb4_i2c.sv index ee367af9..26372198 100644 --- a/rtl/ip/serial/apb4_i2c.sv +++ b/rtl/ip/serial/apb4_i2c.sv @@ -13,6 +13,7 @@ module apb4_i2c #( parameter int RxFifoDepth = 16 ) ( // verilog_format: off -- preserve reviewed column alignment + output logic lifecycle_idle_o, input logic clk_i, input logic rst_n_i, output logic dma_tx_stall_o, @@ -66,10 +67,12 @@ module apb4_i2c #( logic s_recovery_failed_event; logic s_core_cmd_flush; - assign i2c.scl_o = 1'b0; - assign i2c.sda_o = 1'b0; - assign i2c.scl_oe_o = s_scl_pull_low; - assign i2c.sda_oe_o = s_sda_pull_low; + assign lifecycle_idle_o = !s_en && !s_busy && !s_recovery_active; + + assign i2c.scl_o = 1'b0; + assign i2c.sda_o = 1'b0; + assign i2c.scl_oe_o = s_scl_pull_low; + assign i2c.sda_oe_o = s_sda_pull_low; i2c_filter u_i2c_filter ( .clk_i (clk_i), diff --git a/rtl/ip/serial/apb4_i2s.sv b/rtl/ip/serial/apb4_i2s.sv index be1a54f8..969de72a 100644 --- a/rtl/ip/serial/apb4_i2s.sv +++ b/rtl/ip/serial/apb4_i2s.sv @@ -41,6 +41,8 @@ module apb4_i2s ( output logic tx_underrun_evt_o, output logic rx_overrun_evt_o, output logic rx_flush_busy_o, + output logic lifecycle_idle_o, + output logic lifecycle_reset_ready_o, apb4_if.slave apb4, axi4_stream_if.sink tx_axis, axi4_stream_if.source rx_axis, @@ -120,6 +122,36 @@ module apb4_i2s ( logic s_tx_underrun_sys; logic s_rx_overrun_sys; logic s_tx_flush_sent_d, s_tx_flush_sent_q; + logic s_cfg_ready; + logic [31:0] s_cfg_sent_d, s_cfg_sent_q; + logic s_audio_used_d, s_audio_used_q; + + // A disabled host which has never enabled its PHY may change host clock + // even when AUDIO is absent. After use, require the disabled configuration + // to reach AUDIO, empty FIFOs, and completed flush acknowledgements. + assign s_cfg_sent_d = s_cfg_ready ? s_cfg_sys : s_cfg_sent_q; + assign s_audio_used_d = s_audio_used_q || s_cfg_sys[`APB4_I2S__CFG_ENABLE]; + assign lifecycle_reset_ready_o = s_tx_src_ready && !s_tx_flush_busy && !s_rx_flush_busy; + assign lifecycle_idle_o = !s_cfg_sys[`APB4_I2S__CFG_ENABLE] && + !s_stream_tx_en && !s_stream_rx_en && + (!s_audio_used_q || ((s_cfg_sent_q == s_cfg_sys) && s_cfg_ready && + s_tx_level == 0 && s_rx_level == 0 && !s_tx_flush_busy && !s_rx_flush_busy)); + dffr #( + .DATA_WIDTH(32) + ) u_cfg_sent ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_cfg_sent_d), + .dat_o (s_cfg_sent_q) + ); + dffr #( + .DATA_WIDTH(1) + ) u_audio_used ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_audio_used_d), + .dat_o (s_audio_used_q) + ); assign s_cmd_sys = {s_cmd_rx_flush, s_cmd_tx_flush}; assign s_tx_src_clear = s_cmd_tx_flush && !s_tx_flush_sent_q && !s_tx_src_clear_busy; @@ -191,7 +223,7 @@ module apb4_i2s ( .src_rst_n_i(rst_n_i), .src_data_i (s_cfg_sys), .src_valid_i(1'b1), - .src_ready_o(), + .src_ready_o(s_cfg_ready), .dst_clk_i (clk_aud_i), .dst_rst_n_i(rst_aud_n_i), .dst_data_o (s_cfg_aud), diff --git a/rtl/ip/serial/apb4_uart.sv b/rtl/ip/serial/apb4_uart.sv index a3ab46cb..f29f25e2 100644 --- a/rtl/ip/serial/apb4_uart.sv +++ b/rtl/ip/serial/apb4_uart.sv @@ -13,6 +13,7 @@ module apb4_uart #( parameter int RxFifoDepth = 64 ) ( // verilog_format: off -- preserve reviewed column alignment + output logic lifecycle_idle_o, input logic clk_i, input logic rst_n_i, output logic dma_tx_stall_o, @@ -53,8 +54,10 @@ module apb4_uart #( logic s_tx_flow_blocked; logic s_cts_change; - assign uart.tx_o = s_tx; - assign uart.irq_o = s_irq; + assign lifecycle_idle_o = !s_tx_busy && !s_rx_active && !s_tx_data_valid && !s_rx_en && !s_break; + + assign uart.tx_o = s_tx; + assign uart.irq_o = s_irq; uart_reg #( .TxFifoDepth(TxFifoDepth), diff --git a/rtl/ip/serial/apb4_ws2812.sv b/rtl/ip/serial/apb4_ws2812.sv index e0a365d7..0b117927 100644 --- a/rtl/ip/serial/apb4_ws2812.sv +++ b/rtl/ip/serial/apb4_ws2812.sv @@ -12,6 +12,7 @@ module apb4_ws2812 #( parameter int TxFifoDepth = 16 ) ( // verilog_format: off -- preserve reviewed column alignment + output logic lifecycle_idle_o, input logic clk_i, input logic rst_n_i, apb4_if.slave apb4, @@ -33,8 +34,10 @@ module apb4_ws2812 #( logic s_done, s_underflow, s_aborted; logic s_irq, s_dat; - assign ws2812.dat_o = s_dat; - assign ws2812.irq_o = s_irq; + assign lifecycle_idle_o = !s_busy && !s_reset_active && !s_start && !s_data_valid; + + assign ws2812.dat_o = s_dat; + assign ws2812.irq_o = s_irq; ws2812_reg #( .TxFifoDepth(TxFifoDepth) diff --git a/rtl/ip/storage/apb4_sdio.sv b/rtl/ip/storage/apb4_sdio.sv index fbb3237e..fc872bb6 100644 --- a/rtl/ip/storage/apb4_sdio.sv +++ b/rtl/ip/storage/apb4_sdio.sv @@ -11,17 +11,20 @@ `timescale 1ns / 1ps module apb4_sdio #( - parameter int InputClockHz = 72_000_000, - parameter int AddrWidth = 32, - parameter int DataWidth = 32, - parameter int DescCount = 16 + parameter int InputClockHz = 72_000_000, + parameter bit UseRuntimeClockHz = 1'b0, + parameter int AddrWidth = 32, + parameter int DataWidth = 32, + parameter int DescCount = 16 ) ( // verilog_format: off -- preserve the APB, AXI, and pad column alignment + output logic lifecycle_idle_o, input logic clk_i, input logic rst_n_i, apb4_if.slave apb4, axi4_if.master dma_axi4, - sdio_if.dut sdio + sdio_if.dut sdio, + input logic [31:0] clock_hz_i // verilog_format: on ); logic s_busy; @@ -80,6 +83,10 @@ module apb4_sdio #( logic s_pio_read_consume; sdio_if u_sdio_core_if (); + logic s_bus_busy; + assign s_bus_busy = dma_axi4.awvalid || dma_axi4.arvalid || dma_axi4.wvalid; + assign lifecycle_idle_o = !s_busy && !s_clock_enable && !s_bus_busy; + assign u_sdio_core_if.cmd_di_i = sdio.cmd_di_i; assign u_sdio_core_if.dat_di_i = sdio.dat_di_i; assign sdio.sck_o = u_sdio_core_if.sck_o; @@ -149,14 +156,16 @@ module apb4_sdio #( ); sdio_core #( - .InputClockHz(InputClockHz), - .AddrWidth (AddrWidth), - .DataWidth (DataWidth), - .DescCount (DescCount) + .InputClockHz (InputClockHz), + .UseRuntimeClockHz(UseRuntimeClockHz), + .AddrWidth (AddrWidth), + .DataWidth (DataWidth), + .DescCount (DescCount) ) u_sdio_core ( .clk_i (clk_i), .rst_n_i (rst_n_i), .host_enable_i (s_host_enable), + .clock_hz_i (clock_hz_i), .host_irq_enable_i (s_host_irq_enable), .clock_enable_i (s_clock_enable), .half_period_i (s_half_period), diff --git a/rtl/ip/storage/apb4_xpi.sv b/rtl/ip/storage/apb4_xpi.sv index b07e4d01..14320df7 100644 --- a/rtl/ip/storage/apb4_xpi.sv +++ b/rtl/ip/storage/apb4_xpi.sv @@ -20,6 +20,7 @@ module apb4_xpi ( // verilog_format: off -- preserve reviewed port alignment + output logic lifecycle_idle_o, input logic clk_i, input logic rst_n_i, input logic dma_xfer_done_i, @@ -176,6 +177,11 @@ module apb4_xpi ( logic s_unused_dma_xfer_done; logic s_unused_mm_perf_split; + logic s_protocol_idle; + assign s_protocol_idle = !s_mm_busy && !s_indirect_busy && !s_core_busy; + assign lifecycle_idle_o = s_protocol_idle && + (s_owner_q == OwnerNone) && s_tx_fifo_empty && s_rx_fifo_empty; + assign s_owner_q = xpi_owner_e'(s_owner_bits_q); assign s_selected_req_valid = s_mm_busy ? s_mm_req_valid : (s_indirect_req_valid ? s_indirect_req_valid : s_mm_req_valid); diff --git a/rtl/ip/storage/sdio_core.sv b/rtl/ip/storage/sdio_core.sv index 2e4b9059..833832d4 100644 --- a/rtl/ip/storage/sdio_core.sv +++ b/rtl/ip/storage/sdio_core.sv @@ -11,15 +11,17 @@ `timescale 1ns / 1ps module sdio_core #( - parameter int InputClockHz = 72_000_000, - parameter int AddrWidth = 32, - parameter int DataWidth = 32, - parameter int DescCount = 16 + parameter int InputClockHz = 72_000_000, + parameter bit UseRuntimeClockHz = 1'b0, + parameter int AddrWidth = 32, + parameter int DataWidth = 32, + parameter int DescCount = 16 ) ( // verilog_format: off -- preserve the host, status, and external bus groups input logic clk_i, input logic rst_n_i, input logic host_enable_i, + input logic [31:0] clock_hz_i, input logic host_irq_enable_i, input logic clock_enable_i, input logic [15:0] half_period_i, @@ -236,7 +238,9 @@ module sdio_core #( assign bytes_done_o = s_dma_bytes_done; assign dma_error_addr_o = s_dma_err_addr; assign dma_error_o = {24'd0, s_dma_err_code}; - assign clock_actual_o = (half_period_i == 16'd0) ? 32'd0 : InputClockHz / (2 * half_period_i); + logic [31:0] s_input_clock_hz; + assign s_input_clock_hz = UseRuntimeClockHz && (clock_hz_i != 32'd0) ? clock_hz_i : InputClockHz; + assign clock_actual_o = (half_period_i == 16'd0) ? 32'd0 : s_input_clock_hz / (2 * half_period_i); assign status_o = { 23'd0, s_clock_running, diff --git a/rtl/mk/software.mk b/rtl/mk/software.mk index 70a877d7..741dc220 100644 --- a/rtl/mk/software.mk +++ b/rtl/mk/software.mk @@ -120,6 +120,7 @@ CRT_SRCS := $(ROOT_PATH)/crt/arch/riscv/startup.S \ $(ROOT_PATH)/crt/src/hal/clint.c \ $(ROOT_PATH)/crt/src/hal/clock.c \ $(ROOT_PATH)/crt/src/hal/sysctrl.c \ + $(ROOT_PATH)/crt/src/hal/rcu.c \ $(ROOT_PATH)/crt/src/hal/uart_math.c \ $(ROOT_PATH)/crt/src/hal/uart.c \ $(ROOT_PATH)/crt/src/hal/gpio_math.c \ @@ -180,7 +181,7 @@ TINY_CRT_NAMES := startup.S clzsi2.c divdi3.c ffssi2.c udivdi3.c umoddi3.c \ stdlib.c string.c console.c printf.c archinfo.c clint.c uart_math.c uart.c \ gpio_math.c gpio.c timer_math.c timer.c pwm.c rtc.c wdg.c watchdog.c i2c_math.c i2c.c \ dma_math.c dma.c onchip_sram.c xpi.c rng.c crc.c ws2812_math.c ws2812.c \ - crypto.c crypto_lifecycle.c i2s_math.c i2s.c sdio_math.c sdio.c + crypto.c crypto_lifecycle.c i2s_math.c i2s.c sdio_math.c sdio.c rcu.c CRT_SRCS := $(foreach source,$(CRT_SRCS),$(if $(filter $(notdir $(source)),$(TINY_CRT_NAMES)),$(source))) CRT_SRCS += $(ROOT_PATH)/crt/src/hal/sysctrl.c endif diff --git a/rtl/rtl_style_audit.json b/rtl/rtl_style_audit.json index d41e1a1b..41edcd49 100644 --- a/rtl/rtl_style_audit.json +++ b/rtl/rtl_style_audit.json @@ -441,12 +441,22 @@ "tiny_cpu_mem.sv", "tiny_cpu_wrapper.sv", "tiny_fault_collector.sv", + "tiny_clock_reset_subsystem.sv", + "tiny_axi4_async_bridge.sv", + "tiny_apb_async_bridge.sv", + "tiny_axi4_admission.sv", + "tiny_managed_lifecycle.sv", + "tiny_peripheral_leaf.sv", + "tiny_peripheral_control.sv", + "tiny_clock_output.sv", + "tiny_clock_gate.sv", "tiny_reset_tree.sv", "tiny_sram.sv", "tiny_sram_axi4.sv", "tiny_sram_group.sv", "tiny_sram_port_if.sv", - "tiny_sysctrl.sv" + "tiny_sysctrl.sv", + "tiny_rcu_regs.svh" ] }, "reviewed_boundaries": [ diff --git a/rtl/tech/tc_pll.sv b/rtl/tech/tc_pll.sv index 434178e4..81eb3830 100644 --- a/rtl/tech/tc_pll.sv +++ b/rtl/tech/tc_pll.sv @@ -8,6 +8,7 @@ // MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. +`timescale 1ns / 1ps module tc_pll #( parameter bit UseIcs55EcosPll = 1'b0, parameter bit Ics55Parked = 1'b0 @@ -28,10 +29,10 @@ module tc_pll #( logic [2:0] s_lock_count_d, s_lock_count_q; logic s_lock_d, s_lock_q; logic s_apply_seen_d, s_apply_seen_q; - logic s_cfg_apply; - logic s_force_lock_fail; - time s_last_ref_edge; - time s_ref_period; + logic s_cfg_apply; + logic s_force_lock_fail; + realtime s_last_ref_edge; + realtime s_ref_period; function automatic int pll_multiplier(input logic [2:0] cfg_sel); case (cfg_sel) @@ -55,14 +56,14 @@ module tc_pll #( always @(posedge fref_i) begin if (s_last_ref_edge != 0) begin - s_ref_period = $time - s_last_ref_edge; + s_ref_period = $realtime - s_last_ref_edge; end - s_last_ref_edge = $time; + s_last_ref_edge = $realtime; end always begin @(posedge fref_i); - wait (pll_lock_o); + wait (pll_lock_o && s_ref_period > 0.0); while (pll_lock_o) begin #(s_ref_period / (2 * pll_multiplier(s_cfg_sel_q))); if (pll_lock_o) begin diff --git a/rtl/tiny/dv/retrosoc_tiny_tb.sv b/rtl/tiny/dv/retrosoc_tiny_tb.sv index 9e32acab..1cef5b6f 100644 --- a/rtl/tiny/dv/retrosoc_tiny_tb.sv +++ b/rtl/tiny/dv/retrosoc_tiny_tb.sv @@ -6,7 +6,10 @@ module retrosoc_tiny_tb; logic s_clk_driver = 1'b0; wire s_clk = s_clk_driver; - logic s_reset_driver = 1'b0; + // Assert RESET_N after time zero. With a divided REF24 there are no REF + // edges while reset is held, so a declaration initialized low is not an + // observable asynchronous assertion for every four-state simulator process. + logic s_reset_driver = $test$plusargs("p7_legacy_reset_level") ? 1'b0 : 1'b1; wire s_rst_n = s_reset_driver; wire s_uart0_tx, s_uart1_tx; wire s_uart0_rx = 1'b1; @@ -26,6 +29,7 @@ module retrosoc_tiny_tb; integer s_cycles; `ifndef RETROSOC_SOC__TINY_NETLIST `include "tiny_p6_devices.svh" + `include "tiny_p7_observers.svh" `endif integer s_max_cycles = 20000000; integer s_done_cycles = 0; @@ -47,8 +51,12 @@ module retrosoc_tiny_tb; wire [7:0] s_test_code = u_dut.s_test_code; `endif - always #20.833333 s_clk_driver = !s_clk_driver; + always #(500000000.0 / `SOC_EXT_CLK_HZ) s_clk_driver = !s_clk_driver; initial begin + if (!$test$plusargs("p7_legacy_reset_level")) begin + #1; + s_reset_driver = 1'b0; + end if ($value$plusargs("max_cycles=%d", s_max_cycles)) begin end repeat (20) @(posedge s_clk); @@ -57,7 +65,8 @@ module retrosoc_tiny_tb; for (s_cycles = 0; s_cycles < s_max_cycles; s_cycles = s_cycles + 1) begin @(posedge s_clk); if (u_dut.s_test_done) begin - if (!u_dut.s_test_pass) $fatal(1, "SIM_TEST_FAIL code=%0d", s_test_code); + if (u_dut.s_test_pass !== 1'b1 || (^s_test_code) === 1'bx) + $fatal(1, "SIM_TEST_FAIL code=%0d", s_test_code); s_done_cycles = s_done_cycles + 1; if (s_done_cycles >= 128 && s_jtag_checks_done) begin $display("SIM_TEST_PASS Tiny cycles=%0d", s_cycles); diff --git a/rtl/tiny/dv/tiny_p6_devices.svh b/rtl/tiny/dv/tiny_p6_devices.svh index 123de212..460bee1e 100644 --- a/rtl/tiny/dv/tiny_p6_devices.svh +++ b/rtl/tiny/dv/tiny_p6_devices.svh @@ -1,6 +1,6 @@ // Verification-only external devices on the Gen1 GPIO routes. No DUT state is driven. logic s_p6_io_trace_started = 0; -always @(negedge s_clk) begin +always @(negedge u_dut.u_soc.s_i2c0_clk) begin if ($test$plusargs("p6_debug_io")) begin if (u_dut.u_soc.u_i2c0.s_cmd_valid && !s_p6_io_trace_started) begin s_p6_io_trace_started = 1; @@ -62,7 +62,7 @@ initial begin end integer s_p6_frames = 0, s_p6_completed = 0, s_p6_underflows = 0, s_p6_aborts = 0; longint unsigned s_p6_sdio_overlap = 0, s_p6_overlap_fetches = 0; -always @(posedge s_clk) begin +always @(posedge u_dut.u_soc.s_exec_clk) begin if (u_dut.u_soc.u_dma.s_busy[2] && u_dut.u_soc.u_sdio.s_dma_status[0]) begin s_p6_sdio_overlap++; if (u_dut.u_soc.u_cpu_ahbl_if[0].hready && u_dut.u_soc.u_cpu_ahbl_if[0].htrans[1]) @@ -124,7 +124,7 @@ logic s_p6_expected_high; logic s_p6_audio_reference = 1'b0; logic s_p6_audio_running = 1'b0; initial begin - repeat (200) @(negedge s_clk); + repeat (200) @(negedge u_dut.u_soc.s_ref_clk); if (!u_dut.u_soc.s_cpu_ready_rst_n) $fatal(1, "SIM_TEST_FAIL CPU boot waited for absent AUDIO clock"); s_p6_audio_running = 1'b1; @@ -135,12 +135,12 @@ always @(posedge s_gpio[27]) begin if (s_p6_frames == 4 && s_p6_clock_stops < 2) begin wait (s_p6_audio_reference == (s_p6_clock_stops != 0)); s_p6_audio_running = 0; - repeat (4800) @(negedge s_clk); + repeat (4800) @(negedge u_dut.u_soc.s_ref_clk); s_p6_clock_stops++; s_p6_audio_running = 1; end end -always @(negedge s_clk) begin +always @(negedge u_dut.u_soc.s_ws2812_clk) begin if (`_P6_WS.s_busy && !s_p6_busy_previous) begin s_p6_frames++; s_p6_word = 0; @@ -218,8 +218,11 @@ for (genvar owner = 0; owner < 3; owner++) begin : gen_p6_axi_observer logic pending_read = 0, pending_write = 0, descriptor = 0; logic [2:0] read_size = 0; `define _P6_AXI u_dut.u_soc.u_masters_axi4_if[owner] - always @(posedge s_clk) begin - if (s_rst_n) begin + always @(posedge u_dut.u_soc.s_exec_clk) begin + if (!u_dut.u_soc.s_fabric_rst_n) begin + pending_read = 0; + pending_write = 0; + end else begin if (`_P6_AXI.arvalid && `_P6_AXI.arready) begin if (pending_read) $fatal(1, "SIM_TEST_FAIL P6 duplicate read admission"); pending_read = 1; @@ -297,7 +300,7 @@ assign s_gpio_2 = u_dut.u_soc.u_sdio_if.dat_oe_o[0] ? 1'bz : s_p6_card_data[0]; assign s_gpio_3 = u_dut.u_soc.u_sdio_if.dat_oe_o[1] ? 1'bz : s_p6_card_data[1]; assign s_gpio_4 = u_dut.u_soc.u_sdio_if.dat_oe_o[2] ? 1'bz : s_p6_card_data[2]; assign s_gpio_5 = u_dut.u_soc.u_sdio_if.dat_oe_o[3] ? 1'bz : s_p6_card_data[3]; -always @(negedge s_clk) begin +always @(negedge u_dut.u_soc.s_sdio_clk) begin // Reuse the standalone card's explicit data-stimulus API. The wire command // fixes direction/address/length; host DATA_START only schedules admission. // This is repository controller integration evidence, not card compliance. diff --git a/rtl/tiny/dv/tiny_p7_observers.svh b/rtl/tiny/dv/tiny_p7_observers.svh new file mode 100644 index 00000000..9c04a5dc --- /dev/null +++ b/rtl/tiny/dv/tiny_p7_observers.svh @@ -0,0 +1,65 @@ +// P7-only assertions over production paths. No DUT state is written. +logic s_p7_monitor = 0; +longint unsigned s_p7_gated_cycles = 0, s_p7_dma_gated_responses = 0; +integer s_p7_warm_resets = 0; +logic [3:0] s_p7_profiles = 0; +initial s_p7_monitor = $test$plusargs("p7_monitor"); +initial begin + if ($test$plusargs("p7_monitor") || $test$plusargs("p7_cold_reset_probe")) begin + wait (s_rst_n === 1'b0); + @(posedge s_rst_n); + wait (u_dut.u_soc.s_cpu_boot_ready === 1'b1); + repeat (2) @(negedge u_dut.u_soc.s_ref_clk); + // Case comparisons require the exact known reset values in both simulators. + if (u_dut.u_soc.u_sysctrl.s_reset_cause_q !== 32'd1 || + u_dut.u_soc.u_sysctrl.s_rcu_fault_q !== 32'd0 || + u_dut.u_soc.s_backend_current !== 2'd0 || u_dut.u_soc.s_gate_request !== 14'd0) + $fatal( + 1, + "SIM_TEST_FAIL P7 cold reset state cause=%h fault=%h profile=%h gates=%h", + u_dut.u_soc.u_sysctrl.s_reset_cause_q, + u_dut.u_soc.u_sysctrl.s_rcu_fault_q, + u_dut.u_soc.s_backend_current, + u_dut.u_soc.s_gate_request + ); + if ($test$plusargs("p7_cold_reset_probe")) begin + $display("P7_COLD_RESET_PASS"); + $finish; // Diagnostic only; never emits the full Tiny acceptance marker. + end + end +end +always @(posedge u_dut.u_soc.s_exec_clk) begin + if (s_p7_monitor && u_dut.u_soc.s_actual_gate[0]) begin + s_p7_gated_cycles++; + if (u_dut.u_soc.u_masters_axi4_if[1].bvalid && u_dut.u_soc.u_masters_axi4_if[1].bready) + s_p7_dma_gated_responses++; + end +end +always @(posedge u_dut.u_soc.s_ref_clk) begin + if (s_p7_monitor && u_dut.u_soc.s_clock_done) begin + s_p7_profiles[u_dut.u_soc.s_backend_current] = 1; + $display("P7_CLOCK_COMMIT profile=%0d sys=%0d mem=%0d pclk=%0d", u_dut.u_soc.s_backend_current, + u_dut.u_soc.s_backend_sys_hz, u_dut.u_soc.s_backend_mem_hz, + u_dut.u_soc.s_backend_pclk_hz); + $fflush(); + end + if (s_p7_monitor && u_dut.u_soc.s_system_reset) begin + s_p7_warm_resets++; + $display("P7_SOFTWARE_RESET request=%0d", s_p7_warm_resets); + $fflush(); + end +end +final begin + if (s_p7_monitor && u_dut.s_test_done && u_dut.s_test_pass) begin + if (s_p7_gated_cycles == 0 || s_p7_dma_gated_responses == 0 || s_p7_warm_resets != 1) + $fatal(1, "SIM_TEST_FAIL P7 WFI/DMA/software-reset coverage"); +`ifdef HAVE_PLL + if ((s_p7_profiles & 4'b1101) != 4'b1101) $fatal(1, "SIM_TEST_FAIL P7 PLL profile coverage"); +`else + if ((s_p7_profiles & 4'b0011) != 4'b0011) + $fatal(1, "SIM_TEST_FAIL P7 external profile coverage"); +`endif + $display("P7_LIFECYCLE_PASS gated_cycles=%0d dma_responses_while_gated=%0d warm_resets=%0d", + s_p7_gated_cycles, s_p7_dma_gated_responses, s_p7_warm_resets); + end +end diff --git a/rtl/tiny/filelist/commonip.fl b/rtl/tiny/filelist/commonip.fl index d603e2c4..f11c72ee 100644 --- a/rtl/tiny/filelist/commonip.fl +++ b/rtl/tiny/filelist/commonip.fl @@ -6,6 +6,7 @@ /clusterip/common/rtl/interface/axi4_stream_if.sv /clusterip/common/rtl/interface/ram_if.sv /clusterip/common/rtl/utils/register.sv +/clusterip/common/rtl/utils/xchecker.sv /clusterip/common/rtl/utils/spill_register.sv /clusterip/common/rtl/stream/round_robin_arbiter.sv /clusterip/common/rtl/utils/lfsr.sv @@ -18,5 +19,7 @@ /clusterip/common/rtl/cdc/cdc_2phase.sv /clusterip/common/rtl/clkrst/rst_sync.sv /clusterip/common/rtl/clkrst/counter.sv +/clusterip/common/rtl/clock/safe_clock_mux.sv +/clusterip/common/rtl/clock/clock_divider.sv /ip/util/soc_common_fifo.sv /ip/util/soc_common_cdc.sv diff --git a/rtl/tiny/filelist/top.fl b/rtl/tiny/filelist/top.fl index 5ea33070..288d99ff 100644 --- a/rtl/tiny/filelist/top.fl +++ b/rtl/tiny/filelist/top.fl @@ -5,6 +5,15 @@ /top/tiny_sysctrl.sv /top/tiny_archinfo.sv /top/tiny_reset_tree.sv +/top/tiny_clock_reset_subsystem.sv +/top/tiny_axi4_async_bridge.sv +/top/tiny_apb_async_bridge.sv +/top/tiny_axi4_admission.sv +/top/tiny_managed_lifecycle.sv +/top/tiny_peripheral_leaf.sv +/top/tiny_peripheral_control.sv +/top/tiny_clock_output.sv +/top/tiny_clock_gate.sv /top/tiny_sram_port_if.sv /top/tiny_sram_group.sv /top/tiny_sram_axi4.sv diff --git a/rtl/tiny/integration/clock_reset_domains.json b/rtl/tiny/integration/clock_reset_domains.json index af67d80d..ffc83066 100644 --- a/rtl/tiny/integration/clock_reset_domains.json +++ b/rtl/tiny/integration/clock_reset_domains.json @@ -1,14 +1,14 @@ { "schema_version": 2, "external_fabric": { - "phase": "TINY-R2-P6", + "phase": "TINY-R2-P7", "clock_domain": "system", "target_controllers": "u_soc.u_fabric.gen_target[%d].u_controller", "target_count": 27, - "apb_bridges": "u_soc.u__bridge; corresponding existing peripheral reset leaf", + "apb_bridges": "SYS frontends; acknowledged per-endpoint crossings to AON, PCLK and MEM", "fabric_reset": "fabric leaf; target reset blocks admission and aborts accepted target work with DECERR", "hart_reset": "CPU-only accepted-work drain; DMA and all target responses stay active", - "runtime_peripheral_reset": "not exposed; quiescence-controlled RCU remains P7" + "runtime_peripheral_reset": "RCU blocks admissions, drains accepted work, holds reset for five local edges, waits for local release and bus barriers" }, "local_memory": { "phase": "TINY-R2-P4", @@ -22,32 +22,69 @@ "main_sram_payload_reset": "none", "local_uncontended_max_cycles": 3 }, - "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" ], "domains": [ + { + "name": "aon", + "clock": "s_ref_clk", + "reset": "ref_rst_n_o", + "reset_source": "rst_n_i", + "reset_primitive": "rst_sync", + "path": "rtl/tiny/top/tiny_clock_reset_subsystem.sv", + "instance": "u_ref_rst", + "sta": { + "source_port": "extclk_i_pad", + "pin": "u_clock_buffer/clk_o", + "period_ns": 41.666666667, + "async_group": "core" + } + }, { "name": "system", - "clock": "clk_i", + "clock": "s_backend_sys_clk", "reset": "system_rst_n_o", "reset_source": "s_system_req_n", "reset_primitive": "rst_sync", "path": "rtl/tiny/top/tiny_reset_tree.sv", "instance": "u_system_rst_sync", "sta": { - "source_port": "extclk_i_pad", - "pin": "u_clock_buffer/clk_o", + "source_domain": "aon", + "pin": "u_soc.u_clock_backend.u_sys_mux/clk_o", "period_ns": 41.666666667, - "async_group": "system" + "async_group": "core" + } + }, + { + "name": "memory", + "clock": "s_backend_mem_clk", + "reset": "s_backend_mem_rst_n", + "reset_source": "rst_n_i", + "reset_primitive": "rst_sync", + "path": "rtl/tiny/top/tiny_clock_reset_subsystem.sv", + "instance": "u_mem_rst", + "sta": { + "source_domain": "system", + "pin": "u_soc.u_clock_backend.u_mem_mux/clk_o", + "period_ns": 41.666666667, + "async_group": "core" + } + }, + { + "name": "peripheral", + "clock": "s_backend_pclk", + "reset": "pclk_rst_n_o", + "reset_source": "rst_n_i", + "reset_primitive": "rst_sync", + "path": "rtl/tiny/top/tiny_clock_reset_subsystem.sv", + "instance": "u_pclk_rst", + "sta": { + "source_domain": "system", + "pin": "u_soc.u_clock_backend.u_pclk_mux/clk_o", + "period_ns": 41.666666667, + "async_group": "core" } }, { @@ -85,7 +122,7 @@ "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", + "system_source": "por_rst_n_o && !watchdog_reset_req_i && backend_reset_n_i", "leaf_instance_pattern": "u_soc.u_reset_tree.gen_leaf[%d].u_leaf_rst_sync", "leaf_names": [ "fabric", @@ -110,16 +147,103 @@ "i2s", "sdio" ], - "cpu_prerequisite": "all leaf resets released", + "cpu_prerequisite": "all global leaf resets and actual APB/DMA/XPI/RCU barriers; boot readiness latches until the next system reset", "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", + "watchdog_functional_reset": "REF24 five-edge release after backend retains the watchdog reset event; PCLK APB uses the wdg leaf", "main_sram_payload_reset": "none", "crypto": "six private macro banks; reset invalidates then scrubs/readbacks; CPU does not wait for READY", - "clock_domain": "system" + "clock_domain": "domain-specific", + "peripheral": "tiny_peripheral_leaf; five running hold edges and five release edges; I2S additionally requires actual audio-side barrier readiness" }, "crossings": [ + { + "name": "debug_pclk_status", + "source": "system", "destination": "peripheral", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_debug_pclk_sync" + }, + { + "name": "debug_aon_status", + "source": "system", "destination": "aon", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_debug_ref_sync" + }, + { + "name": "rtc_wake", + "source": "aon", "destination": "system", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_rtc_wake_sync" + }, + { + "name": "hart_reset_event", + "source": "system", "destination": "aon", "primitive": "cdc_2phase", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_hart_event_cdc" + }, + { + "name": "peripheral_interrupts", + "source": "peripheral", "destination": "system", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_irq_sync", + "notes": "sticky interrupt levels; this vector also contains AON/MEM and synchronous SYS bits" + }, + { + "name": "wfi_permission", + "source": "aon", "destination": "system", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_wfi_gate_sync" + }, + { + "name": "admission_block_system", + "source": "aon", "destination": "system", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_block_sys" + }, + { + "name": "admission_block_dma", + "source": "aon", "destination": "peripheral", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_block_pclk" + }, + { + "name": "gate_reset_disabled", + "source": "aon", "destination": "system", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_disabled_sync" + }, + { + "name": "peripheral_idle", + "source": "peripheral", "destination": "aon", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_periph_idle_sync" + }, + { + "name": "xpi_idle", + "source": "memory", "destination": "aon", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_xpi_idle_sync" + }, + { + "name": "dma_ready", + "source": "peripheral", "destination": "system", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_dma_ready_sync" + }, + { + "name": "dma_retirement", + "source": "peripheral", "destination": "system", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_dma_idle_sync" + }, + { + "name": "fabric_retirement", + "source": "system", "destination": "aon", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_bus_idle_ref" + }, + { + "name": "gate_acknowledgements", + "source": "peripheral", "destination": "aon", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_gate_feedback" + }, + { + "name": "reset_hold_acknowledgements", + "source": "peripheral", "destination": "aon", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_held_feedback" + }, + { + "name": "reset_release_acknowledgements", + "source": "system", "destination": "aon", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_ready_feedback" + }, { "name": "debug_dmi", "source": "jtag", @@ -128,6 +252,17 @@ "path": "rtl/managed/hazard3/hdl/debug/dtm/hazard3_jtag_dtm_core.v", "instance": "inst_hazard3_apb_async_bridge" }, + { + "name": "xpi_mem_axi", + "source": "system", + "destination": "memory", + "primitive": "tiny_axi4_async_bridge", + "path": "rtl/tiny/top/retrosoc_tiny.sv", + "instance": "u_xpi_mem_cdc", + "source_reset": "s_fabric_rst_n", + "destination_reset": "s_xpi_rst_n", + "reset_semantics": "five-edge link release; source-owned held responses survive destination reset; drain remaining W before B and synthesize only missing R beats with DECERR; unfinished warm clear requires destination reset before re-admission" + }, { "name": "debug_hard_reset", "source": "jtag", @@ -138,7 +273,7 @@ }, { "name": "i2s_configuration", - "source": "system", + "source": "peripheral", "destination": "audio", "primitive": "cdc_2phase", "path": "rtl/ip/serial/apb4_i2s.sv", @@ -146,7 +281,7 @@ }, { "name": "i2s_commands", - "source": "system", + "source": "peripheral", "destination": "audio", "primitive": "cdc_2phase", "path": "rtl/ip/serial/apb4_i2s.sv", @@ -154,7 +289,7 @@ }, { "name": "i2s_tx_samples", - "source": "system", + "source": "peripheral", "destination": "audio", "primitive": "cdc_fifo_warm_flush", "path": "rtl/ip/serial/apb4_i2s.sv", @@ -163,7 +298,7 @@ { "name": "i2s_rx_samples", "source": "audio", - "destination": "system", + "destination": "peripheral", "primitive": "cdc_fifo_warm_flush", "path": "rtl/ip/serial/apb4_i2s.sv", "instance": "u_rx_cdc_fifo" @@ -171,7 +306,7 @@ { "name": "i2s_tx_occupancy", "source": "audio", - "destination": "system", + "destination": "peripheral", "primitive": "cdc_2phase", "path": "rtl/ip/serial/apb4_i2s.sv", "instance": "u_tx_pop_cnt_cdc" @@ -179,10 +314,108 @@ { "name": "i2s_rx_occupancy", "source": "audio", - "destination": "system", + "destination": "peripheral", "primitive": "cdc_2phase", "path": "rtl/ip/serial/apb4_i2s.sv", "instance": "u_rx_push_cnt_cdc" + }, + { + "name": "dma_axi", + "source": "peripheral", + "destination": "system", + "primitive": "tiny_axi4_async_bridge", + "path": "rtl/tiny/top/retrosoc_tiny.sv", + "instance": "u_dma_cdc", + "source_reset": "s_dma_rst_n", + "destination_reset": "s_fabric_rst_n", + "reset_semantics": "five-edge link release; source-owned held responses survive destination reset; drain remaining W before B and synthesize only missing R beats with DECERR; unfinished warm clear requires destination reset before re-admission" + }, + { + "name": "rcu_commands", + "source": "system", + "destination": "aon", + "primitive": "tiny_apb_async_bridge", + "path": "rtl/tiny/top/tiny_sysctrl.sv", + "instance": "u_rcu_cdc" + }, + { + "name": "system_rate", + "source": "aon", + "destination": "system", + "primitive": "cdc_2phase", + "path": "rtl/tiny/top/retrosoc_tiny.sv", + "instance": "u_sys_rate_cdc" + }, + { + "name": "peripheral_rate", + "source": "aon", + "destination": "peripheral", + "primitive": "cdc_2phase", + "path": "rtl/tiny/top/retrosoc_tiny.sv", + "instance": "u_pclk_rate_cdc" + }, + { + "name": "dma_done", + "source": "peripheral", + "destination": "memory", + "primitive": "cdc_2phase", + "path": "rtl/tiny/top/retrosoc_tiny.sv", + "instance": "u_dma_done_cdc" + }, + { + "name": "xpi_requests", + "source": "memory", + "destination": "peripheral", + "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", + "instance": "u_xpi_stall_sync" } - ] + ], + "clock_contract": "TINY-R2-P7", + "macros": { + "ics55_pll": { + "cell": "PLL_TOP", + "scope": "single RCU-owned macro; off after cold reset; selectors 5/7", + "active_clock_domain": true, + "qualification": "digital activity/rate qualification only; no analog LOCK or characterized timing arcs" + } + }, + "generated_apb_crossings": { + "generator": "scripts/generate_tiny.py", + "template": "tiny_apb_bridges.svh", + "primitive": "tiny_apb_async_bridge", + "source": "system", + "destinations": { + "aon": [ + "clint", + "archinfo" + ], + "memory": [ + "xpi" + ], + "peripheral": [ + "gpio", + "uart0", + "timer0", + "timer1", + "i2c0", + "dma", + "pwm", + "rtc", + "wdg", + "rng", + "crc", + "ws2812", + "crypto", + "i2s" + ] + }, + "same_clock_isolation": [ + "sdio" + ], + "native_system": [ + "sysctrl", + "sram" + ] + } } diff --git a/rtl/tiny/integration/soc_topology.json b/rtl/tiny/integration/soc_topology.json index 9dae7d3d..0692b805 100644 --- a/rtl/tiny/integration/soc_topology.json +++ b/rtl/tiny/integration/soc_topology.json @@ -253,6 +253,11 @@ "name": "crypto", "core_bit": 23, "signal": "s_crypto_irq" + }, + { + "name": "rcu", + "core_bit": 31, + "signal": "s_rcu_irq" } ], "gpio_alt_functions": [ @@ -588,8 +593,8 @@ }, "alt1": { "inputs": [], - "do": "1'b0", - "oe": "1'b0" + "do": "s_clkout", + "oe": "s_clkout_enable" } }, { @@ -652,8 +657,8 @@ "pin": 28, "alt0": { "inputs": [], - "do": "1'b0", - "oe": "1'b0" + "do": "s_clkout", + "oe": "s_clkout_enable" }, "alt1": { "inputs": [], @@ -706,7 +711,7 @@ "banks": 6, "bank_bytes": 4096, "macro_instance": "u_soc.u_crypto.u_storage.gen_bank[{bank}].gen_macro.u_sram.u_mem", - "clock_domain": "system", - "logical_domain": "PCLK (SAFE24 alias)" + "clock_domain": "peripheral", + "logical_domain": "PCLK" } } diff --git a/rtl/tiny/mk/config.mk b/rtl/tiny/mk/config.mk index 1b89e8b9..95c55e68 100644 --- a/rtl/tiny/mk/config.mk +++ b/rtl/tiny/mk/config.mk @@ -1,4 +1,5 @@ -# Both Tiny integrations remain SAFE24; PLL presence is not SYS selection. +# Tiny always boots from normalized SAFE24. Dynamic selection is RCU-owned. +override TINY_R2_P7 := YES ifneq ($(MINI_MODE),NONE) $(error SOC=TINY requires MINI_MODE=NONE) endif @@ -10,12 +11,12 @@ $(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) +$(error SOC=TINY PDK=ICS55 requires HAVE_PLL=YES) 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 +override TINY_SAFE24_PLL_OFF := NO else ifneq ($(HAVE_PLL),NO) $(error SOC=TINY PDK=IHP130 requires HAVE_PLL=NO) @@ -31,11 +32,16 @@ endif ifneq ($(SRAM_SIZE_KIB),128) $(error SOC=TINY first release requires SRAM_SIZE_KIB=128) endif -ifneq ($(EXT_CLK_HZ),24000000) -$(error SOC=TINY first release requires EXT_CLK_HZ=24000000) +ifeq ($(filter $(EXT_CLK_HZ),24000000 48000000 96000000),) +$(error SOC=TINY requires external XIN of 24, 48 or 96 MHz) endif -ifneq ($(AUD_CLK_HZ),$(EXT_CLK_HZ)) -$(error SOC=TINY uses the system clock for RTC and watchdog) +ifeq ($(HAVE_PLL),YES) +ifneq ($(EXT_CLK_HZ),24000000) +$(error SOC=TINY with PLL requires EXT_CLK_HZ=24000000) +endif +endif +ifneq ($(AUD_CLK_HZ),24000000) +$(error SOC=TINY RTC and watchdog use normalized REF24) endif ifneq ($(CLINT_TIMEBASE_HZ),1000000) $(error SOC=TINY requires CLINT_TIMEBASE_HZ=1000000) diff --git a/rtl/tiny/top/retrosoc_tiny.sv b/rtl/tiny/top/retrosoc_tiny.sv index 5677dc40..55b3add3 100644 --- a/rtl/tiny/top/retrosoc_tiny.sv +++ b/rtl/tiny/top/retrosoc_tiny.sv @@ -27,7 +27,9 @@ module retrosoc_tiny ( logic s_master_rst_n [ 3]; logic s_target_rst_n [`RETROSOC_TINY__TARGET_COUNT]; logic s_debug_halted; + logic s_halted_pclk, s_halted_ref, s_rtc_wake_sys; logic s_core_rst_n, s_cpu_bus_idle, s_cpu_quiesce; + logic s_cpu_clock_idle; logic [31:0] s_irq; logic s_fault_write; logic [ 1:0] s_fault_master; @@ -47,12 +49,70 @@ module retrosoc_tiny ( logic [4:0] s_tick_count_d, s_tick_count_q; logic s_tick; logic s_audio_pad_clk, s_audio_clk, s_audio_rst_n; + logic s_rtc_func_rst_n, s_wdg_func_rst_n; + logic [31:0] s_xin_hz, s_ref_hz, s_sys_hz, s_mem_hz, s_pclk_hz, s_clint_hz; + logic [1:0] s_rcu_current; + logic s_rcu_busy; + logic s_clock_apply; + logic [1:0] s_clock_profile; + logic s_backend_busy, s_backend_fault; + logic s_backend_sys_clk, s_backend_mem_clk, s_backend_pclk; + logic s_backend_sys_rst_n, s_backend_mem_rst_n, s_backend_pclk_rst_n; + logic [31:0] s_backend_xin_hz, s_backend_ref_hz, s_backend_sys_hz; + logic [31:0] s_backend_mem_hz, s_backend_pclk_hz; + logic s_exec_clk; + logic s_ref_clk; + logic [`RETROSOC_TINY__LEAF_COUNT-1:0] s_leaf_clk; + logic [`RETROSOC_TINY__LEAF_COUNT-2:0] s_apb_cdc_idle, s_apb_cdc_ready; + logic [ 2:0] s_owner_idle; + logic [`RETROSOC_TINY__TARGET_COUNT-1:0] s_target_idle; + logic s_clock_safe; + logic s_xpi_cdc_busy; + logic s_dma_cdc_busy; + logic s_dma_done_pending_d, s_dma_done_pending_q; + logic s_dma_done_ready, s_dma_done_mem; + logic [ 1:0] s_xpi_stall_pclk; + logic [31:0] s_irq_async; + logic s_cpu_boot_ready; + logic s_dma_cdc_ready, s_dma_ready_sys, s_xpi_cdc_ready; + logic s_rcu_link_ready; + logic s_clock_block, s_block_sys, s_block_pclk; + logic s_clock_done, s_system_reset, s_backend_reset_n, s_pll_locked, s_pll_selected; + logic s_rcu_irq; + logic s_hart_reset, s_hart_seen_d, s_hart_seen_q, s_hart_pending_d, s_hart_pending_q; + logic s_hart_ack, s_hart_reset_ref; + logic [31:0] s_pclk_report_d, s_pclk_report_q, s_sys_report_d, s_sys_report_q; + logic [31:0] s_pclk_snapshot, s_sys_snapshot; + logic s_pclk_snapshot_valid, s_sys_snapshot_valid; + logic s_rate_ref_pclk_rst_n, s_rate_pclk_rst_n; + logic s_rate_ref_sys_rst_n, s_rate_sys_rst_n; + logic [31:0] s_clkout_control; + logic s_clkout, s_clkout_enable; + logic s_clkout_idle_ref; + logic [13:0] s_gate_request, s_local_reset, s_actual_gate, s_reset_held, s_reset_ready; + logic [13:0] s_gate_ref, s_held_ref, s_ready_ref, s_disabled_sys; + logic s_periph_apply, s_periph_reset_command, s_periph_block, s_periph_done; + logic [13:0] s_periph_request, s_periph_reset_done; + logic [4:0] s_periph_fault; + logic s_bus_idle_ref; + logic s_boot_release_d, s_boot_release_q; + logic s_wfi_gate_sys; + logic [13:0] s_periph_idle, s_periph_idle_ref; + logic s_xpi_idle, s_xpi_idle_ref; + logic s_pwm_write, s_crc_write; + logic [ 4:0] s_clock_fault; + logic [31:0] s_clock_timeout; + logic [ 1:0] s_backend_current; + logic s_dma_admission_idle, s_dma_idle_sys, s_sdio_admission_idle; logic s_rng_en, s_rng_ready, s_rng_valid, s_rng_qualified, s_rng_fault, s_rng_irq; logic [31:0] s_rng_data; logic s_crypto_irq, s_crypto_input_ready, s_crypto_output_ready; logic s_i2s_tx_stall, s_i2s_rx_stall; - ahbl_if u_cpu_ahbl_if[2] ( - .hclk (clk_i), + logic s_i2s_reset_ready; + logic s_done_pclk_rst_n, s_done_mem_rst_n; + `include "tiny_apb_interfaces.svh" +ahbl_if u_cpu_ahbl_if[2] ( + .hclk (s_exec_clk), .hresetn(s_core_rst_n) ); tiny_sram_port_if u_cpu_local_if[2] (); @@ -62,11 +122,10 @@ module retrosoc_tiny ( .ID_WIDTH (1), .USER_WIDTH(1) ) u_sram_axi4_if[4] ( - .aclk (clk_i), + .aclk (s_exec_clk), .aresetn(s_sram_rst_n) ); - `include "tiny_apb_interfaces.svh" -uart_if u_uart0_if (); + uart_if u_uart0_if (); i2c_if u_i2c0_if (); pwm_if u_pwm_if (); clint_if u_clint_if (); @@ -77,14 +136,14 @@ uart_if u_uart0_if (); ws2812_if u_ws2812_if (); sdio_if u_sdio_if (); rtc_if u_rtc_if ( - .rtc_clk_i (clk_i), - .rtc_rst_n_i(s_rtc_rst_n) + .rtc_clk_i (s_ref_clk), + .rtc_rst_n_i(s_rtc_func_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. + // resets after the AON backend retains the watchdog request. wdg_if u_wdg_if ( - .wdg_clk_i (clk_i), - .wdg_rst_n_i(s_por_rst_n) + .wdg_clk_i (s_ref_clk), + .wdg_rst_n_i(s_wdg_func_rst_n) ); axi4_if #( .ADDR_WIDTH(32), @@ -92,57 +151,466 @@ uart_if u_uart0_if (); .ID_WIDTH (1), .USER_WIDTH(1) ) u_masters_axi4_if[3] ( - .aclk (clk_i), + .aclk (s_exec_clk), .aresetn(s_master_rst_n) ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) u_dma_pclk_axi4_if ( + .aclk (s_dma_clk), + .aresetn(s_dma_rst_n) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) u_dma_admitted_axi4_if ( + .aclk (s_backend_pclk), + .aresetn(s_dma_rst_n) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) u_sdio_sys_axi4_if ( + .aclk (s_sdio_clk), + .aresetn(s_sdio_rst_n) + ); axi4_if #( .ADDR_WIDTH(32), .DATA_WIDTH(32), .ID_WIDTH (1), .USER_WIDTH(1) ) u_targets_axi4_if[`RETROSOC_TINY__TARGET_COUNT] ( - .aclk (clk_i), + .aclk (s_exec_clk), .aresetn(s_target_rst_n) ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) u_xpi_mem_axi4_if ( + .aclk (s_backend_mem_clk), + .aresetn(s_backend_mem_rst_n) + ); axi4_stream_if #( .DATA_WIDTH(32) ) u_i2s_tx_axis_if ( - .aclk (clk_i), + .aclk (s_dma_clk), .aresetn(s_dma_rst_n) ); axi4_stream_if #( .DATA_WIDTH(32) ) u_i2s_rx_axis_if ( - .aclk (clk_i), + .aclk (s_dma_clk), .aresetn(s_dma_rst_n) ); axi4_stream_if #( .DATA_WIDTH(32) ) u_dvp_rx_axis_if ( - .aclk (clk_i), + .aclk (s_dma_clk), .aresetn(s_dma_rst_n) ); axi4_stream_if #( .DATA_WIDTH(32) ) u_crypto_tx_axis_if ( - .aclk (clk_i), + .aclk (s_dma_clk), .aresetn(s_dma_rst_n) ); axi4_stream_if #( .DATA_WIDTH(32) ) u_crypto_rx_axis_if ( - .aclk (clk_i), + .aclk (s_dma_clk), .aresetn(s_dma_rst_n) ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_debug_pclk_sync ( + .clk_i (s_backend_pclk), + .rst_n_i(s_backend_pclk_rst_n), + .dat_i (s_debug_halted), + .dat_o (s_halted_pclk) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_debug_ref_sync ( + .clk_i (s_ref_clk), + .rst_n_i(s_por_rst_n), + .dat_i (s_debug_halted), + .dat_o (s_halted_ref) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_rtc_wake_sync ( + .clk_i (s_exec_clk), + .rst_n_i(s_fabric_rst_n), + .dat_i (u_rtc_if.wake_o), + .dat_o (s_rtc_wake_sys) + ); + rst_sync #( + .STAGE(5) + ) u_rtc_func_reset ( + .clk_i (s_ref_clk), + .rst_n_i(s_por_rst_n && s_backend_reset_n), + .rst_n_o(s_rtc_func_rst_n) + ); + rst_sync #( + .STAGE(5) + ) u_wdg_func_reset ( + .clk_i (s_ref_clk), + .rst_n_i(s_por_rst_n && s_backend_reset_n), + .rst_n_o(s_wdg_func_rst_n) + ); + assign s_hart_seen_d = s_hart_reset; + assign s_hart_pending_d = (s_hart_reset && !s_hart_seen_q) || (s_hart_pending_q && !s_hart_ack); + dffr #( + .DATA_WIDTH(2) + ) u_hart_event ( + .clk_i (s_exec_clk), + .rst_n_i(rst_n_i), + .dat_i ({s_hart_pending_d, s_hart_seen_d}), + .dat_o ({s_hart_pending_q, s_hart_seen_q}) + ); + cdc_2phase #( + .DATA_WIDTH(1) + ) u_hart_event_cdc ( + .src_clk_i (s_exec_clk), + .src_rst_n_i(rst_n_i), + .src_valid_i(s_hart_pending_q), + .src_ready_o(s_hart_ack), + .src_data_i (1'b1), + .dst_clk_i (s_ref_clk), + .dst_rst_n_i(s_por_rst_n), + .dst_valid_o(s_hart_reset_ref), + .dst_ready_i(1'b1), + .dst_data_o () + ); + cdc_reset_barrier #( + .STAGES(5) + ) u_pclk_rate_barrier ( + .clk_a_i (s_ref_clk), + .clk_b_i (s_backend_pclk), + .rst_n_i (s_por_rst_n && s_backend_pclk_rst_n), + .release_i(1'b1), + .rst_a_n_o(s_rate_ref_pclk_rst_n), + .rst_b_n_o(s_rate_pclk_rst_n) + ); + cdc_reset_barrier #( + .STAGES(5) + ) u_sys_rate_barrier ( + .clk_a_i (s_ref_clk), + .clk_b_i (s_exec_clk), + .rst_n_i (s_por_rst_n && s_backend_sys_rst_n), + .release_i(1'b1), + .rst_a_n_o(s_rate_ref_sys_rst_n), + .rst_b_n_o(s_rate_sys_rst_n) + ); + cdc_2phase #( + .DATA_WIDTH(32) + ) u_pclk_rate_cdc ( + .src_clk_i (s_ref_clk), + .src_rst_n_i(s_rate_ref_pclk_rst_n), + .src_data_i (s_backend_pclk_hz), + .src_valid_i(1'b1), + .src_ready_o(), + .dst_clk_i (s_backend_pclk), + .dst_rst_n_i(s_rate_pclk_rst_n), + .dst_data_o (s_pclk_snapshot), + .dst_valid_o(s_pclk_snapshot_valid), + .dst_ready_i(1'b1) + ); + cdc_2phase #( + .DATA_WIDTH(32) + ) u_sys_rate_cdc ( + .src_clk_i (s_ref_clk), + .src_rst_n_i(s_rate_ref_sys_rst_n), + .src_data_i (s_backend_sys_hz), + .src_valid_i(1'b1), + .src_ready_o(), + .dst_clk_i (s_exec_clk), + .dst_rst_n_i(s_rate_sys_rst_n), + .dst_data_o (s_sys_snapshot), + .dst_valid_o(s_sys_snapshot_valid), + .dst_ready_i(1'b1) + ); + assign s_pclk_report_d = s_pclk_snapshot_valid ? s_pclk_snapshot : s_pclk_report_q; + assign s_sys_report_d = s_sys_snapshot_valid ? s_sys_snapshot : s_sys_report_q; + dffrc #( + .DATA_WIDTH(32), + .RESET_VAL (32'd24_000_000) + ) u_pclk_report ( + .clk_i (s_backend_pclk), + .rst_n_i(s_backend_pclk_rst_n), + .dat_i (s_pclk_report_d), + .dat_o (s_pclk_report_q) + ); + dffrc #( + .DATA_WIDTH(32), + .RESET_VAL (32'd24_000_000) + ) u_sys_report ( + .clk_i (s_exec_clk), + .rst_n_i(s_backend_sys_rst_n), + .dat_i (s_sys_report_d), + .dat_o (s_sys_report_q) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_clkout_idle_sync ( + .clk_i (s_ref_clk), + .rst_n_i(s_por_rst_n), + .dat_i (!s_clkout_enable), + .dat_o (s_clkout_idle_ref) + ); + tiny_clock_output u_clock_output ( + .clk_ref_i (s_ref_clk), + .clk_sys_i (s_exec_clk), + .clk_mem_i (s_backend_mem_clk), + .clk_periph_i(s_backend_pclk), + .rst_n_i (s_por_rst_n && s_backend_reset_n), + .control_i (s_clkout_control), + .clk_o (s_clkout), + .enable_o (s_clkout_enable) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_wfi_gate_sync ( + .clk_i (s_exec_clk), + .rst_n_i(s_fabric_rst_n), + .dat_i (s_gate_request[0]), + .dat_o (s_wfi_gate_sys) + ); + assign s_reset_held[0] = 1'b0; + assign s_reset_ready[0] = 1'b0; + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(14) + ) u_gate_feedback ( + .clk_i (s_ref_clk), + .rst_n_i(s_por_rst_n), + .dat_i (s_actual_gate), + .dat_o (s_gate_ref) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(14) + ) u_held_feedback ( + .clk_i (s_ref_clk), + .rst_n_i(s_por_rst_n), + .dat_i (s_reset_held), + .dat_o (s_held_ref) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(14) + ) u_ready_feedback ( + .clk_i (s_ref_clk), + .rst_n_i(s_por_rst_n), + .dat_i (s_reset_ready), + .dat_o (s_ready_ref) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(14) + ) u_disabled_sync ( + .clk_i (s_exec_clk), + .rst_n_i(s_fabric_rst_n), + .dat_i (s_gate_request | s_local_reset), + .dat_o (s_disabled_sys) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_bus_idle_ref ( + .clk_i (s_ref_clk), + .rst_n_i(s_por_rst_n), + .dat_i (s_clock_safe), + .dat_o (s_bus_idle_ref) + ); + tiny_peripheral_control u_peripheral_control ( + .clk_i (s_ref_clk), + .rst_n_i (s_por_rst_n), + .warm_reset_i (!s_backend_reset_n), + .apply_i (s_periph_apply), + .reset_command_i(s_periph_reset_command), + .request_i (s_periph_request), + .idle_i (s_periph_idle_ref), + .bus_idle_i (s_bus_idle_ref), + .gate_actual_i (s_gate_ref), + .reset_held_i (s_held_ref), + .reset_ready_i (s_ready_ref), + .timeout_i (s_clock_timeout), + .gate_o (s_gate_request), + .reset_o (s_local_reset), + .block_o (s_periph_block), + .done_o (s_periph_done), + .fault_o (s_periph_fault), + .reset_done_o (s_periph_reset_done) + ); + assign s_pwm_write = u_pwm_apb4_if.psel && u_pwm_apb4_if.penable && u_pwm_apb4_if.pready && + u_pwm_apb4_if.pwrite && !u_pwm_apb4_if.pslverr && u_pwm_apb4_if.pstrb[0]; + assign s_crc_write = u_crc_apb4_if.psel && u_crc_apb4_if.penable && u_crc_apb4_if.pready && + u_crc_apb4_if.pwrite && !u_crc_apb4_if.pslverr && u_crc_apb4_if.pstrb[0]; + tiny_managed_lifecycle u_managed_lifecycle ( + .clk_i (s_backend_pclk), + .pwm_rst_n_i(s_pwm_rst_n), + .crc_rst_n_i(s_crc_rst_n), + .pwm_write_i(s_pwm_write), + .pwm_addr_i (u_pwm_apb4_if.paddr[11:0]), + .pwm_data_i (u_pwm_apb4_if.pwdata), + .crc_write_i(s_crc_write), + .crc_addr_i (u_crc_apb4_if.paddr[11:0]), + .crc_data_i (u_crc_apb4_if.pwdata), + .pwm_idle_o (s_periph_idle[6]), + .crc_idle_o (s_periph_idle[12]) + ); + assign s_periph_idle[0] = s_cpu_bus_idle; + assign s_periph_idle[1] = 1'b1; // GPIO has no independent transaction engine; USER is absent. + assign s_periph_idle[11] = !s_rng_en && !s_rng_valid; + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(14) + ) u_periph_idle_sync ( + .clk_i (s_ref_clk), + .rst_n_i(s_por_rst_n), + .dat_i (s_periph_idle), + .dat_o (s_periph_idle_ref) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_xpi_idle_sync ( + .clk_i (s_ref_clk), + .rst_n_i(s_por_rst_n), + .dat_i (s_xpi_idle), + .dat_o (s_xpi_idle_ref) + ); + assign s_exec_clk = s_backend_sys_clk; + assign s_leaf_clk[0] = s_exec_clk; + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_dma_ready_sync ( + .clk_i (s_exec_clk), + .rst_n_i(s_fabric_rst_n), + .dat_i (s_dma_cdc_ready), + .dat_o (s_dma_ready_sys) + ); + assign s_boot_release_d = s_boot_release_q || (s_cpu_ready_rst_n && + (&s_apb_cdc_ready) && s_xpi_cdc_ready && s_dma_ready_sys && s_rcu_link_ready); + dffr #( + .DATA_WIDTH(1) + ) u_boot_release ( + .clk_i (s_exec_clk), + .rst_n_i(s_fabric_rst_n), + .dat_i (s_boot_release_d), + .dat_o (s_boot_release_q) + ); + rst_sync #( + .STAGE(5) + ) u_boot_reset ( + .clk_i (s_exec_clk), + .rst_n_i(s_cpu_ready_rst_n && s_boot_release_q), + .rst_n_o(s_cpu_boot_ready) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(2) + ) u_xpi_stall_sync ( + .clk_i (s_dma_clk), + .rst_n_i(s_dma_rst_n), + .dat_i (s_dma_stall[1:0]), + .dat_o (s_xpi_stall_pclk) + ); + // Central DMA emits one completion pulse. Retain it until MEM acknowledges; + // a new completion coincident with acknowledgement wins over clearing. + assign s_dma_done_pending_d = s_dma_done || (s_dma_done_pending_q && !s_dma_done_ready); + dffr #( + .DATA_WIDTH(1) + ) u_dma_done_pending ( + .clk_i (s_dma_clk), + .rst_n_i(s_dma_rst_n), + .dat_i (s_dma_done_pending_d), + .dat_o (s_dma_done_pending_q) + ); + cdc_reset_barrier #( + .STAGES(5) + ) u_dma_done_barrier ( + .clk_a_i (s_dma_clk), + .clk_b_i (s_backend_mem_clk), + .rst_n_i (s_dma_rst_n && s_xpi_rst_n), + .release_i(1'b1), + .rst_a_n_o(s_done_pclk_rst_n), + .rst_b_n_o(s_done_mem_rst_n) + ); + cdc_2phase #( + .DATA_WIDTH(1) + ) u_dma_done_cdc ( + .src_clk_i (s_dma_clk), + .src_rst_n_i(s_done_pclk_rst_n), + .src_valid_i(s_dma_done_pending_q), + .src_ready_o(s_dma_done_ready), + .src_data_i (1'b1), + .dst_clk_i (s_backend_mem_clk), + .dst_rst_n_i(s_done_mem_rst_n), + .dst_valid_o(s_dma_done_mem), + .dst_ready_i(1'b1), + .dst_data_o () + ); + assign s_clock_safe = s_cpu_clock_idle && (&s_owner_idle) && (&s_target_idle) && + (&s_apb_cdc_idle) && s_dma_idle_sys && s_sdio_admission_idle && !s_xpi_cdc_busy; + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_block_sys ( + .clk_i (s_exec_clk), + .rst_n_i(s_fabric_rst_n), + .dat_i (s_clock_block || s_periph_block), + .dat_o (s_block_sys) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_block_pclk ( + .clk_i (s_dma_clk), + .rst_n_i(s_dma_rst_n), + .dat_i (s_clock_block || s_periph_block), + .dat_o (s_block_pclk) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_dma_idle_sync ( + .clk_i (s_exec_clk), + .rst_n_i(s_fabric_rst_n), + .dat_i (s_dma_admission_idle && !s_dma_done_pending_q && s_dma_done_ready), + .dat_o (s_dma_idle_sys) + ); assign s_fabric_rst_n = s_leaf_rst_n[0]; - assign s_master_rst_n = '{s_cpu_ready_rst_n, s_dma_rst_n, s_sdio_rst_n}; - assign s_target_rst_n[4] = s_xpi_rst_n; + assign s_master_rst_n = '{s_cpu_ready_rst_n, s_fabric_rst_n, s_sdio_rst_n}; + // The fabric-side XPI target is SYS-clocked; only the MEM endpoint uses the + // XPI leaf reset. The async bridge invalidates accepted work if MEM resets. + assign s_target_rst_n[4] = s_fabric_rst_n; assign s_target_rst_n[`RETROSOC_TINY__DECODE_ERROR] = s_fabric_rst_n; assign s_target_rst_n[`RETROSOC_TINY__PROTOCOL_ERROR] = s_fabric_rst_n; tiny_reset_tree #( - .LeafCount(`RETROSOC_TINY__LEAF_COUNT) + .LeafCount (`RETROSOC_TINY__LEAF_COUNT), + .SplitDomains(1'b1) ) u_reset_tree ( - .clk_i (clk_i), + .clk_i (s_exec_clk), + .por_clk_i (s_ref_clk), + .leaf_clk_i (s_leaf_clk), + .backend_reset_n_i (s_backend_reset_n), .rst_n_i (rst_n_i), .watchdog_reset_req_i(u_wdg_if.reset_req_o), .por_rst_n_o (s_por_rst_n), @@ -155,18 +623,22 @@ uart_if u_uart0_if (); dffr #( .DATA_WIDTH(5) ) u_tick_reg ( - .clk_i (clk_i), - .rst_n_i(s_fabric_rst_n), + .clk_i (s_ref_clk), + .rst_n_i(s_clint_rst_n), .dat_i (s_tick_count_d), .dat_o (s_tick_count_q) ); tiny_cpu_wrapper #( .ExternalIrqCount(30), .EnableAtomics (1'b0), - .ResetSyncStages (5) + .ResetSyncStages (5), + .EnableWfiGate (1'b1), + .PreserveDebug (1'b1) ) u_cpu ( - .clk_i (clk_i), - .rst_n_i (s_cpu_ready_rst_n), + .clk_i (s_exec_clk), + .rst_n_i (s_cpu_boot_ready), + .debug_rst_n_i (rst_n_i), + .hart_reset_o (s_hart_reset), .irq_i (s_irq), .jtag_tck_i (jtag_tck_i), .jtag_tms_i (jtag_tms_i), @@ -175,14 +647,20 @@ uart_if u_uart0_if (); .jtag_tdo_o (jtag_tdo_o), .debug_halted_o(s_debug_halted), .bus_idle_i (s_cpu_bus_idle), + .wfi_gate_i (s_wfi_gate_sys), + .gated_o (s_actual_gate[0]), .core_rst_n_o (s_core_rst_n), .quiesce_o (s_cpu_quiesce), .cpu (u_cpu_ahbl_if) ); - tiny_cpu_mem u_cpu_mem ( - .clk_i (clk_i), + tiny_cpu_mem #( + .ClockPause(1'b1) + ) u_cpu_mem ( + .clk_i (s_exec_clk), .rst_n_i (s_core_rst_n), .quiesce_i (s_cpu_quiesce), + .clock_pause_i (s_block_sys), + .clock_idle_o (s_cpu_clock_idle), .idle_o (s_cpu_bus_idle), .local_fault_valid_o(s_local_fault_valid), .local_fault_addr_o (s_local_fault_addr), @@ -192,21 +670,21 @@ uart_if u_uart0_if (); .slow_axi4 (u_masters_axi4_if[0]) ); tiny_axi4_fabric u_fabric ( - .clk_i(clk_i), - .rst_n_i(s_fabric_rst_n), - .target_rst_n_i(s_target_rst_n), - .masters(u_masters_axi4_if), - .targets(u_targets_axi4_if), - .perf_enable_i(s_perf_enable), - .perf_clear_i(s_perf_clear), - .fault_valid_o(s_external_fault_valid), - .fault_addr_o(s_external_fault_addr), - .fault_resp_o(s_external_fault_resp), + .clk_i (s_exec_clk), + .rst_n_i (s_fabric_rst_n), + .target_rst_n_i (s_target_rst_n), + .masters (u_masters_axi4_if), + .targets (u_targets_axi4_if), + .perf_enable_i (s_perf_enable), + .perf_clear_i (s_perf_clear), + .fault_valid_o (s_external_fault_valid), + .fault_addr_o (s_external_fault_addr), + .fault_resp_o (s_external_fault_resp), .fault_instruction_o(s_external_fault_instruction), - .owner_idle_o(), // Reserved lifecycle observations; RCU remains P7. - .target_idle_o(), // The P5 testbench checks all accepted-work barriers. - .cpu_wait_o(s_cpu_wait), - .dma_wait_o(s_dma_wait) + .owner_idle_o (s_owner_idle), + .target_idle_o (s_target_idle), + .cpu_wait_o (s_cpu_wait), + .dma_wait_o (s_dma_wait) ); for (genvar group = 0; group < 4; group++) begin : gen_sram_attachment assign s_target_rst_n[group] = s_sram_rst_n; @@ -216,7 +694,7 @@ uart_if u_uart0_if (); ); end tiny_sram u_sram ( - .clk_i (clk_i), + .clk_i (s_exec_clk), .rst_n_i (s_sram_rst_n), .local_ports (u_cpu_local_if), .external_ports(u_sram_axi4_if), @@ -224,29 +702,76 @@ uart_if u_uart0_if (); .perf_enable_i (s_perf_enable), .perf_clear_i (s_perf_clear) ); + tiny_axi4_async_bridge #( + .DataWidth(32), + .IdWidth (1) + ) u_dma_cdc ( + .src_clk_i (s_dma_clk), + .src_rst_n_i (s_dma_rst_n), + .dst_clk_i (s_exec_clk), + .dst_rst_n_i (s_fabric_rst_n), + .clear_i (1'b0), + .clear_busy_o(s_dma_cdc_busy), + .ready_o (s_dma_cdc_ready), + .epoch_o (), + .src_axi4 (u_dma_admitted_axi4_if), + .dst_axi4 (u_masters_axi4_if[1]) + ); + tiny_axi4_admission u_dma_admission ( + .clk_i (s_dma_clk), + .rst_n_i(s_dma_rst_n), + .block_i(s_block_pclk), + .idle_o (s_dma_admission_idle), + .source (u_dma_pclk_axi4_if), + .sink (u_dma_admitted_axi4_if) + ); + tiny_axi4_admission u_sdio_admission ( + .clk_i (s_sdio_clk), + .rst_n_i(s_sdio_rst_n), + .block_i(s_block_sys), + .idle_o (s_sdio_admission_idle), + .source (u_sdio_sys_axi4_if), + .sink (u_masters_axi4_if[2]) + ); + tiny_axi4_async_bridge #( + .DataWidth(32), + .IdWidth (1) + ) u_xpi_mem_cdc ( + .src_clk_i (s_exec_clk), + .src_rst_n_i (s_fabric_rst_n), + .dst_clk_i (s_backend_mem_clk), + .dst_rst_n_i (s_xpi_rst_n), + .clear_i (1'b0), + .clear_busy_o(s_xpi_cdc_busy), + .ready_o (s_xpi_cdc_ready), + .epoch_o (), + .src_axi4 (u_targets_axi4_if[4]), + .dst_axi4 (u_xpi_mem_axi4_if) + ); apb4_xpi u_xpi ( - .clk_i (clk_i), - .rst_n_i (s_xpi_rst_n), - .mem_axi4 (u_targets_axi4_if[4]), - .apb4 (u_xpi_apb4_if), - .xpi (xpi), - .dma_xfer_done_i(s_dma_done), - .dma_tx_stall_o (s_dma_stall[0]), - .dma_rx_stall_o (s_dma_stall[1]), - .irq_o (s_xpi_irq) + .lifecycle_idle_o(s_xpi_idle), + .clk_i (s_backend_mem_clk), + .rst_n_i (s_xpi_rst_n), + .mem_axi4 (u_xpi_mem_axi4_if), + .apb4 (u_xpi_apb4_if), + .xpi (xpi), + .dma_xfer_done_i (s_dma_done_mem), + .dma_tx_stall_o (s_dma_stall[0]), + .dma_rx_stall_o (s_dma_stall[1]), + .irq_o (s_xpi_irq) ); `include "tiny_apb_bridges.svh" axi4_error_slave #( .Response(2'b11) ) u_decode_error ( - .clk_i (clk_i), + .clk_i (s_exec_clk), .rst_n_i(s_fabric_rst_n), .axi4 (u_targets_axi4_if[`RETROSOC_TINY__DECODE_ERROR]) ); axi4_error_slave #( .Response(2'b10) ) u_protocol_error ( - .clk_i (clk_i), + .clk_i (s_exec_clk), .rst_n_i(s_fabric_rst_n), .axi4 (u_targets_axi4_if[`RETROSOC_TINY__PROTOCOL_ERROR]) ); @@ -264,27 +789,107 @@ axi4_error_slave #( .write_o (s_fault_write), .resp_o (s_fault_resp) ); - tiny_sysctrl u_sysctrl ( - .clk_i (clk_i), - .rst_n_i (s_sysctrl_rst_n), - .apb4 (u_sysctrl_apb4_if), - .fault_valid_i (s_fault_count != 4'd0), - .fault_count_i (s_fault_count), - .fault_addr_i (s_fault_addr), - .fault_write_i (s_fault_write), - .fault_master_i(s_fault_master), - .fault_resp_i (s_fault_resp), - .cpu_wait_i (s_cpu_wait), - .dma_wait_i (s_dma_wait), - .rtc_wake_i (u_rtc_if.wake_o), - .perf_enable_o (s_perf_enable), - .perf_clear_o (s_perf_clear), - .test_done_o (test_done_o), - .test_pass_o (test_pass_o), - .test_code_o (test_code_o) +`ifdef HAVE_PLL + localparam bit TinyPllPresent = 1'b1; +`else + localparam bit TinyPllPresent = 1'b0; +`endif + tiny_sysctrl #( + .PllPresent (TinyPllPresent), + .PllProfiles(TinyPllPresent), + .XinHzValue (`SOC_EXT_CLK_HZ), + .TargetMask (14'h3fff) + ) u_sysctrl ( + .clk_i (s_exec_clk), + .rst_n_i (s_sysctrl_rst_n), + .clk_ref_i (s_ref_clk), + .rst_ref_n_i (s_por_rst_n), + .rcu_link_ready_o(s_rcu_link_ready), + .apb4 (u_sysctrl_apb4_if), + .fault_valid_i (s_fault_count != 4'd0), + .fault_count_i (s_fault_count), + .fault_addr_i (s_fault_addr), + .fault_write_i (s_fault_write), + .fault_master_i (s_fault_master), + .fault_resp_i (s_fault_resp), + .cpu_wait_i (s_cpu_wait), + .dma_wait_i (s_dma_wait), + .rtc_wake_i (s_rtc_wake_sys), + .clock_done_i (s_clock_done), + .periph_done_i (s_periph_done), + .periph_fault_i (s_periph_fault), + .gate_actual_i (s_gate_ref), + .reset_done_i (s_periph_reset_done), + .periph_apply_o (s_periph_apply), + .periph_reset_o (s_periph_reset_command), + .periph_request_o(s_periph_request), + .clkout_control_o(s_clkout_control), + .clkout_idle_i (s_clkout_idle_ref), + .clock_fault_i (s_clock_fault), + .clock_current_i (s_backend_current), + .pll_locked_i (s_pll_locked), + .pll_selected_i (s_pll_selected), + .warm_reset_i (!s_backend_reset_n), + .system_reset_o (s_system_reset), + .watchdog_reset_i(u_wdg_if.reset_req_o), + .hart_reset_i (s_hart_reset_ref), + .rcu_irq_o (s_rcu_irq), + .clock_timeout_o (s_clock_timeout), + .perf_enable_o (s_perf_enable), + .perf_clear_o (s_perf_clear), + .test_done_o (test_done_o), + .test_pass_o (test_pass_o), + .test_code_o (test_code_o), + .xin_hz_o (s_xin_hz), + .ref_hz_o (s_ref_hz), + .sys_hz_o (s_sys_hz), + .mem_hz_o (s_mem_hz), + .pclk_hz_o (s_pclk_hz), + .clint_hz_o (s_clint_hz), + .rcu_current_o (s_rcu_current), + .rcu_busy_o (s_rcu_busy), + .clock_apply_o (s_clock_apply), + .clock_profile_o (s_clock_profile) + ); + tiny_clock_reset_subsystem #( + .RefClkHz(`SOC_EXT_CLK_HZ), + .HavePll (TinyPllPresent) + ) u_clock_backend ( + .ref_clk_i (clk_i), + .rst_n_i (rst_n_i), + .idle_i (s_clock_safe), + .clients_idle_i ((&s_periph_idle_ref[13:1]) && s_xpi_idle_ref), + .warm_reset_i (u_wdg_if.reset_req_o), + .system_request_i (s_system_reset), + .command_ack_i (s_cpu_clock_idle && s_target_idle[`RETROSOC_TINY__TARGET_SYSCTRL]), + .timeout_i (s_clock_timeout), + .ref_clk_o (s_ref_clk), + .ref_rst_n_o (), + .system_reset_n_o (s_backend_reset_n), + .block_o (s_clock_block), + .done_o (s_clock_done), + .fault_code_o (s_clock_fault), + .pll_locked_o (s_pll_locked), + .pll_selected_o (s_pll_selected), + .profile_i (s_clock_profile), + .apply_i (s_clock_apply), + .current_profile_o(s_backend_current), + .busy_o (s_backend_busy), + .fault_o (s_backend_fault), + .sys_clk_o (s_backend_sys_clk), + .mem_clk_o (s_backend_mem_clk), + .pclk_o (s_backend_pclk), + .sys_rst_n_o (s_backend_sys_rst_n), + .mem_rst_n_o (s_backend_mem_rst_n), + .pclk_rst_n_o (s_backend_pclk_rst_n), + .xin_hz_o (s_backend_xin_hz), + .ref_hz_o (s_backend_ref_hz), + .sys_hz_o (s_backend_sys_hz), + .mem_hz_o (s_backend_mem_hz), + .pclk_hz_o (s_backend_pclk_hz) ); tiny_archinfo u_archinfo ( - .clk_i (clk_i), + .clk_i (s_ref_clk), .rst_n_i(s_archinfo_rst_n), .apb4 (u_archinfo_apb4_if) ); @@ -293,7 +898,7 @@ axi4_error_slave #( .HasPullUp (gpio_pad_caps_pkg::HasPullUp), .HasPullDown (gpio_pad_caps_pkg::HasPullDown) ) u_gpio ( - .clk_i (clk_i), + .clk_i (s_gpio_clk), .rst_n_i (s_gpio_rst_n), .apb4 (u_gpio_apb4_if), .gpio (u_gpio_if), @@ -309,52 +914,67 @@ axi4_error_slave #( // Legacy ASIC observation pads are retained, not a Gen1 package binding. assign uart1_tx_o = 1'b1; assign u_uart0_if.cts_n_i = 1'b0; - assign u_wdg_if.debug_halted_i = s_debug_halted; + assign u_wdg_if.debug_halted_i = s_halted_ref; `include "tiny_gpio.svh" `include "tiny_irq.svh" -apb4_uart u_uart0 ( - .clk_i (clk_i), - .rst_n_i (s_uart0_rst_n), - .apb4 (u_uart0_apb4_if), - .uart (u_uart0_if), - .dma_tx_stall_o(s_dma_stall[2]), - .dma_rx_stall_o(s_dma_stall[3]) +cdc_sync #( + .STAGE (2), + .DATA_WIDTH(32) + ) u_irq_sync ( + .clk_i (s_exec_clk), + .rst_n_i(s_fabric_rst_n), + .dat_i (s_irq_async), + .dat_o (s_irq) + ); + apb4_uart u_uart0 ( + .lifecycle_idle_o(s_periph_idle[2]), + .clk_i (s_uart0_clk), + .rst_n_i (s_uart0_rst_n), + .apb4 (u_uart0_apb4_if), + .uart (u_uart0_if), + .dma_tx_stall_o (s_dma_stall[2]), + .dma_rx_stall_o (s_dma_stall[3]) ); apb4_i2c u_i2c0 ( - .clk_i (clk_i), - .rst_n_i (s_i2c0_rst_n), - .apb4 (u_i2c0_apb4_if), - .i2c (u_i2c0_if), - .dma_tx_stall_o(s_dma_stall[4]), - .dma_rx_stall_o(s_dma_stall[5]) + .lifecycle_idle_o(s_periph_idle[3]), + .clk_i (s_i2c0_clk), + .rst_n_i (s_i2c0_rst_n), + .apb4 (u_i2c0_apb4_if), + .i2c (u_i2c0_if), + .dma_tx_stall_o (s_dma_stall[4]), + .dma_rx_stall_o (s_dma_stall[5]) ); apb4_timer u_timer0 ( - .clk_i (clk_i), - .rst_n_i (s_timer0_rst_n), - .debug_halted_i(s_debug_halted), - .apb4 (u_timer0_apb4_if), - .irq_o (s_timer0_irq) + .lifecycle_idle_o(s_periph_idle[4]), + .clk_i (s_timer0_clk), + .rst_n_i (s_timer0_rst_n), + .debug_halted_i (s_halted_pclk), + .apb4 (u_timer0_apb4_if), + .irq_o (s_timer0_irq) ); apb4_timer u_timer1 ( - .clk_i (clk_i), - .rst_n_i (s_timer1_rst_n), - .debug_halted_i(s_debug_halted), - .apb4 (u_timer1_apb4_if), - .irq_o (s_timer1_irq) + .lifecycle_idle_o(s_periph_idle[5]), + .clk_i (s_timer1_clk), + .rst_n_i (s_timer1_rst_n), + .debug_halted_i (s_halted_pclk), + .apb4 (u_timer1_apb4_if), + .irq_o (s_timer1_irq) ); apb4_clint u_clint ( - .clk_i (clk_i), + .clk_i (s_ref_clk), .rst_n_i (s_clint_rst_n), .timebase_tick_i(s_tick), .apb4 (u_clint_apb4_if), .clint (u_clint_if) ); apb4_pwm #( - .PCLK_HZ(24_000_000) + .PCLK_HZ (24_000_000), + .UseRuntimeClockHz(1'b1) ) u_pwm ( - .debug_halted_i(s_debug_halted), + .debug_halted_i(s_halted_pclk), .apb4 (u_pwm_apb4_if), - .pwm (u_pwm_if) + .pwm (u_pwm_if), + .pclk_hz_i (s_pclk_report_q) ); apb4_rtc #( .RTC_CLOCK_HZ(24_000_000) @@ -376,25 +996,26 @@ apb4_uart u_uart0 ( .RequestMask (16'h31ff), .EnableStreams(1'b1) ) u_dma ( - .clk_i (clk_i), - .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), - .apb4 (u_dma_apb4_if), - .axi4 (u_masters_axi4_if[1]), - .i2s_tx_axis (u_i2s_tx_axis_if), - .i2s_rx_axis (u_i2s_rx_axis_if), - .dvp_rx_axis (u_dvp_rx_axis_if), - .crypto_in_axis (u_crypto_tx_axis_if), - .crypto_out_axis(u_crypto_rx_axis_if) + .lifecycle_idle_o(s_periph_idle[9]), + .clk_i (s_dma_clk), + .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), + .apb4 (u_dma_apb4_if), + .axi4 (u_dma_pclk_axi4_if), + .i2s_tx_axis (u_i2s_tx_axis_if), + .i2s_rx_axis (u_i2s_rx_axis_if), + .dvp_rx_axis (u_dvp_rx_axis_if), + .crypto_in_axis (u_crypto_tx_axis_if), + .crypto_out_axis (u_crypto_rx_axis_if) ); assign u_dma_req_if.i2s_tx_proc = !s_i2s_tx_stall; assign u_dma_req_if.i2s_rx_proc = !s_i2s_rx_stall; assign u_dma_req_if.crypto_in_proc = s_crypto_input_ready; assign u_dma_req_if.crypto_out_proc = s_crypto_output_ready; - assign u_dma_req_if.qspi_tx_proc = !s_dma_stall[0]; - assign u_dma_req_if.qspi_rx_proc = !s_dma_stall[1]; + assign u_dma_req_if.qspi_tx_proc = !s_xpi_stall_pclk[0]; + assign u_dma_req_if.qspi_rx_proc = !s_xpi_stall_pclk[1]; assign u_dma_req_if.uart_tx_proc = !s_dma_stall[2]; assign u_dma_req_if.uart_rx_proc = !s_dma_stall[3]; assign u_dma_req_if.i2c0_tx_proc = !s_dma_stall[4]; @@ -423,37 +1044,44 @@ apb4_uart u_uart0 ( .rst_n_o(s_audio_rst_n) ); apb4_i2s u_i2s ( - .clk_i (clk_i), - .rst_n_i (s_i2s_rst_n), - .clk_aud_i (s_audio_clk), - .rst_aud_n_i (s_audio_rst_n), - .dma_tx_stall_o (s_i2s_tx_stall), - .dma_rx_stall_o (s_i2s_rx_stall), - .tx_underrun_evt_o(), // Sticky status and IRQ retain these events. - .rx_overrun_evt_o (), - .rx_flush_busy_o (), // No APU consumer in Tiny. - .apb4 (u_i2s_apb4_if), - .tx_axis (u_i2s_tx_axis_if), - .rx_axis (u_i2s_rx_axis_if), - .i2s (u_i2s_if) + .lifecycle_reset_ready_o(s_i2s_reset_ready), + .lifecycle_idle_o (s_periph_idle[7]), + .clk_i (s_i2s_clk), + .rst_n_i (s_i2s_rst_n), + .clk_aud_i (s_audio_clk), + .rst_aud_n_i (s_audio_rst_n), + .dma_tx_stall_o (s_i2s_tx_stall), + .dma_rx_stall_o (s_i2s_rx_stall), + .tx_underrun_evt_o (), // Sticky status and IRQ retain these events. + .rx_overrun_evt_o (), + .rx_flush_busy_o (), // No APU consumer in Tiny. + .apb4 (u_i2s_apb4_if), + .tx_axis (u_i2s_tx_axis_if), + .rx_axis (u_i2s_rx_axis_if), + .i2s (u_i2s_if) ); apb4_sdio #( - .InputClockHz(24_000_000) + .InputClockHz (24_000_000), + .UseRuntimeClockHz(1'b1) ) u_sdio ( - .clk_i (clk_i), - .rst_n_i (s_sdio_rst_n), - .apb4 (u_sdio_apb4_if), - .dma_axi4(u_masters_axi4_if[2]), - .sdio (u_sdio_if) + .lifecycle_idle_o(s_periph_idle[8]), + .clk_i (s_sdio_clk), + .rst_n_i (s_sdio_rst_n), + .apb4 (u_sdio_apb4_if), + .dma_axi4 (u_sdio_sys_axi4_if), + .sdio (u_sdio_if), + .clock_hz_i (s_sys_report_q) ); apb4_ws2812 u_ws2812 ( - .clk_i (clk_i), - .rst_n_i(s_ws2812_rst_n), - .apb4 (u_ws2812_apb4_if), - .ws2812 (u_ws2812_if) + .lifecycle_idle_o(s_periph_idle[10]), + .clk_i (s_ws2812_clk), + .rst_n_i (s_ws2812_rst_n), + .apb4 (u_ws2812_apb4_if), + .ws2812 (u_ws2812_if) ); apb4_crypto u_crypto ( - .clk_i (clk_i), + .lifecycle_idle_o (s_periph_idle[13]), + .clk_i (s_crypto_clk), .rst_n_i (s_crypto_rst_n), .dma_input_proc_o (s_crypto_input_ready), .dma_output_proc_o(s_crypto_output_ready), @@ -464,7 +1092,7 @@ apb4_uart u_uart0 ( ); apb4_crc u_crc (.apb4(u_crc_apb4_if)); rng_deterministic_source u_rng_source ( - .clk_i (clk_i), + .clk_i (s_rng_clk), .rst_n_i (s_rng_rst_n), .enable_i (s_rng_en), .ready_i (s_rng_ready), diff --git a/rtl/tiny/top/retrosoc_tiny_asic.sv b/rtl/tiny/top/retrosoc_tiny_asic.sv index ffa3ad18..0c89657b 100644 --- a/rtl/tiny/top/retrosoc_tiny_asic.sv +++ b/rtl/tiny/top/retrosoc_tiny_asic.sv @@ -14,22 +14,6 @@ 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_apb_async_bridge.sv b/rtl/tiny/top/tiny_apb_async_bridge.sv new file mode 100644 index 00000000..948fc3d6 --- /dev/null +++ b/rtl/tiny/top/tiny_apb_async_bridge.sv @@ -0,0 +1,156 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// One outstanding SYS control request. Either endpoint reset flushes both +// mailboxes. A destination reset aborts the accepted source transfer with an +// error; it never replays it when the destination returns. The SYS endpoint +// remains clocked so a stopped/disabled peripheral is still reachable. +module tiny_apb_async_bridge ( + input logic src_clk_i, + input logic src_rst_n_i, + input logic dst_clk_i, + input logic dst_rst_n_i, + input logic disabled_i, + output logic idle_o, + output logic ready_o, + apb4_if.slave src_apb4, + apb4_if.master dst_apb4 +); + typedef enum logic [1:0] { + SrcIdle, + SrcSend, + SrcWait, + SrcResponse + } src_state_e; + typedef enum logic [1:0] { + DstIdle, + DstSetup, + DstAccess, + DstResponse + } dst_state_e; + typedef struct packed { + src_state_e state; + logic [71:0] req; + logic [32:0] resp; + } src_t; + typedef struct packed { + dst_state_e state; + logic [71:0] req; + logic [32:0] resp; + } dst_t; + src_t s_src_d, s_src_q; + dst_t s_dst_d, s_dst_q; + logic s_link_src_rst_n, s_link_dst_rst_n; + logic s_req_ready, s_req_valid, s_resp_ready, s_resp_valid; + logic [71:0] s_req_data; + logic [32:0] s_resp_data; + + cdc_reset_barrier #( + .STAGES(5) + ) u_barrier ( + .clk_a_i (src_clk_i), + .clk_b_i (dst_clk_i), + .rst_n_i (src_rst_n_i && dst_rst_n_i), + .release_i(1'b1), + .rst_a_n_o(s_link_src_rst_n), + .rst_b_n_o(s_link_dst_rst_n) + ); + cdc_2phase #( + .DATA_WIDTH(72) + ) u_req_cdc ( + .src_clk_i (src_clk_i), + .src_rst_n_i(s_link_src_rst_n), + .src_data_i (s_src_q.req), + .src_valid_i(s_src_q.state == SrcSend), + .src_ready_o(s_req_ready), + .dst_clk_i (dst_clk_i), + .dst_rst_n_i(s_link_dst_rst_n), + .dst_data_o (s_req_data), + .dst_valid_o(s_req_valid), + .dst_ready_i(s_dst_q.state == DstIdle) + ); + cdc_2phase #( + .DATA_WIDTH(33) + ) u_resp_cdc ( + .src_clk_i (dst_clk_i), + .src_rst_n_i(s_link_dst_rst_n), + .src_data_i (s_dst_q.resp), + .src_valid_i(s_dst_q.state == DstResponse), + .src_ready_o(s_resp_ready), + .dst_clk_i (src_clk_i), + .dst_rst_n_i(s_link_src_rst_n), + .dst_data_o (s_resp_data), + .dst_valid_o(s_resp_valid), + .dst_ready_i(s_src_q.state == SrcWait) + ); + assign ready_o = s_link_src_rst_n && s_req_ready; + assign idle_o = (s_src_q.state == SrcIdle) && s_req_ready && !s_resp_valid; + assign src_apb4.pready = s_src_q.state == SrcResponse; + assign src_apb4.pslverr = s_src_q.resp[32]; + assign src_apb4.prdata = s_src_q.resp[31:0]; + assign {dst_apb4.paddr, dst_apb4.pprot, dst_apb4.pwrite, + dst_apb4.pwdata, dst_apb4.pstrb} = s_dst_q.req; + assign dst_apb4.psel = s_link_dst_rst_n && + ((s_dst_q.state == DstSetup) || (s_dst_q.state == DstAccess)); + assign dst_apb4.penable = s_link_dst_rst_n && (s_dst_q.state == DstAccess); + + always_comb begin + s_src_d = s_src_q; + unique case (s_src_q.state) + SrcIdle: + if (src_apb4.psel && src_apb4.penable) begin + s_src_d.req = { + src_apb4.paddr, src_apb4.pprot, src_apb4.pwrite, src_apb4.pwdata, src_apb4.pstrb + }; + s_src_d.resp = {1'b1, 32'd0}; + s_src_d.state = disabled_i ? SrcResponse : SrcSend; + end + SrcSend: if (s_req_ready) s_src_d.state = SrcWait; + SrcWait: + if (s_resp_valid) begin + s_src_d.resp = s_resp_data; + s_src_d.state = SrcResponse; + end + SrcResponse: s_src_d.state = SrcIdle; + default: s_src_d.state = SrcIdle; + endcase + if (((s_src_q.state == SrcSend) || (s_src_q.state == SrcWait)) && !s_link_src_rst_n) begin + s_src_d.resp = {1'b1, 32'd0}; + s_src_d.state = SrcResponse; + end + end + always_comb begin + s_dst_d = s_dst_q; + unique case (s_dst_q.state) + DstIdle: + if (s_req_valid) begin + s_dst_d.req = s_req_data; + s_dst_d.state = DstSetup; + end + DstSetup: s_dst_d.state = DstAccess; + DstAccess: + if (dst_apb4.pready) begin + s_dst_d.resp = {dst_apb4.pslverr, dst_apb4.prdata}; + s_dst_d.state = DstResponse; + end + DstResponse: if (s_resp_ready) s_dst_d.state = DstIdle; + default: s_dst_d.state = DstIdle; + endcase + end + dffr #( + .DATA_WIDTH($bits(src_t)) + ) u_src ( + .clk_i (src_clk_i), + .rst_n_i(src_rst_n_i), + .dat_i (s_src_d), + .dat_o (s_src_q) + ); + dffr #( + .DATA_WIDTH($bits(dst_t)) + ) u_dst ( + .clk_i (dst_clk_i), + .rst_n_i(s_link_dst_rst_n), + .dat_i (s_dst_d), + .dat_o (s_dst_q) + ); +endmodule diff --git a/rtl/tiny/top/tiny_axi4_admission.sv b/rtl/tiny/top/tiny_axi4_admission.sv new file mode 100644 index 00000000..5824180a --- /dev/null +++ b/rtl/tiny/top/tiny_axi4_admission.sv @@ -0,0 +1,153 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// One retained address and terminal response per direction. Blocking affects +// only upstream acceptance; already accepted addresses remain VALID downstream +// and write data/responses drain. Unaccepted requests never enter idle accounting. +module tiny_axi4_admission ( + input logic clk_i, + input logic rst_n_i, + input logic block_i, + output logic idle_o, + axi4_if.slave source, + axi4_if.master sink +); + typedef struct packed { + logic [0:0] id; + logic [31:0] addr; + logic [7:0] len; + logic [2:0] size; + logic [1:0] burst; + logic [0:0] lock; + logic [3:0] cache; + logic [2:0] prot; + logic [3:0] qos; + logic [3:0] region; + logic [0:0] user; + } address_t; + address_t s_aw_d, s_aw_q, s_ar_d, s_ar_q; + logic [1:0] s_pending_d, s_pending_q, s_sent_d, s_sent_q; + assign source.awready = rst_n_i && !block_i && !s_pending_q[0]; + assign source.arready = rst_n_i && !block_i && !s_pending_q[1]; + assign sink.awvalid = s_pending_q[0] && !s_sent_q[0]; + assign sink.arvalid = s_pending_q[1] && !s_sent_q[1]; + assign sink.awid = s_aw_q.id; + assign sink.awaddr = s_aw_q.addr; + assign sink.awlen = s_aw_q.len; + assign sink.awsize = s_aw_q.size; + assign sink.awburst = s_aw_q.burst; + assign sink.awlock = s_aw_q.lock; + assign sink.awcache = s_aw_q.cache; + assign sink.awprot = s_aw_q.prot; + assign sink.awqos = s_aw_q.qos; + assign sink.awregion = s_aw_q.region; + assign sink.awuser = s_aw_q.user; + assign sink.arid = s_ar_q.id; + assign sink.araddr = s_ar_q.addr; + assign sink.arlen = s_ar_q.len; + assign sink.arsize = s_ar_q.size; + assign sink.arburst = s_ar_q.burst; + assign sink.arlock = s_ar_q.lock; + assign sink.arcache = s_ar_q.cache; + assign sink.arprot = s_ar_q.prot; + assign sink.arqos = s_ar_q.qos; + assign sink.arregion = s_ar_q.region; + assign sink.aruser = s_ar_q.user; + assign sink.wvalid = source.wvalid && s_pending_q[0] && + (s_sent_q[0] || (sink.awvalid && sink.awready)); + assign source.wready = sink.wready && s_pending_q[0] && + (s_sent_q[0] || (sink.awvalid && sink.awready)); + assign sink.wdata = source.wdata; + assign sink.wstrb = source.wstrb; + assign sink.wlast = source.wlast; + assign sink.wuser = source.wuser; + assign sink.bready = source.bready; + assign sink.rready = source.rready; + assign source.bid = sink.bid; + assign source.bresp = sink.bresp; + assign source.buser = sink.buser; + assign source.bvalid = sink.bvalid; + assign source.rid = sink.rid; + assign source.rdata = sink.rdata; + assign source.rresp = sink.rresp; + assign source.rlast = sink.rlast; + assign source.ruser = sink.ruser; + assign source.rvalid = sink.rvalid; + assign idle_o = s_pending_q == 0; + always_comb begin + s_aw_d = s_aw_q; + s_ar_d = s_ar_q; + s_pending_d = s_pending_q; + s_sent_d = s_sent_q; + if (source.bvalid && source.bready) begin + s_pending_d[0] = 1'b0; + s_sent_d[0] = 1'b0; + end + if (source.rvalid && source.rready && source.rlast) begin + s_pending_d[1] = 1'b0; + s_sent_d[1] = 1'b0; + end + if (sink.awvalid && sink.awready) s_sent_d[0] = 1'b1; + if (sink.arvalid && sink.arready) s_sent_d[1] = 1'b1; + if (source.awvalid && source.awready) begin + s_pending_d[0] = 1'b1; + s_aw_d.id = source.awid; + s_aw_d.addr = source.awaddr; + s_aw_d.len = source.awlen; + s_aw_d.size = source.awsize; + s_aw_d.burst = source.awburst; + s_aw_d.lock = source.awlock; + s_aw_d.cache = source.awcache; + s_aw_d.prot = source.awprot; + s_aw_d.qos = source.awqos; + s_aw_d.region = source.awregion; + s_aw_d.user = source.awuser; + end + if (source.arvalid && source.arready) begin + s_pending_d[1] = 1'b1; + s_ar_d.id = source.arid; + s_ar_d.addr = source.araddr; + s_ar_d.len = source.arlen; + s_ar_d.size = source.arsize; + s_ar_d.burst = source.arburst; + s_ar_d.lock = source.arlock; + s_ar_d.cache = source.arcache; + s_ar_d.prot = source.arprot; + s_ar_d.qos = source.arqos; + s_ar_d.region = source.arregion; + s_ar_d.user = source.aruser; + end + end + dffr #( + .DATA_WIDTH($bits(address_t)) + ) u_aw ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_aw_d), + .dat_o (s_aw_q) + ); + dffr #( + .DATA_WIDTH($bits(address_t)) + ) u_ar ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_ar_d), + .dat_o (s_ar_q) + ); + dffr #( + .DATA_WIDTH(2) + ) u_pending ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_pending_d), + .dat_o (s_pending_q) + ); + dffr #( + .DATA_WIDTH(2) + ) u_sent ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_sent_d), + .dat_o (s_sent_q) + ); +endmodule diff --git a/rtl/tiny/top/tiny_axi4_async_bridge.sv b/rtl/tiny/top/tiny_axi4_async_bridge.sv new file mode 100644 index 00000000..c95a39b8 --- /dev/null +++ b/rtl/tiny/top/tiny_axi4_async_bridge.sv @@ -0,0 +1,432 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +module tiny_axi4_async_bridge #( + parameter int unsigned AddrWidth = 32, + parameter int unsigned DataWidth = 32, + parameter int unsigned IdWidth = 1, + parameter int unsigned UserWidth = 1, + parameter int unsigned FifoDepth = 4 +) ( + // verilog_format: off -- preserve the source/destination clock boundary columns + input logic src_clk_i, + input logic src_rst_n_i, + input logic dst_clk_i, + input logic dst_rst_n_i, + input logic clear_i, + output logic clear_busy_o, + output logic ready_o, + output logic [7:0] epoch_o, + axi4_if.slave src_axi4, + axi4_if.master dst_axi4 + // verilog_format: on +); + localparam int unsigned StrbWidth = DataWidth / 8; + localparam int unsigned AwWidth = IdWidth + AddrWidth + 8 + 3 + 2 + 1 + 4 + 3 + 4 + 4 + UserWidth; + localparam int unsigned WWidth = DataWidth + StrbWidth + 1 + UserWidth; + localparam int unsigned BWidth = IdWidth + 2 + UserWidth; + localparam int unsigned ArWidth = AwWidth; + localparam int unsigned RWidth = IdWidth + DataWidth + 2 + 1 + UserWidth; + + logic [AwWidth-1:0] s_aw_src_data; + logic [AwWidth-1:0] s_aw_dst_data; + logic [ WWidth-1:0] s_w_src_data; + logic [ WWidth-1:0] s_w_dst_data; + logic [ BWidth-1:0] s_b_dst_data; + logic [ BWidth-1:0] s_b_src_data; + logic [ArWidth-1:0] s_ar_src_data; + logic [ArWidth-1:0] s_ar_dst_data; + logic [ RWidth-1:0] s_r_dst_data; + logic [ RWidth-1:0] s_r_src_data; + logic s_clear_src; + logic s_clear_dst; + logic s_clear_q; + logic s_clear_dst_q; + logic s_clear_src_pulse; + logic s_clear_dst_pulse; + logic s_dst_rst_n_src_q; + logic s_dst_reset_evt; + logic [7:0] s_epoch_d, s_epoch_q; + logic s_aw_src_clear_busy, s_aw_dst_clear_busy; + logic s_w_src_clear_busy, s_w_dst_clear_busy; + logic s_b_src_clear_busy, s_b_dst_clear_busy; + logic s_ar_src_clear_busy, s_ar_dst_clear_busy; + logic s_r_src_clear_busy, s_r_dst_clear_busy; + logic s_dst_busy_src; + logic s_link_src_rst_n, s_link_dst_rst_n; + logic s_src_link_rst_n; + logic s_src_accept; + logic s_aw_src_ready_i, s_w_src_ready_i, s_ar_src_ready_i; + logic s_b_src_valid, s_r_src_valid; + logic s_write_pending_d, s_write_pending_q; + logic s_read_pending_d, s_read_pending_q; + logic s_abort_write_d, s_abort_write_q; + logic s_abort_read_d, s_abort_read_q; + logic [IdWidth-1:0] s_write_id_d, s_write_id_q; + logic [IdWidth-1:0] s_read_id_d, s_read_id_q; + logic [8:0] s_write_left_d, s_write_left_q, s_read_left_d, s_read_left_q; + logic [BWidth-1:0] s_b_hold_d, s_b_hold_q; + logic [RWidth-1:0] s_r_hold_d, s_r_hold_q; + logic s_b_hold_valid_d, s_b_hold_valid_q, s_r_hold_valid_d, s_r_hold_valid_q; + logic s_b_take, s_r_take, s_abort_evt; + logic s_clear_wait_reset_d, s_clear_wait_reset_q; + + cdc_reset_barrier #( + .STAGES(5) + ) u_link_barrier ( + .clk_a_i (src_clk_i), + .clk_b_i (dst_clk_i), + .rst_n_i (src_rst_n_i && dst_rst_n_i), + .release_i(1'b1), + .rst_a_n_o(s_link_src_rst_n), + .rst_b_n_o(s_link_dst_rst_n) + ); + assign s_src_link_rst_n = s_link_src_rst_n; + assign s_src_accept = s_src_link_rst_n && + !clear_busy_o && !s_abort_write_q && !s_abort_read_q && !s_abort_evt && !s_clear_wait_reset_q; + assign ready_o = s_src_accept; + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_busy_sync ( + .clk_i(src_clk_i), + .rst_n_i(src_rst_n_i), + .dat_i(s_aw_dst_clear_busy || s_w_dst_clear_busy || s_b_dst_clear_busy || + s_ar_dst_clear_busy || s_r_dst_clear_busy || !s_link_dst_rst_n), + .dat_o(s_dst_busy_src) + ); + + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_clear_src_sync ( + .clk_i (src_clk_i), + .rst_n_i(src_rst_n_i), + .dat_i (clear_i), + .dat_o (s_clear_src) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_clear_sync ( + .clk_i (dst_clk_i), + .rst_n_i(dst_rst_n_i), + .dat_i (clear_i), + .dat_o (s_clear_dst) + ); + assign s_clear_src_pulse = s_clear_src && !s_clear_q; + assign s_clear_dst_pulse = s_clear_dst && !s_clear_dst_q; + assign s_dst_reset_evt = s_dst_rst_n_src_q && !s_link_src_rst_n; + assign s_abort_evt = s_dst_reset_evt || s_clear_src_pulse; + + assign src_axi4.awready = s_src_accept && !s_write_pending_q && s_aw_src_ready_i; + // Accepted write data must drain even when its destination session is lost. + // Never report B until all AWLEN+1 source beats have been consumed. + assign src_axi4.wready = s_write_pending_q && s_write_left_q != 0 && + (s_abort_write_q || (s_src_accept && s_w_src_ready_i)); + assign src_axi4.arready = s_src_accept && !s_read_pending_q && s_ar_src_ready_i; + + // These registers belong to the surviving source session. Remote reset may + // discard FIFO payload, but cannot revoke VALID or rewrite a held response. + assign {src_axi4.bid, src_axi4.bresp, src_axi4.buser} = s_b_hold_q; + assign {src_axi4.rid, src_axi4.rdata, src_axi4.rresp, + src_axi4.rlast, src_axi4.ruser} = s_r_hold_q; + assign src_axi4.bvalid = s_b_hold_valid_q; + assign src_axi4.rvalid = s_r_hold_valid_q; + assign s_b_take = (!s_b_hold_valid_q || src_axi4.bready) && s_write_pending_q && + !(src_axi4.bvalid && src_axi4.bready) && + s_write_left_q == 0 && !s_abort_write_q && !s_abort_evt && s_src_accept; + assign s_r_take = (!s_r_hold_valid_q || src_axi4.rready) && s_read_pending_q && + !(src_axi4.rvalid && src_axi4.rready && src_axi4.rlast) && + !s_abort_read_q && !s_abort_evt && s_src_accept; + + always_comb begin + s_write_pending_d = s_write_pending_q; + s_read_pending_d = s_read_pending_q; + s_abort_write_d = s_abort_write_q; + s_abort_read_d = s_abort_read_q; + s_write_id_d = s_write_id_q; + s_read_id_d = s_read_id_q; + s_write_left_d = s_write_left_q; + s_read_left_d = s_read_left_q; + s_b_hold_d = s_b_hold_q; + s_r_hold_d = s_r_hold_q; + s_b_hold_valid_d = s_b_hold_valid_q; + s_r_hold_valid_d = s_r_hold_valid_q; + s_clear_wait_reset_d = s_clear_wait_reset_q; + if (src_axi4.awvalid && src_axi4.awready) begin + s_write_pending_d = 1'b1; + s_write_id_d = src_axi4.awid; + s_write_left_d = {1'b0, src_axi4.awlen} + 9'd1; + end + if (src_axi4.arvalid && src_axi4.arready) begin + s_read_pending_d = 1'b1; + s_read_id_d = src_axi4.arid; + s_read_left_d = {1'b0, src_axi4.arlen} + 9'd1; + end + if (src_axi4.wvalid && src_axi4.wready) s_write_left_d = s_write_left_q - 9'd1; + if (src_axi4.bvalid && src_axi4.bready) begin + s_write_pending_d = 1'b0; + s_abort_write_d = 1'b0; + s_b_hold_valid_d = 1'b0; + end + if (src_axi4.rvalid && src_axi4.rready) begin + s_r_hold_valid_d = 1'b0; + s_read_left_d = s_read_left_q - 9'd1; + if (src_axi4.rlast) begin + s_read_pending_d = 1'b0; + s_abort_read_d = 1'b0; + end + end + // A source handshake is irrevocable. Abort only the work still pending + // after that handshake; do not create a second terminal response. + if (s_abort_evt) begin + s_abort_write_d = s_write_pending_d; + s_abort_read_d = s_read_pending_d; + end + // A FIFO clear alone cannot cancel a transaction already accepted by a + // remote AXI slave. Return local errors, but quarantine late responses + // until an actual destination reset establishes a new session. Normal + // drained clears and already exposed terminal responses need no reset. + if (s_clear_src_pulse && + ((s_write_pending_d && !s_b_hold_valid_q) || + (s_read_pending_d && !(s_r_hold_valid_q && src_axi4.rlast)))) + s_clear_wait_reset_d = 1'b1; + if (s_dst_reset_evt) s_clear_wait_reset_d = 1'b0; + if (s_b_take && s_b_src_valid) begin + s_b_hold_d = s_b_src_data; + s_b_hold_valid_d = 1'b1; + end else if (!s_b_hold_valid_d && s_abort_write_d && s_write_left_d == 0) begin + s_b_hold_d = {s_write_id_q, 2'b11, {UserWidth{1'b0}}}; + s_b_hold_valid_d = 1'b1; + end + if (s_r_take && s_r_src_valid) begin + s_r_hold_d = s_r_src_data; + s_r_hold_valid_d = 1'b1; + end else if (!s_r_hold_valid_d && s_abort_read_d && s_read_left_d != 0) begin + s_r_hold_d = { + s_read_id_q, {DataWidth{1'b0}}, 2'b11, (s_read_left_d == 9'd1), {UserWidth{1'b0}} + }; + s_r_hold_valid_d = 1'b1; + end + end + + assign s_aw_src_data = { + src_axi4.awid, + src_axi4.awaddr, + src_axi4.awlen, + src_axi4.awsize, + src_axi4.awburst, + src_axi4.awlock, + src_axi4.awcache, + src_axi4.awprot, + src_axi4.awqos, + src_axi4.awregion, + src_axi4.awuser + }; + assign { + dst_axi4.awid, + dst_axi4.awaddr, + dst_axi4.awlen, + dst_axi4.awsize, + dst_axi4.awburst, + dst_axi4.awlock, + dst_axi4.awcache, + dst_axi4.awprot, + dst_axi4.awqos, + dst_axi4.awregion, + dst_axi4.awuser + } = s_aw_dst_data; + + cdc_fifo_warm_flush #( + .DATA_WIDTH (AwWidth), + .BUFFER_DEPTH(FifoDepth) + ) u_aw_fifo ( + .src_clk_i (src_clk_i), + .src_rst_n_i (s_src_link_rst_n), + .src_clear_i (s_clear_src_pulse), + .src_clear_busy_o(s_aw_src_clear_busy), + .src_data_i (s_aw_src_data), + .src_valid_i (src_axi4.awvalid && s_src_accept && !s_write_pending_q), + .src_ready_o (s_aw_src_ready_i), + .dst_clk_i (dst_clk_i), + .dst_rst_n_i (dst_rst_n_i), + .dst_clear_busy_o(s_aw_dst_clear_busy), + .dst_data_o (s_aw_dst_data), + .dst_valid_o (dst_axi4.awvalid), + .dst_ready_i (dst_axi4.awready) + ); + + assign s_w_src_data = {src_axi4.wdata, src_axi4.wstrb, src_axi4.wlast, src_axi4.wuser}; + assign {dst_axi4.wdata, dst_axi4.wstrb, dst_axi4.wlast, dst_axi4.wuser} = s_w_dst_data; + + cdc_fifo_warm_flush #( + .DATA_WIDTH (WWidth), + .BUFFER_DEPTH(FifoDepth) + ) u_w_fifo ( + .src_clk_i(src_clk_i), + .src_rst_n_i(s_src_link_rst_n), + .src_clear_i(s_clear_src_pulse), + .src_clear_busy_o(s_w_src_clear_busy), + .src_data_i(s_w_src_data), + .src_valid_i(src_axi4.wvalid && s_src_accept && s_write_pending_q && s_write_left_q != 0), + .src_ready_o(s_w_src_ready_i), + .dst_clk_i(dst_clk_i), + .dst_rst_n_i(dst_rst_n_i), + .dst_clear_busy_o(s_w_dst_clear_busy), + .dst_data_o(s_w_dst_data), + .dst_valid_o(dst_axi4.wvalid), + .dst_ready_i(dst_axi4.wready) + ); + + assign s_b_dst_data = {dst_axi4.bid, dst_axi4.bresp, dst_axi4.buser}; + cdc_fifo_warm_flush #( + .DATA_WIDTH (BWidth), + .BUFFER_DEPTH(FifoDepth) + ) u_b_fifo ( + .src_clk_i (dst_clk_i), + .src_rst_n_i (dst_rst_n_i), + .src_clear_i (s_clear_dst_pulse), + .src_clear_busy_o(s_b_dst_clear_busy), + .src_data_i (s_b_dst_data), + .src_valid_i (dst_axi4.bvalid), + .src_ready_o (dst_axi4.bready), + .dst_clk_i (src_clk_i), + .dst_rst_n_i (s_src_link_rst_n), + .dst_clear_busy_o(s_b_src_clear_busy), + .dst_data_o (s_b_src_data), + .dst_valid_o (s_b_src_valid), + .dst_ready_i (s_b_take) + ); + + assign s_ar_src_data = { + src_axi4.arid, + src_axi4.araddr, + src_axi4.arlen, + src_axi4.arsize, + src_axi4.arburst, + src_axi4.arlock, + src_axi4.arcache, + src_axi4.arprot, + src_axi4.arqos, + src_axi4.arregion, + src_axi4.aruser + }; + assign { + dst_axi4.arid, + dst_axi4.araddr, + dst_axi4.arlen, + dst_axi4.arsize, + dst_axi4.arburst, + dst_axi4.arlock, + dst_axi4.arcache, + dst_axi4.arprot, + dst_axi4.arqos, + dst_axi4.arregion, + dst_axi4.aruser + } = s_ar_dst_data; + + cdc_fifo_warm_flush #( + .DATA_WIDTH (ArWidth), + .BUFFER_DEPTH(FifoDepth) + ) u_ar_fifo ( + .src_clk_i (src_clk_i), + .src_rst_n_i (s_src_link_rst_n), + .src_clear_i (s_clear_src_pulse), + .src_clear_busy_o(s_ar_src_clear_busy), + .src_data_i (s_ar_src_data), + .src_valid_i (src_axi4.arvalid && s_src_accept && !s_read_pending_q), + .src_ready_o (s_ar_src_ready_i), + .dst_clk_i (dst_clk_i), + .dst_rst_n_i (dst_rst_n_i), + .dst_clear_busy_o(s_ar_dst_clear_busy), + .dst_data_o (s_ar_dst_data), + .dst_valid_o (dst_axi4.arvalid), + .dst_ready_i (dst_axi4.arready) + ); + + assign s_r_dst_data = { + dst_axi4.rid, dst_axi4.rdata, dst_axi4.rresp, dst_axi4.rlast, dst_axi4.ruser + }; + cdc_fifo_warm_flush #( + .DATA_WIDTH (RWidth), + .BUFFER_DEPTH(FifoDepth) + ) u_r_fifo ( + .src_clk_i (dst_clk_i), + .src_rst_n_i (dst_rst_n_i), + .src_clear_i (s_clear_dst_pulse), + .src_clear_busy_o(s_r_dst_clear_busy), + .src_data_i (s_r_dst_data), + .src_valid_i (dst_axi4.rvalid), + .src_ready_o (dst_axi4.rready), + .dst_clk_i (src_clk_i), + .dst_rst_n_i (s_src_link_rst_n), + .dst_clear_busy_o(s_r_src_clear_busy), + .dst_data_o (s_r_src_data), + .dst_valid_o (s_r_src_valid), + .dst_ready_i (s_r_take) + ); + + assign clear_busy_o = s_aw_src_clear_busy || s_w_src_clear_busy || + s_b_src_clear_busy || s_ar_src_clear_busy || + s_r_src_clear_busy || s_dst_busy_src; + assign s_epoch_d = ((s_clear_src_pulse && !clear_busy_o) || s_dst_reset_evt) ? + s_epoch_q + 1'b1 : s_epoch_q; + assign epoch_o = s_epoch_q; + + always_ff @(posedge src_clk_i or negedge src_rst_n_i) begin + if (!src_rst_n_i) begin + s_clear_q <= 1'b0; + s_dst_rst_n_src_q <= 1'b0; + s_epoch_q <= '0; + s_write_pending_q <= 1'b0; + s_read_pending_q <= 1'b0; + s_abort_write_q <= 1'b0; + s_abort_read_q <= 1'b0; + s_write_id_q <= '0; + s_read_id_q <= '0; + s_write_left_q <= '0; + s_read_left_q <= '0; + s_b_hold_q <= '0; + s_r_hold_q <= '0; + s_b_hold_valid_q <= 1'b0; + s_r_hold_valid_q <= 1'b0; + s_clear_wait_reset_q <= 1'b0; + end else begin + s_clear_q <= s_clear_src; + s_dst_rst_n_src_q <= s_link_src_rst_n; + s_epoch_q <= s_epoch_d; + s_write_pending_q <= s_write_pending_d; + s_read_pending_q <= s_read_pending_d; + s_abort_write_q <= s_abort_write_d; + s_abort_read_q <= s_abort_read_d; + s_write_id_q <= s_write_id_d; + s_read_id_q <= s_read_id_d; + s_write_left_q <= s_write_left_d; + s_read_left_q <= s_read_left_d; + s_b_hold_q <= s_b_hold_d; + s_r_hold_q <= s_r_hold_d; + s_b_hold_valid_q <= s_b_hold_valid_d; + s_r_hold_valid_q <= s_r_hold_valid_d; + s_clear_wait_reset_q <= s_clear_wait_reset_d; + end + end + + always_ff @(posedge dst_clk_i or negedge dst_rst_n_i) begin + if (!dst_rst_n_i) begin + s_clear_dst_q <= 1'b0; + end else begin + s_clear_dst_q <= s_clear_dst; + end + end + +`ifndef SYNTHESIS + initial begin + if ((AddrWidth < 1) || (DataWidth < 8) || ((DataWidth % 8) != 0) || + (IdWidth < 1) || (UserWidth < 1) || (FifoDepth < 2) || + ((FifoDepth & (FifoDepth - 1)) != 0)) begin + $fatal(1, "axi4_async_bridge: invalid interface or FIFO parameters"); + end + end +`endif +endmodule diff --git a/rtl/tiny/top/tiny_clock_gate.sv b/rtl/tiny/top/tiny_clock_gate.sv new file mode 100644 index 00000000..26b8eae0 --- /dev/null +++ b/rtl/tiny/top/tiny_clock_gate.sv @@ -0,0 +1,21 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 +module tiny_clock_gate ( + input logic clk_i, + input logic rst_n_i, + input logic gate_i, + output logic clk_o, + output logic gated_o +); + logic s_gate_q; + ndffr #( + .DATA_WIDTH(1) + ) u_gate ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (gate_i), + .dat_o (s_gate_q) + ); + assign clk_o = clk_i && !s_gate_q; + assign gated_o = s_gate_q; +endmodule diff --git a/rtl/tiny/top/tiny_clock_output.sv b/rtl/tiny/top/tiny_clock_output.sv new file mode 100644 index 00000000..c07321eb --- /dev/null +++ b/rtl/tiny/top/tiny_clock_output.sv @@ -0,0 +1,66 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// SYSCTRL validates the source/divisor and requires disable-before-change. +// Synchronize enable to the selected source, configuring the divider while +// disabled. Both package routes consume this one output and enable. +module tiny_clock_output ( + input logic clk_ref_i, + input logic clk_sys_i, + input logic clk_mem_i, + input logic clk_periph_i, + input logic rst_n_i, + input logic [31:0] control_i, + output logic clk_o, + output logic enable_o +); + logic s_src_clk, s_enabled, s_en_req; + logic [19:0] s_cfg_d, s_cfg_q; + // Retain the source/divisor through disable acknowledgement. Source zero + // only disables the output; it does not change a still-running clock. + assign s_cfg_d = control_i[2:0] != 0 ? + {1'b1, control_i[23:8], control_i[2:0]} : {1'b0, s_cfg_q[18:0]}; + dffrc #( + .DATA_WIDTH(20), + .RESET_VAL ({1'b0, 16'd1, 3'd1}) + ) u_config ( + .clk_i (clk_ref_i), + .rst_n_i(rst_n_i), + .dat_i (s_cfg_d), + .dat_o (s_cfg_q) + ); + assign s_src_clk = s_cfg_q[2:0] == 3 ? clk_mem_i : + s_cfg_q[2:0] == 4 ? clk_periph_i : s_cfg_q[2:0] == 2 ? clk_sys_i : clk_ref_i; + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_enable_sync ( + .clk_i (s_src_clk), + .rst_n_i(rst_n_i), + .dat_i (s_cfg_q[19]), + .dat_o (s_en_req) + ); + // Finish the current output high pulse before disabling the Common divider. + ndffr #( + .DATA_WIDTH(1) + ) u_enable ( + .clk_i (s_src_clk), + .rst_n_i(rst_n_i), + .dat_i (s_en_req || clk_o), + .dat_o (s_enabled) + ); + clock_divider #( + .DIV_WIDTH(16), + .RESET_DIV(16'd1) + ) u_divider ( + .clk_i (s_src_clk), + .rst_n_i (rst_n_i), + .enable_i (s_enabled), + .div_i (s_cfg_q[18:3]), + .div_valid_i(!s_enabled), + .div_ready_o(), + .clk_o (clk_o), + .count_o () + ); + assign enable_o = s_enabled; +endmodule diff --git a/rtl/tiny/top/tiny_clock_reset_subsystem.sv b/rtl/tiny/top/tiny_clock_reset_subsystem.sv new file mode 100644 index 00000000..291ec047 --- /dev/null +++ b/rtl/tiny/top/tiny_clock_reset_subsystem.sv @@ -0,0 +1,407 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// Control runs on normalized REF24. All live changes drain first, park SYS, +// prepare conservative divisors, qualify the requested source, and commit last. +// A lost PLL resets the data session before resetting the safe mux. REF/XIN +// loss cannot be recovered by logic which depends on that same input. +module tiny_clock_reset_subsystem #( + parameter int unsigned RefClkHz = 24_000_000, + parameter bit HavePll = 1'b0, + parameter int unsigned SwitchCycles = 128 +) ( + input logic ref_clk_i, + input logic rst_n_i, + input logic [ 1:0] profile_i, + input logic apply_i, + input logic idle_i, + input logic clients_idle_i, + input logic warm_reset_i, + input logic system_request_i, + input logic command_ack_i, + input logic [31:0] timeout_i, + output logic [ 1:0] current_profile_o, + output logic busy_o, + output logic fault_o, + output logic [ 4:0] fault_code_o, + output logic done_o, + output logic block_o, + output logic ref_clk_o, + output logic ref_rst_n_o, + output logic system_reset_n_o, + output logic pll_locked_o, + output logic pll_selected_o, + output logic sys_clk_o, + output logic mem_clk_o, + output logic pclk_o, + output logic sys_rst_n_o, + output logic mem_rst_n_o, + output logic pclk_rst_n_o, + output logic [31:0] xin_hz_o, + output logic [31:0] ref_hz_o, + output logic [31:0] sys_hz_o, + output logic [31:0] mem_hz_o, + output logic [31:0] pclk_hz_o +); + typedef enum logic [3:0] { + Idle, + Drain, + Park, + Program, + Unlock, + Lock, + Divide, + Switch, + Commit, + Recover, + WarmDrain + } state_e; + typedef struct packed { + state_e state; + logic [1:0] requested; + logic [1:0] current; + logic fast; + logic mem_div; + logic [1:0] pclk_div; + logic [2:0] pll_sel; + logic pll_apply; + logic reset_n; + logic done; + logic [4:0] fault; + logic [31:0] count; + } control_t; + localparam control_t ResetValue = { + Idle, 2'd0, 2'd0, 1'b0, 1'b0, 2'd0, 3'd5, 1'b0, 1'b1, 1'b0, 5'd0, 32'd0 + }; + control_t s_ctrl_d, s_ctrl_q; + logic s_pll_capable, s_pll_lock, s_pll_clk, s_fast_clk; + logic s_div2_clk, s_div4_clk, s_pclk_mid; + logic s_mux_rst_n; + logic [31:0] s_limit, s_req_hz; + logic s_idle_ref, s_clients_idle_ref; + logic s_cmd_ack_ref; + logic s_fault_sticky_d, s_fault_sticky_q; + + if (RefClkHz == 24_000_000) begin : gen_ref_direct + assign ref_clk_o = ref_clk_i; + end else begin : gen_ref_divided + (* keep_hierarchy = 1 *) + clock_divider #( + .DIV_WIDTH(2), + .RESET_DIV(2'(RefClkHz / 48_000_000)) + ) u_ref_div ( + .clk_i (ref_clk_i), + .rst_n_i (rst_n_i), + .enable_i (1'b1), + .div_i (2'(RefClkHz / 48_000_000)), + .div_valid_i(1'b0), + .div_ready_o(), + .clk_o (ref_clk_o), + .count_o () + ); + end + rst_sync #( + .STAGE(5) + ) u_ref_rst ( + .clk_i (ref_clk_o), + .rst_n_i(rst_n_i), + .rst_n_o(ref_rst_n_o) + ); +`ifdef HAVE_PLL + tc_pll #( + .UseIcs55EcosPll(1'b1), + .Ics55Parked (1'b0) + ) u_pll ( + .fref_i (ref_clk_o), + .rst_n_i (ref_rst_n_o), + .cfg_sel_i (s_ctrl_q.pll_sel), + .cfg_apply_i (s_ctrl_q.pll_apply), + .pll_capable_o(s_pll_capable), + .pll_lock_o (s_pll_lock), + .pll_clk_o (s_pll_clk) + ); +`else + assign s_pll_capable = 1'b0; + assign s_pll_lock = 1'b0; + assign s_pll_clk = 1'b0; +`endif + assign s_fast_clk = HavePll ? s_pll_clk : ref_clk_i; + assign s_mux_rst_n = ref_rst_n_o && s_ctrl_q.reset_n; + (* keep_hierarchy = 1 *) + safe_clock_mux u_sys_mux ( + .clk0_i (ref_clk_o), + .clk1_i (s_fast_clk), + .rst_n_i (s_mux_rst_n), + .select_i(s_ctrl_q.fast), + .clk_o (sys_clk_o) + ); + // Both divided sources remain live across handover, including returning to + // SAFE24. Disabling a selected source would deadlock Common safe_clock_mux. + clock_divider #( + .DIV_WIDTH(1), + .RESET_DIV(1'b1) + ) u_div2 ( + .clk_i (sys_clk_o), + .rst_n_i (sys_rst_n_o), + .enable_i (1'b1), + .div_i (1'b1), + .div_valid_i(1'b0), + .div_ready_o(), + .count_o (), + .clk_o (s_div2_clk) + ); + clock_divider #( + .DIV_WIDTH(2), + .RESET_DIV(2'd2) + ) u_div4 ( + .clk_i (sys_clk_o), + .rst_n_i (sys_rst_n_o), + .enable_i (1'b1), + .div_i (2'd2), + .div_valid_i(1'b0), + .div_ready_o(), + .count_o (), + .clk_o (s_div4_clk) + ); + (* keep_hierarchy = 1 *) + safe_clock_mux u_mem_mux ( + .clk0_i (sys_clk_o), + .clk1_i (s_div2_clk), + .rst_n_i (s_mux_rst_n), + .select_i(s_ctrl_q.mem_div), + .clk_o (mem_clk_o) + ); + (* keep_hierarchy = 1 *) + safe_clock_mux u_pclk_div2_mux ( + .clk0_i (sys_clk_o), + .clk1_i (s_div2_clk), + .rst_n_i (s_mux_rst_n), + .select_i(s_ctrl_q.pclk_div != 0), + .clk_o (s_pclk_mid) + ); + (* keep_hierarchy = 1 *) + safe_clock_mux u_pclk_mux ( + .clk0_i (s_pclk_mid), + .clk1_i (s_div4_clk), + .rst_n_i (s_mux_rst_n), + .select_i(s_ctrl_q.pclk_div == 2), + .clk_o (pclk_o) + ); + assign system_reset_n_o = rst_n_i && s_ctrl_q.reset_n; + rst_sync #( + .STAGE(5) + ) u_sys_rst ( + .clk_i (sys_clk_o), + .rst_n_i(system_reset_n_o), + .rst_n_o(sys_rst_n_o) + ); + rst_sync #( + .STAGE(5) + ) u_mem_rst ( + .clk_i (mem_clk_o), + .rst_n_i(system_reset_n_o), + .rst_n_o(mem_rst_n_o) + ); + rst_sync #( + .STAGE(5) + ) u_pclk_rst ( + .clk_i (pclk_o), + .rst_n_i(system_reset_n_o), + .rst_n_o(pclk_rst_n_o) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_idle_sync ( + .clk_i (ref_clk_o), + .rst_n_i(ref_rst_n_o), + .dat_i (idle_i), + .dat_o (s_idle_ref) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_client_sync ( + .clk_i (ref_clk_o), + .rst_n_i(ref_rst_n_o), + .dat_i (clients_idle_i), + .dat_o (s_clients_idle_ref) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_command_ack_sync ( + .clk_i (ref_clk_o), + .rst_n_i(ref_rst_n_o), + .dat_i (command_ack_i), + .dat_o (s_cmd_ack_ref) + ); + assign s_limit = timeout_i == 0 ? 32'd1 : timeout_i; + assign s_req_hz = s_ctrl_q.requested == 2 ? 32'd192_000_000 : + s_ctrl_q.requested == 3 ? 32'd240_000_000 : + s_ctrl_q.requested == 1 ? 32'(RefClkHz) : 32'd24_000_000; + always_comb begin + s_ctrl_d = s_ctrl_q; + s_ctrl_d.done = 1'b0; + s_ctrl_d.pll_apply = 1'b0; + s_ctrl_d.fault = 5'd0; + s_ctrl_d.count = s_ctrl_q.count + 32'd1; + unique case (s_ctrl_q.state) + Idle: begin + s_ctrl_d.count = 0; + if (system_request_i) s_ctrl_d.state = WarmDrain; + else if (apply_i) begin + s_ctrl_d.requested = profile_i; + if ((HavePll && profile_i == 1) || (profile_i >= 2 && (!HavePll || !s_pll_capable))) begin + s_ctrl_d.done = 1'b1; + s_ctrl_d.fault[0] = 1'b1; + end else s_ctrl_d.state = Drain; + end + end + WarmDrain: begin + if (s_ctrl_q.count >= 16 && s_cmd_ack_ref) begin + s_ctrl_d.reset_n = 1'b0; + s_ctrl_d.fast = 1'b0; + s_ctrl_d.count = 0; + s_ctrl_d.state = Recover; + end else if (s_ctrl_q.count >= s_limit) begin + s_ctrl_d.fault[1] = 1'b1; + s_ctrl_d.done = 1'b1; + s_ctrl_d.state = Idle; + end + end + Drain: begin + // Delay covers block propagation and the accepted command's response. + if (s_limit >= SwitchCycles && s_ctrl_q.count >= 16 && + s_idle_ref && s_clients_idle_ref) begin + s_ctrl_d.fast = 1'b0; + s_ctrl_d.count = 0; + s_ctrl_d.state = Park; + end else if (s_ctrl_q.count >= s_limit) begin + s_ctrl_d.fault[1] = 1'b1; + s_ctrl_d.done = 1'b1; + s_ctrl_d.state = Idle; + end + end + Park: + if (s_ctrl_q.count >= SwitchCycles) begin + s_ctrl_d.count = 0; + s_ctrl_d.state = s_ctrl_q.requested >= 2 ? Program : Divide; + end + Program: begin + s_ctrl_d.pll_sel = s_ctrl_q.requested == 2 ? 3'd5 : 3'd7; + s_ctrl_d.pll_apply = 1'b1; + s_ctrl_d.count = 0; + s_ctrl_d.state = Unlock; + end + Unlock: + if (!s_pll_lock) begin + s_ctrl_d.count = 0; + s_ctrl_d.state = Lock; + end else if (s_ctrl_q.count >= s_limit) begin + s_ctrl_d.requested = 0; + s_ctrl_d.fault[2] = 1'b1; + s_ctrl_d.count = 0; + s_ctrl_d.state = Divide; + end + Lock: + if (s_pll_lock) begin + s_ctrl_d.count = 0; + s_ctrl_d.state = Divide; + end else if (s_ctrl_q.count >= s_limit) begin + s_ctrl_d.requested = 0; + s_ctrl_d.fault[2] = 1'b1; + s_ctrl_d.count = 0; + s_ctrl_d.state = Divide; + end + Divide: begin + s_ctrl_d.mem_div = s_req_hz > 120_000_000; + s_ctrl_d.pclk_div = s_req_hz > 120_000_000 ? 2'd2 : s_req_hz > 60_000_000 ? 2'd1 : 2'd0; + if (s_ctrl_q.count >= SwitchCycles) begin + s_ctrl_d.fast = s_ctrl_q.requested != 0; + s_ctrl_d.count = 0; + s_ctrl_d.state = Switch; + end + end + Switch: + if (s_ctrl_q.count >= SwitchCycles) begin + s_ctrl_d.current = s_ctrl_q.requested; + s_ctrl_d.state = Commit; + s_ctrl_d.count = 0; + end + Commit: + if (s_ctrl_q.count >= SwitchCycles) begin + // Keep admissions blocked while domain-local rate snapshots cross. + s_ctrl_d.done = 1'b1; + s_ctrl_d.state = Idle; + end + Recover: begin + s_ctrl_d.fast = 1'b0; + s_ctrl_d.mem_div = 1'b0; + s_ctrl_d.pclk_div = 0; + s_ctrl_d.current = 0; + if (s_ctrl_q.count >= SwitchCycles && !warm_reset_i) begin + s_ctrl_d.reset_n = 1'b1; + s_ctrl_d.state = Idle; + end + end + default: begin + s_ctrl_d = ResetValue; + s_ctrl_d.reset_n = 1'b0; + s_ctrl_d.state = Recover; + s_ctrl_d.fault[4] = 1'b1; + end + endcase + if (s_ctrl_q.state != Recover && (warm_reset_i || + (HavePll && s_ctrl_q.fast && !s_pll_lock))) begin + // Only the session-reset path may break a stopped-clock mux deadlock. + s_ctrl_d.reset_n = 1'b0; + s_ctrl_d.fast = 1'b0; + s_ctrl_d.count = 0; + s_ctrl_d.state = Recover; + if (!warm_reset_i) s_ctrl_d.fault[3] = 1'b1; + end + if ((apply_i || system_request_i) && s_ctrl_q.state != Idle) s_ctrl_d.fault[0] = 1'b1; + end + dffrc #( + .DATA_WIDTH($bits(control_t)), + .RESET_VAL (ResetValue) + ) u_control ( + .clk_i (ref_clk_o), + .rst_n_i(ref_rst_n_o), + .dat_i (s_ctrl_d), + .dat_o (s_ctrl_q) + ); + assign s_fault_sticky_d = s_fault_sticky_q || (s_ctrl_q.fault != 0); + dffr #( + .DATA_WIDTH(1) + ) u_fault_sticky ( + .clk_i (ref_clk_o), + .rst_n_i(ref_rst_n_o), + .dat_i (s_fault_sticky_d), + .dat_o (s_fault_sticky_q) + ); + assign current_profile_o = s_ctrl_q.current; + assign busy_o = s_ctrl_q.state != Idle; + assign block_o = s_ctrl_q.state != Idle; + assign fault_o = s_fault_sticky_q; + assign fault_code_o = s_ctrl_q.fault; + assign done_o = s_ctrl_q.done; + assign pll_locked_o = s_pll_lock; + assign pll_selected_o = s_ctrl_q.fast && HavePll; + assign xin_hz_o = RefClkHz; + assign ref_hz_o = 24_000_000; + assign sys_hz_o = s_ctrl_q.current == 2 ? 32'd192_000_000 : + s_ctrl_q.current == 3 ? 32'd240_000_000 : + s_ctrl_q.current == 1 ? 32'(RefClkHz) : 32'd24_000_000; + assign mem_hz_o = sys_hz_o > 120_000_000 ? sys_hz_o / 2 : sys_hz_o; + assign pclk_hz_o = sys_hz_o > 120_000_000 ? sys_hz_o / 4 : + sys_hz_o > 60_000_000 ? sys_hz_o / 2 : sys_hz_o; +`ifndef SYNTHESIS + initial begin + if (!(RefClkHz == 24_000_000 || RefClkHz == 48_000_000 || RefClkHz == 96_000_000) || + (HavePll && RefClkHz != 24_000_000) || SwitchCycles < 128) + $fatal(1, "Tiny clock source configuration is invalid"); + end +`endif +endmodule diff --git a/rtl/tiny/top/tiny_cpu_mem.sv b/rtl/tiny/top/tiny_cpu_mem.sv index 9caae697..5a229270 100644 --- a/rtl/tiny/top/tiny_cpu_mem.sv +++ b/rtl/tiny/top/tiny_cpu_mem.sv @@ -5,10 +5,14 @@ // Independent one-operation I/D frontends. AHB write data belongs to the cycle // following address acceptance. Local stores complete only after macro commit. // Only non-SRAM work enters the tagged, non-posted slow-path merge. -module tiny_cpu_mem ( +module tiny_cpu_mem #( + parameter bit ClockPause = 1'b0 +) ( input logic clk_i, input logic rst_n_i, input logic quiesce_i, + input logic clock_pause_i, + output logic clock_idle_o, output logic idle_o, output logic [1:0] local_fault_valid_o, output logic [1:0][31:0] local_fault_addr_o, @@ -64,14 +68,23 @@ module tiny_cpu_mem ( .aclk (clk_i), .aresetn(rst_n_i) ); - logic s_adapter_idle; + logic s_adapter_idle; + logic s_clock_pause; + logic [1:0] s_clock_front_idle; + assign s_clock_pause = ClockPause && clock_pause_i; + // A pipeline-overlap address acknowledged with the preceding AHB response + // remains in the input register until resume. Only target admissions are + // blocked: every accepted bank/AXI operation and response still drains. + assign clock_idle_o = (&s_clock_front_idle) && (s_slow_q.state == SlowIdle) && s_adapter_idle; for (genvar port_index = 0; port_index < 2; port_index++) begin : gen_port assign local_fault_valid_o[port_index] = (s_req_q[port_index].state == ErrorLast) && !s_req_q[port_index].external_error; assign local_fault_addr_o[port_index] = s_req_q[port_index].addr; assign local_fault_write_o[port_index] = s_req_q[port_index].write; - assign s_slow_req[port_index] = s_req_q[port_index].state == SlowIssue; + assign s_slow_req[port_index] = (s_req_q[port_index].state == SlowIssue) && !s_clock_pause; + assign s_clock_front_idle[port_index] = (s_req_q[port_index].state == Idle) || + (s_req_q[port_index].state == LocalIssue) || (s_req_q[port_index].state == SlowIssue); assign s_front_idle[port_index] = (s_req_q[port_index].state == Idle) && (quiesce_i || !cpu[port_index].htrans[1]); assign s_err[port_index] = (s_req_q[port_index].state == ErrorFirst) || @@ -87,7 +100,8 @@ module tiny_cpu_mem ( assign cpu[port_index].hresp = s_err[port_index]; assign cpu[port_index].hrdata = (s_req_q[port_index].state == LocalWait) ? local_ports[port_index].rdata : s_slow_q.rdata; - assign local_ports[port_index].req_valid = s_req_q[port_index].state == LocalIssue; + assign local_ports[port_index].req_valid = (s_req_q[port_index].state == LocalIssue) && + !s_clock_pause; assign local_ports[port_index].addr = s_req_q[port_index].addr; assign local_ports[port_index].write = s_req_q[port_index].write; assign local_ports[port_index].wdata = s_req_q[port_index].data; @@ -118,7 +132,9 @@ module tiny_cpu_mem ( `SOC_ADDR_IS_SRAM(s_req_q[port_index].addr) ? LocalIssue : SlowIssue; end - LocalIssue: if (local_ports[port_index].req_ready) s_req_d[port_index].state = LocalWait; + LocalIssue: + if (local_ports[port_index].req_valid && local_ports[port_index].req_ready) + s_req_d[port_index].state = LocalWait; SlowIssue: if (s_slow_accept && s_slow_grant[port_index]) s_req_d[port_index].state = SlowWait; SlowWait: diff --git a/rtl/tiny/top/tiny_cpu_wrapper.sv b/rtl/tiny/top/tiny_cpu_wrapper.sv index 530d2b8e..20bbc7f3 100644 --- a/rtl/tiny/top/tiny_cpu_wrapper.sv +++ b/rtl/tiny/top/tiny_cpu_wrapper.sv @@ -13,11 +13,15 @@ module tiny_cpu_wrapper #( parameter int ExternalIrqCount = 30, parameter bit EnableAtomics = 1'b0, - parameter int ResetSyncStages = 3 + parameter int ResetSyncStages = 3, + parameter bit EnableWfiGate = 1'b0, + parameter bit PreserveDebug = 1'b0 ) ( // verilog_format: off -- preserve reviewed column alignment input logic clk_i, input logic rst_n_i, + input logic debug_rst_n_i, + output logic hart_reset_o, input logic [ExternalIrqCount+1:0] irq_i, input logic jtag_tck_i, input logic jtag_tms_i, @@ -26,13 +30,16 @@ module tiny_cpu_wrapper #( output logic jtag_tdo_o, output logic debug_halted_o, input logic bus_idle_i, + input logic wfi_gate_i, + output logic gated_o, output logic core_rst_n_o, output logic quiesce_o, ahbl_if.master cpu[2] // verilog_format: on ); - - logic s_pwrup_req; + logic s_pwrup_req; + logic s_debug_reset_n, s_abort_debug; + logic s_cpu_clk, s_cpu_clk_en, s_gate_d, s_gate_q; logic s_reset_pending; logic s_hart_idle; logic s_core_rst_n; @@ -58,6 +65,21 @@ module tiny_cpu_wrapper #( logic [31:0] s_dbg_sbus_wdata; logic [31:0] s_dbg_sbus_rdata; + assign s_debug_reset_n = PreserveDebug ? debug_rst_n_i : rst_n_i; + assign s_abort_debug = PreserveDebug && !rst_n_i; + assign hart_reset_o = s_reset_pending; + assign s_gate_d = EnableWfiGate && wfi_gate_i && !s_cpu_clk_en && bus_idle_i && + !(|irq_i) && !s_dbg_req_halt && !s_dbg_req_resume && !s_reset_pending; + (* keep_hierarchy = 1 *) + tiny_clock_gate u_cpu_gate ( + .clk_i (clk_i), + .rst_n_i(s_core_rst_n), + .gate_i (s_gate_d), + .clk_o (s_cpu_clk), + .gated_o(s_gate_q) + ); + assign gated_o = s_gate_q; + assign debug_halted_o = s_dbg_halted; assign core_rst_n_o = s_core_rst_n; // Request a normal debug halt before draining/resetting the core. Never cut @@ -66,11 +88,13 @@ module tiny_cpu_wrapper #( assign s_hart_idle = bus_idle_i && (!s_reset_pending || s_dbg_halted); mgmt_debug_wrapper #( - .JtagIdcode (`SOC_JTAG_IDCODE), - .ResetSyncStages(ResetSyncStages) + .JtagIdcode (`SOC_JTAG_IDCODE), + .ResetSyncStages (ResetSyncStages), + .SeparateSystemReset(PreserveDebug) ) u_mgmt_debug_wrapper ( .clk_i (clk_i), - .rst_n_i (rst_n_i), + .rst_n_i (s_debug_reset_n), + .system_rst_n_i (rst_n_i), .bridge_idle_i (s_hart_idle), .jtag_tck_i (jtag_tck_i), .jtag_tms_i (jtag_tms_i), @@ -86,18 +110,18 @@ module tiny_cpu_wrapper #( .dbg_running_i (s_dbg_running), .dbg_data0_rdata_o (s_dbg_data0_rdata), .dbg_data0_wdata_i (s_dbg_data0_wdata), - .dbg_data0_wen_i (s_dbg_data0_wen), + .dbg_data0_wen_i (s_dbg_data0_wen && !s_abort_debug), .dbg_instr_data_o (s_dbg_instr_data), .dbg_instr_data_vld_o (s_dbg_instr_data_vld), - .dbg_instr_data_rdy_i (s_dbg_instr_data_rdy), - .dbg_instr_caught_exception_i(s_dbg_instr_caught_exception), - .dbg_instr_caught_ebreak_i (s_dbg_instr_caught_ebreak), + .dbg_instr_data_rdy_i (s_dbg_instr_data_rdy || s_abort_debug), + .dbg_instr_caught_exception_i(s_dbg_instr_caught_exception || s_abort_debug), + .dbg_instr_caught_ebreak_i (s_dbg_instr_caught_ebreak || s_abort_debug), .dbg_sbus_addr_o (s_dbg_sbus_addr), .dbg_sbus_write_o (s_dbg_sbus_write), .dbg_sbus_size_o (s_dbg_sbus_size), .dbg_sbus_vld_o (s_dbg_sbus_vld), - .dbg_sbus_rdy_i (s_dbg_sbus_rdy), - .dbg_sbus_err_i (s_dbg_sbus_err), + .dbg_sbus_rdy_i (s_dbg_sbus_rdy || s_abort_debug), + .dbg_sbus_err_i (s_dbg_sbus_err || s_abort_debug), .dbg_sbus_wdata_o (s_dbg_sbus_wdata), .dbg_sbus_rdata_i (s_dbg_sbus_rdata) ); @@ -123,7 +147,7 @@ module tiny_cpu_wrapper #( .EXTENSION_XH3BEXTM (1), .EXTENSION_XH3IRQ (1), .EXTENSION_XH3PMPM (0), - .EXTENSION_XH3POWER (0), + .EXTENSION_XH3POWER (EnableWfiGate), .CSR_M_MANDATORY (1), .CSR_M_TRAP (1), .CSR_COUNTER (1), @@ -152,12 +176,12 @@ module tiny_cpu_wrapper #( .BRANCH_PREDICTOR (1), .MTVEC_WMASK (32'hfffffffd) ) u_hazard3_cpu_2port ( - .clk (clk_i), + .clk (s_cpu_clk), .clk_always_on (clk_i), .rst_n (s_core_rst_n), .pwrup_req (s_pwrup_req), .pwrup_ack (s_pwrup_req), - .clk_en (), + .clk_en (s_cpu_clk_en), .unblock_out (), .unblock_in (1'b0), .i_haddr (cpu[0].haddr), @@ -198,14 +222,14 @@ module tiny_cpu_wrapper #( .dbg_data0_wdata (s_dbg_data0_wdata), .dbg_data0_wen (s_dbg_data0_wen), .dbg_instr_data (s_dbg_instr_data), - .dbg_instr_data_vld (s_dbg_instr_data_vld && !s_reset_pending), + .dbg_instr_data_vld (s_dbg_instr_data_vld && !s_reset_pending && !s_abort_debug), .dbg_instr_data_rdy (s_dbg_instr_data_rdy), .dbg_instr_caught_exception(s_dbg_instr_caught_exception), .dbg_instr_caught_ebreak (s_dbg_instr_caught_ebreak), .dbg_sbus_addr (s_dbg_sbus_addr), .dbg_sbus_write (s_dbg_sbus_write), .dbg_sbus_size (s_dbg_sbus_size), - .dbg_sbus_vld (s_dbg_sbus_vld), + .dbg_sbus_vld (s_dbg_sbus_vld && !s_abort_debug), .dbg_sbus_rdy (s_dbg_sbus_rdy), .dbg_sbus_err (s_dbg_sbus_err), .dbg_sbus_wdata (s_dbg_sbus_wdata), diff --git a/rtl/tiny/top/tiny_managed_lifecycle.sv b/rtl/tiny/top/tiny_managed_lifecycle.sv new file mode 100644 index 00000000..abac6f7f --- /dev/null +++ b/rtl/tiny/top/tiny_managed_lifecycle.sv @@ -0,0 +1,53 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// Product lifecycle observations for managed register-only endpoints. Track +// only successful APB writes, respecting byte enables. This conservative +// mirror never infers idle from quiet output pins or a missing clock. PWM +// requires explicit global/capture disable; CRC requires FINISH or ABORT. +module tiny_managed_lifecycle ( + input logic clk_i, + input logic pwm_rst_n_i, + input logic crc_rst_n_i, + input logic pwm_write_i, + input logic [11:0] pwm_addr_i, + input logic [31:0] pwm_data_i, + input logic crc_write_i, + input logic [11:0] crc_addr_i, + input logic [31:0] crc_data_i, + output logic pwm_idle_o, + output logic crc_idle_o +); + logic [1:0] s_pwm_d, s_pwm_q; + logic s_crc_d, s_crc_q; + always_comb begin + s_pwm_d = s_pwm_q; + if (pwm_write_i) begin + if (pwm_addr_i == 12'h000) s_pwm_d[0] = pwm_data_i[0]; + if (pwm_addr_i == 12'h400) s_pwm_d[1] = pwm_data_i[0]; + end + s_crc_d = s_crc_q; + if (crc_write_i && crc_addr_i == 12'h000) begin + if (crc_data_i[2:0] == 3'b001) s_crc_d = 1'b1; + if (crc_data_i[2:0] == 3'b010 || crc_data_i[2:0] == 3'b100) s_crc_d = 1'b0; + end + end + dffr #( + .DATA_WIDTH(2) + ) u_pwm_state ( + .clk_i (clk_i), + .rst_n_i(pwm_rst_n_i), + .dat_i (s_pwm_d), + .dat_o (s_pwm_q) + ); + dffr #( + .DATA_WIDTH(1) + ) u_crc_state ( + .clk_i (clk_i), + .rst_n_i(crc_rst_n_i), + .dat_i (s_crc_d), + .dat_o (s_crc_q) + ); + assign pwm_idle_o = s_pwm_q == 0; + assign crc_idle_o = !s_crc_q; +endmodule diff --git a/rtl/tiny/top/tiny_peripheral_control.sv b/rtl/tiny/top/tiny_peripheral_control.sv new file mode 100644 index 00000000..ac28ef87 --- /dev/null +++ b/rtl/tiny/top/tiny_peripheral_control.sv @@ -0,0 +1,133 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// Atomic Tiny gate/reset transactions on REF24. Inputs are synchronized to +// REF24 by the integration; gate masks exclude non-gateable boot infrastructure. +module tiny_peripheral_control ( + input logic clk_i, + input logic rst_n_i, + input logic warm_reset_i, + input logic apply_i, + input logic reset_command_i, + input logic [13:0] request_i, + input logic [13:0] idle_i, + input logic bus_idle_i, + input logic [13:0] gate_actual_i, + input logic [13:0] reset_held_i, + input logic [13:0] reset_ready_i, + input logic [31:0] timeout_i, + output logic [13:0] gate_o, + output logic [13:0] reset_o, + output logic block_o, + output logic done_o, + output logic [ 4:0] fault_o, + output logic [13:0] reset_done_o +); + typedef enum logic [2:0] { + Idle, + Drain, + GateWait, + Hold, + Release, + Complete + } state_e; + typedef struct packed { + state_e state; + logic reset_command; + logic [13:0] request_mask, gates, previous_gates, resets, reset_done, affected; + logic [31:0] count; + logic done; + logic [4:0] fault; + } control_t; + control_t s_ctrl_d, s_ctrl_q; + logic [13:0] s_affected; + assign s_affected = s_ctrl_q.affected; + assign gate_o = s_ctrl_q.gates; + assign reset_o = s_ctrl_q.resets; + assign reset_done_o = s_ctrl_q.reset_done; + // Arming the WFI leaf permission must not pause an unrelated DMA job. + // The CPU leaf itself waits for its accepted I/D work before stopping. + assign block_o = (s_ctrl_q.state != Idle) && (s_affected[13:1] != 0); + assign done_o = s_ctrl_q.done; + assign fault_o = s_ctrl_q.fault; + always_comb begin + s_ctrl_d = s_ctrl_q; + s_ctrl_d.done = 1'b0; + s_ctrl_d.fault = 0; + s_ctrl_d.count = s_ctrl_q.count + 32'd1; + unique case (s_ctrl_q.state) + Idle: begin + s_ctrl_d.count = 0; + if (apply_i) begin + if (reset_command_i && (request_i == 0 || request_i[0] || + (request_i & s_ctrl_q.gates) != 0)) begin + s_ctrl_d.done = 1'b1; + s_ctrl_d.fault[0] = 1'b1; + end else begin + s_ctrl_d.request_mask = request_i; + s_ctrl_d.previous_gates = s_ctrl_q.gates; + s_ctrl_d.affected = reset_command_i ? request_i : s_ctrl_q.gates ^ request_i; + s_ctrl_d.reset_command = reset_command_i; + if (reset_command_i) s_ctrl_d.reset_done = 0; + s_ctrl_d.state = Drain; + end + end + end + Drain: begin + if (s_ctrl_q.count >= 16 && ((s_affected[13:1] == 0) || (bus_idle_i && idle_i[9])) && + ((idle_i[13:1] & s_affected[13:1]) == s_affected[13:1])) begin + s_ctrl_d.count = 0; + if (s_ctrl_q.reset_command) begin + s_ctrl_d.resets = s_ctrl_q.request_mask; + s_ctrl_d.state = Hold; + end else begin + s_ctrl_d.gates = s_ctrl_q.request_mask; + s_ctrl_d.state = GateWait; + end + end else if (s_ctrl_q.count >= timeout_i) begin + s_ctrl_d.fault[1] = 1'b1; + s_ctrl_d.done = 1'b1; + s_ctrl_d.state = Idle; + end + end + GateWait: if (gate_actual_i[13:1] == s_ctrl_q.gates[13:1]) s_ctrl_d.state = Complete; + Hold: + if ((reset_held_i & s_ctrl_q.request_mask) == s_ctrl_q.request_mask) begin + s_ctrl_d.resets = 0; + s_ctrl_d.count = 0; + s_ctrl_d.state = Release; + end + Release: + if ((reset_ready_i & s_ctrl_q.request_mask) == s_ctrl_q.request_mask) begin + s_ctrl_d.reset_done = s_ctrl_q.request_mask; + s_ctrl_d.state = Complete; + end + Complete: begin + s_ctrl_d.done = 1'b1; + s_ctrl_d.state = Idle; + end + default: begin + s_ctrl_d.state = Idle; + s_ctrl_d.fault[4] = 1'b1; + end + endcase + if ((s_ctrl_q.state == GateWait || s_ctrl_q.state == Hold || s_ctrl_q.state == Release) && + s_ctrl_q.count >= timeout_i) begin + s_ctrl_d.fault[4] = 1'b1; + s_ctrl_d.done = 1'b1; + s_ctrl_d.state = Idle; + // A failed reset retains isolation; it has no RESET_DONE acknowledgement. + if (s_ctrl_q.reset_command) s_ctrl_d.resets = s_ctrl_q.request_mask; + else s_ctrl_d.gates = s_ctrl_q.previous_gates; + end + if (warm_reset_i) s_ctrl_d = '0; + end + dffr #( + .DATA_WIDTH($bits(control_t)) + ) u_control ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_ctrl_d), + .dat_o (s_ctrl_q) + ); +endmodule diff --git a/rtl/tiny/top/tiny_peripheral_leaf.sv b/rtl/tiny/top/tiny_peripheral_leaf.sv new file mode 100644 index 00000000..ff31010a --- /dev/null +++ b/rtl/tiny/top/tiny_peripheral_leaf.sv @@ -0,0 +1,55 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// The base clock and lifecycle registers never stop. A gate only stops its +// peripheral leaf, at a falling edge. Local reset asserts asynchronously; +// hold/release acknowledgements require five actual leaf clock edges. +module tiny_peripheral_leaf ( + input logic clk_i, + input logic rst_n_i, + input logic gate_i, + input logic reset_i, + output logic clk_o, + output logic rst_n_o, + output logic gated_o, + output logic held_o +); + logic s_gate_sync, s_gate_q; + logic [2:0] s_hold_d, s_hold_q; + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_gate_sync ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (gate_i), + .dat_o (s_gate_sync) + ); + ndffr #( + .DATA_WIDTH(1) + ) u_gate ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_gate_sync), + .dat_o (s_gate_q) + ); + assign clk_o = clk_i && !s_gate_q; + assign gated_o = s_gate_q; + assign s_hold_d = s_hold_q < 5 ? s_hold_q + 3'd1 : s_hold_q; + dffr #( + .DATA_WIDTH(3) + ) u_hold ( + .clk_i (clk_o), + .rst_n_i(rst_n_i && reset_i), + .dat_i (s_hold_d), + .dat_o (s_hold_q) + ); + assign held_o = s_hold_q == 5; + rst_sync #( + .STAGE(5) + ) u_local_reset ( + .clk_i (clk_o), + .rst_n_i(rst_n_i && !reset_i), + .rst_n_o(rst_n_o) + ); +endmodule diff --git a/rtl/tiny/top/tiny_rcu_regs.svh b/rtl/tiny/top/tiny_rcu_regs.svh new file mode 100644 index 00000000..b71d4806 --- /dev/null +++ b/rtl/tiny/top/tiny_rcu_regs.svh @@ -0,0 +1,30 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 +// Handwritten Tiny RCU ABI 1.0; paired with retrosoc/hal/rcu_regs.h. +`ifndef RETROSOC_TINY_RCU__REGS_SVH +`define RETROSOC_TINY_RCU__REGS_SVH +`define APB4_TINY_RCU__ID 32'h100 +`define APB4_TINY_RCU__VERSION 32'h104 +`define APB4_TINY_RCU__CAPABILITY 32'h108 +`define APB4_TINY_RCU__CLOCK_REQUEST 32'h10c +`define APB4_TINY_RCU__CLOCK_CURRENT 32'h110 +`define APB4_TINY_RCU__COMMAND 32'h114 +`define APB4_TINY_RCU__STATUS 32'h118 +`define APB4_TINY_RCU__TIMEOUT 32'h11c +`define APB4_TINY_RCU__GATE_REQUEST 32'h120 +`define APB4_TINY_RCU__GATE_STATUS 32'h124 +`define APB4_TINY_RCU__RESET_REQUEST 32'h128 +`define APB4_TINY_RCU__RESET_DONE 32'h12c +`define APB4_TINY_RCU__RESET_CAUSE 32'h130 +`define APB4_TINY_RCU__FAULT 32'h134 +`define APB4_TINY_RCU__IRQ_STATE 32'h138 +`define APB4_TINY_RCU__IRQ_ENABLE 32'h13c +`define APB4_TINY_RCU__XIN_HZ 32'h140 +`define APB4_TINY_RCU__REF_HZ 32'h144 +`define APB4_TINY_RCU__SYS_HZ 32'h148 +`define APB4_TINY_RCU__MEM_HZ 32'h14c +`define APB4_TINY_RCU__PCLK_HZ 32'h150 +`define APB4_TINY_RCU__CLINT_HZ 32'h154 +`define APB4_TINY_RCU__TARGET_CAP 32'h158 +`define APB4_TINY_RCU__CLKOUT 32'h15c +`endif diff --git a/rtl/tiny/top/tiny_reset_tree.sv b/rtl/tiny/top/tiny_reset_tree.sv index 45c0dac3..1e50da5a 100644 --- a/rtl/tiny/top/tiny_reset_tree.sv +++ b/rtl/tiny/top/tiny_reset_tree.sv @@ -1,12 +1,17 @@ // 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. +// SYS-clocked reset distribution with an always-on POR synchronizer. The POR +// source remains on the external reference while all functional leaves release +// on the selected SYS clock. module tiny_reset_tree #( - parameter int LeafCount = 17 + parameter int LeafCount = 17, + parameter bit SplitDomains = 1'b0 ) ( input logic clk_i, + input logic por_clk_i, + input logic [LeafCount-1:0] leaf_clk_i, + input logic backend_reset_n_i, input logic rst_n_i, input logic watchdog_reset_req_i, output logic por_rst_n_o, @@ -16,7 +21,8 @@ module tiny_reset_tree #( ); logic s_system_req_n; - assign s_system_req_n = por_rst_n_o && !watchdog_reset_req_i; + assign s_system_req_n = por_rst_n_o && !watchdog_reset_req_i && + (!SplitDomains || backend_reset_n_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; @@ -24,7 +30,7 @@ module tiny_reset_tree #( rst_sync #( .STAGE(5) ) u_por_rst_sync ( - .clk_i (clk_i), + .clk_i (SplitDomains ? por_clk_i : clk_i), .rst_n_i(rst_n_i), .rst_n_o(por_rst_n_o) ); @@ -42,7 +48,7 @@ module tiny_reset_tree #( rst_sync #( .STAGE(5) ) u_leaf_rst_sync ( - .clk_i (clk_i), + .clk_i (SplitDomains ? leaf_clk_i[leaf] : clk_i), .rst_n_i(system_rst_n_o), .rst_n_o(leaf_rst_n_o[leaf]) ); diff --git a/rtl/tiny/top/tiny_sysctrl.sv b/rtl/tiny/top/tiny_sysctrl.sv index 13f9db10..fd730151 100644 --- a/rtl/tiny/top/tiny_sysctrl.sv +++ b/rtl/tiny/top/tiny_sysctrl.sv @@ -1,10 +1,19 @@ // Copyright (c) 2026 Yuchi Miao // SPDX-License-Identifier: MulanPSL-2.0 `include "sysctrl_define.svh" +`include "tiny_rcu_regs.svh" -module tiny_sysctrl ( +module tiny_sysctrl #( + parameter bit PllPresent = 1'b0, + parameter bit PllProfiles = 1'b0, + parameter int unsigned XinHzValue = 24_000_000, + parameter logic [13:0] TargetMask = 14'd0 +) ( input logic clk_i, input logic rst_n_i, + input logic clk_ref_i, + input logic rst_ref_n_i, + output logic rcu_link_ready_o, apb4_if.slave apb4, input logic fault_valid_i, input logic [ 3:0] fault_count_i, @@ -15,12 +24,66 @@ module tiny_sysctrl ( input logic [63:0] cpu_wait_i, input logic [63:0] dma_wait_i, input logic rtc_wake_i, + input logic clock_done_i, + input logic [ 4:0] clock_fault_i, + input logic [ 1:0] clock_current_i, + input logic pll_locked_i, + input logic pll_selected_i, + input logic warm_reset_i, + input logic watchdog_reset_i, + input logic hart_reset_i, + output logic system_reset_o, + output logic rcu_irq_o, + output logic [31:0] clock_timeout_o, + input logic periph_done_i, + input logic [ 4:0] periph_fault_i, + input logic [13:0] gate_actual_i, + input logic [13:0] reset_done_i, + output logic periph_apply_o, + output logic periph_reset_o, + output logic [13:0] periph_request_o, + output logic [31:0] clkout_control_o, + input logic clkout_idle_i, output logic perf_enable_o, output logic perf_clear_o, output logic test_done_o, output logic test_pass_o, - output logic [ 7:0] test_code_o + output logic [ 7:0] test_code_o, + output logic [31:0] xin_hz_o, + output logic [31:0] ref_hz_o, + output logic [31:0] sys_hz_o, + output logic [31:0] mem_hz_o, + output logic [31:0] pclk_hz_o, + output logic [31:0] clint_hz_o, + output logic [ 1:0] rcu_current_o, + output logic rcu_busy_o, + output logic clock_apply_o, + output logic [ 1:0] clock_profile_o ); + localparam logic [31:0] RcuId = `APB4_TINY_RCU__ID; + localparam logic [31:0] RcuVersion = `APB4_TINY_RCU__VERSION; + localparam logic [31:0] RcuCapability = `APB4_TINY_RCU__CAPABILITY; + localparam logic [31:0] ClockRequest = `APB4_TINY_RCU__CLOCK_REQUEST; + localparam logic [31:0] ClockCurrent = `APB4_TINY_RCU__CLOCK_CURRENT; + localparam logic [31:0] RcuCommand = `APB4_TINY_RCU__COMMAND; + localparam logic [31:0] RcuStatus = `APB4_TINY_RCU__STATUS; + localparam logic [31:0] TimeoutRefCycles = `APB4_TINY_RCU__TIMEOUT; + localparam logic [31:0] GateRequest = `APB4_TINY_RCU__GATE_REQUEST; + localparam logic [31:0] GateStatus = `APB4_TINY_RCU__GATE_STATUS; + localparam logic [31:0] ResetRequest = `APB4_TINY_RCU__RESET_REQUEST; + localparam logic [31:0] ResetDone = `APB4_TINY_RCU__RESET_DONE; + localparam logic [31:0] ResetCause = `APB4_TINY_RCU__RESET_CAUSE; + localparam logic [31:0] RcuFault = `APB4_TINY_RCU__FAULT; + localparam logic [31:0] RcuIrqState = `APB4_TINY_RCU__IRQ_STATE; + localparam logic [31:0] RcuIrqEnable = `APB4_TINY_RCU__IRQ_ENABLE; + localparam logic [31:0] XinHz = `APB4_TINY_RCU__XIN_HZ; + localparam logic [31:0] RefHz = `APB4_TINY_RCU__REF_HZ; + localparam logic [31:0] SysHz = `APB4_TINY_RCU__SYS_HZ; + localparam logic [31:0] MemHz = `APB4_TINY_RCU__MEM_HZ; + localparam logic [31:0] PclkHz = `APB4_TINY_RCU__PCLK_HZ; + localparam logic [31:0] ClintHz = `APB4_TINY_RCU__CLINT_HZ; + localparam logic [31:0] TargetCapability = `APB4_TINY_RCU__TARGET_CAP; + localparam logic [31:0] ClkoutControl = `APB4_TINY_RCU__CLKOUT; logic [31:0] s_test_d, s_test_q; logic [31:0] s_fault_addr_d, s_fault_addr_q; logic [31:0] s_fault_count_d, s_fault_count_q; @@ -38,23 +101,137 @@ module tiny_sysctrl ( logic [31:0] s_read_data; logic [31:0] s_offset; logic [31:0] s_rdata_d, s_rdata_q; + logic [1:0] s_clk_req_d, s_clk_req_q; + logic [1:0] s_clk_cur_d, s_clk_cur_q; + logic [1:0] s_clk_latched_d, s_clk_latched_q; + logic s_system_reset_d, s_system_reset_q; + logic s_periph_apply_d, s_periph_apply_q, s_periph_reset_d, s_periph_reset_q; + logic [13:0] s_periph_mask_d, s_periph_mask_q; + logic [3:0] s_active_cmd_d, s_active_cmd_q; + logic s_clock_apply_d, s_clock_apply_q; + logic s_rcu_busy_d, s_rcu_busy_q; + logic [31:0] s_timeout_ref_d, s_timeout_ref_q; + logic [31:0] s_gate_req_d, s_gate_req_q; + logic [31:0] s_gate_stat_d, s_gate_stat_q; + logic [31:0] s_rst_req_d, s_rst_req_q; + logic [31:0] s_reset_done_d, s_reset_done_q; + logic [31:0] s_reset_cause_d, s_reset_cause_q; + logic [31:0] s_rcu_fault_d, s_rcu_fault_q; + logic [31:0] s_rcu_irq_state_d, s_rcu_irq_state_q; + logic [31:0] s_rcu_irq_en_d, s_rcu_irq_en_q; + logic [31:0] s_clkout_control_d, s_clkout_control_q; + logic [31:0] s_sys_hz_d, s_sys_hz_q; + logic [31:0] s_mem_hz_d, s_mem_hz_q; + logic [31:0] s_pclk_hz_d, s_pclk_hz_q; + logic [31:0] s_clkout_hz; + + logic s_rcu_selected, s_rcu_req, s_rcu_write, s_rcu_valid, s_rcu_writable, s_rcu_err; + logic s_rcu_config_err; + logic s_rcu_ready_d, s_rcu_ready_q, s_rcu_err_d, s_rcu_err_q; + logic [31:0] s_rcu_offset, s_rcu_read_data, s_rcu_rdata_d, s_rcu_rdata_q; + apb4_if u_rcu_src ( + .pclk (clk_i), + .presetn(rst_n_i) + ); + apb4_if u_rcu_dst ( + .pclk (clk_ref_i), + .presetn(rst_ref_n_i) + ); + + assign s_rcu_selected = apb4.paddr[11:8] == 4'h1; + assign u_rcu_src.paddr = apb4.paddr; + assign u_rcu_src.pprot = apb4.pprot; + assign u_rcu_src.pwrite = apb4.pwrite; + assign u_rcu_src.pwdata = apb4.pwdata; + assign u_rcu_src.pstrb = apb4.pstrb; + assign u_rcu_src.psel = apb4.psel && s_rcu_selected; + assign u_rcu_src.penable = apb4.penable; + tiny_apb_async_bridge u_rcu_cdc ( + .src_clk_i (clk_i), + .src_rst_n_i(rst_n_i), + .dst_clk_i (clk_ref_i), + .dst_rst_n_i(rst_ref_n_i), + .disabled_i (1'b0), + .idle_o (), + .ready_o (rcu_link_ready_o), + .src_apb4 (u_rcu_src), + .dst_apb4 (u_rcu_dst) + ); + assign s_rcu_offset = {20'd0, u_rcu_dst.paddr[11:0]}; + assign s_rcu_req = u_rcu_dst.psel && u_rcu_dst.penable && !s_rcu_ready_q; + assign s_rcu_err = (u_rcu_dst.paddr[1:0] != 0) || !s_rcu_valid || + (u_rcu_dst.pwrite && (!s_rcu_writable || u_rcu_dst.pstrb != 4'hf)) || + (!u_rcu_dst.pwrite && s_rcu_offset == RcuCommand) || + (u_rcu_dst.pwrite && s_rcu_config_err); + assign s_rcu_write = s_rcu_req && u_rcu_dst.pwrite && !s_rcu_err; + assign s_rcu_ready_d = s_rcu_req; + assign s_rcu_err_d = s_rcu_req && s_rcu_err; + assign s_rcu_rdata_d = s_rcu_req ? s_rcu_read_data : s_rcu_rdata_q; + assign u_rcu_dst.pready = s_rcu_ready_q; + assign u_rcu_dst.pslverr = s_rcu_err_q; + assign u_rcu_dst.prdata = s_rcu_rdata_q; + dffr #( + .DATA_WIDTH(1) + ) u_rcu_ready ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_rcu_ready_d), + .dat_o (s_rcu_ready_q) + ); + dffr #( + .DATA_WIDTH(1) + ) u_rcu_error ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_rcu_err_d), + .dat_o (s_rcu_err_q) + ); + dffr #( + .DATA_WIDTH(32) + ) u_rcu_rdata ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_rcu_rdata_d), + .dat_o (s_rcu_rdata_q) + ); assign s_offset = {20'd0, apb4.paddr[11:0]}; - assign s_req = apb4.psel && apb4.penable && !s_ready_q; + assign s_req = apb4.psel && !s_rcu_selected && apb4.penable && !s_ready_q; assign s_write = s_req && apb4.pwrite && !s_access_err; assign s_access_err = (apb4.paddr[1:0] != 2'd0) || !s_valid || - (apb4.pwrite && (!s_writable || (apb4.pstrb != 4'hf))); + (apb4.pwrite && (!s_writable || (apb4.pstrb != 4'hf))) || + (!apb4.pwrite && (s_offset == RcuCommand)); assign s_ready_d = s_req; assign s_err_d = s_req && s_access_err; assign s_rdata_d = s_req ? s_read_data : s_rdata_q; - assign apb4.pready = s_ready_q; - assign apb4.pslverr = s_err_q; - assign apb4.prdata = s_rdata_q; + assign apb4.pready = s_rcu_selected ? u_rcu_src.pready : s_ready_q; + assign apb4.pslverr = s_rcu_selected ? u_rcu_src.pslverr : s_err_q; + assign apb4.prdata = s_rcu_selected ? u_rcu_src.prdata : s_rdata_q; assign perf_enable_o = s_perf_en_q; assign perf_clear_o = s_write && (s_offset == `APB4_SYSCTRL__PERF_CTRL) && apb4.pwdata[1]; assign test_done_o = s_test_q[31]; assign test_pass_o = s_test_q[0]; assign test_code_o = s_test_q[15:8]; + assign xin_hz_o = XinHzValue; + assign ref_hz_o = 32'd24_000_000; + assign sys_hz_o = s_sys_hz_q; + assign mem_hz_o = s_mem_hz_q; + assign pclk_hz_o = s_pclk_hz_q; + assign clint_hz_o = 32'd1_000_000; + assign rcu_current_o = s_clk_cur_q; + assign rcu_busy_o = s_rcu_busy_q; + assign clock_apply_o = s_clock_apply_q; + assign clock_profile_o = s_clk_latched_q; + assign clock_timeout_o = s_timeout_ref_q; + assign clkout_control_o = s_clkout_control_q; + assign s_clkout_hz = u_rcu_dst.pwdata[2:0] == 2 ? s_sys_hz_q : + u_rcu_dst.pwdata[2:0] == 3 ? s_mem_hz_q : + u_rcu_dst.pwdata[2:0] == 4 ? s_pclk_hz_q : 32'd24_000_000; + assign periph_apply_o = s_periph_apply_q; + assign periph_reset_o = s_periph_reset_q; + assign periph_request_o = s_periph_mask_q; + assign system_reset_o = s_system_reset_q; + assign rcu_irq_o = |(s_rcu_irq_state_q[1:0] & s_rcu_irq_en_q[1:0]); always_comb begin s_valid = 1'b1; @@ -91,6 +268,275 @@ module tiny_sysctrl ( default: s_valid = 1'b0; endcase end + always_comb begin + s_rcu_valid = 1'b1; + s_rcu_writable = 1'b0; + s_rcu_read_data = 32'd0; + unique case (s_rcu_offset) + RcuId: s_rcu_read_data = 32'h5452_4355; + RcuVersion: s_rcu_read_data = 32'h0001_0000; + // Advertise only the instantiated product backend and lifecycle paths. + RcuCapability: + s_rcu_read_data = 32'h0000_0201 | + (PllPresent ? 32'h100 : 32'h2) | (PllProfiles ? 32'hc : 32'd0) | + (TargetMask != 0 ? 32'h1800 : 32'd0); + ClockRequest: begin + s_rcu_read_data = {30'd0, s_clk_req_q}; + s_rcu_writable = 1'b1; + end + ClockCurrent: s_rcu_read_data = {30'd0, s_clk_cur_q}; + RcuCommand: begin + s_rcu_read_data = 32'd0; + s_rcu_writable = 1'b1; + end + RcuStatus: begin + s_rcu_read_data = 32'd0; + s_rcu_read_data[0] = s_rcu_busy_q; + s_rcu_read_data[1] = (s_clk_cur_q == 2'd0); + s_rcu_read_data[2] = pll_locked_i; + s_rcu_read_data[3] = pll_selected_i; + s_rcu_read_data[4] = |s_rcu_fault_q; + s_rcu_read_data[11:8] = s_active_cmd_q; + end + TimeoutRefCycles: begin + s_rcu_read_data = s_timeout_ref_q; + s_rcu_writable = 1'b1; + end + GateRequest: begin + s_rcu_read_data = s_gate_req_q; + s_rcu_writable = 1'b1; + end + GateStatus: s_rcu_read_data = {18'd0, gate_actual_i}; + ResetRequest: begin + s_rcu_read_data = s_rst_req_q; + s_rcu_writable = 1'b1; + end + ResetDone: s_rcu_read_data = s_reset_done_q; + ResetCause: begin + s_rcu_read_data = s_reset_cause_q; + s_rcu_writable = 1'b1; + end + RcuFault: begin + s_rcu_read_data = s_rcu_fault_q; + s_rcu_writable = 1'b1; + end + RcuIrqState: begin + s_rcu_read_data = s_rcu_irq_state_q; + s_rcu_writable = 1'b1; + end + RcuIrqEnable: begin + s_rcu_read_data = s_rcu_irq_en_q; + s_rcu_writable = 1'b1; + end + XinHz: s_rcu_read_data = XinHzValue; + RefHz: s_rcu_read_data = 32'd24_000_000; + SysHz: s_rcu_read_data = s_sys_hz_q; + MemHz: s_rcu_read_data = s_mem_hz_q; + PclkHz: s_rcu_read_data = s_pclk_hz_q; + ClintHz: s_rcu_read_data = 32'd1_000_000; + TargetCapability: s_rcu_read_data = {18'd0, TargetMask}; + ClkoutControl: begin + s_rcu_read_data = s_clkout_control_q; + s_rcu_writable = 1'b1; + end + default: s_rcu_valid = 1'b0; + endcase + end + // Validate before generating a write strobe, so every rejected operation + // returns PSLVERR and preserves staging as well as committed state. + always_comb begin + s_rcu_config_err = 1'b0; + unique case (s_rcu_offset) + ClockRequest: s_rcu_config_err = s_rcu_busy_q || u_rcu_dst.pwdata[31:2] != 0; + TimeoutRefCycles: s_rcu_config_err = s_rcu_busy_q || u_rcu_dst.pwdata == 0; + GateRequest: + s_rcu_config_err = s_rcu_busy_q || (u_rcu_dst.pwdata & ~{18'd0, TargetMask}) != 0; + ResetRequest: + s_rcu_config_err = s_rcu_busy_q || (u_rcu_dst.pwdata & ~{18'd0, TargetMask[13:1], 1'b0}) != 0; + ResetCause: s_rcu_config_err = u_rcu_dst.pwdata[31:6] != 0; + RcuFault: s_rcu_config_err = u_rcu_dst.pwdata[31:5] != 0; + RcuIrqState, RcuIrqEnable: s_rcu_config_err = u_rcu_dst.pwdata[31:2] != 0; + RcuCommand: begin + s_rcu_config_err = s_rcu_busy_q; + unique case (u_rcu_dst.pwdata) + 32'd1: + s_rcu_config_err |= s_clkout_control_q[2:0] != 0 || !clkout_idle_i || + !(s_clk_req_q == 0 || (s_clk_req_q == 1 && !PllPresent) || + (s_clk_req_q >= 2 && PllPresent && PllProfiles)); + 32'd2: s_rcu_config_err |= TargetMask == 0 || (s_gate_req_q & ~{18'd0, TargetMask}) != 0; + 32'd4: + s_rcu_config_err |= s_rst_req_q == 0 || + (s_rst_req_q & ~{18'd0, TargetMask[13:1], 1'b0}) != 0 || + (s_rst_req_q & {18'd0, gate_actual_i}) != 0; + 32'd8: begin + end + default: s_rcu_config_err = 1'b1; + endcase + end + ClkoutControl: + s_rcu_config_err = s_rcu_busy_q || + (u_rcu_dst.pwdata & 32'hff00_00f8) != 0 || u_rcu_dst.pwdata[2:0] > 4 || + (u_rcu_dst.pwdata[2:0] != 0 && (s_clkout_control_q[2:0] != 0 || + !clkout_idle_i || u_rcu_dst.pwdata[23:8] == 0 || + {32'd0, s_clkout_hz} > (64'd48_000_000 * {48'd0, u_rcu_dst.pwdata[23:8]}))); + default: begin + end + endcase + end + always_comb begin + s_clk_req_d = s_clk_req_q; + s_clk_cur_d = s_clk_cur_q; + s_rcu_busy_d = s_rcu_busy_q; + s_clk_latched_d = s_clk_latched_q; + s_system_reset_d = 1'b0; + s_periph_apply_d = 1'b0; + s_periph_reset_d = s_periph_reset_q; + s_periph_mask_d = s_periph_mask_q; + s_active_cmd_d = s_active_cmd_q; + s_clock_apply_d = 1'b0; + s_timeout_ref_d = s_timeout_ref_q; + s_gate_req_d = s_gate_req_q; + s_gate_stat_d = s_gate_stat_q; + s_rst_req_d = s_rst_req_q; + s_reset_done_d = s_reset_done_q; + s_reset_cause_d = s_reset_cause_q; + s_rcu_fault_d = s_rcu_fault_q; + s_rcu_irq_state_d = s_rcu_irq_state_q; + s_rcu_irq_en_d = s_rcu_irq_en_q; + s_clkout_control_d = s_clkout_control_q; + s_sys_hz_d = s_sys_hz_q; + s_mem_hz_d = s_mem_hz_q; + s_pclk_hz_d = s_pclk_hz_q; + if (s_rcu_write) begin + unique case (s_rcu_offset) + RcuCommand: begin + if (s_rcu_busy_q || !(u_rcu_dst.pwdata == 1 || u_rcu_dst.pwdata == 2 || + u_rcu_dst.pwdata == 4 || u_rcu_dst.pwdata == 8)) begin + s_rcu_fault_d[0] = 1'b1; + s_rcu_irq_state_d[1] = 1'b1; + end else if (u_rcu_dst.pwdata == 1) begin + if (s_clkout_control_q[2:0] == 0 && clkout_idle_i && + (s_clk_req_q == 0 || (s_clk_req_q == 1 && !PllPresent) || + (s_clk_req_q >= 2 && PllPresent && PllProfiles))) begin + s_clk_latched_d = s_clk_req_q; + s_clock_apply_d = 1'b1; + s_rcu_busy_d = 1'b1; + s_active_cmd_d = 4'd1; + end else begin + s_rcu_fault_d[0] = 1'b1; + s_rcu_irq_state_d[1] = 1'b1; + end + end else if (u_rcu_dst.pwdata == 8) begin + s_system_reset_d = 1'b1; + s_reset_cause_d[1] = 1'b1; + s_rcu_busy_d = 1'b1; + s_active_cmd_d = 4'd8; + end else if (TargetMask != 0 && u_rcu_dst.pwdata == 2 && + (s_gate_req_q & ~{18'd0, TargetMask}) == 0) begin + s_periph_mask_d = s_gate_req_q[13:0]; + s_periph_reset_d = 1'b0; + s_periph_apply_d = 1'b1; + s_rcu_busy_d = 1'b1; + s_active_cmd_d = 4'd2; + end else if (u_rcu_dst.pwdata == 4 && s_rst_req_q != 0 && + (s_rst_req_q & ~{18'd0, TargetMask[13:1], 1'b0}) == 0 && + (s_rst_req_q & {18'd0, gate_actual_i}) == 0) begin + s_periph_mask_d = s_rst_req_q[13:0]; + s_periph_reset_d = 1'b1; + s_periph_apply_d = 1'b1; + s_reset_done_d = 0; + s_rcu_busy_d = 1'b1; + s_active_cmd_d = 4'd4; + end else begin + s_rcu_fault_d[0] = 1'b1; + s_rcu_irq_state_d[1] = 1'b1; + end + end + ClockRequest: begin + if (u_rcu_dst.pwdata[31:2] == 0) s_clk_req_d = u_rcu_dst.pwdata[1:0]; + else begin + s_rcu_fault_d[0] = 1'b1; + s_rcu_irq_state_d[1] = 1'b1; + end + end + TimeoutRefCycles: begin + if (u_rcu_dst.pwdata != 0) s_timeout_ref_d = u_rcu_dst.pwdata; + else begin + s_rcu_fault_d[0] = 1'b1; + s_rcu_irq_state_d[1] = 1'b1; + end + end + GateRequest: s_gate_req_d = u_rcu_dst.pwdata; + ResetRequest: s_rst_req_d = u_rcu_dst.pwdata; + ResetCause: s_reset_cause_d = s_reset_cause_q & ~u_rcu_dst.pwdata; + RcuFault: s_rcu_fault_d = s_rcu_fault_q & ~u_rcu_dst.pwdata; + RcuIrqState: s_rcu_irq_state_d = s_rcu_irq_state_q & ~u_rcu_dst.pwdata; + RcuIrqEnable: s_rcu_irq_en_d = u_rcu_dst.pwdata & 32'd3; + ClkoutControl: begin + if (s_rcu_busy_q || (u_rcu_dst.pwdata & 32'hff00_00f8) != 0 || + u_rcu_dst.pwdata[2:0] > 4 || + (u_rcu_dst.pwdata[2:0] != 0 && + (s_clkout_control_q[2:0] != 0 || !clkout_idle_i || u_rcu_dst.pwdata[23:8] == 0 || + {32'd0, s_clkout_hz} > (64'd48_000_000 * {48'd0, u_rcu_dst.pwdata[23:8]})))) begin + s_rcu_fault_d[0] = 1'b1; + s_rcu_irq_state_d[1] = 1'b1; + end else s_clkout_control_d = u_rcu_dst.pwdata; + end + default: begin + end + endcase + end + if (clock_done_i) begin + s_rcu_busy_d = 1'b0; + s_active_cmd_d = 0; + s_clk_cur_d = clock_current_i; + s_sys_hz_d = clock_current_i == 2 ? 32'd192_000_000 : + clock_current_i == 3 ? 32'd240_000_000 : + clock_current_i == 1 ? 32'(XinHzValue) : 32'd24_000_000; + s_mem_hz_d = s_sys_hz_d > 120_000_000 ? s_sys_hz_d / 2 : s_sys_hz_d; + s_pclk_hz_d = s_sys_hz_d > 120_000_000 ? s_sys_hz_d / 4 : + s_sys_hz_d > 60_000_000 ? s_sys_hz_d / 2 : s_sys_hz_d; + s_rcu_irq_state_d[0] = 1'b1; + end + if (periph_done_i) begin + s_rcu_busy_d = 1'b0; + s_active_cmd_d = 0; + s_reset_done_d = {18'd0, reset_done_i}; + if (s_periph_reset_q && reset_done_i != 0) s_reset_cause_d[5] = 1'b1; + s_rcu_irq_state_d[0] = 1'b1; + end + if (warm_reset_i) begin + s_rcu_busy_d = 1'b0; + s_active_cmd_d = 0; + s_rcu_irq_en_d = 0; + s_rcu_irq_state_d[0] = 1'b0; + s_clk_cur_d = 0; + s_clk_req_d = 0; + s_gate_req_d = 0; + s_gate_stat_d = 0; + s_rst_req_d = 0; + s_reset_done_d = 0; + s_periph_mask_d = 0; + s_periph_reset_d = 1'b0; + s_clkout_control_d = 0; + s_sys_hz_d = 24_000_000; + s_mem_hz_d = 24_000_000; + s_pclk_hz_d = 24_000_000; + end + // Hardware set wins over W1C, including rejected APB operations. + if (s_rcu_req && s_rcu_err) begin + s_rcu_fault_d[0] = 1'b1; + s_rcu_irq_state_d[1] = 1'b1; + end + if (watchdog_reset_i) s_reset_cause_d[2] = 1'b1; + if (clock_fault_i[3]) s_reset_cause_d[3] = 1'b1; + if (hart_reset_i) s_reset_cause_d[4] = 1'b1; + if ((clock_fault_i | periph_fault_i) != 0) begin + s_rcu_fault_d = s_rcu_fault_d | {27'd0, clock_fault_i | periph_fault_i}; + s_rcu_irq_state_d[1] = 1'b1; + end + end + assign s_fault_sum = {1'b0, s_fault_count_q} + {29'd0, fault_count_i}; always_comb begin s_test_d = s_test_q; @@ -152,6 +598,147 @@ module tiny_sysctrl ( .dat_i (s_fault_addr_d), .dat_o (s_fault_addr_q) ); + dffr #( + .DATA_WIDTH(2) + ) u_clock_latched ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_clk_latched_d), + .dat_o (s_clk_latched_q) + ); + dffr #( + .DATA_WIDTH(20) + ) u_periph_command ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i ({s_active_cmd_d, s_periph_mask_d, s_periph_reset_d, s_periph_apply_d}), + .dat_o ({s_active_cmd_q, s_periph_mask_q, s_periph_reset_q, s_periph_apply_q}) + ); + dffr #( + .DATA_WIDTH(1) + ) u_system_reset ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_system_reset_d), + .dat_o (s_system_reset_q) + ); + dffr #(2) u_rcu_clock_request ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_clk_req_d), + .dat_o (s_clk_req_q) + ); + dffr #(2) u_rcu_clock_current ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_clk_cur_d), + .dat_o (s_clk_cur_q) + ); + dffr #(1) u_rcu_busy ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_rcu_busy_d), + .dat_o (s_rcu_busy_q) + ); + dffr #(1) u_clock_apply ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_clock_apply_d), + .dat_o (s_clock_apply_q) + ); + dffrc #( + .DATA_WIDTH(32), + .RESET_VAL (32'd240000) + ) u_rcu_timeout ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_timeout_ref_d), + .dat_o (s_timeout_ref_q) + ); + dffr #(32) u_rcu_gate_request ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_gate_req_d), + .dat_o (s_gate_req_q) + ); + dffr #(32) u_rcu_gate_status ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_gate_stat_d), + .dat_o (s_gate_stat_q) + ); + dffr #(32) u_rcu_reset_request ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_rst_req_d), + .dat_o (s_rst_req_q) + ); + dffr #(32) u_rcu_reset_done ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_reset_done_d), + .dat_o (s_reset_done_q) + ); + dffrc #( + .DATA_WIDTH(32), + .RESET_VAL (32'd1) + ) u_rcu_reset_cause ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_reset_cause_d), + .dat_o (s_reset_cause_q) + ); + dffr #(32) u_rcu_fault ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_rcu_fault_d), + .dat_o (s_rcu_fault_q) + ); + dffr #(32) u_rcu_irq_state ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_rcu_irq_state_d), + .dat_o (s_rcu_irq_state_q) + ); + dffr #(32) u_rcu_irq_enable ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_rcu_irq_en_d), + .dat_o (s_rcu_irq_en_q) + ); + dffr #(32) u_rcu_clkout ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_clkout_control_d), + .dat_o (s_clkout_control_q) + ); + dffrc #( + .DATA_WIDTH(32), + .RESET_VAL (32'd24000000) + ) u_rcu_sys_hz ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_sys_hz_d), + .dat_o (s_sys_hz_q) + ); + dffrc #( + .DATA_WIDTH(32), + .RESET_VAL (32'd24000000) + ) u_rcu_mem_hz ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_mem_hz_d), + .dat_o (s_mem_hz_q) + ); + dffrc #( + .DATA_WIDTH(32), + .RESET_VAL (32'd24000000) + ) u_rcu_pclk_hz ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_pclk_hz_d), + .dat_o (s_pclk_hz_q) + ); dffr #( .DATA_WIDTH(32) ) u_fault_count_reg ( diff --git a/scripts/check_clock_reset_domains.py b/scripts/check_clock_reset_domains.py index 6d0acbe7..ed6ca33b 100644 --- a/scripts/check_clock_reset_domains.py +++ b/scripts/check_clock_reset_domains.py @@ -16,6 +16,8 @@ CDC_PRIMITIVES = { "async_fifo", "async_reqack", "axi4_async_bridge", + "tiny_axi4_async_bridge", + "tiny_apb_async_bridge", "cdc_2phase", "cdc_fifo", "cdc_sync", @@ -129,6 +131,21 @@ def validate(document_path: Path, root: Path) -> None: raw_crossings = document.get("crossings") if not isinstance(raw_crossings, list): raise ValueError("crossings must be a list") + generated = document.get("generated_apb_crossings") + if generated is not None: + if (document.get("clock_contract") != "TINY-R2-P7" or + generated.get("generator") != "scripts/generate_tiny.py" or + generated.get("primitive") != "tiny_apb_async_bridge" or + generated.get("source") != "system"): + raise ValueError("unrecognized generated APB crossing contract") + expected = {"aon": ["clint", "archinfo"], "memory": ["xpi"], + "peripheral": ["gpio", "uart0", "timer0", "timer1", "i2c0", "dma", + "pwm", "rtc", "wdg", "rng", "crc", "ws2812", "crypto", "i2s"]} + if (generated.get("destinations") != expected or + generated.get("same_clock_isolation") != ["sdio"] or + generated.get("native_system") != ["sysctrl", "sram"] or + not set(expected).issubset(names)): + raise ValueError("P7 generated APB domain assignment mismatch") crossing_names: set[str] = set() for index, value in enumerate(raw_crossings): field = f"crossings[{index}]" diff --git a/scripts/generate_tiny.py b/scripts/generate_tiny.py index 631c0606..22d04c40 100644 --- a/scripts/generate_tiny.py +++ b/scripts/generate_tiny.py @@ -16,6 +16,9 @@ from scripts.rtl.generate_pin_map import generate as generate_pin_map # noqa: E from scripts.setup_helpers import atomic_write # noqa: E402 TINY = ROOT / "rtl/tiny" +RCU_TARGETS = {"gpio": 1, "uart0": 2, "i2c0": 3, "timer0": 4, "timer1": 5, + "pwm": 6, "i2s": 7, "sdio": 8, "dma": 9, "ws2812": 10, + "rng": 11, "crc": 12, "crypto": 13} def read_topology(path: Path, memory_map: Path) -> dict: @@ -77,9 +80,9 @@ def read_topology(path: Path, memory_map: Path) -> dict: if document.get("private_crypto") != { "banks": 6, "bank_bytes": 4096, "macro_instance": "u_soc.u_crypto.u_storage.gen_bank[{bank}].gen_macro.u_sram.u_mem", - "clock_domain": "system", "logical_domain": "PCLK (SAFE24 alias)", + "clock_domain": "peripheral", "logical_domain": "PCLK", }: - raise ValueError("Tiny P6 requires six private Crypto banks on SAFE24 PCLK") + raise ValueError("Tiny requires six private Crypto banks on PCLK") return document @@ -111,7 +114,7 @@ def generate_bindings(document: dict, output: Path, pdk: str = "IHP130", board.append("wire [31:0] s_gpio = {" + ", ".join(f"s_gpio_{pin}" for pin in reversed(range(32))) + "};\n") atomic_write(output / "rtl/tiny_tb_gpio.svh", "\n".join(board)) - irq = [comment, "always_comb begin", " s_irq = '0;"] + irq = [comment, "always_comb begin", " s_irq_async = '0;"] header = ["#ifndef RETROSOC_GENERATED_IRQ_METADATA_H", "#define RETROSOC_GENERATED_IRQ_METADATA_H", "#include ", "#define RS_SOC_IRQ_VECTOR_WIDTH UINT32_C(32)", @@ -119,7 +122,7 @@ def generate_bindings(document: dict, output: Path, pdk: str = "IHP130", f"#define RS_SOC_ALLOCATED_IRQ_COUNT UINT32_C({len(document['interrupts']) - 2})"] for item in document["interrupts"]: bit, name = item["core_bit"], item["name"].upper() - irq.append(f" s_irq[{bit}] = {item['signal']};") + irq.append(f" s_irq_async[{bit}] = {item['signal']};") header.append(f"#define RS_SOC_IRQ_{name} UINT32_C({bit})") if bit >= 2: header.append(f"#define RS_SOC_EXT_IRQ_{name} UINT32_C({bit - 2})") @@ -164,13 +167,51 @@ def generate_bindings(document: dict, output: Path, pdk: str = "IHP130", reset_assignments = [] for index, target in enumerate(document["apb_targets"]): name = target["name"] + clock = ("s_ref_clk" if name in ("clint", "archinfo") else + "s_backend_mem_clk" if name == "xpi" else + "s_exec_clk" if name in ("sysctrl", "sram", "sdio") else "s_backend_pclk") + target_bit = RCU_TARGETS.get(name) + ip_clock = f"s_{name}_clk" if target_bit is not None else clock + if target_bit is not None: + declarations.append(f"logic {ip_clock};") 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}];") - bridges.append(f"assign s_target_rst_n[{index + 5}] = s_{name}_rst_n;") + declarations.append(f"apb4_if u_{name}_apb4_if (.pclk({ip_clock}), .presetn(s_{name}_rst_n));") + if target_bit is None: + reset_assignments.append(f"assign s_{name}_rst_n = s_leaf_rst_n[{index + 1}];") + else: + reset_assignments.append(f"(* keep_hierarchy = 1 *) tiny_peripheral_leaf u_{name}_lifecycle (" + f".clk_i({clock}), .rst_n_i(s_leaf_rst_n[{index + 1}]), " + f".gate_i(s_gate_request[{target_bit}]), " + f".reset_i(s_local_reset[{target_bit}]), " + f".clk_o({ip_clock}), .rst_n_o(s_{name}_rst_n), " + f".gated_o(s_actual_gate[{target_bit}]), " + f".held_o(s_reset_held[{target_bit}]));") + extra_ready = " && s_i2s_reset_ready" if name == "i2s" else "" + if name == "dma": + # CDC-ready outputs are observed in SYS, matching APB readiness. + extra_ready = " && s_dma_ready_sys && s_dma_idle_sys" + reset_assignments.append(f"assign s_reset_ready[{target_bit}] = s_{name}_rst_n && " + f"s_apb_cdc_ready[{index}]{extra_ready};") + reset_assignments.append(f"assign s_leaf_clk[{index + 1}] = {clock};") + bridges.append(f"assign s_target_rst_n[{index + 5}] = s_fabric_rst_n;") + crossed = clock != "s_exec_clk" or target_bit is not None + endpoint = f"u_{name}_sys_apb4_if" if crossed else f"u_{name}_apb4_if" bridges.append(f"tiny_axi42apb4 u_{name}_bridge (" - f".clk_i(clk_i), .rst_n_i(s_{name}_rst_n), " - f".axi4(u_targets_axi4_if[{index + 5}]), .apb4(u_{name}_apb4_if));") + f".clk_i(s_exec_clk), .rst_n_i(s_fabric_rst_n), " + f".axi4(u_targets_axi4_if[{index + 5}]), .apb4({endpoint}));") + if crossed: + disabled = f"s_disabled_sys[{target_bit}]" if target_bit is not None else "1'b0" + declarations.append(f"apb4_if {endpoint} (.pclk(s_exec_clk), .presetn(s_fabric_rst_n));") + bridges.append(f"tiny_apb_async_bridge u_{name}_cfg_cdc (" + f".src_clk_i(s_exec_clk), .src_rst_n_i(s_fabric_rst_n), " + f".dst_clk_i({ip_clock}), .dst_rst_n_i(s_{name}_rst_n), " + f".disabled_i({disabled}), " + f".idle_o(s_apb_cdc_idle[{index}]), " + f".ready_o(s_apb_cdc_ready[{index}]), " + f".src_apb4({endpoint}), .dst_apb4(u_{name}_apb4_if));") + else: + bridges.append(f"assign s_apb_cdc_idle[{index}] = 1'b1;") + bridges.append(f"assign s_apb_cdc_ready[{index}] = 1'b1;") decode.append(f"if ({' || '.join(f'`SOC_ADDR_IS_{r}(addr_i)' for r in target['regions'])}) " f"return 5'd{index + 5};") atomic_write(output / "rtl/tiny_apb_interfaces.svh", "\n".join(declarations + reset_assignments) + "\n") diff --git a/scripts/run_c_tests.py b/scripts/run_c_tests.py index 50795283..fe896652 100644 --- a/scripts/run_c_tests.py +++ b/scripts/run_c_tests.py @@ -43,6 +43,7 @@ TEST_SOURCES = ( "crt/src/hal/fabric_monitor.c", "crt/src/hal/sysctrl.c", "crt/src/hal/clock.c", + "crt/src/hal/rcu.c", "crt/src/hal/resource.c", "crt/src/hal/extension.c", "crt/src/hal/user_ip.c", @@ -122,6 +123,7 @@ def main() -> int: "-DRS_NPU_TEST_MMIO", "-DRS_FABRIC_MONITOR_TEST_MMIO", "-DRS_RESOURCE_TEST_MMIO", + "-DRS_TINY_RCU_TEST_MMIO", "-I", str(memory_map_root / "include"), "-I", diff --git a/scripts/tiny_ics55_platform.py b/scripts/tiny_ics55_platform.py index 6db9dfb6..281765c6 100644 --- a/scripts/tiny_ics55_platform.py +++ b/scripts/tiny_ics55_platform.py @@ -114,10 +114,12 @@ def verify_locked_views(root: Path) -> dict: return verified -def inspect_netlist(document: dict) -> dict: +def expand_netlist(document: dict) -> tuple: + """Resolve hierarchy aliases without assigning a product clock contract.""" modules = document["modules"] top = "retrosoc_tiny_asic" leaves = [] + boundaries = {} aliases = {} def canonical(bit): @@ -159,6 +161,7 @@ def inspect_netlist(document: dict) -> dict: path = f"{scope}/{name}" attrs = modules.get(kind, {}).get("attributes", {}) if kind in modules and not int(str(attrs.get("blackbox", "0")), 2): + boundaries[path] = {"path": path, "type": kind, "ports": ports} visit(kind, path, ports) else: directions = cell.get("port_directions", { @@ -168,9 +171,15 @@ def inspect_netlist(document: dict) -> dict: "directions": directions}) visit(top, top, {}) - for c in leaves: + for c in [*leaves, *boundaries.values()]: for port, bits in c["ports"].items(): c["ports"][port] = [canonical(bit) for bit in bits] + return modules, top, leaves, canonical, boundaries + + +def inspect_netlist(document: dict) -> dict: + # Keep the original P1 parked-PLL/direct-SYS contract fail-closed. + modules, top, leaves, canonical, _ = expand_netlist(document) 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") diff --git a/scripts/tiny_r2_p7.py b/scripts/tiny_r2_p7.py new file mode 100644 index 00000000..082c9f41 --- /dev/null +++ b/scripts/tiny_r2_p7.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +"""P7 structural clock/macro evidence; separate from the frozen P1 audit.""" +from __future__ import annotations + +import argparse +import json +from pathlib import Path +import sys + +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_ics55_platform import expand_netlist, inspect_clock_source # noqa: E402 + +PHASE = "TINY-R2-P7" +RAM_TYPES = {"ICS55": ("ics55_ecos_sram_1024x32_m8", "CLK"), + "IHP130": ("RM_IHPSG13_1P_1024x32_c2_bm_bist", "A_CLK")} + + +def inspect_netlist(document: dict, pdk: str) -> dict: + modules, top, leaves, canonical, boundaries = expand_netlist(document) + + def boundary(suffix: str, kind: str) -> dict: + found = [cell for name, cell in boundaries.items() + if name.replace("/", ".").endswith("." + suffix) + and cell["type"].split("$")[0] == kind] + if len(found) != 1: + raise ValueError(f"missing or ambiguous P7 clock boundary: {suffix}") + return found[0] + + def pin(cell: dict, name: str) -> str: + bits = cell["ports"].get(name, []) + if len(bits) != 1 or bits[0] in ("0", "1", "x", "z"): + raise ValueError(f"missing/constant P7 clock: {cell['path']}/{name}") + return bits[0] + + root = boundary("u_soc.u_clock_backend.u_sys_mux", "safe_clock_mux") + memory = boundary("u_soc.u_clock_backend.u_mem_mux", "safe_clock_mux") + peripheral = boundary("u_soc.u_clock_backend.u_pclk_mux", "safe_clock_mux") + cpu_gate = boundary("u_soc.u_cpu.u_cpu_gate", "tiny_clock_gate") + crypto_gate = boundary("u_soc.u_crypto_lifecycle", "tiny_peripheral_leaf") + sys_clock = pin(root, "clk_o") + pclk = pin(peripheral, "clk_o") + if pin(cpu_gate, "clk_i") != sys_clock or pin(memory, "clk0_i") != sys_clock: + raise ValueError("CPU gate/MEM source is not common SYS") + if pin(crypto_gate, "clk_i") != pclk: + raise ValueError("private Crypto storage gate is not sourced from PCLK") + cpu_clock = pin(cpu_gate, "clk_o") + crypto_clock = pin(crypto_gate, "clk_o") + ram_type, ram_pin = RAM_TYPES[pdk] + macros = [cell for cell in leaves if cell["type"] == ram_type] + main_names = {f"u_soc.u_sram.gen_group[{g}].u_group.gen_bank[{n}].u_ram.u_mem" + for g in range(4) for n in range(8)} + crypto_names = {f"u_soc.u_crypto.u_storage.gen_bank[{n}].gen_macro.u_sram.u_mem" + for n in range(6)} + observed = set() + for cell in macros: + name = next((name for name in main_names | crypto_names + if cell["path"].replace("/", ".").endswith("." + name)), None) + if name is None or name in observed: + raise ValueError("extra, duplicated or renamed physical SRAM macro") + observed.add(name) + expected_clock = sys_clock if name in main_names else crypto_clock + if pin(cell, ram_pin) != expected_clock: + raise ValueError(f"physical SRAM clock mismatch: {name}") + if len(macros) != 38 or observed != main_names | crypto_names: + raise ValueError("P7 requires 32 main SRAM plus six private Crypto macros") + cpu = [cell for cell in leaves if "u_hazard3_cpu_2port" in cell["path"] + and ("CK" in cell["ports"] or "CKN" in cell["ports"] or "CLK" in cell["ports"])] + if not cpu: + raise ValueError("missing CPU clock endpoints") + for cell in cpu: + for name in ("CK", "CKN", "CLK"): + if name in cell["ports"] and pin(cell, name) not in (sys_clock, cpu_clock): + raise ValueError("CPU has an unapproved clock or a hidden divider") + pll = [cell for cell in leaves if cell["type"] == "PLL_TOP"] + if pdk == "ICS55": + if len(pll) != 1 or pin(pll[0], "CKOUT1") != pin(root, "clk1_i"): + raise ValueError("P7 requires one PLL CKOUT1 connected to the SYS mux") + if pin(pll[0], "REFCLK") != pin(root, "clk0_i"): + raise ValueError("PLL reference and safe SYS source differ") + constants = {"0": 0, "1": 1} + for cell in leaves: + if cell["type"] in ("TIELOH7R", "TIEHIH7R"): + for bit in cell["ports"]["Z"]: + constants[bit] = int(cell["type"] == "TIEHIH7R") + if pll[0]["ports"].get("EN", ["x"])[0] in constants: + raise ValueError("P7 PLL enable must be controlled, not tied on/off") + external = modules[top]["ports"]["extclk_i_pad"]["bits"] + if len(external) != 1: + raise ValueError("missing external reference input") + inspect_clock_source(leaves, pin(root, "clk0_i"), + canonical(f"{top}:{external[0]}"), constants) + elif pll: + raise ValueError("no-PLL compatibility instantiated a PLL") + return {"system_clock": sys_clock, "memory_clock": pin(memory, "clk_o"), + "peripheral_clock": pclk, "cpu_clock": cpu_clock, + "main_macros": 32, "private_crypto_macros": 6, "pll_macros": len(pll), + "cpu_clocked_cells": len(cpu), "macro_bindings": macros, + "clock_boundaries": [root, memory, peripheral, cpu_gate, crypto_gate], + "qualification": "structural domain binding only; no physical operating-point qualification"} + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("action", choices=("audit-netlist", "verify-netlist", "capture-source", "verify-source")) + parser.add_argument("--variant-root", type=Path, required=True) + parser.add_argument("--netlist", type=Path) + parser.add_argument("--config", type=Path) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + try: + if args.action in ("capture-source", "verify-source"): + inputs = b.source_inputs(ROOT, "ICS55") | b.source_inputs(ROOT, "IHP130") + for folder in ("tests", "configs/ci"): + for path in (ROOT / folder).rglob("*"): + if path.is_file() and path.suffix in (".py", ".sv", ".svh", ".c", ".S", ".ld", ".mk"): + inputs[str(path.relative_to(ROOT))] = b.sha256(path) + contract = b.artifact(ROOT / "docs/ip/tiny-soc.md") + if args.action == "capture-source": + if args.output.exists(): + raise ValueError("source evidence already exists; use a new attempt instead of overwriting") + b.dump(args.output, {"phase": PHASE, "repository": b.git_info(ROOT), + "specification": contract, "source_inputs": inputs, + "source_digest": b.digest_map(inputs), "tools": b.tools_identity(ROOT), + "snapshot": b.snapshot(ROOT, args.output.parent / "source", inputs)}) + else: + recorded = b.read_json(args.output) + if (recorded.get("phase") != PHASE or recorded.get("source_inputs") != inputs or + recorded.get("specification") != contract): + raise ValueError("P7 sources changed after the retained source snapshot") + print(f"{PHASE} {args.action}: PASS") + return 0 + if args.netlist is None or args.config is None: + raise ValueError("netlist actions require --netlist and --config") + manifest = b.configuration(args.variant_root) + pdk = manifest["configuration"]["PDK"] + config = dict(line.split("=", 1) for line in args.config.read_text().splitlines() if "=" in line) + for key, value in {"SOC": "TINY", "PDK": pdk, + "HAVE_PLL": "YES" if pdk == "ICS55" else "NO", + "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"P7 consumed 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 args.variant_root.name.rsplit("-", 1)[1] != expected: + raise ValueError("P7 consumed configuration digest mismatch") + paths = (args.netlist, Path(str(args.netlist) + ".json"), args.config) + if args.action == "audit-netlist" and not paths[1].is_file(): + # A mapped Verilog export is also a valid audit input. Serialize it + # without optimization or remapping; retain the conversion result. + # This supports the pre-P7 IHP130 recipe which did not emit JSON. + flow = b.read_json(args.variant_root / "syn/yosys/result-synth.json") + if flow.get("status") != "passed" or flow.get("exit_code") != 0: + raise ValueError("cannot derive audit JSON from an unsuccessful synthesis") + b.execute(ROOT, args.output.parent, "p7-netlist-json", + ["timeout", "--foreground", "120s", "yosys", "-Q", "-T", "-p", + f'read_verilog "{paths[0]}"; write_json "{paths[1]}"'], quiet=True) + inputs = [b.artifact(path) for path in paths] + auditors = [b.artifact(ROOT / "scripts" / name) for name in + ("tiny_r2_p7.py", "tiny_ics55_platform.py", "tiny_r2_baseline.py", "config_key.py")] + if args.action == "audit-netlist": + result = {"phase": PHASE, "status": "passed", "pdk": pdk, + "audit_scripts": auditors, "inputs": inputs, + "configuration": manifest["configuration"], "config_digest": expected, + "binding": inspect_netlist(json.loads(paths[1].read_text()), pdk)} + b.dump(args.output, result) + else: + result = b.read_json(args.output) + if (result.get("status") != "passed" or result.get("phase") != PHASE or + result.get("audit_scripts") != auditors or result.get("config_digest") != expected or + result.get("inputs") != inputs): + raise ValueError("P7 structural evidence is missing, stale, or not passed") + except (OSError, ValueError, KeyError) as error: + print(f"{PHASE}: {error}", file=sys.stderr) + return 1 + print(f"{PHASE} {args.action}: PASS (structural evidence only)") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/c/test_runtime.c b/tests/c/test_runtime.c index 985bb920..664778e6 100644 --- a/tests/c/test_runtime.c +++ b/tests/c/test_runtime.c @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include #include @@ -65,6 +67,7 @@ uint32_t rs_ga2d_test_mem_pad_mode; volatile uint32_t rs_npu_test_mmio[1024]; volatile uint32_t rs_fabric_monitor_test_mmio[1024]; volatile uint32_t rs_resource_test_mmio[1024]; +volatile uint32_t rs_tiny_rcu_test_mmio[1024]; volatile uint32_t rs_dma_test_mmio[1024]; static uint32_t ws_registers[16]; static uint32_t ws_pushed; @@ -3148,6 +3151,61 @@ static int test_crypto_lifecycle_contract(void) { return 0; } +static int test_tiny_rcu_boundaries(void) { + rs_tiny_rcu_snapshot_t snapshot; + uint32_t hz = 0U; + rs_tiny_rcu_test_mmio[RS_TINY_RCU_CAPABILITY_OFFSET / 4U] = 3U; + rs_tiny_rcu_test_mmio[RS_TINY_RCU_TARGET_CAP_OFFSET / 4U] = 0x3FFFU; + rs_tiny_rcu_test_mmio[RS_TINY_RCU_XIN_HZ_OFFSET / 4U] = 96000000U; + rs_tiny_rcu_test_mmio[RS_TINY_RCU_REF_HZ_OFFSET / 4U] = 24000000U; + rs_tiny_rcu_test_mmio[RS_TINY_RCU_COMMAND_OFFSET / 4U] = 0xDEADU; + if (rs_tiny_rcu_request_profile(RS_TINY_RCU_PROFILE_PLL192, 10U) != RS_ENOTSUP || + rs_tiny_rcu_request_profile((rs_tiny_rcu_profile_t)4, 10U) != RS_EINVAL || + rs_tiny_rcu_apply_gate(0U, 0U) != RS_EINVAL || + rs_tiny_rcu_apply_reset(2U, 0U) != RS_EINVAL || + rs_tiny_rcu_apply_reset(1U, 10U) != RS_EINVAL || + rs_tiny_rcu_apply_gate(1U << 14U, 10U) != RS_ENOTSUP || + rs_tiny_rcu_apply_reset(1U << 17U, 10U) != RS_ENOTSUP) { + return 1; + } + rs_tiny_rcu_test_mmio[RS_TINY_RCU_STATUS_OFFSET / 4U] = RS_TINY_RCU_STATUS_BUSY; + if (rs_tiny_rcu_get_snapshot(&snapshot) != RS_EBUSY || + rs_tiny_rcu_apply_gate(2U, 10U) != RS_EBUSY || + rs_tiny_rcu_request_profile(RS_TINY_RCU_PROFILE_SAFE24, 10U) != RS_EBUSY) { + return 2; + } + rs_tiny_rcu_test_mmio[RS_TINY_RCU_STATUS_OFFSET / 4U] = 0U; + rs_tiny_rcu_test_mmio[RS_TINY_RCU_GATE_STATUS_OFFSET / 4U] = 2U; + if (rs_tiny_rcu_apply_reset(2U, 10U) != RS_EBUSY) { + return 3; + } + rs_tiny_rcu_test_mmio[RS_TINY_RCU_FAULT_OFFSET / 4U] = 4U; + if (rs_tiny_rcu_apply_gate(2U, 10U) != RS_ETIMEOUT) { + return 4; + } + rs_tiny_rcu_test_mmio[RS_TINY_RCU_FAULT_OFFSET / 4U] = 8U; + if (rs_tiny_rcu_apply_gate(2U, 10U) != RS_EIO || + rs_tiny_rcu_test_mmio[RS_TINY_RCU_COMMAND_OFFSET / 4U] != 0xDEADU) { + return 5; + } + rs_tiny_rcu_test_mmio[RS_TINY_RCU_FAULT_OFFSET / 4U] = 0U; + if (rs_tiny_rcu_profile_hz(RS_TINY_RCU_PROFILE_XIN, &hz) != RS_OK || hz != 96000000U || + rs_tiny_rcu_get_snapshot(NULL) != RS_EINVAL || + rs_tiny_rcu_get_snapshot(&snapshot) != RS_OK || snapshot.xin_hz != hz || + rs_tiny_rcu_set_clkout(5U, 1U) != RS_EINVAL || + rs_tiny_rcu_set_clkout(1U, 0U) != RS_EINVAL || + rs_tiny_rcu_clear_fault(32U) != RS_EINVAL || + rs_tiny_rcu_clear_reset_cause(64U) != RS_EINVAL) { + return 6; + } + if (rs_tiny_rcu_set_clkout(1U, 1U) != RS_OK || + rs_tiny_rcu_set_clkout(1U, 2U) != RS_EBUSY || + rs_tiny_rcu_set_clkout(0U, 0U) != RS_OK) { + return 7; + } + return 0; +} + int main(void) { const int results[] = { test_string_helpers(), @@ -3156,6 +3214,7 @@ int main(void) { test_wait_helper(), test_crypto_lifecycle_contract(), test_clock_frequency_contract(), + test_tiny_rcu_boundaries(), test_ws2812_helpers(), test_dma_sessions(), test_ws2812_refill(), diff --git a/tests/rtl/tiny_apb_async_bridge_tb.sv b/tests/rtl/tiny_apb_async_bridge_tb.sv new file mode 100644 index 00000000..0c3790c1 --- /dev/null +++ b/tests/rtl/tiny_apb_async_bridge_tb.sv @@ -0,0 +1,87 @@ +`timescale 1ns / 1ps +module tiny_apb_async_bridge_tb; + logic clk_i = 0, dst_clk = 0, rst_n_i = 0, dst_rst_n = 0; + logic run_dst = 1, disabled = 0, idle, ready; + logic respond = 1; + integer writes = 0; + logic [31:0] saved; + logic [31:0] value; + always #5 clk_i = !clk_i; + always #17 if (run_dst) dst_clk = !dst_clk; + apb4_if apb4 ( + .pclk (clk_i), + .presetn(rst_n_i) + ); + apb4_if endpoint ( + .pclk (dst_clk), + .presetn(dst_rst_n) + ); + tiny_apb_async_bridge u_dut ( + .src_clk_i (clk_i), + .src_rst_n_i(rst_n_i), + .dst_clk_i (dst_clk), + .dst_rst_n_i(dst_rst_n), + .disabled_i (disabled), + .idle_o (idle), + .ready_o (ready), + .src_apb4 (apb4), + .dst_apb4 (endpoint) + ); + assign endpoint.pready = endpoint.psel && endpoint.penable && respond; + assign endpoint.pslverr = endpoint.paddr[8]; + assign endpoint.prdata = saved ^ endpoint.paddr; + always @(posedge dst_clk) begin + if (dst_rst_n && endpoint.psel && endpoint.penable && endpoint.pready && endpoint.pwrite) begin + writes = writes + 1; + if (endpoint.pstrb != 4'h5 || endpoint.pprot != 0) $fatal(1, "APB payload changed"); + saved = endpoint.pwdata; + end + end + `include "apb4_bfm.svh" + initial begin + apb4_idle(); + #2; + rst_n_i = 0; + dst_rst_n = 0; + repeat (6) @(negedge clk_i); + rst_n_i = 1; + dst_rst_n = 1; + wait (ready); + repeat (5) @(negedge clk_i); + apb4_write(32'h20, 32'h87654321, 4'h5, 0); + apb4_read(32'h20, 0, value); + if (value != 32'h87654301 || writes != 1) $fatal(1, "APB CDC data/duplicate"); + apb4_read(32'h100, 1, value); + respond = 0; + fork + apb4_read(32'h20, 1, value); + begin + wait (endpoint.psel && endpoint.penable); + #3 dst_rst_n = 0; + repeat (15) @(negedge clk_i); + dst_rst_n = 1; + end + join + respond = 1; + wait (ready); + repeat (30) @(negedge clk_i); + if (endpoint.psel) $fatal(1, "reset replayed an old APB request"); + disabled = 1; + @(negedge dst_clk); + run_dst = 0; + apb4_read(32'h20, 1, value); + apb4_write(32'h20, 32'hbad, 4'h5, 1); + if (writes != 1) $fatal(1, "disabled APB side effect"); + run_dst = 1; + disabled = 0; + repeat (30) @(negedge clk_i); + apb4_read(32'h20, 0, value); + if (value != 32'h87654301) $fatal(1, "CDC restart data"); + $display("SIM_TEST_PASS Tiny APB CDC"); + $finish; + end + initial begin + #100000; + $fatal(1, "SIM_TEST_TIMEOUT Tiny APB CDC"); + end +endmodule diff --git a/tests/rtl/tiny_axi4_admission_tb.sv b/tests/rtl/tiny_axi4_admission_tb.sv new file mode 100644 index 00000000..0f151659 --- /dev/null +++ b/tests/rtl/tiny_axi4_admission_tb.sv @@ -0,0 +1,135 @@ +`timescale 1ns / 1ps +module tiny_axi4_admission_tb; + logic clk = 0, rst_n = 0, block_new = 0, idle; + always #5 clk = !clk; + axi4_if source ( + .aclk (clk), + .aresetn(rst_n) + ); + axi4_if sink ( + .aclk (clk), + .aresetn(rst_n) + ); + tiny_axi4_admission u_dut ( + .clk_i (clk), + .rst_n_i(rst_n), + .block_i(block_new), + .idle_o (idle), + .source (source), + .sink (sink) + ); + task automatic tick; + @(posedge clk); + #1; + @(negedge clk); + endtask + initial begin + source.awvalid = 0; + source.arvalid = 0; + source.wvalid = 0; + source.awid = 1; + source.awaddr = 'h30001234; + source.awlen = 1; + source.awsize = 2; + source.awburst = 1; + source.awlock = 0; + source.awcache = 0; + source.awprot = 0; + source.awqos = 0; + source.awregion = 0; + source.awuser = 0; + source.arid = 0; + source.araddr = 'h30005678; + source.arlen = 1; + source.arsize = 2; + source.arburst = 1; + source.arlock = 0; + source.arcache = 0; + source.arprot = 0; + source.arqos = 0; + source.arregion = 0; + source.aruser = 0; + source.wdata = 'h12345678; + source.wstrb = 15; + source.wlast = 0; + source.wuser = 0; + source.bready = 0; + source.rready = 0; + sink.awready = 0; + sink.arready = 0; + sink.wready = 1; + sink.bid = 1; + sink.bresp = 0; + sink.buser = 0; + sink.bvalid = 0; + sink.rid = 0; + sink.rdata = 'hdeadbeef; + sink.rresp = 0; + sink.rlast = 0; + sink.ruser = 0; + sink.rvalid = 0; + repeat (3) tick(); + rst_n = 1; + tick(); + source.awvalid = 1; + source.arvalid = 1; + source.wvalid = 1; + tick(); + block_new = 1; + source.awvalid = 0; + source.arvalid = 0; + source.awaddr = 0; + source.araddr = 0; + repeat (5) begin + tick(); + if (idle || !sink.awvalid || !sink.arvalid || source.wready || + sink.awaddr != 'h30001234 || sink.araddr != 'h30005678) + $fatal(1, "accepted stalled address lost when admission blocked"); + end + sink.awready = 1; + sink.arready = 1; + tick(); + sink.awready = 0; + sink.arready = 0; + source.wlast = 1; + tick(); + source.wvalid = 0; + sink.bvalid = 1; + sink.rvalid = 1; + repeat (3) begin + tick(); + if (idle || !source.bvalid || !source.rvalid || source.rdata != 'hdeadbeef) + $fatal(1, "response backpressure lost outstanding work"); + end + source.bready = 1; + source.rready = 1; + tick(); + sink.bvalid = 0; + sink.rlast = 1; + if (idle) $fatal(1, "read retired before RLAST"); + tick(); + sink.rvalid = 0; + source.awvalid = 1; + source.arvalid = 1; + repeat (3) begin + tick(); + if (!idle || source.awready || source.arready || sink.awvalid || sink.arvalid) + $fatal(1, "unaccepted blocked request counted as outstanding"); + end + block_new = 0; + tick(); + source.awvalid = 0; + source.arvalid = 0; + if (idle || !sink.awvalid || !sink.arvalid) + $fatal(1, "retained upstream requests did not resume"); + rst_n = 0; + tick(); + if (!idle || sink.awvalid || sink.arvalid) $fatal(1, "reset replayed old session"); + $display("SIM_TEST_PASS Tiny AXI admission drain/backpressure/resume"); + $finish; + end + initial begin + #10000; + $fatal(1, "SIM_TEST_TIMEOUT admission"); + end +endmodule diff --git a/tests/rtl/tiny_axi4_async_bridge_tb.sv b/tests/rtl/tiny_axi4_async_bridge_tb.sv new file mode 100644 index 00000000..577698e0 --- /dev/null +++ b/tests/rtl/tiny_axi4_async_bridge_tb.sv @@ -0,0 +1,432 @@ +`timescale 1ns / 1ps + +module tiny_axi4_async_bridge_tb; + logic src_clk = 1'b0; + logic dst_clk = 1'b0; + logic src_rst_n = 1'b0; + logic dst_rst_n = 1'b0; + logic clear = 1'b0; + logic clear_busy; + logic ready; + integer sink_ar_count = 0; + integer sink_aw_count = 0; + integer source_r_count = 0; + integer source_b_count = 0; + logic test_reset_response; + logic test_write_response; + + always @(posedge src_clk) begin + if (src_rst_n && source.rvalid && source.rready) source_r_count++; + if (src_rst_n && source.bvalid && source.bready) source_b_count++; + end + + always #5 src_clk = !src_clk; + always #7 dst_clk = !dst_clk; + + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) source ( + .aclk (src_clk), + .aresetn(src_rst_n) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) sink ( + .aclk (dst_clk), + .aresetn(dst_rst_n) + ); + + tiny_axi4_async_bridge u_dut ( + .src_clk_i (src_clk), + .src_rst_n_i (src_rst_n), + .dst_clk_i (dst_clk), + .dst_rst_n_i (dst_rst_n), + .clear_i (clear), + .clear_busy_o(clear_busy), + .ready_o (ready), + .epoch_o (), + .src_axi4 (source), + .dst_axi4 (sink) + ); + + always @(posedge dst_clk) begin + if (dst_rst_n && sink.arvalid && sink.arready) sink_ar_count = sink_ar_count + 1; + if (dst_rst_n && sink.awvalid && sink.awready) sink_aw_count = sink_aw_count + 1; + end + + task automatic source_defaults; + begin + source.awid = 0; + source.awaddr = '0; + source.awlen = 0; + source.awsize = 2; + source.awburst = 1; + source.awlock = 0; + source.awcache = 0; + source.awprot = 0; + source.awqos = 0; + source.awregion = 0; + source.awuser = 0; + source.awvalid = 0; + source.wdata = 32'h1234_5678; + source.wstrb = 4'hf; + source.wlast = 1; + source.wuser = 0; + source.wvalid = 0; + source.bready = 1; + source.arid = 0; + source.araddr = '0; + source.arlen = 0; + source.arsize = 2; + source.arburst = 1; + source.arlock = 0; + source.arcache = 0; + source.arprot = 0; + source.arqos = 0; + source.arregion = 0; + source.aruser = 0; + source.arvalid = 0; + source.rready = 1; + end + endtask + + task automatic sink_defaults; + begin + sink.awready = 1; + sink.wready = 1; + sink.bid = 0; + sink.bresp = 0; + sink.buser = 0; + sink.bvalid = 0; + sink.arready = 1; + sink.rid = 0; + sink.rdata = 32'hcafe_babe; + sink.rresp = 0; + sink.rlast = 1; + sink.ruser = 0; + sink.rvalid = 0; + end + endtask + + task automatic wait_ready; + begin + wait (ready === 1'b1); + repeat (3) @(posedge src_clk); + end + endtask + + task automatic issue_read(input logic [31:0] address); + begin + @(negedge src_clk); + source.araddr = address; + source.arvalid = 1; + do @(posedge src_clk); while (!source.arready); + @(negedge src_clk); + source.arvalid = 0; + end + endtask + + task automatic issue_write(input logic [31:0] address); + begin + @(negedge src_clk); + source.awaddr = address; + source.awvalid = 1; + do @(posedge src_clk); while (!source.awready); + @(negedge src_clk); + source.awvalid = 0; + source.wvalid = 1; + do @(posedge src_clk); while (!source.wready); + @(negedge src_clk); + source.wvalid = 0; + end + endtask + + initial begin + source_defaults(); + sink_defaults(); + test_reset_response = $test$plusargs("same_cycle_reset_read") || + $test$plusargs("same_cycle_reset_write"); + test_write_response = $test$plusargs("same_cycle_write") || + $test$plusargs("held_write_stability") || $test$plusargs("same_cycle_reset_write"); + repeat (5) @(posedge src_clk); + src_rst_n = 1; + dst_rst_n = 1; + wait_ready(); + + if ($test$plusargs("clear_pending")) begin + source.rready = 0; + issue_read(32'h5400_0010); + repeat (12) @(negedge dst_clk); + @(negedge src_clk); + clear = 1; + wait (source.rvalid === 1'b1); + @(negedge src_clk); + if (source.rresp !== 2'b11) $fatal(1, "SIM_TEST_FAIL clear must return error"); + clear = 0; + source.rready = 1; + @(posedge src_clk); + @(negedge src_clk); + repeat (200) @(negedge src_clk); + if (source_r_count != 1 || ready || source.arready) + $fatal(1, "SIM_TEST_FAIL pending clear lost isolation"); + // A delayed pre-clear response must never complete a new request. + @(negedge dst_clk); + sink.rvalid = 1; + do @(posedge dst_clk); while (!sink.rready); + @(negedge dst_clk); + sink.rvalid = 0; + repeat (20) @(negedge src_clk); + if (source.rvalid || source_r_count != 1) + $fatal(1, "SIM_TEST_FAIL late response escaped clear isolation"); + dst_rst_n = 0; + repeat (8) @(negedge src_clk); + dst_rst_n = 1; + wait_ready(); + repeat (20) @(negedge src_clk); + if (source.rvalid || source_r_count != 1) + $fatal(1, "SIM_TEST_FAIL late response replay after reset"); + $display("SIM_TEST_PASS Tiny AXI pending-clear quarantine/recovery"); + $finish; + end + + if ($test$plusargs("burst_abort")) begin + source.arlen = 3; + source.arid = 1; + source.rready = 0; + issue_read(32'h5400_0100); + wait (sink.arvalid === 1'b1); + @(negedge dst_clk); + sink.rvalid = 1; + sink.rlast = 0; + sink.rid = 1; + do @(posedge dst_clk); while (!sink.rready); + @(negedge dst_clk); + sink.rvalid = 0; + wait (source.rvalid === 1'b1); + @(negedge src_clk); + dst_rst_n = 0; + repeat (8) @(negedge src_clk); + // Preserve the already exposed first beat; synthesize exactly the three + // missing error beats, with RLAST only on the original fourth beat. + for (integer beat = 0; beat < 4; beat++) begin + wait (source.rvalid === 1'b1); + @(negedge src_clk); + if (source.rid !== 1'b1 || source.rlast !== (beat == 3) || + source.rresp !== (beat == 0 ? 2'b00 : 2'b11)) + $fatal(1, "SIM_TEST_FAIL reset read burst beat=%0d", beat); + source.rready = 1; + @(posedge src_clk); + @(negedge src_clk); + source.rready = 0; + end + dst_rst_n = 1; + wait_ready(); + if (source_r_count != 4 || source.rvalid) $fatal(1, "SIM_TEST_FAIL read burst count"); + + source.awlen = 3; + source.awid = 1; + source.bready = 0; + @(negedge src_clk); + source.awvalid = 1; + do @(posedge src_clk); while (!source.awready); + @(negedge src_clk); + source.awvalid = 0; + dst_rst_n = 0; + repeat (8) @(negedge src_clk); + for (integer beat = 0; beat < 4; beat++) begin + if (source.bvalid) $fatal(1, "SIM_TEST_FAIL reset B before write data drain"); + source.wvalid = 1; + source.wlast = beat == 3; + do @(posedge src_clk); while (!source.wready); + @(negedge src_clk); + source.wvalid = 0; + end + wait (source.bvalid === 1'b1); + @(negedge src_clk); + if (source.bid !== 1'b1 || source.bresp !== 2'b11) + $fatal(1, "SIM_TEST_FAIL reset burst write response"); + source.bready = 1; + @(posedge src_clk); + @(negedge src_clk); + dst_rst_n = 1; + wait_ready(); + repeat (20) @(negedge src_clk); + if (source_b_count != 1 || source.bvalid) $fatal(1, "SIM_TEST_FAIL write burst count"); + $display("SIM_TEST_PASS Tiny AXI burst abort conservation"); + $finish; + end + + if (test_reset_response || $test$plusargs( + "same_cycle_clear" + ) || $test$plusargs( + "same_cycle_write" + ) || $test$plusargs( + "held_read_stability" + ) || $test$plusargs( + "held_write_stability" + )) begin + if (test_write_response) begin + source.bready = 0; + issue_write(32'h5400_0020); + repeat (12) @(negedge dst_clk); + sink.bvalid = 1; + do @(posedge dst_clk); while (!sink.bready); + @(negedge dst_clk); + sink.bvalid = 0; + wait (source.bvalid === 1'b1); + end else begin + source.rready = 0; + issue_read(32'h5400_0020); + wait (sink.arvalid === 1'b1); + @(negedge dst_clk); + sink.rvalid = 1; + do @(posedge dst_clk); while (!sink.rready); + @(negedge dst_clk); + sink.rvalid = 0; + wait (source.rvalid === 1'b1); + end + @(negedge src_clk); + if ($test$plusargs("held_read_stability") || $test$plusargs("held_write_stability")) begin + // Source reset remains high. A stalled response is still owned by + // the source interface even when the remote endpoint resets. + dst_rst_n = 0; + repeat (8) begin + @(negedge src_clk); + if ($test$plusargs( + "held_read_stability" + ) && (source.rvalid !== 1'b1 || source.rresp !== 2'b00 || + source.rdata !== 32'hcafe_babe || source.rlast !== 1'b1)) + $fatal(1, "SIM_TEST_FAIL stalled source R response changed on remote reset"); + if ($test$plusargs( + "held_write_stability" + ) && (source.bvalid !== 1'b1 || source.bresp !== 2'b00)) + $fatal(1, "SIM_TEST_FAIL stalled source B response changed on remote reset"); + end + $display("SIM_TEST_PASS Tiny AXI held response stability"); + $finish; + end + if (test_reset_response) begin + dst_rst_n = 0; + end else begin + clear = 1; + wait (u_dut.s_clear_src_pulse === 1'b1); + @(negedge src_clk); + end + source.rready = 1; + source.bready = 1; + @(posedge src_clk); + @(negedge src_clk); + clear = 0; + repeat (200) @(negedge src_clk); + if (test_write_response && source_b_count != 1) + $fatal(1, "SIM_TEST_FAIL one AW produced %0d B handshakes during clear", source_b_count); + if (!test_write_response && source_r_count != 1) + $fatal(1, "SIM_TEST_FAIL one AR produced %0d R handshakes during clear", source_r_count); + $display("SIM_TEST_PASS Tiny AXI same-cycle clear conservation"); + $finish; + end + + issue_read(32'h5400_0040); + wait (sink.arvalid === 1'b1); + @(negedge dst_clk); + sink.rvalid = 1; + do @(posedge dst_clk); while (!sink.rready); + @(negedge dst_clk); + sink.rvalid = 0; + wait (source.rvalid === 1'b1); + if (source.rresp != 2'b00 || source.rdata != 32'hcafe_babe || !source.rlast) + $fatal(1, "normal Tiny AXI read response mismatch"); + @(posedge src_clk); + @(negedge src_clk); + + source.rready = 0; + issue_read(32'h5400_0060); + wait (sink.arvalid === 1'b1); + @(negedge dst_clk); + sink.rvalid = 1; + do @(posedge dst_clk); while (!sink.rready); + @(negedge dst_clk); + sink.rvalid = 0; + wait (source.rvalid === 1'b1); + dst_rst_n = 0; + wait (u_dut.s_abort_read_q === 1'b1); + #1; + if (!source.rvalid || source.rresp != 2'b00 || !source.rlast || source.rdata != 32'hcafe_babe) + $fatal(1, "remote reset changed held read response"); + source.rready = 1; + do @(posedge src_clk); while (u_dut.s_abort_read_q); + source.rready = 0; + @(negedge dst_clk); + dst_rst_n = 1; + wait_ready(); + source.rready = 1; + + issue_read(32'h5400_0080); + wait (sink.arvalid === 1'b1); + sink.arready = 0; + @(negedge dst_clk); + dst_rst_n = 0; + wait (source.rvalid === 1'b1); + if (source.rresp != 2'b11 || !source.rlast || source.rdata != 0) + $fatal(1, "destination reset did not return read DECERR"); + if (sink.arvalid || sink.awvalid) $fatal(1, "destination reset retained stale request"); + @(negedge dst_clk); + dst_rst_n = 1; + sink.arready = 1; + wait_ready(); + if (sink_ar_count != 2) $fatal(1, "reset replayed the read request"); + + sink.awready = 0; + sink.wready = 0; + issue_write(32'h5400_00c0); + wait (sink.awvalid === 1'b1); + @(negedge dst_clk); + dst_rst_n = 0; + wait (source.bvalid === 1'b1); + if (source.bresp != 2'b11) $fatal(1, "destination reset did not return write DECERR"); + if (sink.arvalid || sink.awvalid || sink.wvalid) + $fatal(1, "destination reset retained stale write request"); + @(negedge dst_clk); + dst_rst_n = 1; + sink.awready = 1; + sink.wready = 1; + wait_ready(); + if (sink_aw_count != 0) $fatal(1, "reset replayed the write request"); + + source.bready = 0; + issue_write(32'h5400_00e0); + repeat (12) @(negedge dst_clk); + @(negedge dst_clk); + sink.bvalid = 1; + do @(posedge dst_clk); while (!sink.bready); + @(negedge dst_clk); + sink.bvalid = 0; + wait (source.bvalid === 1'b1); + dst_rst_n = 0; + wait (u_dut.s_abort_write_q === 1'b1); + #1; + if (!source.bvalid || source.bresp != 2'b00) + $fatal(1, "remote reset changed held write response"); + source.bready = 1; + do @(posedge src_clk); while (u_dut.s_abort_write_q); + source.bready = 0; + @(negedge dst_clk); + dst_rst_n = 1; + wait_ready(); + source.bready = 1; + + $display("SIM_TEST_PASS Tiny AXI CDC reset abort/error/no-replay"); + $finish; + end + + initial begin + #100000; + $fatal(1, "SIM_TEST_TIMEOUT Tiny AXI CDC"); + end +endmodule diff --git a/tests/rtl/tiny_clock_external_tb.sv b/tests/rtl/tiny_clock_external_tb.sv new file mode 100644 index 00000000..2c8a1fe0 --- /dev/null +++ b/tests/rtl/tiny_clock_external_tb.sv @@ -0,0 +1,159 @@ +`timescale 1ns / 1ps +module tiny_clock_external_tb; + localparam integer XinHz = `TINY_TEST_XIN_HZ; + logic xin = 0, run_xin = 1, rst_n = 0, apply = 0; + logic [1:0] profile = 0, current_profile; + logic ref_clk, sys_clk, mem_clk, pclk, done, busy, fault; + logic [31:0] xin_hz, ref_hz, sys_hz, mem_hz, pclk_hz; + logic por_reset_n, old_por_reset_n; + logic [2:0] reset_outputs, old_reset_outputs; + integer ref_edges = 0; + always #(500000000.0 / XinHz) if (run_xin) xin = !xin; + tiny_reset_tree #( + .LeafCount (1), + .SplitDomains(1) + ) u_reset_tree ( + .clk_i (sys_clk), + .por_clk_i (ref_clk), + .leaf_clk_i (sys_clk), + .backend_reset_n_i (1'b1), + .rst_n_i (rst_n), + .watchdog_reset_req_i(1'b0), + .por_rst_n_o (por_reset_n), + .system_rst_n_o (reset_outputs[0]), + .leaf_rst_n_o (reset_outputs[1]), + .cpu_ready_rst_n_o (reset_outputs[2]) + ); + tiny_reset_tree #( + .LeafCount (1), + .SplitDomains(1) + ) u_old_reset_tree ( + .clk_i (sys_clk), + .por_clk_i (xin), + .leaf_clk_i (sys_clk), + .backend_reset_n_i (1'b1), + .rst_n_i (rst_n), + .watchdog_reset_req_i(1'b0), + .por_rst_n_o (old_por_reset_n), + .system_rst_n_o (old_reset_outputs[0]), + .leaf_rst_n_o (old_reset_outputs[1]), + .cpu_ready_rst_n_o (old_reset_outputs[2]) + ); + always @(posedge ref_clk or negedge rst_n) begin + if (!rst_n) ref_edges = 0; + else ref_edges++; + end + always @(posedge por_reset_n) begin + if (ref_edges < 5) $fatal(1, "AON reset released before five REF24 edges"); + end + always @(negedge xin) begin + if (XinHz == 24000000 && rst_n && + ({por_reset_n, reset_outputs} !== {old_por_reset_n, old_reset_outputs})) + $fatal(1, "24 MHz reset connection correction changed behavior"); + end + tiny_clock_reset_subsystem #( + .RefClkHz(XinHz), + .HavePll (1'b0) + ) u_dut ( + .ref_clk_i (xin), + .rst_n_i (rst_n), + .profile_i (profile), + .apply_i (apply), + .idle_i (1'b1), + .clients_idle_i (1'b1), + .warm_reset_i (1'b0), + .system_request_i (1'b0), + .command_ack_i (1'b1), + .timeout_i (32'd512), + .current_profile_o(current_profile), + .busy_o (busy), + .fault_o (fault), + .fault_code_o (), + .done_o (done), + .block_o (), + .ref_clk_o (ref_clk), + .ref_rst_n_o (), + .system_reset_n_o (), + .pll_locked_o (), + .pll_selected_o (), + .sys_clk_o (sys_clk), + .mem_clk_o (mem_clk), + .pclk_o (pclk), + .sys_rst_n_o (), + .mem_rst_n_o (), + .pclk_rst_n_o (), + .xin_hz_o (xin_hz), + .ref_hz_o (ref_hz), + .sys_hz_o (sys_hz), + .mem_hz_o (mem_hz), + .pclk_hz_o (pclk_hz) + ); + task automatic command(input logic [1:0] requested); + @(negedge ref_clk); + profile = requested; + apply = 1; + @(negedge ref_clk); + apply = 0; + repeat (600) @(negedge ref_clk); + if (busy) $fatal(1, "external clock command stuck"); + endtask + task automatic measure(input real expected_sys, input real expected_pclk); + realtime start_time, period; + @(posedge sys_clk); + start_time = $realtime; + repeat (100) @(posedge sys_clk); + period = ($realtime - start_time) / 100.0; + if (period < expected_sys - 0.01 || period > expected_sys + 0.01) + $fatal(1, "external SYS frequency mismatch"); + @(posedge mem_clk); + start_time = $realtime; + repeat (100) @(posedge mem_clk); + period = ($realtime - start_time) / 100.0; + if (period < expected_sys - 0.01 || period > expected_sys + 0.01) + $fatal(1, "external MEM frequency mismatch"); + @(posedge pclk); + start_time = $realtime; + repeat (100) @(posedge pclk); + period = ($realtime - start_time) / 100.0; + if (period < expected_pclk - 0.02 || period > expected_pclk + 0.02) + $fatal(1, "external PCLK frequency mismatch"); + endtask + initial begin + #1 rst_n = 1; + #1 rst_n = 0; + repeat (20) @(negedge xin); + rst_n = 1; + repeat (200) @(negedge xin); + if (xin_hz != XinHz || ref_hz != 24000000 || sys_hz != 24000000) + $fatal(1, "external SAFE24 normalization failed"); + measure(1000.0 / 24.0, 1000.0 / 24.0); + command(1); + if (fault || current_profile != 1 || sys_hz != XinHz || mem_hz != XinHz || + pclk_hz != (XinHz > 60000000 ? XinHz / 2 : XinHz)) + $fatal(1, "external profile commit failed"); + measure(1000000000.0 / XinHz, (XinHz > 60000000 ? 2000000000.0 : 1000000000.0) / XinHz); + command(0); + measure(1000.0 / 24.0, 1000.0 / 24.0); + command(2); + if (!fault || current_profile != 0) $fatal(1, "no-PLL profile accepted a PLL request"); + @(negedge xin); + run_xin = 0; + #1000; + // XIN loss has no independent safety clock. Recovery is external reset. + rst_n = 0; + #100; + if (por_reset_n || reset_outputs != 0) $fatal(1, "stopped-clock reset did not assert"); + run_xin = 1; + repeat (20) @(negedge xin); + rst_n = 1; + repeat (200) @(negedge xin); + if (fault || current_profile != 0) $fatal(1, "external reset recovery failed"); + measure(1000.0 / 24.0, 1000.0 / 24.0); + $display("SIM_TEST_PASS Tiny external XIN=%0d", XinHz); + $finish; + end + initial begin + #1000000; + $fatal(1, "SIM_TEST_TIMEOUT Tiny external clock"); + end +endmodule diff --git a/tests/rtl/tiny_clock_output_tb.sv b/tests/rtl/tiny_clock_output_tb.sv new file mode 100644 index 00000000..9b6ea09c --- /dev/null +++ b/tests/rtl/tiny_clock_output_tb.sv @@ -0,0 +1,56 @@ +`timescale 1ns / 1ps +module tiny_clock_output_tb; + logic ref_clk = 0, sys_clk = 0, mem_clk = 0, pclk = 0, rst_n = 0; + logic [31:0] control = 0; + wire clk_out, enabled; + always #20 ref_clk = !ref_clk; + always #2.5 sys_clk = !sys_clk; + always #5 mem_clk = !mem_clk; + always #10 pclk = !pclk; + tiny_clock_output u_dut ( + .clk_ref_i (ref_clk), + .clk_sys_i (sys_clk), + .clk_mem_i (mem_clk), + .clk_periph_i(pclk), + .rst_n_i (rst_n), + .control_i (control), + .clk_o (clk_out), + .enable_o (enabled) + ); + task automatic measure(input integer source, divisor, input realtime expected); + realtime previous_edge; + @(negedge ref_clk); + control = (divisor << 8) | source; + repeat (12) @(negedge ref_clk); + if (!enabled) $fatal(1, "CLKOUT enable missing"); + @(posedge clk_out); + previous_edge = $realtime; + repeat (8) begin + @(posedge clk_out); + if ($realtime - previous_edge < expected - 0.001 || + $realtime - previous_edge > expected + 0.001) + $fatal(1, "CLKOUT source/divisor period mismatch source=%0d", source); + previous_edge = $realtime; + end + @(negedge ref_clk); + control = 0; + repeat (12) @(negedge ref_clk); + if (enabled || clk_out) $fatal(1, "CLKOUT disable not acknowledged low"); + endtask + initial begin + repeat (4) @(negedge ref_clk); + rst_n = 1; + repeat (4) @(negedge ref_clk); + if (enabled || clk_out) $fatal(1, "CLKOUT active at cold reset"); + measure(1, 1, 80.0); + measure(2, 5, 50.0); + measure(3, 3, 60.0); + measure(4, 2, 80.0); + $display("SIM_TEST_PASS Tiny CLKOUT measured sources/divisors/off"); + $finish; + end + initial begin + #100000; + $fatal(1, "SIM_TEST_TIMEOUT CLKOUT"); + end +endmodule diff --git a/tests/rtl/tiny_clock_reset_pll_tb.sv b/tests/rtl/tiny_clock_reset_pll_tb.sv new file mode 100644 index 00000000..7a3f9ddc --- /dev/null +++ b/tests/rtl/tiny_clock_reset_pll_tb.sv @@ -0,0 +1,175 @@ +`timescale 1ns / 1ps + +module tiny_clock_reset_pll_tb; + logic ref_clk_i = 1'b0; + logic rst_n_i = 1'b0; + logic [1:0] profile = 2'd0; + logic apply = 1'b0; + logic [1:0] current_profile; + logic busy, fault; + logic idle = 1, clients_idle = 1; + logic reset_session_n, done, pll_selected; + logic [4:0] fault_code; + logic [4:0] observed_faults = 0; + always @(posedge ref_clk_i) observed_faults <= observed_faults | fault_code; + logic sys_clk, mem_clk, pclk; + logic sys_rst_n, mem_rst_n, pclk_rst_n; + logic [31:0] xin_hz, ref_hz, sys_hz, mem_hz, pclk_hz; + + // Match the selected 24 MHz reference expected by the ICS55 PLL model. + always #20.833333 ref_clk_i = !ref_clk_i; + + tiny_clock_reset_subsystem #( + .RefClkHz(24_000_000), + .HavePll (1'b1) + ) u_dut ( + .ref_clk_i (ref_clk_i), + .rst_n_i (rst_n_i), + .profile_i (profile), + .apply_i (apply), + .idle_i (idle), + .clients_idle_i (clients_idle), + .warm_reset_i (1'b0), + .system_request_i (1'b0), + .command_ack_i (1'b1), +`ifdef TINY_PLL_SUCCESS + .timeout_i (32'd10000), +`else + .timeout_i (32'd512), +`endif + .fault_code_o (fault_code), + .done_o (done), + .block_o (), + .ref_clk_o (), + .ref_rst_n_o (), + .system_reset_n_o (reset_session_n), + .pll_locked_o (), + .pll_selected_o (pll_selected), + .current_profile_o(current_profile), + .busy_o (busy), + .fault_o (fault), + .sys_clk_o (sys_clk), + .mem_clk_o (mem_clk), + .pclk_o (pclk), + .sys_rst_n_o (sys_rst_n), + .mem_rst_n_o (mem_rst_n), + .pclk_rst_n_o (pclk_rst_n), + .xin_hz_o (xin_hz), + .ref_hz_o (ref_hz), + .sys_hz_o (sys_hz), + .mem_hz_o (mem_hz), + .pclk_hz_o (pclk_hz) + ); + + task automatic apply_profile(input logic [1:0] requested); + begin + @(negedge ref_clk_i); + profile = requested; + apply = 1'b1; + @(negedge ref_clk_i); + apply = 1'b0; + end + endtask + + task automatic wait_done; + integer cycles; + begin + cycles = 0; + while (!done && cycles < 12000) begin + @(negedge ref_clk_i); + cycles++; + end + if (!done || busy) $fatal(1, "backend did not finish with bounded handshake"); + end + endtask + + task automatic check_periods(input real sys_period, input real mem_period, + input real pclk_period); + realtime started, measured; + begin + @(posedge sys_clk); + started = $realtime; + repeat (100) @(posedge sys_clk); + measured = ($realtime - started) / 100.0; + if (measured < sys_period - 0.01 || measured > sys_period + 0.01) + $fatal(1, "SYS measured period %f expected %f", measured, sys_period); + @(posedge mem_clk); + started = $realtime; + repeat (100) @(posedge mem_clk); + measured = ($realtime - started) / 100.0; + if (measured < mem_period - 0.02 || measured > mem_period + 0.02) + $fatal(1, "MEM measured period %f expected %f", measured, mem_period); + @(posedge pclk); + started = $realtime; + repeat (100) @(posedge pclk); + measured = ($realtime - started) / 100.0; + if (measured < pclk_period - 0.04 || measured > pclk_period + 0.04) + $fatal(1, "PCLK measured period %f expected %f", measured, pclk_period); + end + endtask + + initial begin + #1 rst_n_i = 1; + #1 rst_n_i = 0; + repeat (5) @(negedge ref_clk_i); + rst_n_i = 1'b1; + repeat (32) @(negedge ref_clk_i); + idle = 0; + clients_idle = 0; + apply_profile(2'd2); + repeat (30) @(negedge ref_clk_i); + if (!busy || current_profile != 0 || pll_selected) + $fatal(1, "profile changed before accepted work drained"); + idle = 1; + clients_idle = 1; + wait_done(); +`ifdef TINY_PLL_SUCCESS + if (fault || current_profile != 2 || sys_hz != 192_000_000 || + mem_hz != 96_000_000 || pclk_hz != 48_000_000) + $fatal(1, "PLL192 commit mismatch"); + check_periods(1000.0 / 192.0, 1000.0 / 96.0, 1000.0 / 48.0); + apply_profile(2'd3); + wait_done(); + if (fault || current_profile != 3 || sys_hz != 240_000_000 || + mem_hz != 120_000_000 || pclk_hz != 60_000_000) + $fatal(1, "PLL240 commit mismatch"); + check_periods(1000.0 / 240.0, 1000.0 / 120.0, 1000.0 / 60.0); + apply_profile(2'd0); + wait_done(); + if (current_profile != 0 || sys_hz != 24_000_000) $fatal(1, "SAFE24 return failed"); + check_periods(1000.0 / 24.0, 1000.0 / 24.0, 1000.0 / 24.0); +`ifdef TINY_PLL_NATIVE + // Real locked ICS55 monitor must detect either stopped polarity, reset + // the affected session, and restart from REF24 without a PLL edge. + apply_profile(2'd2); + wait_done(); + force u_dut.u_pll.gen_ecos.u_backend.pll_clk_o = 1'b0; + repeat (400) @(negedge ref_clk_i); + if (!observed_faults[3] || current_profile != 0 || !reset_session_n) + $fatal(1, "PLL stopped-low recovery failed"); + check_periods(1000.0 / 24.0, 1000.0 / 24.0, 1000.0 / 24.0); + release u_dut.u_pll.gen_ecos.u_backend.pll_clk_o; + apply_profile(2'd3); + wait_done(); + force u_dut.u_pll.gen_ecos.u_backend.pll_clk_o = 1'b1; + repeat (400) @(negedge ref_clk_i); + if (current_profile != 0 || !reset_session_n) $fatal(1, "PLL stopped-high recovery failed"); + check_periods(1000.0 / 24.0, 1000.0 / 24.0, 1000.0 / 24.0); + release u_dut.u_pll.gen_ecos.u_backend.pll_clk_o; +`endif + $display("SIM_TEST_PASS Tiny PLL transitions and measured clocks"); +`else + if (!fault || current_profile != 0 || observed_faults != 5'b00100 || + sys_hz != 24_000_000 || mem_hz != 24_000_000 || pclk_hz != 24_000_000) + $fatal(1, "PLL lock timeout was not fail-closed"); + check_periods(1000.0 / 24.0, 1000.0 / 24.0, 1000.0 / 24.0); + $display("SIM_TEST_PASS Tiny PLL lock-timeout backend"); +`endif + $finish; + end + + initial begin + #3000000; + $fatal(1, "SIM_TEST_TIMEOUT Tiny PLL clock/reset backend"); + end +endmodule diff --git a/tests/rtl/tiny_clock_reset_subsystem_tb.sv b/tests/rtl/tiny_clock_reset_subsystem_tb.sv new file mode 100644 index 00000000..f8ec9b40 --- /dev/null +++ b/tests/rtl/tiny_clock_reset_subsystem_tb.sv @@ -0,0 +1,87 @@ +`timescale 1ns / 1ps + +module tiny_clock_reset_subsystem_tb; + logic ref_clk_i = 1'b0; + logic rst_n_i = 1'b0; + logic [1:0] profile = 2'd0; + logic apply = 1'b0; + logic [1:0] current_profile; + logic busy, fault; + logic sys_clk, mem_clk, pclk; + logic sys_rst_n, mem_rst_n, pclk_rst_n; + logic [31:0] xin_hz, ref_hz, sys_hz, mem_hz, pclk_hz; + + always #5 ref_clk_i = !ref_clk_i; + + tiny_clock_reset_subsystem #( + .RefClkHz(24_000_000), + .HavePll (1'b0) + ) u_dut ( + .ref_clk_i (ref_clk_i), + .rst_n_i (rst_n_i), + .profile_i (profile), + .apply_i (apply), + .idle_i (1'b1), + .clients_idle_i (1'b1), + .warm_reset_i (1'b0), + .timeout_i (32'd512), + .system_request_i (1'b0), + .command_ack_i (1'b1), + .fault_code_o (), + .done_o (), + .block_o (), + .ref_clk_o (), + .ref_rst_n_o (), + .system_reset_n_o (), + .pll_locked_o (), + .pll_selected_o (), + .current_profile_o(current_profile), + .busy_o (busy), + .fault_o (fault), + .sys_clk_o (sys_clk), + .mem_clk_o (mem_clk), + .pclk_o (pclk), + .sys_rst_n_o (sys_rst_n), + .mem_rst_n_o (mem_rst_n), + .pclk_rst_n_o (pclk_rst_n), + .xin_hz_o (xin_hz), + .ref_hz_o (ref_hz), + .sys_hz_o (sys_hz), + .mem_hz_o (mem_hz), + .pclk_hz_o (pclk_hz) + ); + + task automatic apply_profile(input logic [1:0] requested); + begin + @(negedge ref_clk_i); + profile = requested; + apply = 1'b1; + @(negedge ref_clk_i); + apply = 1'b0; + repeat (600) @(negedge ref_clk_i); + end + endtask + + initial begin + repeat (5) @(negedge ref_clk_i); + rst_n_i = 1'b1; + repeat (32) @(negedge ref_clk_i); + if (current_profile != 0 || sys_hz != 24_000_000 || mem_hz != 24_000_000 || + pclk_hz != 24_000_000 || !sys_rst_n || !mem_rst_n || !pclk_rst_n) begin + $fatal(1, "SAFE24 backend state mismatch"); + end + apply_profile(2'd1); + if (current_profile != 1 || fault || sys_hz != 24_000_000) begin + $fatal(1, "external profile handling mismatch"); + end + apply_profile(2'd2); + if (!fault || current_profile != 1) $fatal(1, "unsupported PLL was accepted"); + $display("SIM_TEST_PASS Tiny clock/reset backend"); + $finish; + end + + initial begin + #100000; + $fatal(1, "SIM_TEST_TIMEOUT Tiny clock/reset backend"); + end +endmodule diff --git a/tests/rtl/tiny_p7_source_reset_tb.sv b/tests/rtl/tiny_p7_source_reset_tb.sv new file mode 100644 index 00000000..18969d98 --- /dev/null +++ b/tests/rtl/tiny_p7_source_reset_tb.sv @@ -0,0 +1,415 @@ +`timescale 1ns / 1ps +`include "tiny_topology.svh" + +// Production DMA master, admission, CDC and lifecycle/reset components. The +// include is extracted from the current top and its generated bindings by the +// Python test. Unrelated CPU/APB/SDIO clients are idle; the AXI target is a +// backpressured behavioral slave. No reset command bypasses the controller. +module tiny_p7_source_reset_tb; + logic s_ref_clk = 0, s_exec_clk = 0, s_backend_pclk = 0, s_backend_mem_clk = 0; + realtime sys_half = 20.833333, pclk_half = 20.833333, mem_half = 20.833333; + always #20.833333 s_ref_clk = !s_ref_clk; + always #(sys_half) s_exec_clk = !s_exec_clk; + always #(pclk_half) s_backend_pclk = !s_backend_pclk; + always #(mem_half) s_backend_mem_clk = !s_backend_mem_clk; + logic rst_n_i = 0, s_backend_reset_n = 1; + logic s_por_rst_n, s_rst_n, s_cpu_ready_rst_n, s_fabric_rst_n, s_xpi_rst_n; + logic [`RETROSOC_TINY__LEAF_COUNT-1:0] s_leaf_clk, s_leaf_rst_n; + struct packed {logic reset_req_o;} u_wdg_if; + assign u_wdg_if.reset_req_o = 0; + logic s_dma_clk, s_dma_rst_n; + logic s_periph_apply = 0, s_periph_reset_command = 1; + logic [13:0] s_periph_request = 14'h200; + logic [13:0] s_periph_idle, s_periph_idle_ref; + logic [13:0] s_gate_request, s_local_reset, s_actual_gate, s_reset_held, s_reset_ready; + logic [13:0] s_gate_ref, s_held_ref, s_ready_ref, s_periph_reset_done; + logic s_bus_idle_ref, s_periph_block, s_periph_done, s_block_pclk; + logic [ 4:0] s_periph_fault; + logic [31:0] s_clock_timeout = 256; + logic s_clock_block = 0, s_clock_safe, s_dma_admission_idle, s_dma_idle_sys; + logic s_dma_cdc_ready, s_dma_cdc_busy, s_dma_ready_sys; + // Completion-mailbox and unrelated clients have no work in this fixture. + logic s_dma_done_pending_q = 0, s_dma_done_ready = 1; + logic s_cpu_clock_idle = 1, s_sdio_admission_idle = 1, s_xpi_cdc_busy = 0; + logic [ 2:0] s_owner_idle; + logic [`RETROSOC_TINY__TARGET_COUNT-1:0] s_target_idle; + logic [`RETROSOC_TINY__LEAF_COUNT-2:0] s_apb_cdc_idle = '1, s_apb_cdc_ready = '1; + assign s_actual_gate[13:10] = 0; + assign s_actual_gate[8:0] = 0; + assign s_reset_held[13:10] = 0; + assign s_reset_held[8:0] = 0; + assign s_reset_ready[13:10] = 0; + assign s_reset_ready[8:0] = 0; + + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) u_dma_pclk_axi4_if ( + .aclk (s_dma_clk), + .aresetn(s_dma_rst_n) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) u_dma_admitted_axi4_if ( + .aclk (s_dma_clk), + .aresetn(s_dma_rst_n) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) u_masters_axi4_if[3] ( + .aclk (s_exec_clk), + .aresetn(s_fabric_rst_n) + ); + + `include "source_reset_bindings.svh" + + logic rd_start = 0, wr_start = 0, rd_start_ready, wr_start_ready; + logic rd_busy, wr_busy, rd_valid, rd_ready = 1, rd_last, rd_done, wr_done; + logic wr_valid = 0, wr_ready; + logic [31:0] rd_addr = 32'h30000000, wr_addr = 32'h30000100; + logic [4:0] rd_beats = 4, wr_beats = 4; + logic [31:0] rd_data, wr_data = 32'hcafef00d; + logic [1:0] rd_resp, wr_resp; + logic rd_id_error, wr_id_error; + dma_axi4_master u_dma_master ( + .clk_i (s_dma_clk), + .rst_n_i (s_dma_rst_n), + .clear_i (1'b0), + .read_start_valid_i (rd_start), + .read_start_ready_o (rd_start_ready), + .read_addr_i (rd_addr), + .read_beats_i (rd_beats), + .read_fixed_i (1'b0), + .read_busy_o (rd_busy), + .read_beat_valid_o (rd_valid), + .read_beat_ready_i (rd_ready), + .read_data_o (rd_data), + .read_resp_o (rd_resp), + .read_last_o (rd_last), + .read_expected_last_o(), + .read_id_error_o (rd_id_error), + .read_done_o (rd_done), + .write_start_valid_i (wr_start), + .write_start_ready_o (wr_start_ready), + .write_addr_i (wr_addr), + .write_beats_i (wr_beats), + .write_fixed_i (1'b0), + .write_busy_o (wr_busy), + .write_data_valid_i (wr_valid), + .write_data_ready_o (wr_ready), + .write_data_i (wr_data), + .write_strb_i (4'hf), + .write_done_o (wr_done), + .write_resp_o (wr_resp), + .write_id_error_o (wr_id_error), + .axi4 (u_dma_pclk_axi4_if) + ); + assign s_periph_idle = {4'hf, !(rd_busy || wr_busy || rd_start || wr_start), 9'h1ff}; + + logic hold_ar = 0, hold_aw = 0, hold_w = 0, hold_r = 0, hold_b = 0; + logic target_rd = 0, target_wr = 0; + logic [31:0] target_addr; + integer target_rleft, target_wleft; + wire target_idle = !target_rd && !target_wr; + assign s_owner_idle = {1'b1, target_idle, 1'b1}; + assign s_target_idle = {`RETROSOC_TINY__TARGET_COUNT{target_idle}}; + assign u_masters_axi4_if[1].arready = !target_rd && !hold_ar; + assign u_masters_axi4_if[1].awready = !target_wr && !hold_aw; + assign u_masters_axi4_if[1].wready = target_wr && target_wleft != 0 && !hold_w; + assign u_masters_axi4_if[1].rid = 0; + assign u_masters_axi4_if[1].rresp = 0; + assign u_masters_axi4_if[1].ruser = 0; + assign u_masters_axi4_if[1].bid = 0; + assign u_masters_axi4_if[1].bresp = 0; + assign u_masters_axi4_if[1].buser = 0; + logic ar_stalled = 0, aw_stalled = 0; + logic [39:0] held_ar, held_aw; + always @(posedge s_exec_clk or negedge s_fabric_rst_n) begin + if (!s_fabric_rst_n) begin + ar_stalled <= 0; + aw_stalled <= 0; + end else begin + if (ar_stalled && (!u_masters_axi4_if[1].arvalid || + {u_masters_axi4_if[1].araddr, u_masters_axi4_if[1].arlen} !== held_ar)) + $fatal(1, "SIM_TEST_FAIL stalled AR revoked or changed"); + if (aw_stalled && (!u_masters_axi4_if[1].awvalid || + {u_masters_axi4_if[1].awaddr, u_masters_axi4_if[1].awlen} !== held_aw)) + $fatal(1, "SIM_TEST_FAIL stalled AW revoked or changed"); + ar_stalled <= u_masters_axi4_if[1].arvalid && !u_masters_axi4_if[1].arready; + aw_stalled <= u_masters_axi4_if[1].awvalid && !u_masters_axi4_if[1].awready; + held_ar <= {u_masters_axi4_if[1].araddr, u_masters_axi4_if[1].arlen}; + held_aw <= {u_masters_axi4_if[1].awaddr, u_masters_axi4_if[1].awlen}; + end + end + always @(posedge s_exec_clk or negedge s_fabric_rst_n) begin + if (!s_fabric_rst_n) begin + target_rd <= 0; + target_wr <= 0; + target_rleft <= 0; + target_wleft <= 0; + target_addr <= 0; + u_masters_axi4_if[1].rvalid <= 0; + u_masters_axi4_if[1].rdata <= 0; + u_masters_axi4_if[1].rlast <= 0; + u_masters_axi4_if[1].bvalid <= 0; + end else begin + if (u_masters_axi4_if[1].arvalid && u_masters_axi4_if[1].arready) begin + target_rd <= 1; + target_addr <= u_masters_axi4_if[1].araddr; + target_rleft <= int'(u_masters_axi4_if[1].arlen) + 1; + end + if (target_rd && !hold_r && !u_masters_axi4_if[1].rvalid) begin + u_masters_axi4_if[1].rvalid <= 1; + u_masters_axi4_if[1].rdata <= target_addr ^ 32'h12345678; + u_masters_axi4_if[1].rlast <= target_rleft == 1; + end + if (u_masters_axi4_if[1].rvalid && u_masters_axi4_if[1].rready) begin + u_masters_axi4_if[1].rvalid <= 0; + target_rleft <= target_rleft - 1; + target_addr <= target_addr + 4; + if (u_masters_axi4_if[1].rlast) target_rd <= 0; + end + if (u_masters_axi4_if[1].awvalid && u_masters_axi4_if[1].awready) begin + target_wr <= 1; + target_wleft <= int'(u_masters_axi4_if[1].awlen) + 1; + end + if (u_masters_axi4_if[1].wvalid && u_masters_axi4_if[1].wready) begin + if (u_masters_axi4_if[1].wlast !== (target_wleft == 1)) + $fatal(1, "SIM_TEST_FAIL write burst boundary"); + target_wleft <= target_wleft - 1; + end + if (target_wr && target_wleft == 0 && !hold_b) u_masters_axi4_if[1].bvalid <= 1; + if (u_masters_axi4_if[1].bvalid && u_masters_axi4_if[1].bready) begin + u_masters_axi4_if[1].bvalid <= 0; + target_wr <= 0; + end + end + end + + // Independent scoreboard survives local source reset. Looking only at the + // bridge's reset-cleared pending bits would conceal an unsafe reset. + integer reads = 0, read_completions = 0, writes = 0, write_completions = 0; + integer read_beat = 0, local_resets = 0; + always @(posedge s_dma_clk or negedge rst_n_i or negedge s_backend_reset_n) begin + if (!rst_n_i || !s_backend_reset_n) begin + reads = 0; + read_completions = 0; + writes = 0; + write_completions = 0; + read_beat = 0; + end else if (s_dma_rst_n) begin + if (u_dma_pclk_axi4_if.arvalid && u_dma_pclk_axi4_if.arready) reads++; + if (u_dma_pclk_axi4_if.awvalid && u_dma_pclk_axi4_if.awready) writes++; + if (rd_valid && rd_ready) begin + if (rd_resp != 0 || rd_id_error || rd_data !== ((rd_addr + 4 * read_beat) ^ 32'h12345678) || + rd_last !== (read_beat == int'(rd_beats) - 1)) + $fatal(1, "SIM_TEST_FAIL stale or malformed read response"); + read_beat++; + if (rd_last) begin + read_completions++; + read_beat = 0; + end + end + if (wr_done) begin + if (wr_resp != 0 || wr_id_error) $fatal(1, "SIM_TEST_FAIL write response"); + write_completions++; + end + if (read_completions > reads || write_completions > writes) + $fatal(1, "SIM_TEST_FAIL duplicate terminal response"); + end + end + always @(posedge s_local_reset[9]) begin + if (reads != read_completions || writes != write_completions || !target_idle || + rd_busy || wr_busy || !s_dma_admission_idle) + $fatal(1, "SIM_TEST_FAIL P7_SOURCE_RESET_UNDRAINED"); + local_resets++; + end + // SYS-source reset of XPI is never an independent reset event. Allow delta + // propagation of the asynchronously asserted production reset tree. + always @(negedge s_fabric_rst_n) begin + #0.001; + if (s_xpi_rst_n !== 0 || s_dma_rst_n !== 0) + $fatal(1, "SIM_TEST_FAIL common system reset left an endpoint alive"); + end + + task automatic ready; + wait (s_dma_rst_n && s_dma_cdc_ready && s_dma_ready_sys && s_ready_ref[9]); + repeat (24) @(negedge s_ref_clk); + endtask + task automatic start_read; + @(negedge s_dma_clk); + rd_addr += 32'h100; + rd_start = 1; + do @(posedge s_dma_clk); while (!rd_start_ready); + @(negedge s_dma_clk); + rd_start = 0; + endtask + task automatic start_write; + @(negedge s_dma_clk); + wr_start = 1; + do @(posedge s_dma_clk); while (!wr_start_ready); + @(negedge s_dma_clk); + wr_start = 0; + endtask + task automatic send_words(input integer words); + @(negedge s_dma_clk); + wr_valid = 1; + repeat (words) begin + do @(posedge s_dma_clk); while (!wr_ready); + @(negedge s_dma_clk); + end + wr_valid = 0; + endtask + task automatic reset_command; + @(negedge s_ref_clk); + s_periph_apply = 1; + @(negedge s_ref_clk); + s_periph_apply = 0; + endtask + task automatic completion(input logic [4:0] expected_fault); + while (!s_periph_done) @(negedge s_ref_clk); + if (s_periph_fault !== expected_fault || + (expected_fault == 0 && s_periph_reset_done !== 14'h200) || + (expected_fault != 0 && s_local_reset != 0)) + $fatal(1, "SIM_TEST_FAIL reset command result fault=%h", s_periph_fault); + @(negedge s_ref_clk); + if (expected_fault == 0) ready(); + endtask + task automatic require_draining; + repeat (48) @(negedge s_ref_clk); + if (!s_periph_block || s_local_reset != 0 || !s_dma_rst_n || s_periph_done) + $fatal(1, "SIM_TEST_FAIL reset did not preserve outstanding work"); + endtask + task automatic recovery_read; + start_read(); + wait (!rd_busy); + repeat (24) @(negedge s_ref_clk); + if (reads != read_completions || writes != write_completions) + $fatal(1, "SIM_TEST_FAIL recovery completion accounting"); + endtask + + initial begin + if ($test$plusargs("xin48")) begin + sys_half = 10.416667; + mem_half = 10.416667; + pclk_half = 10.416667; + end + if ($test$plusargs("xin96")) begin + sys_half = 5.208333; + mem_half = 5.208333; + pclk_half = 10.416667; + end + if ($test$plusargs("pll192")) begin + sys_half = 2.604167; + mem_half = 5.208333; + pclk_half = 10.416667; + end + if ($test$plusargs("pll240")) begin + sys_half = 2.083333; + mem_half = 4.166667; + pclk_half = 8.333333; + end + // Deliberately falsified idle observations must trip the independent + // scoreboard. This is a negative test, never a qualifying DUT mode. + if ($test$plusargs("break_drain")) begin + force s_bus_idle_ref = 1'b1; + force s_periph_idle_ref = 14'h3fff; + end + #1 rst_n_i = 1; + #1 rst_n_i = 0; + repeat (8) @(negedge s_ref_clk); + rst_n_i = 1; + ready(); + recovery_read(); + + // An accepted but downstream-stalled AR survives until normal completion. + hold_ar = 1; + start_read(); + wait (u_masters_axi4_if[1].arvalid); + reset_command(); + require_draining(); + hold_ar = 0; + completion(0); + recovery_read(); + + // A late R prevents local reset, even after a bounded command timeout. + hold_r = 1; + start_read(); + wait (target_rd); + reset_command(); + require_draining(); + completion(5'b00010); + if (!rd_busy) $fatal(1, "SIM_TEST_FAIL timeout discarded read"); + hold_r = 0; + wait (!rd_busy); + reset_command(); + completion(0); + recovery_read(); + + // AW backpressure, an incomplete W burst, and a delayed B all prohibit + // source reset. The real DMA master owns BREADY and the original WLAST. + hold_aw = 1; + hold_b = 1; + start_write(); + wait (u_masters_axi4_if[1].awvalid); + reset_command(); + require_draining(); + hold_aw = 0; + send_words(1); + require_draining(); + hold_w = 1; + send_words(3); + require_draining(); + hold_w = 0; + wait (target_wleft == 0); + completion(5'b00010); + if (!wr_busy) $fatal(1, "SIM_TEST_FAIL timeout discarded write"); + hold_b = 0; + wait (!wr_busy); + reset_command(); + completion(0); + // The first post-reset write must also receive its own response exactly once. + start_write(); + send_words(4); + wait (!wr_busy); + recovery_read(); + + // A response already in the source holder is still outstanding when the + // consumer applies backpressure, even if the target is otherwise idle. + rd_ready = 0; + start_read(); + wait (rd_valid); + reset_command(); + require_draining(); + rd_ready = 1; + completion(0); + recovery_read(); + if (local_resets != 4) $fatal(1, "SIM_TEST_FAIL reset scenario coverage"); + + // The other legal reset route cancels the complete system session. + hold_r = 1; + start_read(); + wait (target_rd); + @(negedge s_ref_clk); + s_backend_reset_n = 0; + repeat (8) @(negedge s_ref_clk); + s_backend_reset_n = 1; + hold_r = 0; + ready(); + recovery_read(); + $display("SIM_TEST_PASS Tiny P7 production source reset local=4 timeout=2 system=1"); + $finish; + end + initial begin + #1000000; + $fatal(1, "SIM_TEST_TIMEOUT Tiny P7 production source reset"); + end +endmodule diff --git a/tests/rtl/tiny_peripheral_control_tb.sv b/tests/rtl/tiny_peripheral_control_tb.sv new file mode 100644 index 00000000..99457750 --- /dev/null +++ b/tests/rtl/tiny_peripheral_control_tb.sv @@ -0,0 +1,122 @@ +`timescale 1ns / 1ps +module tiny_peripheral_control_tb; + logic clk = 0, leaf_clk = 0, rst_n = 0, run_leaf = 1; + logic apply = 0, reset_cmd = 0, bus_idle = 1; + logic [13:0] request_mask = 0, idle_mask = '1; + logic [13:0] gates, resets, actual, held, ready, completed; + logic block_new, done; + logic [4:0] fault; + logic ip_clk, ip_rst_n; + integer hold_edges = 0, release_edges = 0; + always #5 clk = !clk; + always #7 if (run_leaf) leaf_clk = !leaf_clk; + tiny_peripheral_control u_control ( + .clk_i (clk), + .rst_n_i (rst_n), + .warm_reset_i (1'b0), + .apply_i (apply), + .reset_command_i(reset_cmd), + .request_i (request_mask), + .idle_i (idle_mask), + .bus_idle_i (bus_idle), + .gate_actual_i (actual), + .reset_held_i (held), + .reset_ready_i (ready), + .timeout_i (32'd128), + .gate_o (gates), + .reset_o (resets), + .block_o (block_new), + .done_o (done), + .fault_o (fault), + .reset_done_o (completed) + ); + tiny_peripheral_leaf u_leaf ( + .clk_i (leaf_clk), + .rst_n_i(rst_n), + .gate_i (gates[2]), + .reset_i(resets[2]), + .clk_o (ip_clk), + .rst_n_o(ip_rst_n), + .gated_o(actual[2]), + .held_o (held[2]) + ); + assign actual[13:3] = 0; + assign actual[1:0] = 0; + assign held[13:3] = 0; + assign held[1:0] = 0; + assign ready = {11'd0, ip_rst_n, 2'd0}; + always @(posedge ip_clk) begin + if (resets[2]) hold_edges++; + if (!resets[2] && !ip_rst_n && hold_edges != 0) release_edges++; + end + task automatic command(input logic resetting, input logic [13:0] mask); + @(negedge clk); + reset_cmd = resetting; + request_mask = mask; + apply = 1; + @(negedge clk); + apply = 0; + endtask + task automatic completion(input logic [4:0] expected_fault); + integer count; + count = 0; + while (!done && count < 200) begin + @(negedge clk); + count++; + end + if (!done || block_new || fault != expected_fault) + $fatal(1, "lifecycle completion fault=%h expected=%h", fault, expected_fault); + endtask + initial begin + #1 rst_n = 1; + #1 rst_n = 0; + repeat (8) @(negedge clk); + rst_n = 1; + repeat (20) @(negedge clk); + // CPU WFI permission must complete without pausing an unrelated DMA job. + idle_mask[9] = 0; + bus_idle = 0; + command(0, 14'h1); + while (!done) begin + @(negedge clk); + if (block_new) $fatal(1, "CPU-only gate arm blocked independent traffic"); + end + completion(0); + if (gates != 1) $fatal(1, "CPU WFI arm waited for unrelated DMA"); + command(0, 0); + completion(0); + idle_mask[9] = 1; + bus_idle = 1; + idle_mask[2] = 0; + command(0, 14'h4); + completion(5'b00010); + if (gates != 0 || actual != 0) $fatal(1, "busy timeout changed gates"); + idle_mask[2] = 1; + command(0, 14'h4); + completion(0); + if (!actual[2]) $fatal(1, "gate completed before leaf stopped"); + repeat (10) @(negedge clk); + if (ip_clk) $fatal(1, "gated clock high"); + command(1, 14'h4); + completion(5'b00001); + if (resets != 0 || completed != 0) $fatal(1, "reset accepted for gated IP"); + command(0, 0); + completion(0); + command(1, 14'h4); + completion(0); + if (completed != 14'h4 || hold_edges < 5 || release_edges < 5 || !ip_rst_n) + $fatal(1, "reset acknowledged before five hold/release edges"); + @(negedge leaf_clk); + run_leaf = 0; + command(1, 14'h4); + completion(5'b10000); + if (completed != 0 || !resets[2] || ip_rst_n) + $fatal(1, "missing clock acknowledged reset or removed isolation"); + $display("SIM_TEST_PASS Tiny peripheral lifecycle"); + $finish; + end + initial begin + #100000; + $fatal(1, "SIM_TEST_TIMEOUT Tiny peripheral lifecycle"); + end +endmodule diff --git a/tests/rtl/tiny_r2_p4_cpu.S b/tests/rtl/tiny_r2_p4_cpu.S index ce8b62a0..ce5ae705 100644 --- a/tests/rtl/tiny_r2_p4_cpu.S +++ b/tests/rtl/tiny_r2_p4_cpu.S @@ -41,6 +41,16 @@ _start: li t0, 0x1000b084 li t1, 1 sw t1, 0(t0) +#ifdef TINY_TEST_P7 + csrsi 0xbf0, 1 + li t0, 128 + csrs mie, t0 + wfi +5: csrr t1, mip + andi t1, t1, 128 + bnez t1, 5b + wfi +#endif li t0, 0x3001ffe0 2: lw t1, 0(t0) beqz t1, 2b diff --git a/tests/rtl/tiny_r2_p4_cpu_tb.sv b/tests/rtl/tiny_r2_p4_cpu_tb.sv index f45b53f9..54b00783 100644 --- a/tests/rtl/tiny_r2_p4_cpu_tb.sv +++ b/tests/rtl/tiny_r2_p4_cpu_tb.sv @@ -1,6 +1,13 @@ `timescale 1ns / 1ps module tiny_r2_p4_cpu_tb; logic clk_i = 0, rst_n_i = 0; +`ifdef TINY_TEST_P7 + localparam bit P7Lifecycle = 1; +`else + localparam bit P7Lifecycle = 0; +`endif + logic cold_rst_n = 0, clock_pause = 0, clock_idle, gated; + logic [31:0] irq = 0; always #5 clk_i = ~clk_i; wire s_rst_n = rst_n_i; logic s_jtag_tck_driver = 0, s_jtag_tms_driver = 1, s_jtag_tdi_driver = 0; @@ -46,15 +53,21 @@ module tiny_r2_p4_cpu_tb; tiny_cpu_wrapper #( .EnableAtomics (0), + .EnableWfiGate (P7Lifecycle), + .PreserveDebug (P7Lifecycle), .ResetSyncStages(5) ) u_cpu ( .clk_i (clk_i), .rst_n_i (rst_n_i), - .irq_i (32'd0), + .debug_rst_n_i (cold_rst_n), + .wfi_gate_i (P7Lifecycle), + .gated_o (gated), + .hart_reset_o (), + .irq_i (irq), .jtag_tck_i (s_jtag_tck_driver), .jtag_tms_i (s_jtag_tms_driver), .jtag_tdi_i (s_jtag_tdi_driver), - .jtag_trst_n_i (rst_n_i), + .jtag_trst_n_i (cold_rst_n), .jtag_tdo_o (s_jtag_tdo), .debug_halted_o(halted), .bus_idle_i (idle), @@ -62,10 +75,14 @@ tiny_cpu_wrapper #( .quiesce_o (quiesce), .cpu (cpu) ); - tiny_cpu_mem u_cpu_mem ( + tiny_cpu_mem #( + .ClockPause(P7Lifecycle) + ) u_cpu_mem ( .clk_i (clk_i), .rst_n_i (core_rst_n), .quiesce_i (quiesce), + .clock_pause_i (clock_pause), + .clock_idle_o (clock_idle), .idle_o (idle), .local_fault_valid_o(), // P5 fault aggregation has its own fixture. .local_fault_addr_o (), @@ -219,12 +236,22 @@ tiny_cpu_wrapper #( cfg.pstrb = 15; repeat (6) jtag_tick(1, 0, ignored); @(negedge clk_i); - rst_n_i = 1; + rst_n_i = 1; + cold_rst_n = 1; repeat (12) jtag_tick(1, 0, ignored); jtag_idle(1); jtag_ir(5'h11); wait (starts == 1); $display("P4_CPU_STAGE boot-and-boundaries"); + if (P7Lifecycle) begin + wait (gated); + repeat (10) @(negedge clk_i); + irq = 2; // Tiny wrapper timer IRQ is input bit 1. + wait (!gated); + repeat (10) @(negedge clk_i); + irq = 0; + wait (gated); + end jtag_dmi(1, 7'h10, 1, status); jtag_dmi(1, 7'h10, 32'h80000001, status); wait (halted); @@ -279,6 +306,41 @@ tiny_cpu_wrapper #( do @(posedge clk_i); while (!ext[2].rvalid); if (ext[2].rdata !== 32'hdecafbad || ext[2].rresp != 0) $fatal(1, "SRAM payload lost on hart reset"); + if (P7Lifecycle) begin + @(negedge clk_i); + ext[2].rready = 0; + // A clock pause is different from a hart reset: unaccepted requests + // may remain parked, and resume without being dropped or replayed. + clock_pause = 1; + wait (clock_idle); + repeat (20) begin + @(negedge clk_i); + if (!clock_idle || local_ports[0].req_valid || local_ports[1].req_valid) + $fatal(1, "CPU local admission continued while clock-paused"); + end + clock_pause = 0; + jtag_dmi(1, 7'h10, 32'h80000001, status); + wait (halted); + set_gpr(28, 0); + jtag_dmi(1, 7'h04, 32'h5a1234a5, status); + jtag_dmi(1, 7'h20, 32'h000e2e83, status); // lw t4,0(t3) + jtag_dmi(1, 7'h21, 32'h00100073, status); + hold_read = 1; + jtag_dmi(1, 7'h17, 32'h00240000, status); + wait (read_pending); + @(negedge clk_i); + rst_n_i = 0; + repeat (10) @(negedge clk_i); + hold_read = 0; + rst_n_i = 1; + repeat (20) @(negedge clk_i); + jtag_dmi(0, 7'h04, 0, status); + if (status != 32'h5a1234a5) $fatal(1, "warm reset lost retained debug data"); + jtag_dmi(0, 7'h16, 0, status); + if (status[12] || status[10:8] == 0) + $fatal(1, "warm reset did not terminate pending debug command with error"); + $display("P7_CPU_LIFECYCLE_PASS irq-wake debug-wake clock-pause warm-debug-abort"); + end $display("SIM_TEST_PASS Tiny P4 CPU compressed-boundaries fence-i debug-patch hart-drain"); $finish; end diff --git a/tests/rtl/tiny_r2_p5_system_tb.sv b/tests/rtl/tiny_r2_p5_system_tb.sv index 37a89c3b..76fb24ad 100644 --- a/tests/rtl/tiny_r2_p5_system_tb.sv +++ b/tests/rtl/tiny_r2_p5_system_tb.sv @@ -112,6 +112,7 @@ endmodule // XPI device or replace the pin-level NOR regression. The small reset ROM is // only a fixture boot source, never a product Boot ROM. module apb4_xpi ( + output logic lifecycle_idle_o, input logic clk_i, rst_n_i, dma_xfer_done_i, @@ -128,6 +129,7 @@ module apb4_xpi ( logic [31:0] captured_write = 0; logic [31:0] address = 0; logic [7:0] beat = 0, length = 0; + assign lifecycle_idle_o = !active && !(apb4.psel && apb4.penable); initial begin if (!$value$plusargs("firmware=%s", firmware)) $fatal(1, "firmware required"); $readmemh(firmware, rom); diff --git a/tests/rtl/tiny_sysctrl_tb.sv b/tests/rtl/tiny_sysctrl_tb.sv index b0d92800..e43b5b1b 100644 --- a/tests/rtl/tiny_sysctrl_tb.sv +++ b/tests/rtl/tiny_sysctrl_tb.sv @@ -19,30 +19,166 @@ module tiny_sysctrl_tb; logic perf_enable, perf_clear, done, pass; logic [ 7:0] code; logic [31:0] value; + logic [31:0] xin_hz, ref_hz, sys_hz, mem_hz, pclk_hz, clint_hz; + logic [1:0] rcu_current; + logic rcu_busy; + logic clock_apply; + logic [1:0] clock_profile; + logic clock_done = 0; + logic [1:0] clock_current = 0; + logic clock_apply_seen = 0; + logic [4:0] rcu_fault_event = 0; + logic warm_reset = 0, rcu_irq; + always @(posedge clk_i) if (clock_apply) clock_apply_seen <= 1; tiny_sysctrl u_dut ( - .clk_i (clk_i), - .rst_n_i (rst_n_i), - .apb4 (apb4), - .fault_valid_i (fault_valid), - .fault_count_i (fault_count), - .fault_addr_i (fault_addr), - .fault_write_i (1'b1), - .fault_master_i(fault_master), - .fault_resp_i (2'b11), - .cpu_wait_i (cpu_wait), - .dma_wait_i (dma_wait), - .rtc_wake_i (wake), - .perf_enable_o (perf_enable), - .perf_clear_o (perf_clear), - .test_done_o (done), - .test_pass_o (pass), - .test_code_o (code) + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .clk_ref_i (clk_i), + .rst_ref_n_i (rst_n_i), + .rcu_link_ready_o(), + .apb4 (apb4), + .fault_valid_i (fault_valid), + .fault_count_i (fault_count), + .fault_addr_i (fault_addr), + .fault_write_i (1'b1), + .fault_master_i (fault_master), + .fault_resp_i (2'b11), + .cpu_wait_i (cpu_wait), + .dma_wait_i (dma_wait), + .rtc_wake_i (wake), + .clock_done_i (clock_done), + .clock_fault_i (rcu_fault_event), + .clock_current_i (clock_current), + .periph_done_i (1'b0), + .periph_fault_i (5'd0), + .gate_actual_i (14'd0), + .reset_done_i (14'd0), + .periph_apply_o (), + .periph_reset_o (), + .periph_request_o(), + .clkout_control_o(), + .clkout_idle_i (1'b1), + .pll_locked_i (1'b0), + .pll_selected_i (1'b0), + .warm_reset_i (warm_reset), + .watchdog_reset_i(1'b0), + .hart_reset_i (1'b0), + .system_reset_o (), + .rcu_irq_o (rcu_irq), + .clock_timeout_o (), + .perf_enable_o (perf_enable), + .perf_clear_o (perf_clear), + .test_done_o (done), + .test_pass_o (pass), + .test_code_o (code), + .xin_hz_o (xin_hz), + .ref_hz_o (ref_hz), + .sys_hz_o (sys_hz), + .mem_hz_o (mem_hz), + .pclk_hz_o (pclk_hz), + .clint_hz_o (clint_hz), + .rcu_current_o (rcu_current), + .rcu_busy_o (rcu_busy), + .clock_apply_o (clock_apply), + .clock_profile_o (clock_profile) ); `include "apb4_bfm.svh" initial begin apb4_idle(); repeat (5) @(negedge clk_i); rst_n_i = 1; + repeat (40) @(negedge clk_i); + apb4_read(32'h100, 0, value); + if (value != 32'h54524355) $fatal(1, "RCU identity mismatch"); + apb4_read(32'h104, 0, value); + if (value != 32'h00010000) $fatal(1, "RCU version mismatch"); + apb4_read(32'h108, 0, value); + if (value != 32'h00000203) $fatal(1, "RCU capability mismatch"); + apb4_read(32'h158, 0, value); + if (value != 0) $fatal(1, "unsupported target capability was advertised"); + apb4_read(32'h148, 0, value); + if (value != 24_000_000 || sys_hz != 24_000_000 || mem_hz != 24_000_000 || + pclk_hz != 24_000_000 || clint_hz != 1_000_000) + $fatal(1, "SAFE24 rates mismatch"); + apb4_write(32'h10c, 2, 4'hf, 0); + apb4_write(32'h114, 1, 4'hf, 1); + apb4_read(32'h134, 0, value); + if (!value[0] || rcu_current != 0) $fatal(1, "unsupported PLL request was accepted"); + apb4_write(32'h134, 1, 4'hf, 0); + apb4_write(32'h10c, 1, 4'hf, 0); + apb4_write(32'h114, 1, 4'hf, 0); + if (!clock_apply_seen || clock_profile != 1) $fatal(1, "clock apply handshake missing"); + // APB acknowledgement is not clock completion. BUSY/current persist + // until the actual backend supplies its committed result. + apb4_read(32'h118, 0, value); + if (!value[0] || rcu_current != 0) $fatal(1, "RCU completed before backend"); + apb4_write(32'h10c, 0, 4'hf, 1); + apb4_write(32'h11c, 1, 4'hf, 1); + apb4_write(32'h120, 0, 4'hf, 1); + apb4_write(32'h128, 0, 4'hf, 1); + apb4_write(32'h114, 8, 4'hf, 1); + apb4_read(32'h10c, 0, value); + if (value != 1) $fatal(1, "BUSY write changed staged profile"); + apb4_read(32'h11c, 0, value); + if (value != 240000) $fatal(1, "BUSY write changed timeout"); + apb4_write(32'h134, 1, 4'hf, 0); + @(negedge clk_i); + clock_current = 1; + clock_done = 1; + @(negedge clk_i); + clock_done = 0; + apb4_read(32'h110, 0, value); + if (value != 1 || rcu_current != 1 || sys_hz != 24_000_000) + $fatal(1, "external profile commit mismatch"); + apb4_read(32'h118, 0, value); + if (value[1] || value[4]) begin + $display("RCU status=%h fault=%h current=%h", value, u_dut.s_rcu_fault_q, rcu_current); + $fatal(1, "RCU status mismatch"); + end + apb4_write(32'h120, 1, 4'hf, 1); + apb4_write(32'h114, 2, 4'hf, 1); + apb4_read(32'h134, 0, value); + if (!value[0]) $fatal(1, "unsupported gate request was accepted"); + apb4_write(32'h134, 1, 4'hf, 0); + apb4_write(32'h10c, 4, 4'hf, 1); + apb4_write(32'h11c, 0, 4'hf, 1); + apb4_write(32'h13c, 4, 4'hf, 1); + apb4_write(32'h130, 64, 4'hf, 1); + apb4_write(32'h134, 32, 4'hf, 1); + apb4_write(32'h138, 4, 4'hf, 1); + apb4_write(32'h114, 3, 4'hf, 1); + apb4_write(32'h15c, 1, 4'hf, 1); + apb4_write(32'h15c, 32'h105, 4'hf, 1); + apb4_write(32'h15c, 32'h101, 4'hf, 0); + apb4_write(32'h15c, 32'h201, 4'hf, 1); + apb4_write(32'h114, 1, 4'hf, 1); + apb4_read(32'h15c, 0, value); + if (value != 32'h101) $fatal(1, "live CLKOUT change was accepted"); + apb4_write(32'h15c, 0, 4'hf, 0); + apb4_write(32'h134, 31, 4'hf, 0); + apb4_write(32'h138, 3, 4'hf, 0); + apb4_write(32'h13c, 3, 4'hf, 0); + if (rcu_irq) $fatal(1, "cleared RCU IRQ remains asserted"); + @(negedge clk_i); + rcu_fault_event = 8; + apb4_write(32'h134, 8, 4'hf, 0); + apb4_write(32'h138, 2, 4'hf, 0); + apb4_write(32'h130, 8, 4'hf, 0); + @(negedge clk_i); + rcu_fault_event = 0; + apb4_read(32'h134, 0, value); + if (value != 8 || !rcu_irq) $fatal(1, "RCU W1C lost simultaneous fault/IRQ"); + apb4_read(32'h130, 0, value); + if (!value[3]) $fatal(1, "RCU W1C lost simultaneous reset cause"); + @(negedge clk_i); + warm_reset = 1; + @(negedge clk_i); + warm_reset = 0; + apb4_read(32'h138, 0, value); + if (value != 2 || rcu_irq) $fatal(1, "warm reset IRQ retention/enable mismatch"); + apb4_read(32'h134, 0, value); + if (value != 8 || rcu_current != 0) $fatal(1, "warm reset fault/profile mismatch"); + apb4_read(32'h114, 1, value); apb4_write(`APB4_SYSCTRL__HP_CTRL, 1, 4'hf, 1); apb4_write(`APB4_SYSCTRL__PLL_CMD, 1, 4'hf, 1); apb4_read(32'hfff, 1, value); diff --git a/tests/rtl/tiny_watchdog_reset_tb.sv b/tests/rtl/tiny_watchdog_reset_tb.sv new file mode 100644 index 00000000..828a3e97 --- /dev/null +++ b/tests/rtl/tiny_watchdog_reset_tb.sv @@ -0,0 +1,188 @@ +`timescale 1ns / 1ps +module tiny_watchdog_reset_tb; + import wdg_pkg::*; + logic xin = 0, reset_n = 1; + always #20.833 xin = !xin; + logic ref_clk, sys_clk, pclk, backend_reset_n, por_n, system_n, ready; + logic [1:0] leaf_n; + logic wdg_reset_n, request; + logic [ 1:0] current_profile; + logic [ 4:0] clock_fault; + logic clock_done; + integer reset_events = 0; + logic [31:0] value; + wire clk_i = sys_clk; + apb4_if apb4 ( + .pclk (sys_clk), + .presetn(leaf_n[0]) + ); + apb4_if wdg_source ( + .pclk (sys_clk), + .presetn(leaf_n[0]) + ); + apb4_if wdg_apb ( + .pclk (pclk), + .presetn(leaf_n[1]) + ); + wdg_if wdg ( + .wdg_clk_i (ref_clk), + .wdg_rst_n_i(wdg_reset_n) + ); + assign wdg.debug_halted_i = 1'b0; + assign request = wdg.reset_req_o; + tiny_apb_async_bridge u_wdg_cdc ( + .src_clk_i (sys_clk), + .src_rst_n_i(leaf_n[0]), + .dst_clk_i (pclk), + .dst_rst_n_i(leaf_n[1]), + .disabled_i (1'b0), + .src_apb4 (wdg_source), + .dst_apb4 (wdg_apb) + ); + tiny_clock_reset_subsystem u_backend ( + .ref_clk_i (xin), + .rst_n_i (reset_n), + .profile_i (2'd0), + .apply_i (1'b0), + .idle_i (1'b1), + .clients_idle_i (1'b1), + .warm_reset_i (request), + .system_request_i (1'b0), + .command_ack_i (1'b1), + .timeout_i (32'd240000), + .current_profile_o(current_profile), + .ref_clk_o (ref_clk), + .sys_clk_o (sys_clk), + .pclk_o (pclk), + .system_reset_n_o (backend_reset_n), + .fault_code_o (clock_fault), + .done_o (clock_done) + ); + tiny_reset_tree #( + .LeafCount (2), + .SplitDomains(1) + ) u_tree ( + .clk_i (sys_clk), + .por_clk_i (ref_clk), + .leaf_clk_i ({pclk, sys_clk}), + .backend_reset_n_i (backend_reset_n), + .rst_n_i (reset_n), + .watchdog_reset_req_i(request), + .por_rst_n_o (por_n), + .system_rst_n_o (system_n), + .leaf_rst_n_o (leaf_n), + .cpu_ready_rst_n_o (ready) + ); + // Same functional-reset dependency as the production Tiny integration. + rst_sync #( + .STAGE(5) + ) u_wdg_functional_reset ( + .clk_i (ref_clk), + .rst_n_i(por_n && backend_reset_n), + .rst_n_o(wdg_reset_n) + ); + apb4_wdg #( + .WDG_CLOCK_HZ(24000000) + ) u_wdg ( + .apb4(wdg_apb), + .wdg (wdg) + ); + tiny_sysctrl u_rcu ( + .clk_i (sys_clk), + .rst_n_i (leaf_n[0]), + .clk_ref_i (ref_clk), + .rst_ref_n_i (por_n), + .apb4 (apb4), + .fault_valid_i (1'b0), + .fault_count_i (4'd0), + .fault_addr_i (32'd0), + .fault_write_i (1'b0), + .fault_master_i (2'd0), + .fault_resp_i (2'd0), + .cpu_wait_i (64'd0), + .dma_wait_i (64'd0), + .rtc_wake_i (1'b0), + .clock_done_i (clock_done), + .clock_fault_i (clock_fault), + .clock_current_i (current_profile), + .pll_locked_i (1'b0), + .pll_selected_i (1'b0), + .warm_reset_i (!backend_reset_n), + .watchdog_reset_i(request), + .hart_reset_i (1'b0), + .periph_done_i (1'b0), + .periph_fault_i (5'd0), + .gate_actual_i (14'd0), + .reset_done_i (14'd0), + .clkout_idle_i (1'b1) + ); + always @(posedge request) if (reset_n) reset_events++; + task automatic watchdog_access(input logic [31:0] address, input logic write_access, + input logic [31:0] data, output logic [31:0] observed); + integer budget; + @(negedge clk_i); + wdg_source.paddr = address; + wdg_source.pwrite = write_access; + wdg_source.pwdata = data; + wdg_source.pstrb = write_access ? 4'hf : 4'h0; + wdg_source.psel = 1; + @(negedge clk_i); + wdg_source.penable = 1; + budget = 0; + while (wdg_source.pready !== 1'b1 && budget < 512) begin + @(negedge clk_i); + budget++; + end + if (budget == 512 || wdg_source.pslverr !== 0) $fatal(1, "watchdog APB failed"); + observed = wdg_source.prdata; + @(negedge clk_i); + wdg_source.psel = 0; + wdg_source.penable = 0; + endtask + `include "apb4_bfm.svh" + initial begin + wdg_source.paddr = 0; + wdg_source.pwrite = 0; + wdg_source.pwdata = 0; + wdg_source.pstrb = 0; + wdg_source.psel = 0; + wdg_source.penable = 0; + wdg_source.pprot = 0; + apb4_idle(); + #1 reset_n = 0; + repeat (20) @(negedge xin); + reset_n = 1; + wait (ready); + repeat (30) @(negedge ref_clk); + for (integer iteration = 1; iteration <= 2; iteration++) begin + watchdog_access(32'h008, 1, 1, value); + watchdog_access(32'h00c, 1, 64, value); + watchdog_access(32'h018, 1, 4, value); + watchdog_access(32'h000, 1, 1, value); + wait (request); + wait (!backend_reset_n); + #1; + if (system_n !== 0 || leaf_n !== 0 || wdg_reset_n !== 0) + $fatal(1, "watchdog did not reset all functional endpoints"); + wait (backend_reset_n); + wait (ready && wdg_reset_n); + repeat (30) @(negedge ref_clk); + apb4_read(32'h130, 0, value); + if (value !== 32'd5 || request || reset_events != iteration || + u_wdg.u_wdg_core.s_reset_count_q !== 0 || current_profile !== 0) + $fatal(1, "watchdog warm-reset cause/functional state mismatch cause=%h", value); + watchdog_access(32'h000, 1, 2, value); + repeat (30) @(negedge clk_i); + watchdog_access(32'h024, 0, 0, value); + if ((value & 3) !== 1) $fatal(1, "post-reset watchdog snapshot did not complete"); + watchdog_access(32'h040, 0, 0, value); + if (value !== 0) $fatal(1, "watchdog functional reset counter did not clear"); + end + $display("SIM_TEST_PASS Tiny watchdog reset cause retained and functional state reset twice"); + $finish; + end + initial begin + #200000; + $fatal(1, "SIM_TEST_TIMEOUT Tiny watchdog reset"); + end +endmodule diff --git a/tests/test_commercial_flow.py b/tests/test_commercial_flow.py index 177d2148..ce093a44 100644 --- a/tests/test_commercial_flow.py +++ b/tests/test_commercial_flow.py @@ -421,7 +421,7 @@ def test_commercial_timing_contract_supports_tiny(tmp_path: Path) -> None: check=True, ) assert result.stdout.splitlines() == [ - "audio,jtag,system", + "aon,audio,jtag,memory,peripheral,system", "ext_rst_n_i_pad,jtag_trst_n_i_pad", ] assert "observation {u_clock_buffer/clk_o}" in output.read_text( @@ -612,6 +612,7 @@ set ::created_clocks_named {} set ::generated_clocks {} set ::clock_groups {} set ::false_paths 0 +set ::case_selections {} proc get_ports {args} { return [lindex $args end] } proc get_pins {args} { return [list pin_[string map {/ _} [lindex $args end]]] } @@ -629,6 +630,7 @@ proc set_clock_groups {args} { lappend ::clock_groups $args } proc set_clock_uncertainty {args} {} proc set_clock_transition {args} {} proc set_false_path {args} { incr ::false_paths } +proc set_case_analysis {value pins} { lappend ::case_selections [list $value $pins] } proc all_inputs {} { return {} } proc all_outputs {} { return {} } proc current_design {args} { return {} } @@ -642,6 +644,7 @@ puts "masters=$::created_clocks_named" puts "generated=$::generated_clocks" puts "groups=[llength $::clock_groups]" puts "false_paths=$::false_paths" +puts "case_selections=$::case_selections" flow::require_commercial_clock_inventory puts "inventory-ok" """ @@ -660,8 +663,8 @@ CONSTRAINT_SMOKE_CASES = { "TINY": { "domains": "rtl/tiny/integration/clock_reset_domains.json", "pin_map": "rtl/tiny/pin_map/pin_map.json", - "masters": "clk_system clk_jtag clk_audio", - "generated": "", + "masters": "clk_aon clk_jtag clk_audio", + "generated": "clk_system clk_memory clk_peripheral clk_sys_div2 clk_sys_div4 clk_clkout_source", "groups": "1", }, } @@ -713,6 +716,11 @@ def test_constraint_stack_models_product_clocks(tmp_path: Path, soc: str) -> Non assert lines["generated"].strip("-") == case["generated"] assert lines["groups"] == case["groups"] assert "inventory-ok" in result.stdout + if soc == "TINY": + for selection in ("u_sys_mux", "u_mem_mux", "u_pclk_div2_mux", "u_pclk_mux", "u_PLL_TOP_EN"): + assert selection in lines["case_selections"] + assert lines["case_selections"].count("{0 ") == 11 + assert lines["case_selections"].count("{1 ") == 4 def test_commercial_make_graph_covers_both_products(tmp_path: Path) -> None: diff --git a/tests/test_tiny.py b/tests/test_tiny.py index 3c35ed21..330520f4 100644 --- a/tests/test_tiny.py +++ b/tests/test_tiny.py @@ -35,7 +35,13 @@ def test_tiny_sources_and_abi_are_independent(tmp_path: Path) -> None: 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 + if name in {"gpio", "uart0", "i2c0", "timer0", "timer1", "pwm", "i2s", "sdio", + "dma", "ws2812", "rng", "crc", "crypto"}: + assert f"tiny_peripheral_leaf u_{name}_lifecycle" in interfaces + assert f".rst_n_i(s_leaf_rst_n[{index}])" in interfaces + assert f".rst_n_o(s_{name}_rst_n)" in interfaces + else: + 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 @@ -109,10 +115,13 @@ def test_tiny_protocol_contracts(tmp_path: Path, simulator: str, block: str) -> ROOT / "rtl/ip/interconnect/axi4_error_slave.sv", TINY / "top/tiny_axi4_target.sv", TINY / "top/tiny_axi4_fabric.sv"] else: - sources += [COMMON / "interface/apb4_if.sv", TINY / "top/tiny_sysctrl.sv"] + sources += [COMMON / "interface/apb4_if.sv", COMMON / "cdc/cdc_sync.sv", + COMMON / "cdc/cdc_rst_ctrlr.sv", COMMON / "cdc/cdc_2phase.sv", + COMMON / "clkrst/rst_sync.sv", TINY / "top/tiny_apb_async_bridge.sv", + TINY / "top/tiny_sysctrl.sv"] sources.append(ROOT / "tests/rtl" / f"{top}.sv") includes = [f"-I{p}" for p in (COMMON, COMMON / "interface", generated / "rtl", tmp_path / "bindings/rtl", - ROOT / "rtl/ip/peripheral", ROOT / "tests/rtl")] + ROOT / "rtl/ip/peripheral", TINY / "top", ROOT / "tests/rtl")] output = tmp_path / "sim" if simulator == "verilator": cache = tmp_path / "cache" diff --git a/tests/test_tiny_r2_p2.py b/tests/test_tiny_r2_p2.py index 601c30ef..dbccbb20 100644 --- a/tests/test_tiny_r2_p2.py +++ b/tests/test_tiny_r2_p2.py @@ -53,15 +53,17 @@ def test_reset_and_fifo_contracts(tmp_path: Path, simulator: str, fixture: str, 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: +def test_reset_inventory_has_active_domains_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"]) == 21 - assert {domain["name"] for domain in inventory["domains"]} == {"system", "jtag", "audio"} - assert reset["clock_domain"] == "system" and reset["main_sram_payload_reset"] == "none" + assert {domain["name"] for domain in inventory["domains"]} == { + "aon", "system", "memory", "peripheral", "jtag", "audio"} + assert reset["clock_domain"] == "domain-specific" and reset["main_sram_payload_reset"] == "none" + assert inventory["local_memory"]["clock_domain"] == "system" SDC = ("create_clock -name clk_system -period 41.666666667 $clk_system_pin\n" diff --git a/tests/test_tiny_r2_p4.py b/tests/test_tiny_r2_p4.py index fd6a92cd..b3a8d9ae 100644 --- a/tests/test_tiny_r2_p4.py +++ b/tests/test_tiny_r2_p4.py @@ -79,11 +79,13 @@ def test_four_group_local_memory(tmp_path, simulator, pdk): @pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) -def test_actual_dual_port_cpu_debug_and_reset(tmp_path, simulator): +@pytest.mark.parametrize("p7_lifecycle", [False, True]) +def test_actual_dual_port_cpu_debug_and_reset(tmp_path, simulator, p7_lifecycle): for tool in (simulator, "sv2v", "vvp", "riscv32-unknown-elf-gcc", "riscv32-unknown-elf-objcopy"): assert shutil.which(tool), f"required tool unavailable: {tool}" generate_memory_map(ROOT / "rtl/tiny/address_map/memory_map.json", tmp_path / "map", "YES", 128) - run(["riscv32-unknown-elf-gcc", "-march=rv32imc_zicsr_zifencei", "-mabi=ilp32", + lifecycle_defines = ["-DTINY_TEST_P7"] if p7_lifecycle else [] + run(["riscv32-unknown-elf-gcc", *lifecycle_defines, "-march=rv32imc_zicsr_zifencei", "-mabi=ilp32", "-nostdlib", "-Wl,--build-id=none", "-T", str(ROOT / "tests/rtl/tiny_r2_p4_cpu.ld"), str(ROOT / "tests/rtl/tiny_r2_p4_cpu.S"), "-o", str(tmp_path / "cpu.elf")], tmp_path, "firmware") run(["riscv32-unknown-elf-objcopy", "-O", "verilog", str(tmp_path / "cpu.elf"), @@ -97,7 +99,7 @@ def test_actual_dual_port_cpu_debug_and_reset(tmp_path, simulator): if line.startswith("/hazard3/")] sources += [TINY / p for p in ( "tiny_sram_port_if.sv", "tiny_sram_group.sv", "tiny_sram_axi4.sv", - "tiny_sram.sv", "tiny_cpu_mem.sv", "tiny_cpu_wrapper.sv")] + "tiny_sram.sv", "tiny_cpu_mem.sv", "tiny_clock_gate.sv", "tiny_cpu_wrapper.sv")] sources += [ROOT / p for p in ( "rtl/tech/tc_sram.sv", "rtl/tech/tc_clk.sv", "rtl/ip/memory/onchip_ram_reg.sv", "rtl/ip/interconnect/ahbl2axi4.sv", "rtl/ip/core/mgmt_debug_wrapper.sv", @@ -105,7 +107,7 @@ def test_actual_dual_port_cpu_debug_and_reset(tmp_path, simulator): includes = [f"-I{p}" for p in (COMMON, COMMON / "interface", tmp_path / "map/rtl", ROOT / "rtl/ip/memory", ROOT / "rtl/managed/hazard3/hdl", ROOT / "rtl/tiny/dv")] - defines = ["-DSV_ASSRT_DISABLE", "-DPDK_BEHAV", "-DSOC_JTAG_IDCODE=32'hDEADBEEF"] + defines = ["-DSV_ASSRT_DISABLE", "-DPDK_BEHAV", "-DSOC_JTAG_IDCODE=32'hDEADBEEF", *lifecycle_defines] output = tmp_path / "sim" top = "tiny_r2_p4_cpu_tb" if simulator == "verilator": @@ -122,6 +124,8 @@ def test_actual_dual_port_cpu_debug_and_reset(tmp_path, simulator): launch = ["vvp", str(output)] content = run([*launch, f"+firmware={tmp_path / 'cpu.hex'}"], tmp_path, "sim") assert "SIM_TEST_PASS Tiny P4 CPU" in content + if p7_lifecycle: + assert "P7_CPU_LIFECYCLE_PASS" in content def test_group_control_induction(tmp_path): diff --git a/tests/test_tiny_r2_p5.py b/tests/test_tiny_r2_p5.py index 51f7e8fd..ea565906 100644 --- a/tests/test_tiny_r2_p5.py +++ b/tests/test_tiny_r2_p5.py @@ -29,7 +29,7 @@ def sources(tmp_path): files += [TINY / p for p in ("tiny_axi4_target.sv", "tiny_axi4_fabric.sv", "tiny_axi42apb4.sv", "tiny_fault_collector.sv", "tiny_sysctrl.sv")] files += [ROOT / "rtl/ip/interconnect/axi4_error_slave.sv"] - includes = [f"-I{p}" for p in (COMMON, COMMON / "interface", tmp_path / "map/rtl", + includes = [f"-I{p}" for p in (COMMON, COMMON / "interface", TINY, tmp_path / "map/rtl", tmp_path / "bindings/rtl", ROOT / "rtl/ip/peripheral", ROOT / "tests/rtl")] return files, includes @@ -99,7 +99,8 @@ def test_real_cpu_dma_progress_and_abort(tmp_path, simulator): "--output-c", str(tmp_path / "archinfo_integration_metadata.h")], tmp_path, "metadata") includes = [f"-I{p}" for p in (*resolved.incdirs, tmp_path, tmp_path / "map/rtl", tmp_path / "bindings/rtl", ROOT / "rtl/tiny/dv")] - includes += ["-DPDK_BEHAV", "-DRETROSOC_SOC__TINY", "-DSOC_JTAG_IDCODE=32'hDEADBEEF"] + includes += ["-DPDK_BEHAV", "-DRETROSOC_SOC__TINY", "-DSOC_JTAG_IDCODE=32'hDEADBEEF", + "-DSOC_EXT_CLK_HZ=24000000"] log = simulate(tmp_path, simulator, "tiny_r2_p5_system_tb", files, includes, [f"+firmware={tmp_path / 'cpu.hex'}"]) assert "hart_reset=1 abort_drained=1" in log diff --git a/tests/test_tiny_r2_p6.py b/tests/test_tiny_r2_p6.py index 9e94a242..574940e3 100644 --- a/tests/test_tiny_r2_p6.py +++ b/tests/test_tiny_r2_p6.py @@ -404,7 +404,8 @@ def test_p6_map_irq_request_and_pin_contract(): irqs = {i["name"]: i["core_bit"] for i in topology["interrupts"]} assert {k: irqs[k] for k in ("rng", "ws2812", "crypto", "i2s", "sdio")} == { "rng": 16, "ws2812": 17, "crypto": 23, "i2s": 8, "sdio": 10} - assert {15, 19, 24, 25, 26, 27, 31}.isdisjoint(irqs.values()) + assert {15, 19, 24, 25, 26, 27}.isdisjoint(irqs.values()) + assert irqs["rcu"] == 31 pins = topology["gpio_alt_functions"] assert pins[9]["alt0"]["inputs"] == ["u_uart0_if.rx_i"] assert pins[10]["alt0"]["inputs"] == ["u_i2c0_if.scl_i"] @@ -413,8 +414,10 @@ def test_p6_map_irq_request_and_pin_contract(): for pin in (24, 25): assert pins[pin]["alt0"]["inputs"] == [f"u_pwm_if.capture_i[{pin - 24}]"] assert pins[pin]["alt1"]["inputs"] == [] - for pin in range(12, 24): + for pin in range(12, 23): assert pins[pin]["alt1"]["oe"] == "1'b0" + assert pins[23]["alt1"]["do"] == pins[28]["alt0"]["do"] == "s_clkout" + assert pins[23]["alt1"]["oe"] == pins[28]["alt0"]["oe"] == "s_clkout_enable" validate_clocks(TINY / "integration/clock_reset_domains.json", ROOT) diff --git a/tests/test_tiny_r2_p7.py b/tests/test_tiny_r2_p7.py new file mode 100644 index 00000000..a17e814a --- /dev/null +++ b/tests/test_tiny_r2_p7.py @@ -0,0 +1,294 @@ +"""Tiny P7 clock, reset and crossing contracts; behavioral evidence only.""" + +from __future__ import annotations + +import os +import re +import shutil +import subprocess +from pathlib import Path + +import pytest + +from scripts.check_simulation import DEFAULT_FAILURE +from scripts.generate_tiny import generate_bindings, read_topology +from scripts.tiny_r2_p7 import inspect_netlist +from scripts.rtl.generate_memory_map import generate as generate_memory_map +from test_tiny_ics55_platform import mapped_netlist # noqa: F401 -- shared synthetic fixture + +ROOT = Path(__file__).resolve().parents[1] +COMMON = ROOT / "rtl/managed/clusterip/common/rtl" + + +def run_rtl(tmp_path: Path, simulator: str, fixture: str, extra: list[Path], + defines: list[str] | None = None, plusargs: list[str] | None = None, + packages: list[Path] | None = None) -> str: + for tool in (simulator, *([] if simulator == "verilator" else ["sv2v", "vvp"])): + assert shutil.which(tool), f"required tool unavailable: {tool}" + # Put the fixture first so its explicit timescale applies during sv2v. + sources = [*(packages or []), ROOT / f"tests/rtl/{fixture}.sv", + *[COMMON / p for p in ( + "utils/register.sv", "utils/xchecker.sv", "utils/bin2gray.sv", + "utils/gray2bin.sv", "interface/apb4_if.sv", "interface/axi4_if.sv", + "cdc/cdc_sync.sv", "cdc/cdc_rst_ctrlr.sv", "cdc/cdc_2phase.sv", + "clkrst/rst_sync.sv", "clock/safe_clock_mux.sv", "clock/clock_divider.sv")], + *extra] + includes = [f"-I{p}" for p in (COMMON, COMMON / "interface", ROOT / "tests/rtl", + ROOT / "rtl/tiny/top")] + args = ["-DSV_ASSRT_DISABLE", *(defines or []), *includes, *(str(p) for p in sources)] + output = tmp_path / "sim" + + def execute(command: list[str], log: str, timeout: int = 120) -> str: + result = subprocess.run(command, cwd=ROOT, capture_output=True, text=True, + env={**os.environ, "CCACHE_DISABLE": "1"}, timeout=timeout) + content = result.stdout + result.stderr + (tmp_path / log).write_text(content) + assert result.returncode == 0, content + return content + + if simulator == "verilator": + execute([simulator, "--binary", "--timing", "--assert", "--timescale", "1ns/1ps", + "-Wno-fatal", "--top-module", fixture, *args, "--Mdir", str(tmp_path / "obj"), + "-o", str(output)], "compile.log") + launch = [str(output)] + else: + converted = subprocess.run(["sv2v", *args], cwd=ROOT, capture_output=True, + text=True, timeout=60) + assert converted.returncode == 0, converted.stderr + # sv2v strips timescale directives; restore the fixture's declared + # units so fractional PLL/reference delays are not rounded to seconds. + (tmp_path / "converted.v").write_text("`timescale 1ns / 1ps\n" + converted.stdout) + execute([simulator, "-g2012", "-s", fixture, "-o", str(output), + str(tmp_path / "converted.v")], "compile.log") + launch = ["vvp", str(output)] + content = execute([*launch, *(plusargs or [])], "sim.log", 30) + assert not DEFAULT_FAILURE.search(content), content + assert "SIM_TEST_PASS" in content, content + return content + + +@pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) +def test_tiny_apb_async_bridge(tmp_path: Path, simulator: str) -> None: + run_rtl(tmp_path, simulator, "tiny_apb_async_bridge_tb", + [ROOT / "rtl/tiny/top/tiny_apb_async_bridge.sv"]) + + +@pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) +@pytest.mark.parametrize("scenario", ["reset", "same_cycle_clear", "same_cycle_write", + "held_read_stability", "held_write_stability", "burst_abort", + "clear_pending", "same_cycle_reset_read", "same_cycle_reset_write"]) +def test_tiny_axi4_async_bridge_reset_abort(tmp_path: Path, simulator: str, scenario: str) -> None: + run_rtl(tmp_path, simulator, "tiny_axi4_async_bridge_tb", [ + COMMON / "cdc/async_reqack.sv", + COMMON / "cdc/cdc_warm_flush.sv", + COMMON / "cdc/cdc_fifo.sv", + COMMON / "utils/spill_register.sv", + ROOT / "rtl/tiny/top/tiny_axi4_async_bridge.sv", + ], plusargs=[] if scenario == "reset" else [f"+{scenario}"]) + + +def source_reset_bindings(tmp_path: Path) -> None: + """Exercise production reset/admission wiring, not a copied test policy.""" + top = (ROOT / "rtl/tiny/top/retrosoc_tiny.sv").read_text() + topology = read_topology(ROOT / "rtl/tiny/integration/soc_topology.json", + ROOT / "rtl/tiny/address_map/memory_map.json") + generate_bindings(topology, tmp_path / "bindings") + generated = (tmp_path / "bindings/rtl/tiny_apb_interfaces.svh").read_text() + + def instance(source: str, module: str, name: str) -> str: + matches = re.findall(rf"\b{module}\s*(?:#\([^;]*?\)\s*)?{name}\s*\([^;]*?\);", source) + assert len(matches) == 1, (module, name) + return matches[0] + + blocks = [instance(top, module, name) for module, name in ( + ("tiny_reset_tree", "u_reset_tree"), + ("tiny_peripheral_control", "u_peripheral_control"), + ("tiny_axi4_admission", "u_dma_admission"), + ("tiny_axi4_async_bridge", "u_dma_cdc"), + *(("cdc_sync", name) for name in ("u_block_pclk", "u_dma_idle_sync", + "u_dma_ready_sync", "u_bus_idle_ref", + "u_periph_idle_sync", "u_gate_feedback", + "u_held_feedback", "u_ready_feedback")), + )] + blocks.append(instance(generated, "tiny_peripheral_leaf", "u_dma_lifecycle")) + for source, lhs in ((top, "s_fabric_rst_n"), (top, "s_clock_safe"), + (generated, "s_xpi_rst_n"), (generated, "s_reset_ready[9]")): + matches = re.findall(rf"\bassign\s+{re.escape(lhs)}\s*=[^;]*;", source) + assert len(matches) == 1, lhs + blocks.extend(matches) + blocks += re.findall(r"\bassign\s+s_leaf_clk\[\d+\]\s*=[^;]*;", generated) + blocks.append("assign s_leaf_clk[0] = s_exec_clk;") + (tmp_path / "source_reset_bindings.svh").write_text("\n".join(blocks) + "\n") + + +@pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) +def test_tiny_production_source_reset_contract(tmp_path: Path, simulator: str) -> None: + source_reset_bindings(tmp_path) + run_rtl(tmp_path, simulator, "tiny_p7_source_reset_tb", [ + COMMON / "cdc/async_reqack.sv", COMMON / "cdc/cdc_warm_flush.sv", + COMMON / "cdc/cdc_fifo.sv", COMMON / "utils/spill_register.sv", + ROOT / "rtl/ip/peripheral/dma_axi4_master.sv", + *[ROOT / "rtl/tiny/top" / name for name in ( + "tiny_reset_tree.sv", "tiny_peripheral_control.sv", "tiny_peripheral_leaf.sv", + "tiny_axi4_admission.sv", "tiny_axi4_async_bridge.sv")], + ], defines=[f"-I{tmp_path}", f"-I{tmp_path / 'bindings/rtl'}"]) + launch = [str(tmp_path / "sim")] if simulator == "verilator" else ["vvp", str(tmp_path / "sim")] + # Compile once per simulator; reuse the binary at every supported domain + # ratio. These are digital boundary tests, not PDK/frequency qualification. + for mode in ("xin48", "xin96", "pll192", "pll240", "break_drain"): + result = subprocess.run([*launch, f"+{mode}"], cwd=ROOT, text=True, + capture_output=True, timeout=30) + content = result.stdout + result.stderr + (tmp_path / f"{mode}.log").write_text(content) + if mode == "break_drain": + # sv2v may lower $fatal to $finish(1), which vvp can return as + # exit zero. The strict Tiny verdict must still reject the log. + assert DEFAULT_FAILURE.search(content) and "P7_SOURCE_RESET_UNDRAINED" in content, content + assert "SIM_TEST_PASS" not in content, content + else: + assert result.returncode == 0 and not DEFAULT_FAILURE.search(content), content + assert "SIM_TEST_PASS Tiny P7 production source reset" in content, content + + +@pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) +def test_tiny_watchdog_reset_chain(tmp_path: Path, simulator: str) -> None: + generate_memory_map(ROOT / "rtl/tiny/address_map/memory_map.json", tmp_path / "map", "YES", 128) + run_rtl(tmp_path, simulator, "tiny_watchdog_reset_tb", [ + COMMON / "clkrst/counter.sv", + COMMON / "cdc/async_reqack.sv", COMMON / "cdc/cdc_fifo.sv", + *[ROOT / "rtl/managed/clusterip/wdg/rtl" / name for name in ( + "wdg_if.sv", "wdg_core.sv", "wdg_reg.sv", "apb4_wdg.sv")], + *[ROOT / "rtl/tiny/top" / name for name in ( + "tiny_clock_reset_subsystem.sv", "tiny_reset_tree.sv", + "tiny_apb_async_bridge.sv", "tiny_sysctrl.sv")]], + [f"-I{ROOT / 'rtl/ip/peripheral'}", f"-I{tmp_path / 'map/rtl'}", + f"-I{ROOT / 'rtl/managed/clusterip/wdg/rtl'}"], + packages=[ROOT / "rtl/managed/clusterip/wdg/rtl/wdg_pkg.sv"]) + + +@pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) +@pytest.mark.parametrize("module", ["tiny_axi4_admission", "tiny_clock_output"]) +def test_tiny_p7_boundaries(tmp_path: Path, simulator: str, module: str) -> None: + run_rtl(tmp_path, simulator, f"{module}_tb", [ROOT / f"rtl/tiny/top/{module}.sv"]) + + +@pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) +def test_tiny_peripheral_control(tmp_path: Path, simulator: str) -> None: + run_rtl(tmp_path, simulator, "tiny_peripheral_control_tb", + [ROOT / "rtl/tiny/top/tiny_peripheral_leaf.sv", + ROOT / "rtl/tiny/top/tiny_peripheral_control.sv"]) + + +@pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) +def test_tiny_clock_reset_backend_safe24(tmp_path: Path, simulator: str) -> None: + run_rtl(tmp_path, simulator, "tiny_clock_reset_subsystem_tb", + [ROOT / "rtl/tiny/top/tiny_clock_reset_subsystem.sv"]) + + +@pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) +@pytest.mark.parametrize("success", [False, True]) +def test_tiny_clock_reset_backend_pll(tmp_path: Path, simulator: str, success: bool) -> None: + run_rtl(tmp_path, simulator, "tiny_clock_reset_pll_tb", + [ROOT / "rtl/tech/tc_pll.sv", ROOT / "rtl/tiny/top/tiny_clock_reset_subsystem.sv"], + ["-DPDK_BEHAV", "-DHAVE_PLL", *(["-DTINY_PLL_SUCCESS"] if success else [])], + [] if success else ["+pll_lock_fail"]) + + +@pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) +def test_tiny_clock_reset_ics55_model(tmp_path: Path, simulator: str) -> None: + run_rtl(tmp_path, simulator, "tiny_clock_reset_pll_tb", + [ROOT / "rtl/tech/tc_pll.sv", ROOT / "rtl/tech/tc_pll_ics55_ecos.sv", + ROOT / "rtl/tiny/top/tiny_clock_reset_subsystem.sv", + ROOT / ".cache/retrosoc/sources/ics55_ecos_pll/verilog/PLL_TOP.behavioral.v"], + ["-DPDK_ICS55", "-DRETROSOC_SOC__TINY", "-DHAVE_PLL", "-DTINY_PLL_SUCCESS", + "-DTINY_PLL_NATIVE"]) + + +def test_tiny_rcu_register_parity() -> None: + sv = (ROOT / "rtl/tiny/top/tiny_rcu_regs.svh").read_text() + c = (ROOT / "crt/include/retrosoc/hal/rcu_regs.h").read_text() + sv_values = {name: int(value, 16) for name, value in + re.findall(r"`define APB4_TINY_RCU__(\w+)\s+32'h([0-9a-f]+)", sv)} + c_values = {name: int(value, 16) for name, value in + re.findall(r"#define RS_TINY_RCU_(\w+)_OFFSET\s+UINT32_C\(0x([0-9A-F]+)\)", c)} + assert len(sv_values) == 24 + assert sv_values == c_values + assert sorted(sv_values.values()) == list(range(0x100, 0x160, 4)) + + +@pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) +@pytest.mark.parametrize("xin_hz", [24000000, 48000000, 96000000]) +def test_tiny_external_sources(tmp_path: Path, simulator: str, xin_hz: int) -> None: + run_rtl(tmp_path, simulator, "tiny_clock_external_tb", + [ROOT / "rtl/tiny/top/tiny_clock_reset_subsystem.sv", + ROOT / "rtl/tiny/top/tiny_reset_tree.sv"], + [f"-DTINY_TEST_XIN_HZ={xin_hz}"]) + + +def test_aon_reset_uses_normalized_reference() -> None: + top = (ROOT / "rtl/tiny/top/retrosoc_tiny.sv").read_text() + assert re.search(r"\.por_clk_i\s*\(s_ref_clk\)", top) + + +def test_tiny_xpi_cdc_has_split_reset_endpoints() -> None: + top = (ROOT / "rtl/tiny/top/retrosoc_tiny.sv").read_text() + assert re.search(r"assign s_target_rst_n\[4\]\s*=\s*s_fabric_rst_n", top) + bridge = re.search(r"u_xpi_mem_cdc\s*\(.*?\n\s*\);", top, re.DOTALL) + assert bridge is not None + assert re.search(r"\.src_rst_n_i\s*\(s_fabric_rst_n\)", bridge.group(0)) + assert re.search(r"\.dst_rst_n_i\s*\(s_xpi_rst_n\)", bridge.group(0)) + + +@pytest.fixture +def p7_netlist(mapped_netlist): # noqa: F811 -- pytest injects the imported fixture + modules = mapped_netlist["modules"] + cells = modules["retrosoc_tiny_asic"]["cells"] + + def boundary(name, kind, **ports): + modules[kind] = {"ports": {key: {"direction": "output" if key.endswith("_o") else "input", + "bits": [i + 100]} for i, key in enumerate(ports)}, + "cells": {}} + cells[name] = {"type": kind, "connections": {key: [value] for key, value in ports.items()}} + + boundary("u_soc.u_clock_backend.u_sys_mux", "safe_clock_mux$sys", clk0_i=2, clk1_i=4, clk_o=20) + boundary("u_soc.u_clock_backend.u_mem_mux", "safe_clock_mux$mem", clk0_i=20, clk1_i=24, clk_o=21) + boundary("u_soc.u_clock_backend.u_pclk_mux", "safe_clock_mux$pclk", clk0_i=20, clk1_i=25, clk_o=22) + boundary("u_soc.u_cpu.u_cpu_gate", "tiny_clock_gate$cpu", clk_i=20, clk_o=23) + boundary("u_soc.u_crypto_lifecycle", "tiny_peripheral_leaf$crypto", clk_i=22, clk_o=26) + for name, cell in cells.items(): + if "u_soc.u_sram" in name: + cell["connections"]["CLK"] = [20] + if "u_soc.u_crypto.u_storage" in name: + cell["connections"]["CLK"] = [26] + if "u_hazard3_cpu_2port" in name: + cell["connections"]["CK"] = [23] + cells["pll"]["connections"].update(REFCLK=[2], EN=[30]) + return mapped_netlist + + +def test_p7_clock_bindings(p7_netlist) -> None: + binding = inspect_netlist(p7_netlist, "ICS55") + assert binding["main_macros"] == 32 + assert binding["private_crypto_macros"] == 6 + assert binding["pll_macros"] == 1 + + +@pytest.mark.parametrize("mutation", ["half_rate_sram", "cpu_divider", "crypto_sys", + "pll_bypass", "pll_tied", "fake_safe_source"]) +def test_p7_clock_binding_rejects_mismatch(p7_netlist, mutation: str) -> None: + cells = p7_netlist["modules"]["retrosoc_tiny_asic"]["cells"] + if mutation == "half_rate_sram": + cells["u_soc.u_sram.gen_group[0].u_group.gen_bank[0].u_ram.u_mem"]["connections"]["CLK"] = [21] + elif mutation == "cpu_divider": + cells["u_soc.u_cpu.u_hazard3_cpu_2port.core.pc_reg"]["connections"]["CK"] = [21] + elif mutation == "crypto_sys": + cells["u_soc.u_crypto_lifecycle"]["connections"]["clk_i"] = [20] + elif mutation == "pll_bypass": + cells["u_soc.u_clock_backend.u_sys_mux"]["connections"]["clk1_i"] = [2] + elif mutation == "pll_tied": + cells["pll"]["connections"]["EN"] = [3] + else: + cells["u_soc.u_clock_backend.u_sys_mux"]["connections"]["clk0_i"] = [31] + cells["pll"]["connections"]["REFCLK"] = [31] + with pytest.raises(ValueError): + inspect_netlist(p7_netlist, "ICS55") From b3edbaedd482cb4b9b7e7c322f53d545d1c31e10 Mon Sep 17 00:00:00 2001 From: maksyuki Date: Wed, 14 Oct 2026 16:56:34 +0800 Subject: [PATCH 12/12] docs(tiny): record P7 validation and reset integration contracts Document the RCU ownership, profile/PDK mapping, exact validation commands, matched firmware, source snapshots and current 06-00 backend evidence. Separate historical checkpoints and the rejected 04-00 candidate from current response-conservation and source-reset validation. State the production DMA drain and common XPI reset preconditions without claiming unrestricted standalone bridge source-reset support. Record the 54-case directed result, expected-negative checks and unchanged design inputs that justify retaining the original backend evidence. Preserve full-suite failures and focused corrections, interrupted Icarus attempts, final-phase deferral, negative timing and missing physical qualification. Keep P7 human acceptance and later phases separate. --- README.md | 8 +- configs/README.md | 10 +- docs/engineering.md | 6 +- docs/ip/tiny-soc-r2-clock-reset.md | 385 +++++++++++++++++++++++++++++ docs/ip/tiny-soc-verification.md | 227 +++++++++++++++++ physical/commercial/README.md | 9 +- rtl/tiny/README.md | 5 +- 7 files changed, 639 insertions(+), 11 deletions(-) create mode 100644 docs/ip/tiny-soc-r2-clock-reset.md diff --git a/README.md b/README.md index dfab1cbb..edf972d6 100644 --- a/README.md +++ b/README.md @@ -138,8 +138,10 @@ points, not the complete profile or application inventory. 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 default. -The profile instantiates a parked PLL; dynamic SYS switching remains R2-P7 and -physical qualification is pending. IHP130 remains explicit compatibility. +The PLL starts parked off. The [R2-P7 implementation](docs/ip/tiny-soc-r2-clock-reset.md) +adds software-controlled SYS transitions; acceptance evidence is tracked in +the Tiny ledger and physical qualification remains 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. @@ -266,4 +268,4 @@ Thanks to everyone contributing to retroSoC. Star History Chart - \ No newline at end of file + diff --git a/configs/README.md b/configs/README.md index b84ec217..a95675ae 100644 --- a/configs/README.md +++ b/configs/README.md @@ -75,8 +75,14 @@ for the matrix and its pending/current evidence boundary. The [2026-10-07 Tiny refreeze](../docs/ip/tiny-soc.md#ics55-default-platform-and-final-timing-gate-2026-10-07) 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 +`ci/ics55-tiny.mk` boots with the PLL held off; R2-P7 adds explicit software +requests for functional PLL192/240 transitions, subject to separate acceptance. +`ci/ihp130-tiny-xin48.mk` and `ci/ihp130-tiny-xin96.mk` normalize their declared +external input to REF24 at boot and permit no-PLL XIN transitions. See the +[P7 runbook](../docs/ip/tiny-soc-r2-clock-reset.md) for the evidence boundary. +`TINY_P7_ACCEPTANCE=YES APP=ci_smoke` selects the distinct RCU acceptance image +and configuration digest; ordinary Tiny acceptance firmware remains the default. +`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. diff --git a/docs/engineering.md b/docs/engineering.md index 9c7bb55c..41a3ed59 100644 --- a/docs/engineering.md +++ b/docs/engineering.md @@ -3,8 +3,10 @@ ## 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. The executable default keeps the PLL parked off; -PLL-driven SYS and physical qualification remain later work. The table +is ICS55/PLL with SAFE24 boot. The PLL starts parked off; the +[R2-P7 implementation](ip/tiny-soc-r2-clock-reset.md) adds explicit software +transitions with separate functional evidence. Physical qualification remains +pending. 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 diff --git a/docs/ip/tiny-soc-r2-clock-reset.md b/docs/ip/tiny-soc-r2-clock-reset.md new file mode 100644 index 00000000..6fb820a5 --- /dev/null +++ b/docs/ip/tiny-soc-r2-clock-reset.md @@ -0,0 +1,385 @@ +# Tiny R2-P7 clock/reset implementation and evidence + +This phase starts at `43a3c8d453b6d41df3536719ebc6d1f4521bc7cd`, against frozen +specification blob `7e19de49d49efaafacccfa85671e314d53dd15a4`. Implementation +is authorized; phase acceptance still requires review of the completed gates. +No P8 implementation, commit, push, or physical frequency qualification follows. + +## Product and ownership + +`configs/ci/ics55-tiny.mk` remains the default: TINY, ICS55, PLL present, +SAFE24 boot. The single RCU-owned PLL starts off. Software may request the +functional PLL192/PLL240 profiles after draining clients. These are digital +simulation capabilities, not characterized silicon operating points. +`configs/ci/ihp130-tiny.mk` remains the explicit no-PLL 24 MHz compatibility +profile. `ihp130-tiny-xin48.mk` and `ihp130-tiny-xin96.mk` add declared external +input variants, both normalizing the boot/control reference to 24 MHz. + +CPU, all 32 main SRAM macros, bank frontends, fabric and SDIO share active SYS. +Only the CPU leaf stops for WFI. XPI data/control use MEM. DMA, shared serial +and security IP and the six private Crypto macros use PCLK. Crypto capacity +and erasure policy are unchanged. AON owns RCU, CLINT and RTC/WDG functional +timekeeping; AUDIO is optional at boot. The historical ASIC wrapper and frozen +QFN64/pad contract are retained under the existing P6 qualification boundary. + +Tiny owns the clock backend, admission holders, APB/AXI crossings, peripheral +lifecycle controller, CPU gate, CLKOUT and SYSCTRL ABI. Common clock mux, +divider, reset barrier, synchronizers and CDC primitives are reused without +editing managed sources. New optional shared-IP lifecycle outputs preserve +Mini defaults; the debug wrapper's separate system reset is opt-in. Managed +PWM/CRC lifecycle mirrors observe successful writes at their existing ABI. +The already approved PWM lock remains +`38efaf5e20f084a00148920087c60cadab61839b`. + +RCU occupies `SYSCTRL+0x100..0x15c`, IRQ31. Handwritten SVH/C definitions +have a 24-register parity test. `rcu.h` supplies bounded Tiny-specific profile, +snapshot, gate, local/system reset, cause/fault and CLKOUT operations. Invalid, +unsupported, busy, timeout and hardware-error results are distinct. No new +MISRA Required-rule deviation is introduced. Future targets 14–17 remain +absent; no SPI, PIO-lite, DVP, PPALite or PSRAM rollout is included. + +## Lifecycle and failure boundaries + +Clock requests block new admissions after command acceptance, drain accepted +CPU/local/AXI/APB traffic, park on REF24, configure conservative divisors, +qualify the source, then commit rate snapshots before unblocking. Stalled +addresses already accepted into admission holders retain VALID/payload. +Unaccepted requests may remain pending and resume after switching. An +insufficient transition budget fails before changing the committed profile. +PLL lock timeout publishes the complete SAFE24 fallback. PLL stuck-low/high +requires a system/CDC reset before forced mux recovery; XIN loss needs source +restoration and external reset. + +Local resets hold for five actual leaf edges and release through five-edge +synchronizers and bus barriers. I2S additionally requires actual AUDIO-side +reset readiness; missing AUDIO cannot manufacture RESET_DONE. Gate changes +wait for idle; CPU-only WFI permission does not pause unrelated DMA. Current +non-CPU lifecycle commands conservatively drain all central-DMA work before +changing the selected targets. This can reject a request while an unrelated +DMA stream remains active; it does not stop that stream by gating its clock. +Warm reset retains cause/fault/debug state, aborts interrupted debug commands, +restores SAFE24, resets functional RTC/WDG state and preserves main SRAM data. + +The 2026-10-14 P7 review correction keeps the XPI fabric target and AXI bridge +source endpoint on SYS reset while the XPI/MEM endpoint uses its MEM leaf reset. +The later response-conservation correction uses source-owned response holders: +already presented B/R responses remain stable and retire once, while missing +read beats receive DECERR and aborted writes drain W before B. Admission allows +one outstanding request in each direction. Payload source reset uses the +five-edge link barrier; the response holders reset only with the source. +An unfinished warm clear holds isolation until destination reset so that late +responses cannot enter another transaction. Production DMA/XPI clear inputs +remain tied low. A failed drain does not trigger an invented autonomous AXI +timeout or peripheral reset. The focused tests now cover these protocol +boundaries in both simulators; earlier 36-case results did not establish them. + +Source-only reset has a separate production precondition. The DMA source +may reset locally only after its engine and accepted admission/fabric work +drain. The controller blocks new admissions, waits at least 16 REF24 cycles +for the synchronized observations, and leaves the source running on drain +timeout. XPI has no independent SYS-source reset: the common system reset +also asserts its MEM endpoint reset. The standalone AXI bridge does not +cancel a live remote slave's work on an arbitrary source-only reset and must +not be reused with that assumption. + +The focused `tiny_p7_source_reset_tb` extracts the production controller, +admission, bridge, synchronizers and reset bindings into its fixture and uses +the real DMA AXI master. It checks stalled addresses, partial writes, late +responses, held read responses, timeout, recovery and common reset at all five +declared domain ratios. A scoreboard independent of local reset detects the +deliberately broken-drain negative case. Unrelated clients and the completion +mailbox are idle; the target is a behavioral slave. This supplies executable +integration-contract coverage, not a general arbitrary-reset proof or physical +qualification. The new evidence is under `06-00.../meta/tiny-r2-p7-source-reset/` +as detailed in the verification ledger; production RTL and backend inputs are +unchanged by this follow-up. + +The follow-up validation commands are: + +```sh +source .cache/retrosoc/development/tiny-r2-p1/activate.sh +p7_reset_evidence=build/ics55-tiny-2026-10-14-06-00-ff4b5cef6eb5/meta/tiny-r2-p7-source-reset +PYTHONDONTWRITEBYTECODE=1 python3 scripts/run_flow.py --tool pytest \ + --log "$p7_reset_evidence/directed.log" \ + --result "$p7_reset_evidence/result-directed.json" -- \ + python3 -m pytest -q tests/test_tiny_r2_p7.py \ + --basetemp="$p7_reset_evidence/directed-tests" +make --silent CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 \ + rtl-format-check rtl-style-check-all check-clock-reset-domains +ruff check . +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55 --dry-run +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 --dry-run +git diff --check +``` + +These paths identify the retained execution, not an instruction to overwrite +it: use a fresh evidence directory for any retry. `source.json` binds the new +tests and `verification-update.json` compares them to the original `06-00` +snapshot. Only the two verification files differ; all 1728 other technical +inputs and tool identities match. No firmware, full-SoC, synthesis, netlist or +STA flow is repeated for this verification-only correction. The earlier +full-suite Pytest and full-Icarus limitations below remain explicit. + +The first post-fix physical smoke campaign below is historical. Each PDK has +its own profile/configuration digest. These results precede the final response +holders; current source evidence is recorded in the verification ledger. + +| PDK | Variant | Synthesis | Netlist boot | STA | +| --- | --- | --- | --- | --- | +| ICS55 | `build/ics55-tiny-2026-10-14-01-20-ff4b5cef6eb5` | PASS, 2122.685 s | PASS, 1656.024 s, `SIM_TEST_PASS Tiny netlist boot` | PASS observation; WNS max/min `-144.40/-0.31 ns`, TNS max/min `-4859636.50/-45.04` | +| IHP130 | `build/ihp130-tiny-2026-10-14-01-20-8232fa11e7f3` | PASS, 1925.337 s | PASS, 586.861 s, `SIM_TEST_PASS Tiny netlist boot` | PASS observation; WNS max/min `-257.81/-0.47 ns`, TNS max/min `-9807803.00/-100.68` | + +The current frozen-source `06-00` campaign supersedes the historical table for +post-fix qualification: + +| PDK | Variant | Synthesis | Netlist boot | STA | +| --- | --- | --- | --- | --- | +| ICS55 | `build/ics55-tiny-2026-10-14-06-00-ff4b5cef6eb5` | PASS, 2108.388 s | PASS, 1655.980 s, `SIM_TEST_PASS Tiny netlist boot` | PASS observation; WNS max/min `-152.03/-0.31 ns`, TNS max/min `-5622052.50/-44.14` | +| IHP130 | `build/ihp130-tiny-2026-10-14-06-00-8232fa11e7f3` | PASS, 1934.515 s | PASS, 578.869 s, `SIM_TEST_PASS Tiny netlist boot` | PASS observation; WNS max/min `-253.43/-0.49 ns`, TNS max/min `-10035324.00/-153.37` | + +The STA markers explicitly identify SAFE24/observational analysis; neither +variant qualifies an active PLL, routed implementation, PVT corner or product +operating point. The `04-00` source candidate was later rejected by the +response-conservation tests. The `06-00` source-bound post-fix campaign is the +current qualification run and all requested synthesis, netlist and STA gates +pass. Its measured results are recorded in the ledger. Full firmware Icarus +remains deferred to the final Tiny phase. + +Source/divisor/enable for CLKOUT are committed together, cross to the selected +source, and finish the current output high pulse before disabling. GPIO28 ALT0 +and GPIO23 ALT1 consume the same generator. Illegal/live changes return +PSLVERR and leave the previous configuration unchanged. + +## Validation sequence and commands + +Activate the locked environment before these commands: + +```sh +source .cache/retrosoc/development/tiny-r2-p1/activate.sh +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55 --dry-run +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 --dry-run +python3 -m pytest -q tests/test_tiny_r2_p7.py tests/test_tiny.py tests/test_tiny_r2_p4.py tests/test_tiny_r2_p5.py +make sw-format-check sw-policy-check sw-host-test +make rtl-format-check rtl-style-check-all rtl-readiness-check check-clock-reset-domains +ruff check . +python3 scripts/dependency_lock.py --lock dependencies/dependencies.lock.json +python3 -m pytest -q +git diff --check +``` + +The direct long-flow commands use the selected regression's targets with +larger explicit wall-time budgets. The identical Tiny nightly matrix does not +need a second execution. Every row below runs once per PDK, with +`CONFIG=configs/ci/-tiny.mk SOC=TINY PDK= APP=ci_smoke HAVE_SVA=YES`: + +| Purpose | BUILD_TIMESTAMP | Remaining Make arguments | +| --- | --- | --- | +| P7 Verilator | `2026-10-13-17-05` | `TINY_P7_ACCEPTANCE=YES SIMU=VERILATOR SOC_SIM_TIME=3600 TINY_SIM_TIMEOUT=6000000 TINY_SIM_ARGS=+p7_monitor sim` | +| P7 Icarus | `2026-10-13-17-06` | `TINY_P7_ACCEPTANCE=YES SIMU=IVERILOG TINY_SIM_TIMEOUT=6000000 TINY_SIM_ARGS=+p7_monitor 'TINY_RUN=timeout --foreground 14400s $(VVP) $(SIM_BUILD_ROOT)/simv' sim` | +| Physical macro synthesis | `2026-10-13-17-15` | `HAVE_SVA=NO SYNTH=YOSYS YOSYS_TIMEOUT=10800 synth` | +| Full firmware/JTAG, P7 watchdog-cause adaptation, Verilator | `2026-10-13-18-25` | `SIMU=VERILATOR SOC_SIM_TIME=7200 TINY_SIM_TIMEOUT=30000000 TINY_SIM_ARGS=+tiny_jtag_smoke sim` | +| Full firmware/JTAG, P7 watchdog-cause adaptation, Icarus | `2026-10-13-18-26` | `SIMU=IVERILOG TINY_SIM_TIMEOUT=30000000 TINY_SIM_ARGS=+tiny_jtag_smoke 'TINY_RUN=timeout --foreground 36000s $(VVP) $(SIM_BUILD_ROOT)/simv' sim` | +| Netlist boot, after synthesis | `2026-10-13-17-15` | `HAVE_SVA=NO SIMU=IVERILOG netsim-boot` | +| Observational core STA, after synthesis | `2026-10-13-17-15` | `HAVE_SVA=NO 'STA_BUILD_ROOT=$(VARIANT_ROOT)/sta/opensta-p7-v3' STA=OPENSTA sta` | + +Warning/metric collection uses each corresponding variant. New EDA signatures +remain observations; no warning baseline or metric policy is edited. Compiler +warning policy and Tiny functional verdicts remain required. + +After correcting the external-input POR reference, the actual-SoC P7 image is +also run with `CONFIG=configs/ci/ihp130-tiny-xin48.mk` and +`CONFIG=configs/ci/ihp130-tiny-xin96.mk`, `SOC=TINY PDK=IHP130 APP=ci_smoke +TINY_P7_ACCEPTANCE=YES HAVE_SVA=YES TINY_SIM_ARGS=+p7_monitor +TINY_SIM_TIMEOUT=24000000`. Verilator uses timestamp `2026-10-13-18-40`, +`SIMU=VERILATOR SOC_SIM_TIME=7200 sim`; final Icarus uses `2026-10-13-20-05`, +`SIMU=IVERILOG 'TINY_RUN=timeout --foreground 21600s $(VVP) $(SIM_BUILD_ROOT)/simv' sim`. +The larger cycle budget counts raw XIN edges, not SYS/REF24 cycles. These are +additional source configurations, not repetitions of a single benchmark. + +The initial XIN48/96 Icarus attempts (`18-41`) produced a premature terminal +write but failed the independent P7 lifecycle coverage checks, so they remain +FAIL. A short actual-SoC comparison reproduced unknown RCU cause/fault state +with the old reset declaration initialized low at time zero. REF24 has no edges +while its input divider is reset; an explicit external RESET_N assertion at +1 ns, before the first XIN edge, initializes the asynchronous state correctly. +Both old-stimulus negative probes fail and both asserted-reset probes pass, +under `20-05.../meta/cold-reset/`. The testbench now checks exact known cold +state and rejects an unknown terminal pass/code. This is a verification-stimulus +repair, with no forced DUT state or synthesizable RTL change. The Verilator +source cases and existing 24 MHz runs are retained separately; only the two +failed Icarus source cases are replayed, with the same firmware hashes. + +## Workloads and provenance + +The separate `TINY_P7_ACCEPTANCE=YES` configuration key selects the short P7 +firmware without replacing the original acceptance application. It exercises +all supported profile selections for the chosen backend, 128-word SRAM +retention, atomic rate readback, PWM rate readback, CLINT monotonicity and an +independent PCLK timer measurement over at least 200 CLINT ticks. The timer +check allows bounded APB read skew but rejects /2 or /4 domain mistakes. +It gates/ungates/resets timer0, copies 1024 words through DMA while CPU WFI is +armed, wakes on CLINT, and requests software reset with a retained `.noinit` +sentinel. The observer must see actual CPU gated cycles, DMA responses during +that interval, supported profile commits and exactly one software reset. + +Tiny PASS requires command success, TEST_STATUS/SIM_TEST_PASS, and absence of +FAILED/FATAL/assertion failed/%Error/SIM_TEST_FAIL/SIM_TEST_TIMEOUT. P7 also +requires `P7_LIFECYCLE_PASS`. The old full firmware additionally retains its +P6 peripheral/wire/AXI records and JTAG checks. UART output is diagnostic only. + +The completed P7 firmware results below match between Verilator and Icarus. +All eight images use the same retained binary. Cycles count raw XIN edges and +must not be compared as CPU cycles across different XIN configurations. + +| Source/profile | Terminal cycles in each simulator | Actual gated SYS cycles | DMA responses while gated | +| --- | ---: | ---: | ---: | +| ICS55 XIN24, PLL192/240/SAFE24 | 3,525,681 | 71,614 | 64 | +| IHP130 XIN24, external/SAFE24 | 3,521,913 | 71,623 | 64 | +| IHP130 XIN48, external/SAFE24 | 7,038,466 | 71,635 | 64 | +| IHP130 XIN96, external/SAFE24 | 14,071,429 | 71,615 | 64 | + +Each run also records exactly one software reset and verifies SRAM retention. + +The source record is +`build/ics55-tiny-2026-10-13-17-05-f3e42d54c18b/meta/tiny-r2-p7/source.json`. +It retains repository state, the exact specification, locked tool identities, +source hashes, patch and a source archive, including untracked implementation +files. `scripts/tiny_r2_p7.py verify-source` checks the retained source set. +The Make formatter subsequently changed only the terminal newline in eight +Make/configuration files. `make-format-equivalence.json` checks every changed +file byte-for-byte except that final newline; RTL, firmware, tests and tool +scripts are identical. `formatted/source.json` retains the final formatting +snapshot separately, without overwriting the source consumed by running jobs. +Subsequent validation corrections update the commercial six-domain SAFE24 +adapter/test and connect the top-level POR counter to normalized REF24. The +external-source fixture now requires five actual REF24 edges at 24/48/96 MHz +and compares old/new reset-tree outputs at 24 MHz in both simulators (seven +checks pass). For every retained long-flow configuration, XIN is 24 MHz and +the backend directly wires XIN to REF24, so that one port reconnection is an +exact alias substitution. The original source remains the provenance of those +runs; the documented equivalence and focused higher-input-rate tests cover +the correction without repeating default-profile simulations. +Build variants retain effective configuration, generated filelists, firmware +ELF/bin/hex/map, simulator binaries, command logs and structured terminal +results. Match binaries by SHA-256 across simulators, and retain flags and +plusargs with results; do not mix these images with compiler/layout experiments. + +The four P7 binaries (both PDKs and both simulators) have SHA-256 +`0a536fbbae9832de7ba5a82f7688a147b96a57435a66249b5e8de18bad1c01c5`. +The initial full ICS55 firmware matched between simulators at +`e4cb5965b9b9ddb1135a0a672327113ed8b378ace56e1a4ee771374309c18a15`; +the initial full IHP130 firmware matched at +`048bd15221fcbd25986abaa2d973e74d101f3a8ac8f328a6169442ba8acabacb`. +The corrected watchdog-acceptance full images match between simulators at +ICS55 `69b8582099c8735e81014b9c3f5685d9212a58c18b820a8c9a611a6f64a2f6c4` +and IHP130 `efc0b403f32669332e942cb63ee9bb2b035a6e462e68e0a5a1524dea01c6c298`. + +P7 has its own structural netlist audit. It requires 32 main and six private +Crypto physical SRAM macros, main-SRAM/CPU SYS equality, the CPU-only gate, +Crypto PCLK binding and exactly one real ICS55 PLL driving the SYS mux. +The audit binds configuration digest, netlist/JSON and audit-script hashes. +The strict historical P1 parked-PLL/direct-SYS audit is unchanged. + +Core STA analyzes SAFE24 boot, with explicit mux/PLL case analysis. Retain +clocks, setup/hold, coverage, recovery/removal and constraint-violation reports. +It excludes pad/board/routed/PVT and active-PLL qualification. Negative slack +is observational before the final combined product campaign, not evidence of +qualification and not permission to omit synthesis or netlist functionality. +The commercial adapter mirrors the same six-domain SAFE24 inventory and +requires explicit mux/PLL case selections; its host tests pass. Internal /2, +/4 and CLKOUT-source clocks supplement the six root domains. Settled mux gate +state is constrained because a select request alone does not propagate through +the mux's sequential handshake in static analysis. The initial and second STA +attempts remain under `sta/opensta` and `sta/opensta-p7-v2`; the final observation +is under `sta/opensta-p7-v3`. No licensed +commercial execution, active-PLL SDC, routed extraction, board IO or package +qualification is supplied by those host tests. + +Historical `2026-10-13-17-15` SAFE24 observations, in ns, are ICS55 setup/hold WNS −147.48/−0.31, +TNS −4758121.50/−45.30; IHP130 WNS −275.45/−0.50, +TNS −9958118.00/−102.84. The worst setup cone begins at the fabric reset leaf, +whose direct fanout is over 10,000; no reset false path was added to hide it. +The v3 coverage reports retain excluded pad IO budgets, 42 unclocked ICS55 PLL +monitor/mux pins while that source is off, and 5 ICS55 / 6 IHP130 control pins +with multiple clock tags. These are explicit limits of this settled SAFE24 +observation, not complete-mode timing coverage or signoff. These values and +coverage counts belong to that historical netlist; the current `06-00` WNS/TNS +are in the table above, with their own retained coverage reports. + +The single full Pytest attempt records 1696 passed, 6 failed, 60 setup errors +and one optional `pypdf` skip. Two failures were the commercial domain policy; +60 setup errors and one failure shared the missing `RS_EBUSY=-8` SDK return +entry in the publication's source-checked table. Both are corrected; the +commercial/STA tests pass and the publication subset records 110 passed/one +skip. The remaining three Icarus failures used a test module collected before +the external-reset fixture acquired its `tiny_reset_tree` dependency; the +updated test command explicitly supplies it and all seven external-source/reset +checks pass. Keep that original full-suite result and these focused reruns; +do not report an unexecuted clean full-suite pass. Only the shared return-code +table changes in the publication; no source identity, PDF, edition or release +qualification is advanced. + +## Interrupted and historical attempts + +Earlier October 13 build directories are development snapshots. In particular, +the `15-45-f3e42d54c18b` Verilator attempt passed at 3,281,032 cycles and observed +71,612 gated cycles, 64 DMA responses while gated and one warm reset. Later +corrections mean that result is not final-source acceptance. Earlier synthesis +attempts failed obsolete P1 checks; a temporary weakened P1 audit is invalid +evidence and was restored. No current physical result may be inferred from it. + +The first final-campaign synthesis attempt (`17-07`, SVA enabled) failed during +Slang elaboration on unsupported temporal assertion syntax. It did not run +mapping. The `17-15` retry uses the existing regression's `HAVE_SVA=NO` synthesis +configuration; behavioral/SVA verification remains separate and unchanged. + +The `17-08` complete Verilator runs passed all first-boot peripherals, then +incorrectly repeated the firmware after watchdog reset and failed recovery +code 26 on the second pass. The old firmware inferred warm boot from the +watchdog's functional `reset_count`; P7 intentionally resets that state. The +corrected firmware checks retained RCU cause bit 2, verifies that watchdog +functional state was cleared and SAFE24 restored, and requires an SRAM `.noinit` +cookie set only after all first-boot tests. This preserves the complete test +sequence and watchdog acceptance rather than restoring obsolete retention. +The corresponding `17-09` Icarus runs were cancelled as obsolete; controller +exit 130 is retained in `controller-cancellation.json`, separately from any +unfinished child result. Corrected full runs use `18-25` / `18-26` and source +evidence under `build/ics55-tiny-2026-10-13-18-25-f3e42d54c18b/meta/tiny-r2-p7/`. + +The corrected full Icarus attempts under `18-26` reached the watchdog-reset +stage after reporting all first-boot peripherals, but both exhausted the +36,000-second wall-clock budget with exit 124 and no terminal PASS. Those +results remain FAIL. A focused test uses the real WDG APB wrapper, command +mailboxes, Tiny APB bridge, clock backend, reset tree and RCU, and verifies two +successive hardware watchdog resets, retained RCU cause and cleared functional +state in both simulators. It does not replace the full firmware gate. +The exact retained `simv` and firmware were replayed under +`sim/iverilog/attempt-2026-10-14-04-50/` with `timeout --foreground 72000s vvp -i`, +the same `+max_cycles=30000000 +tiny_jtag_smoke` and firmware path. The +maintainer then intentionally interrupted both replays with exit 130 and moved +this long-running full-Icarus gate to the final Tiny phase. The logs and +interrupted `result-sim.json` records are retained as unrun/partial evidence; +they cannot satisfy the P7 terminal verdict. `-i` enabled unbuffered standard +IO; it did not change the design or acceptance workload. No synthesis, +simulator compilation or firmware build was repeated for the replay. + +PIDs are diagnostics in a particular execution namespace. Absence from another +tool invocation's process list does not prove termination. Keep long commands +attached to their execution sessions and use terminal result JSON plus strict +log checks. `run_flow.py` records child/parent PIDs and closes results on caught +exceptions; SIGKILL or host loss can still leave a running record. Such a record +never counts as PASS. NFS clock-skew warnings are retained in the build logs. +Cancellation now writes an interrupted checkpoint before process-group cleanup +and ignores a second termination signal during that cleanup. The final result +records the child's exit code and whether cleanup remains pending; the repeated +signal/TERM-ignoring-child regression passes. An external executor can still +kill a process before it handles a signal, so controller cancellation evidence +must not be mistaken for a fabricated child exit result. + +The ledger records the final pass/fail/unrun matrix. Human review of that matrix, +the diff and remaining qualification boundaries is the P7 acceptance gate. +The generated `audit.py` under the corrected full-run evidence directory reads +all source configurations' terminal results, strict Tiny markers, P7 lifecycle +observations, unchanged P6 workload/wire records, JTAG, paired firmware hashes, +macro audit, netlist boot and STA artifacts. It launches no hardware flow and +writes `acceptance.json`; a pending/missing/failed input cannot produce PASS. diff --git a/docs/ip/tiny-soc-verification.md b/docs/ip/tiny-soc-verification.md index 0f496663..4412e430 100644 --- a/docs/ip/tiny-soc-verification.md +++ b/docs/ip/tiny-soc-verification.md @@ -1,5 +1,232 @@ # Tiny MCU Verification Record +## TINY-R2-P7 source-reset integration contract (2026-10-14) + +The follow-up review identified a missing source-side reset proof, not a +reproduced product failure. The AXI bridge does not support arbitrarily +resetting an active source while leaving its slave session alive. Flushing +the FIFO cannot cancel a request already accepted by that slave. Production +Tiny prevents that sequence: DMA local reset requires engine, admission and +fabric drain before resetting the source; XPI's SYS source reset is a common +system reset that also resets its MEM endpoint. Neither production bridge +uses warm clear. These preconditions are part of the integration contract, +not a claim of unrestricted unilateral-reset support by the standalone bridge. + +`tests/rtl/tiny_p7_source_reset_tb.sv` uses the production DMA master, +admission, CDC, lifecycle controller and reset tree. The test extracts the +actual top-level instances, drain/block synchronizers and generated DMA/XPI +reset bindings, so a hand-copied reset policy cannot silently replace them. +An independent outstanding scoreboard survives local reset and rejects +premature reset. The AXI target is a controllable behavioral slave; unrelated +CPU/APB/SDIO work and the DMA completion mailbox are idle. This is a focused +integration test, not a full-SoC firmware or formal CDC/RDC signoff result. + +The scenarios cover stalled AR/AW, partial W, downstream W backpressure, +delayed R/B, source-held R, drain timeout without reset, post-reset reads and +writes, and common system reset during an outstanding read. The same binary +is reused at SAFE24, XIN48, XIN96, PLL192 and PLL240 domain ratios. They test +digital ordering only; the clock generators do not qualify a PLL or PDK rate. +A negative run falsifies both idle observations and must trip +`P7_SOURCE_RESET_UNDRAINED`; Icarus may return zero for sv2v's lowered fatal, +so the forbidden marker and missing PASS still make that simulation invalid. + +Evidence for this correction is retained under +`build/ics55-tiny-2026-10-14-06-00-ff4b5cef6eb5/meta/tiny-r2-p7-source-reset/`. +The first focused attempt's test-driver failure is retained in `focused-1.log`; +it is not a DUT failure or acceptance result. The final `directed.log` and +`result-directed.json` record **54 passed in 191.71 seconds**. This includes +ten positive source-reset runs (five ratios in each simulator) and two +expected-negative broken-drain runs inside the two new Pytest cases. +RTL format/style, clock/reset inventory, Ruff, both Tiny PDK PR dry-runs and +`git diff --check` pass. The verification-only source comparison is retained +in `verification-update.json`: only the two test files differ; 1728 other +technical inputs and tool identities match. The design, +firmware, configuration, dependencies and physical inputs are unchanged from +the `06-00` source snapshot; the new tests do not relabel earlier simulations +as new executions. Full Icarus firmware remains deferred to final Tiny +qualification. Human P7 acceptance is still required. + +## TINY-R2-P7 response conservation correction (2026-10-14) + +The earlier review recommendation to give abort unconditional priority over +source response retirement was incorrect. A cycle with an already exposed +normal B/R handshake followed by an abort produced two completions for one +request. Four read/write tests reproduced this in Verilator and Icarus. The +failed candidate, diagnostics and source archive are retained below +`build/ics55-tiny-2026-10-14-04-00-ff4b5cef6eb5/meta/tiny-r2-p7-response/`. +The `04-00` synthesis/netlist/STA results belong to that rejected candidate; +their successful boot does not close the protocol finding. + +The corrected bridge holds presented responses in source-owned registers. +Remote reset cannot revoke VALID or rewrite their payload under backpressure, +and a completed source handshake is never followed by a second completion. +Only missing read beats receive DECERR, with RLAST at the original burst end; +a reset write consumes the remaining W beats before emitting B. Outstanding +tracking is one transaction per direction, matching the existing Tiny paths. +The payload reset uses the five-edge source link barrier. An unfinished warm +clear quarantines the bridge until a real destination reset; a FIFO clear +cannot by itself cancel remote slave state or authorize re-use of late replies. +Production DMA/XPI clear inputs remain tied low; no new software reset API, +autonomous AXI timeout, DMA owner, or later-phase capability is introduced. + +The new tests distinguish completed/held responses from missing responses, +cover simultaneous clear and response handshakes, remote reset with READY high, +backpressure, partial bursts and late-response quarantine. That campaign's +full directed selection passed 52 cases. Earlier 36-case and rejected- +candidate passes did not cover these failures and are not proof of the repair. +The failed same-cycle candidate and diagnostics remain in the response +evidence directory. +The final input snapshot is +`build/ics55-tiny-2026-10-14-06-00-ff4b5cef6eb5/meta/tiny-r2-p7-response/final/source-final.json`. +The `06-00` backend campaign is bound to the final source snapshot and all +requested post-fix gates pass. Its variants are +`build/ics55-tiny-2026-10-14-06-00-ff4b5cef6eb5/` and +`build/ihp130-tiny-2026-10-14-06-00-8232fa11e7f3/`. ICS55 +synthesis/netlist/STA durations are `2108.388/1655.980/43.594 s`, with WNS +max/min `-152.03/-0.31 ns` and TNS max/min `-5622052.50/-44.14`; IHP130 +durations are `1934.515/578.869/47.109 s`, with WNS max/min +`-253.43/-0.49 ns` and TNS max/min `-10035324.00/-153.37`. Netlist boot +emitted `SIM_TEST_PASS Tiny netlist boot` for both PDKs. These timing values +remain observational and do not qualify closure or an operating point. +Full Icarus firmware remains deferred by the maintainer to final Tiny +qualification. It is not a current P7 gate; protocol correctness and human +phase acceptance remain required. + +## TINY-R2-P7 final verification campaign (2026-10-13) + +The [P7 runbook](tiny-soc-r2-clock-reset.md) records the current implementation, +exact profile-specific commands, workload definitions and source snapshot. +The integrated design now drives actual SYS/MEM/PCLK consumers, CPU-only WFI +gating, local reset barriers, CLKOUT and the single ICS55 PLL backend. It keeps +the 32 main SRAM macros at CPU SYS and six private Crypto macros at PCLK. +Future target capabilities remain absent. No later phase is authorized here. + +Development validation includes 68 passing Tiny P7/SYSCTRL/P5 directed tests +and a later complete 52-case P7 directed pass, +plus both simulators' real Hazard3 IRQ/debug wake, clock-pause and retained-debug +warm-reset/aborted-command cases. RCU validation now rejects invalid or BUSY +staging writes with PSLVERR, and the CLKOUT waveform test exposed and corrected +enable/divisor ordering. HAL tests cover invalid, absent, busy, gated-reset and +sticky-timeout/error boundaries. These short checks complement the final long +flows; they do not replace full firmware or netlist acceptance. + +Source evidence is retained at +`build/ics55-tiny-2026-10-13-17-05-f3e42d54c18b/meta/tiny-r2-p7/source.json`. +The final command matrix and its retained corrections are listed in the runbook. +Default ICS55/IHP130 and external XIN48/96 P7 firmware pass in both simulators. +Each profile's cycle count and WFI/DMA/reset observations match across simulators. +Both watchdog-corrected full Verilator firmware runs pass the unchanged P6 +peripheral/wire/AXI collector and JTAG. The two corresponding full Icarus +replays were intentionally interrupted with exit 130 after the maintainer +requested that this long-running gate move to the final Tiny phase; their +partial logs and interrupted result records are retained, but they are not +acceptance evidence. +Both PDKs pass synthesis, structural macro binding and Tiny netlist boot. +Current core STA remains observational and negative, with the coverage limits +explicit in the runbook. These are historical campaign results; current phase +acceptance requires the response correction above and human review. The full +Icarus firmware gate belongs to final Tiny qualification. No high-rate physical operating point is +qualified by these functional simulations or SAFE24 core STA. + +## Historical TINY-R2-P7 CDC/reset correction follow-up (2026-10-14) + +This checkpoint predates the response-conservation correction and is +superseded by the `06-00` campaign above. It is not current-source acceptance. +The P7 review identified two source-bound gaps. The XPI fabric target and bridge source side release with +the SYS reset endpoint, while the XPI/MEM side retains its MEM leaf reset. The +Tiny AXI CDC bridge synchronizes destination reset loss, invalidates its +protected request/response state and blocks new source admissions. Its original +single-DECERR completion claim was insufficient for held responses and bursts; +the later response-conservation correction supersedes that claim. The existing DMA/XPI +instances retain their explicit bridge boundaries and the XPI bridge now uses +the split SYS/MEM reset mapping. + +The new `tests/rtl/tiny_axi4_async_bridge_tb.sv` passes under both Verilator +and Icarus for normal read response, reset-aborted read/write, DECERR, +no-replay and bridge recovery. The complete +`PYTHONDONTWRITEBYTECODE=1 python3 -m pytest -q tests/test_tiny_r2_p7.py` +selection eventually passed 52 tests after the later corrections; that count +does not belong to the `01-20` implementation. `make --silent rtl-format-check rtl-style-check-all` +and `git diff --check` pass. An ICS55 default-profile Verilator `comp` also +passes; its existing PDK and simulator warnings remain observations. The +checkpoint's synthesis, netlist boot and observational STA gates passed for +both PDKs. Its historical evidence is retained under +`build/ics55-tiny-2026-10-14-01-20-ff4b5cef6eb5/` and +`build/ihp130-tiny-2026-10-14-01-20-8232fa11e7f3/`. ICS55 reports WNS +max/min `-144.40/-0.31 ns` and TNS max/min `-4859636.50/-45.04`; IHP130 +reports WNS max/min `-257.81/-0.47 ns` and TNS max/min `-9807803.00/-100.68`. +These remain observational negative timing results, not closure or operating +point qualification. The long-running full Icarus firmware gate remains +intentionally deferred to the final Tiny phase; no physical flow was run. + +## Historical initial P7 checkpoint, superseded (2026-10-13) + +The following incomplete checkpoint belongs to earlier development snapshots. +Its unconnected/missing-path statements do not describe the final campaign. + +This checkpoint starts from committed source `43a3c8d453b6d41df3536719ebc6d1f4521bc7cd`, +specification blob `7e19de49d49efaafacccfa85671e314d53dd15a4`, Tiny/ICS55 +profile `configs/ci/ics55-tiny.mk`, and the locked PWM revision +`38efaf5e20f084a00148920087c60cadab61839b`. The dependency lock validator +passes with digest `263402a34f2e315ca46ef4bf6366aab64935bd8de7abab3e469f513e988b0208`. + +The implementation adds the Tiny-owned RCU register bank and handwritten +`rcu.h`/`rcu_regs.h`/`rcu.c` contract, reports SAFE24/domain-rate snapshots, +connects committed PCLK/SYS rate reporting to the upgraded PWM and SDIO +`CLOCK_ACTUAL` paths, and adds focused APB coverage for identity, capability, +profile rejection/commit, status and write-only command behavior. Verilator +and Icarus SYSCTRL focused tests pass; ICS55 Verilator and Icarus top-level +compilation, Tiny firmware linking, C format/policy/host tests and the SDIO +suite pass. Build logs are retained under the generated +`build/ics55-tiny-2026-10-13-09-00-f3e42d54c18b/` and +`build/ics55-tiny-2026-10-13-10-30-f3e42d54c18b/` roots. + +A Tiny-owned `tiny_clock_reset_subsystem` backend is now source-bound and +filelisted. Its SAFE24/no-PLL focused test passes in both simulators and covers +five-stage SYS/MEM/PCLK reset release, external-profile handling and rejected +PLL requests. A second focused test exercises the behavioral PLL lock-timeout +path in both simulators; it does not claim successful macro lock or frequency +qualification. The backend is intentionally not advertised or wired into the +CPU/fabric yet; its SYSCTRL profile/apply handshake is now connected, while +the backend instance is now present in the Tiny top and its SYS output clocks +the CPU, fabric, SRAM, AXI interfaces and system reset tree. XPI now crosses +from SYS to the backend MEM clock through the Tiny-owned warm-flush AXI bridge. +The RCU command checks CPU/fabric/target idle observations and the XPI bridge +before accepting a transition. PCLK outputs are still not connected to all +peripheral consumers. + +The focused command is +`PYTHONDONTWRITEBYTECODE=1 python3 -m pytest -q tests/test_tiny_r2_p7.py`; +its four cases passed. Supporting gates were +`make --silent rtl-format-check rtl-style-check-all`, +`make --silent sw-format-check sw-policy-check sw-host-test`, +`PYTHONDONTWRITEBYTECODE=1 python3 -m pytest -q tests/test_script_tools.py -k 'run_flow'`, +`ruff check scripts/run_flow.py tests/test_tiny_r2_p7.py`, and +`python3 scripts/dependency_lock.py --lock dependencies/dependencies.lock.json`. +The current ICS55 top-level compile used +`source .cache/retrosoc/development/tiny-r2-p1/activate.sh && CCACHE_DISABLE=1 make --silent CONFIG=configs/ci/ics55-tiny.mk APP=ci_smoke SIMU=VERILATOR HAVE_SVA=YES BUILD_TIMESTAMP=2026-10-13-16-30 comp` +and exited zero. These are source/compile/focused checks; none is a strict +full firmware terminal verdict or physical qualification. + +The long-run diagnostic also confirmed that the emulator itself exits normally +with a Tiny timeout at a bounded cycle count and uses about 26 MiB RSS. The +earlier `status=running` records lacked a verified terminal result. The initial +diagnosis attributed this to sandbox cleanup without sufficient evidence; +that conclusion is withdrawn. Separate tool invocations can have different +PID namespaces, so process absence in one does not establish that another +command died. `run_flow.py` now records `parent_pid` and `child_pid` +in the running result and writes a failed terminal result for unexpected +Python exceptions; long runs must remain attached to one polling session. + +This is not phase acceptance. The current RTL now uses the backend SYS clock +for the system/fabric path, but does not yet provide the required executable +MEM/PCLK clock consumers, full CDC bridges, WFI leaf gating, PLL lock/loss +recovery, or target-local five-cycle reset sequences. A full Tiny Verilator firmware +run was started but did not produce a terminal `TEST_STATUS`/`SIM_TEST_PASS` +verdict and was stopped; no physical, STA or netlist qualification is claimed. +The remaining P7 clock/reset implementation and strict terminal regressions +require a follow-up correction before human phase approval. + ## TINY-R2-P6 implementation complete, human review pending (2026-10-12) The maintainer approved the P6 preflight on diff --git a/physical/commercial/README.md b/physical/commercial/README.md index ffa77929..09c765f8 100644 --- a/physical/commercial/README.md +++ b/physical/commercial/README.md @@ -158,9 +158,12 @@ false-paths the canonical reset ports. Product overlays Qualified I/O covers JTAG, DVP (GPIO10-20), ULPI, SDRAM, SDIO, XPI, and asynchronous pads (UART0/1, remaining GPIO); a reviewed `TIMING_IO_MODE_HOOK` is mandatory in qualified mode. -- Tiny (`tiny.tcl`): SAFE24 boot — `clk_system` (external 24 MHz), - `clk_jtag`, and P6's independent `clk_audio` (GPIO17, 18.432 MHz). - The PLL_TOP macro is parked (EN=0), is not a clock source, +- Tiny (`tiny.tcl`): P7 SAFE24 boot — `clk_aon` (external 24 MHz), derived + `clk_system`, `clk_memory`, `clk_peripheral`, `clk_jtag`, and the independent + `clk_audio` (GPIO17, 18.432 MHz). Explicit case analysis selects REF24 in all + four RCU muxes and parks PLL_TOP (EN=0) for this scenario. The live design + supports software transitions, but active PLL timing is not modeled here. + In this scenario the PLL is not a clock source, and has no characterized timing arcs yet (see `docs/ip/tiny-soc.md` TINY-055); its DB/LEF/GDS/CDL views are still required for link, placement, and LVS. The legacy I/O template covers JTAG, XPI, and asynchronous pads; diff --git a/rtl/tiny/README.md b/rtl/tiny/README.md index 55b9239f..9cdf0d8d 100644 --- a/rtl/tiny/README.md +++ b/rtl/tiny/README.md @@ -7,7 +7,10 @@ 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. +validation of the historical SAFE24 platform. The +[R2-P7 runbook](../../docs/ip/tiny-soc-r2-clock-reset.md) describes the integrated +RCU, runtime domains and separate acceptance evidence. Backend frequency tests +alone are not full-SoC or physical qualification. The [R2-P4 implementation](../../docs/ip/tiny-soc-r2-local-memory.md) connects the official dual-port CPU to four independently serviced 32 KiB local SRAM groups.