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:
maksyuki committed 2026-10-06 14:28:52 +08:00
1 parent f06c039e79
commit b5cbc89928
24 files changed
+1209 -66

No files matched your search

+156
View File
@@ -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.
+152 -4
View File
@@ -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"
+4 -2
View File
@@ -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"])
+4 -2
View File
@@ -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),
+6 -2
View File
@@ -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 -4
View File
@@ -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),
+1
View File
@@ -437,6 +437,7 @@
"tiny_archinfo.sv",
"tiny_axi42apb4.sv",
"tiny_axi4_fabric.sv",
"tiny_reset_tree.sv",
"tiny_sysctrl.sv"
]
},
+8
View File
@@ -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
+1
View File
@@ -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
+22 -3
View File
@@ -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",
+52 -39
View File
@@ -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),
+50
View File
@@ -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
+5 -2
View File
@@ -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]
+241
View File
@@ -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())
+9 -5
View File
@@ -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");
+20
View File
@@ -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
+115
View File
@@ -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
+107
View File
@@ -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
+4 -1
View File
@@ -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),
+21 -1
View File
@@ -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]"),
+3 -1
View File
@@ -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
+5
View File
@@ -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
+124
View File
@@ -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