From 4e6ab1a6fb445521703f0bfa412d3a1e8a38cd69 Mon Sep 17 00:00:00 2001 From: maksyuki Date: Sun, 11 Oct 2026 22:55:39 +0800 Subject: [PATCH] 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"])