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