mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
feat: refactor tests for SRAM and CPU in Tiny R2 P4 flow
- Updated SRAM instance paths in to reflect new hierarchy. - Enhanced to include additional source files and updated parameterized tests for SRAM configurations. - Modified to align with new SRAM structure and added a test for stale macro hierarchy. - Introduced to validate dual-port local-memory protocol and CPU debug/reset functionality, including comprehensive simulation checks.
This commit is contained in:
47 files changed
+3627
-163
No files matched your search
@@ -34,3 +34,15 @@ rs_mcu_unmapped_probe:
|
|||||||
.option pop
|
.option pop
|
||||||
ret
|
ret
|
||||||
.size rs_mcu_unmapped_probe, .-rs_mcu_unmapped_probe
|
.size rs_mcu_unmapped_probe, .-rs_mcu_unmapped_probe
|
||||||
|
|
||||||
|
.balign 4
|
||||||
|
.global rs_mcu_execute_probe
|
||||||
|
.type rs_mcu_execute_probe,@function
|
||||||
|
rs_mcu_execute_probe:
|
||||||
|
mv t0, a0
|
||||||
|
.option push
|
||||||
|
.option arch, +zifencei
|
||||||
|
fence.i
|
||||||
|
.option pop
|
||||||
|
jr t0
|
||||||
|
.size rs_mcu_execute_probe, .-rs_mcu_execute_probe
|
||||||
@@ -6,5 +6,6 @@
|
|||||||
uint32_t rs_mcu_compressed_probe(void);
|
uint32_t rs_mcu_compressed_probe(void);
|
||||||
void rs_mcu_atomic_probe(uint32_t *address);
|
void rs_mcu_atomic_probe(uint32_t *address);
|
||||||
void rs_mcu_unmapped_probe(void);
|
void rs_mcu_unmapped_probe(void);
|
||||||
|
uint32_t rs_mcu_execute_probe(uint32_t address);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -92,7 +92,25 @@ static bool rs_mcu_dma(void) {
|
|||||||
}
|
}
|
||||||
config.kind = RS_DMA_KIND_MM_TO_STREAM;
|
config.kind = RS_DMA_KIND_MM_TO_STREAM;
|
||||||
config.request = RS_DMA_REQUEST_I2S_TX;
|
config.request = RS_DMA_REQUEST_I2S_TX;
|
||||||
return rs_dma_config_validate(0U, &config) != RS_OK;
|
if (rs_dma_config_validate(0U, &config) == RS_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/* DMA owns the destination until completion. FENCE.I is executed by the
|
||||||
|
* assembly helper before entering either the DMA-written or patched code. */
|
||||||
|
rs_mcu_source[0] = UINT32_C(0x02a00513); /* addi a0,zero,42 */
|
||||||
|
rs_mcu_source[1] = UINT32_C(0x00008067); /* jalr zero,ra,0 */
|
||||||
|
config.kind = RS_DMA_KIND_MM_TO_MM;
|
||||||
|
config.request = RS_DMA_REQUEST_SOFTWARE;
|
||||||
|
config.byte_count = UINT32_C(8);
|
||||||
|
config.burst_beats = 1U;
|
||||||
|
if ((rs_dma_configure(RS_DMA_CHANNEL_BULK, &config) != RS_OK) ||
|
||||||
|
(rs_dma_start(RS_DMA_CHANNEL_BULK) != RS_OK) ||
|
||||||
|
(rs_dma_wait(RS_DMA_CHANNEL_BULK, RS_TIMEOUT_DEFAULT) != RS_OK) ||
|
||||||
|
(rs_mcu_execute_probe((uint32_t)(uintptr_t)rs_mcu_destination) != UINT32_C(42))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
rs_mcu_destination[0] = UINT32_C(0x02b00513); /* addi a0,zero,43 */
|
||||||
|
return rs_mcu_execute_probe((uint32_t)(uintptr_t)rs_mcu_destination) == UINT32_C(43);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool rs_mcu_interrupts(void) {
|
static bool rs_mcu_interrupts(void) {
|
||||||
|
|||||||
@@ -0,0 +1,387 @@
|
|||||||
|
# TINY-R2-P4 local CPU/SRAM implementation
|
||||||
|
|
||||||
|
## Approved throughput repair (2026-10-10; validated, review pending)
|
||||||
|
|
||||||
|
The maintainer approved the eight-file P4 throughput-repair preflight after
|
||||||
|
the read-only review identified the external frontend's per-beat bubbles.
|
||||||
|
This follow-up remains P4; it does not enable P5 target concurrency.
|
||||||
|
The initial implementation and its measurements below are retained historical
|
||||||
|
evidence for the pre-repair snapshot, not qualification of the changed RTL.
|
||||||
|
Fresh evidence is under
|
||||||
|
`build/ics55-tiny-2026-10-10-15-15-b4aa59f3e5d9/meta/tiny-r2-p4/`.
|
||||||
|
|
||||||
|
The group now reserves two response slots for its external client, counting
|
||||||
|
both queued completions and a macro read still in flight. A response pop may
|
||||||
|
free capacity on the same edge as the next issue. The macro's saved owner/bank
|
||||||
|
still selects the captured read value; FIFO data cannot be overwritten by
|
||||||
|
later local accesses. A same-client pending read prevents a simultaneous write
|
||||||
|
commit into the single-push response FIFO. Local I/D keep one operation each
|
||||||
|
and their original completion pipeline.
|
||||||
|
|
||||||
|
The external frontend separates issued and retired read positions. W uses a
|
||||||
|
two-entry Common FIFO containing captured address, data and byte strobes.
|
||||||
|
Only captured legal beats enter arbitration. Invalid strobes or LAST retain
|
||||||
|
the existing error policy, including earlier committed beats and later legal
|
||||||
|
beats after an invalid strobe. B waits for all queued legal writes and their
|
||||||
|
commit responses. External transaction ownership still lasts through B/RLAST.
|
||||||
|
These are small register queues, not additional user SRAM or new macros.
|
||||||
|
|
||||||
|
Acceptance adds consecutive 16-beat read/write handshakes at one SYS cycle per
|
||||||
|
beat after startup, without local competition or bus backpressure. Logs retain
|
||||||
|
first-beat latency, total transfer time and final-W-to-B delay. Directed checks
|
||||||
|
fill/drain queues, stop READY, interleave local service, retain old read values
|
||||||
|
across local writes, exercise buffered malformed writes and drop stale queued
|
||||||
|
responses on system reset. Induction checks response conservation/capacity in
|
||||||
|
addition to held-response stability and the existing service-count fairness.
|
||||||
|
The unchanged CPU debug/reset, discovery/counters and both real-PDK macro
|
||||||
|
fixtures remain required.
|
||||||
|
|
||||||
|
Use the same immutable P3 flat/banked images and parameters. Each selected
|
||||||
|
Verilator image runs once; banked Icarus runs once. Compare both with their P3
|
||||||
|
samples and the pre-repair P4 report. The repair requires burst-16 cycles below
|
||||||
|
36713/36489 respectively, truthful reporting of all other workload deltas and
|
||||||
|
identical banked results across simulators. It does not promise P3 cycle parity
|
||||||
|
or change compiler/layout settings. Existing shared Mini debug inputs are
|
||||||
|
unchanged by this repair; matching prior compatibility evidence is retained.
|
||||||
|
|
||||||
|
### Repair results and provenance
|
||||||
|
|
||||||
|
The campaign is complete for review. `source.json`, `report.json`, `audit.py`
|
||||||
|
and `evidence.json` in the fresh evidence root bind 1972 technical inputs,
|
||||||
|
six fresh Tiny regression simulations, three matched-image executions,
|
||||||
|
six real/behavioral SRAM fixtures, two CPU fixtures and control induction.
|
||||||
|
The combined ICS55/IHP130 input digest is
|
||||||
|
`58daf68fbb9dee199b3085a73ef07d22eb3de6d00ba875c462e85b76b6091a0d`;
|
||||||
|
the ICS55 replay input digest is
|
||||||
|
`a96a129ffc02d0d38fd2eee7ed8fb62893828db3602bbd4459e0a71ffce0c35f`.
|
||||||
|
Only the approved six technical files differ from the preceding P4 snapshot;
|
||||||
|
the other two changes are this runbook and the ledger. No commit or staging
|
||||||
|
operation is part of this campaign.
|
||||||
|
|
||||||
|
All six SRAM runs record `read_gap_max=1 write_gap_max=1 beats=16`, with first
|
||||||
|
R at three cycles after AR and final R at cycle18; W begins one cycle after
|
||||||
|
AW, final B is at cycle20, four cycles after final W. These are standalone
|
||||||
|
uncontended protocol measurements, not end-to-end DMA or physical timing.
|
||||||
|
All six retain `local_latency_max=3`. The new queue checks cover full-response
|
||||||
|
backpressure, local writes to already captured read addresses, W contention,
|
||||||
|
buffered malformed writes, and both queued-R and accepted-but-uncommitted-W
|
||||||
|
system resets. CPU debug/patch/hart-reset and induction pass in the final suite.
|
||||||
|
|
||||||
|
| Gate | Result under the fresh evidence root | Outcome |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Final Ruff, diff, RTL format/style/readiness | `quality-pass.{json,log}` | Pass |
|
||||||
|
| Full Pytest | `pytest-full.{json,log}` | 1659 passed, one optional `pypdf` skip; 3903.061 s |
|
||||||
|
| Tiny ICS55 PR | `regress-tiny-ics55.{json,log}` | Pass; 3396.035 s |
|
||||||
|
| Tiny IHP130 PR | `regress-tiny-ihp130.{json,log}` | Pass; 2340.150 s |
|
||||||
|
| Verilator, both immutable images once | `replay-verilator.{json,log}` | Pass; 390.698 s |
|
||||||
|
| Icarus, immutable banked image once | `replay-iverilog.{json,log}` | Pass; 5154.223 s |
|
||||||
|
| Cross-simulator report | `report-result.{json,log}`, `report.json` | Complete; all banked observations identical |
|
||||||
|
| Source/artifact audit | `evidence.json`, `audit.py` | Pass; review/phase acceptance pending |
|
||||||
|
|
||||||
|
The initial `quality-final.{json,log}` is a retained failed development attempt:
|
||||||
|
the style gate required `s_req_accept`/`s_resp_accept` abbreviations. The names
|
||||||
|
were corrected before the final source snapshot and all long gates. The first
|
||||||
|
29 focused checks and additional queued-W-reset check also passed; final-suite
|
||||||
|
logs qualify the final technical snapshot. No failed attempt is relabeled.
|
||||||
|
|
||||||
|
| Workload | Flat pre-repair P4 → repaired P4 | Banked pre-repair P4 → repaired P4 |
|
||||||
|
| --- | ---: | ---: |
|
||||||
|
| Empty | 69 → 69 | 66 → 66 |
|
||||||
|
| CPU | 73911 → 73911 | 73914 → 73914 |
|
||||||
|
| CPU copy | 102568 → 102568 | 86214 → 86214 |
|
||||||
|
| DMA burst1 | 91306 → 91306 | 89365 → 89365 |
|
||||||
|
| DMA burst16 | 36713 → 17897 | 36489 → 17317 |
|
||||||
|
| CPU/DMA contention | 303493 → 299881 | 299633 → 299633 |
|
||||||
|
| TCD contention | 303702 → 299878 | 299645 → 299645 |
|
||||||
|
| Chunk256 | 377705 → 361285 | 376970 → 359570 |
|
||||||
|
|
||||||
|
Burst16 cycles decrease 51.25%/52.54%, satisfying the approved repair criterion.
|
||||||
|
They remain 8.34%/4.81% above the original P3 16520/16523 cycles. Burst1 still
|
||||||
|
retains the earlier P4 overhead versus P3; this repair does not claim universal
|
||||||
|
baseline parity. Flat local maximum is four cycles in contended windows, with
|
||||||
|
17697 eligible bank-conflict cycles; banked maximum is three with zero conflicts.
|
||||||
|
Uncontended local latency is still checked independently. Firmware checksums,
|
||||||
|
parameters and each image's compiler/layout identity remain unchanged.
|
||||||
|
|
||||||
|
Fresh physical variants are
|
||||||
|
`build/ics55-tiny-2026-10-10-15-15-42d5beccb726/` and
|
||||||
|
`build/ihp130-tiny-2026-10-10-15-15-bff91e3529ba/`.
|
||||||
|
Both pass synthesis, netlist boot and common-SYS/32-macro binding checks.
|
||||||
|
Their `meta/metrics.json`, `sta/opensta/timing_metrics.rpt`,
|
||||||
|
`sta/opensta/p4-clock-bindings.rpt` and `sta/opensta/retrosoc_sta.log` retain
|
||||||
|
the matching observations:
|
||||||
|
|
||||||
|
| PDK | Cells | Library area | Setup WNS / TNS (ns) | Hold WNS / TNS (ns) |
|
||||||
|
| --- | ---: | ---: | ---: | ---: |
|
||||||
|
| ICS55 | 207029 | 1805809.700799 | -36.88 / -489807.34 | 0 / 0 |
|
||||||
|
| IHP130 | 218773 | 9245382.228597 | -97.37 / -1640251.62 | 0 / 0 |
|
||||||
|
|
||||||
|
Setup remains unclosed; library area is not die area and zero observed hold
|
||||||
|
slack violations are not physical signoff. Lint comparisons to the unchanged
|
||||||
|
baselines report 468/412 new signatures for ICS55/IHP130, zero increased and
|
||||||
|
zero resolved. Relative to pre-repair P4 there are exactly two additional
|
||||||
|
signatures: intentionally unused FIFO `cnt_o`, and `s_full`, which is consumed
|
||||||
|
by the verification assertions but unused in synthesis-oriented lint. No new
|
||||||
|
width/latch warning is attributed to the repair. Warning and metrics policy
|
||||||
|
remain observational and no baseline was edited.
|
||||||
|
|
||||||
|
Commands below used the same timestamp, with `scripts/run_flow.py` retaining
|
||||||
|
outer command/result/log records. Standalone test artifacts use distinct
|
||||||
|
`tests-focused`, `tests-reset` and `tests-full` directories under this variant.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
source .cache/retrosoc/development/tiny-r2-p1/activate.sh
|
||||||
|
export BUILD_TIMESTAMP=2026-10-10-15-15 JOBS=3
|
||||||
|
python3 -m pytest -q tests/test_tiny_r2_p4.py --basetemp=build/ics55-tiny-2026-10-10-15-15-b4aa59f3e5d9/meta/tiny-r2-p4/tests-focused
|
||||||
|
python3 -m pytest -q 'tests/test_tiny_r2_p4.py::test_four_group_local_memory[BEHAV-iverilog]' --basetemp=build/ics55-tiny-2026-10-10-15-15-b4aa59f3e5d9/meta/tiny-r2-p4/tests-reset
|
||||||
|
ruff check .
|
||||||
|
git diff --check
|
||||||
|
make rtl-format-check rtl-style-check-all rtl-readiness-check-all
|
||||||
|
python3 -m pytest -q --basetemp=build/ics55-tiny-2026-10-10-15-15-b4aa59f3e5d9/meta/tiny-r2-p4/tests-full
|
||||||
|
python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55 --dry-run
|
||||||
|
python3 scripts/regress.py --root . --suite nightly --soc TINY --pdk ICS55 --dry-run
|
||||||
|
python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 --dry-run
|
||||||
|
python3 scripts/regress.py --root . --suite nightly --soc TINY --pdk IHP130 --dry-run
|
||||||
|
python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55
|
||||||
|
python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130
|
||||||
|
make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 SIMU=VERILATOR SOC_SIM_TIME=21600 \
|
||||||
|
TINY_P4_REFERENCE=build/ics55-tiny-performance-2026-10-09-17-10-53cc6674d532/meta/tiny-r2-p3/report.json \
|
||||||
|
TINY_P4_CASES=perf-o2-plain-flat,perf-o2-plain-banked tiny-r2-p4-replay
|
||||||
|
make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 SIMU=IVERILOG SOC_SIM_TIME=21600 \
|
||||||
|
TINY_P4_REFERENCE=build/ics55-tiny-performance-2026-10-09-17-10-53cc6674d532/meta/tiny-r2-p3/report.json \
|
||||||
|
TINY_P4_CASES=perf-o2-plain-banked tiny-r2-p4-replay
|
||||||
|
make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 tiny-r2-p4-report
|
||||||
|
make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 APP=ci_smoke SYNTH=YOSYS STA=OPENSTA metrics
|
||||||
|
make CONFIG=configs/ci/ihp130-tiny.mk SOC=TINY PDK=IHP130 APP=ci_smoke SYNTH=YOSYS STA=OPENSTA metrics
|
||||||
|
PYTHONDONTWRITEBYTECODE=1 python3 build/ics55-tiny-2026-10-10-15-15-b4aa59f3e5d9/meta/tiny-r2-p4/audit.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Tiny PR/nightly dry-runs match; no duplicate nightly execution was needed.
|
||||||
|
The six fresh ordinary simulations all require `SIM_TEST_PASS`, including
|
||||||
|
Tiny netlist boot, together with command success and no forbidden error marker.
|
||||||
|
Mini's two prior behavioral results retain their original source attribution;
|
||||||
|
the audit verifies that changed technical inputs are confined to Tiny and its
|
||||||
|
tests. Mini synthesis/STA, the P3 compiler matrix, additional cold repetitions,
|
||||||
|
C format/policy/host reruns, PDF generation, PVT/MMMC, CDC/RDC and place/route
|
||||||
|
were not run for this repair. No C/H code or MISRA deviation changed. The
|
||||||
|
full-suite optional PDF retrieval check remains skipped for missing `pypdf`.
|
||||||
|
No new frequency, physical qualification or P5 authorization is claimed.
|
||||||
|
|
||||||
|
## Original implementation snapshot
|
||||||
|
|
||||||
|
This implements the approved [P4 contract](tiny-soc.md#tiny-r2-p4---dual-port-hazard3-and-four-bank-local-sram)
|
||||||
|
from `4db27524a6282ca40d76b935415b89f043d6ea83`, specification blob
|
||||||
|
`7e19de49d49efaafacccfa85671e314d53dd15a4`. The maintainer approved the preflight
|
||||||
|
and implementation. No P5 execution, dependency update, publication release or
|
||||||
|
physical-frequency qualification is implied. Historical phase evidence remains
|
||||||
|
in the [ledger](tiny-soc-verification.md).
|
||||||
|
|
||||||
|
## Architecture and lifecycle
|
||||||
|
|
||||||
|
Tiny uses locked `hazard3_cpu_2port`, preserving ISA, IRQ, debug, multiply and
|
||||||
|
branch-prediction settings; A remains disabled. Separate I/D AHB frontends decode
|
||||||
|
SRAM before the tagged slow-path merge. One accepted D operation spans all
|
||||||
|
targets. Write data is captured in its AHB data phase; local stores complete
|
||||||
|
after macro commit. Uncontended local completion is at most three SYS cycles.
|
||||||
|
Slow errors retain two-cycle AHB completion. Instruction origin reaches the
|
||||||
|
existing fabric fault path: only NOR boot/NSS0 may be fetched externally, and
|
||||||
|
peripheral fetches cannot trigger register reads.
|
||||||
|
|
||||||
|
Four 32 KiB groups cover `0x30000000..0x3001ffff`, each with eight existing
|
||||||
|
4 KiB macros and I/D/external round robin advancing only on macro issue.
|
||||||
|
Per-client registers capture read responses before macro outputs change.
|
||||||
|
External W waits and R/B backpressure do not lock local macro service.
|
||||||
|
Continuously eligible requests wait for at most two other issues, conditional
|
||||||
|
on progress rather than a wall-clock bound for stopped peers.
|
||||||
|
|
||||||
|
Each external frontend retains one transaction through B/RLAST and alternates
|
||||||
|
read/write preference. A direction-tagged demux preserves the existing external
|
||||||
|
fabric attachment. Whole-product different-target DMA read/write concurrency
|
||||||
|
remains P5; standalone frontend concurrency is not that product qualification.
|
||||||
|
|
||||||
|
Hart reset requests debug halt, prevents new debug instruction injection, drains
|
||||||
|
accepted local/slow operations and responses, then resets the hart/frontends.
|
||||||
|
A terminal-cycle AHB overlap must still drain. SRAM/external frontends and
|
||||||
|
unrelated DMA stay active. System reset invalidates pending responses without
|
||||||
|
clearing SRAM payload. Five-edge release and Mini's default single-port reset
|
||||||
|
behavior are preserved; shared debug only adds a pending-state output.
|
||||||
|
|
||||||
|
## ABI, physical bindings and observations
|
||||||
|
|
||||||
|
Shared SRAM register definitions are unchanged: 128 KiB, `BANK_COUNT=32`,
|
||||||
|
`BANK_BYTES=4096`. Public counters count external AXI events, saturating sums
|
||||||
|
of simultaneous events; stall cycles use OR across groups. Local CPU traffic
|
||||||
|
is not silently added. `R2_AHB` observes the separate I/D ports. `R2_P4_LOCAL`
|
||||||
|
records local completions, latency and address-admission waits; `R2_P4_BANK`
|
||||||
|
records macro issues, eligible-client waits and conflict cycles (two or more
|
||||||
|
eligible clients in a group). These are verification-only observations.
|
||||||
|
|
||||||
|
Macro `g,b` is `u_soc.u_sram.gen_group[g].u_group.gen_bank[b].u_ram.u_mem`,
|
||||||
|
physical index `8*g+b`. IHP130 placement coordinates stay fixed while instance
|
||||||
|
and supply hooks track both indices. CPU and all 32 macros remain directly on
|
||||||
|
SYS24; ICS55 PLL stays off and IHP130 remains no-PLL compatibility. Mapping
|
||||||
|
checks reject stale single-port/flat-bank hierarchies. STA remains observational,
|
||||||
|
with no 96/192/240 MHz or physical signoff claim.
|
||||||
|
|
||||||
|
The Tiny publication extractor and affected draft prose/diagram follow the new
|
||||||
|
bindings. No reviewed publication SHA, delivered PDF or release status advances.
|
||||||
|
|
||||||
|
## Acceptance and matched images
|
||||||
|
|
||||||
|
`tests/test_tiny_r2_p4.py` uses real IHP130/ICS55 macro models and a behavioral
|
||||||
|
model on both simulators: all macro/group boundaries, byte masks, back-to-back
|
||||||
|
AHB, independent groups, held responses, W-before-AW, bursts/malformed writes,
|
||||||
|
discovery/counter semantics and reset payload retention. Deterministic readback
|
||||||
|
uses seed `0x12345678`, 4096 write/read pairs per group. The actual CPU fixture
|
||||||
|
checks mixed 16/32-bit cross-group fetch, FENCE.I, instruction faults, JTAG
|
||||||
|
patch/resume, and reset with a held slow response plus an unrelated held B.
|
||||||
|
Yosys induction proves group control fairness and retained responses with only
|
||||||
|
macro payload abstracted. Normal Tiny firmware additionally executes code
|
||||||
|
copied through the real DMA, then CPU-patched code, after completion/FENCE.I.
|
||||||
|
Its helper is private acceptance assembly, not a new SDK API.
|
||||||
|
|
||||||
|
`tiny-r2-p4-replay` validates retained P3 images/logs, builds a fresh P4 model,
|
||||||
|
and executes each selected image once. Image source/compiler/layout and new
|
||||||
|
RTL/model identities are separate; hardware settings must match. Original P3
|
||||||
|
same-source/three-cold-start rules are unchanged. `tiny-r2-p4-report` revalidates
|
||||||
|
artifacts and requires identical banked-image results across the two simulators.
|
||||||
|
|
||||||
|
The reference is
|
||||||
|
`build/ics55-tiny-performance-2026-10-09-17-10-53cc6674d532/meta/tiny-r2-p3/report.json`.
|
||||||
|
Select `perf-o2-plain-flat` and `perf-o2-plain-banked` for Verilator and the banked
|
||||||
|
image for Icarus. Parameters remain 1024 words, four jobs, 4096 CPU iterations,
|
||||||
|
seed `0x12345678`, burst-1/16 DMA, TCD and chunk-256. Compare each unchanged
|
||||||
|
binary with its own historical samples, not with a different compiler/layout.
|
||||||
|
No fixed speedup is predeclared; latency/fairness requirements remain gates.
|
||||||
|
|
||||||
|
Development evidence begins under
|
||||||
|
`build/ics55-tiny-2026-10-10-10-05-b4aa59f3e5d9/meta/tiny-r2-p4/`.
|
||||||
|
Early fixture failures and corrected runs are retained separately. The final
|
||||||
|
campaign below completes planned functional validation and measurements; human
|
||||||
|
review/phase acceptance remain pending, including the DMA throughput tradeoff.
|
||||||
|
|
||||||
|
## Executed campaign and provenance
|
||||||
|
|
||||||
|
The evidence root above contains `source.json`, `evidence.json`, `audit.py` and
|
||||||
|
`report.json`. The campaign snapshot contains 1972 unchanged technical inputs,
|
||||||
|
base commit `4db27524a6282ca40d76b935415b89f043d6ea83` plus the retained patch and
|
||||||
|
new-file archive, and locked tool/executable/archive identities. Its combined
|
||||||
|
ICS55/IHP130 digest is
|
||||||
|
`9fa311a5500711c99b006eacd95cdff04a74659d52b5b46640c42c49773a7f66`.
|
||||||
|
The ICS55-only replay source set has digest
|
||||||
|
`2f0d285186f84d41422d8381d09b0a01a2afc066dbd011a11ec7a8a85433ad0d`;
|
||||||
|
these are different declared input sets, not conflicting identities.
|
||||||
|
|
||||||
|
The final audit rechecks all captured inputs, eight ordinary regression
|
||||||
|
simulations, the three matched-image executions, six standalone memory runs,
|
||||||
|
two actual-CPU runs and the group-control induction log. Ordinary firmware and
|
||||||
|
model artifacts are hashed post-run; the campaign source hashes were captured
|
||||||
|
before execution and rechecked afterward. Image/model identities and original
|
||||||
|
P3 phase attribution are retained separately.
|
||||||
|
|
||||||
|
| Gate | Retained result under the evidence root | Outcome |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Format/style/readiness, C policy/host, Ruff/diff | `quality-final.{json,log}` | Pass; no new MISRA deviation; partial checks, not MISRA certification |
|
||||||
|
| Full Pytest | `pytest-full.{json,log}` | 1659 passed, one optional `pypdf` environment skip; 3856.630 s |
|
||||||
|
| Tiny ICS55 PR | `regress-tiny-ics55.{json,log}` | Pass; 3295.064 s |
|
||||||
|
| Tiny IHP130 PR | `regress-tiny-ihp130.{json,log}` | Pass; 2297.373 s |
|
||||||
|
| Mini ICS55 behavioral-only PR | `regress-mini-ics55.{json,log}` | Pass; 3093.379 s; no Mini physical evidence |
|
||||||
|
| P4 Verilator replay | `replay-verilator.{json,log}` | Both selected images once; 357.537 s including build/audit |
|
||||||
|
| P4 Icarus replay | `replay-iverilog.json`, `replay-iverilog.json.log` | Banked image once; 5214.736 s including build/audit |
|
||||||
|
| Matched-image aggregation | `report-result.json`, `report.log`, `report.json` | `measurement_complete`; all banked samples match across simulators |
|
||||||
|
|
||||||
|
The six standalone memory logs report `local_latency_max=3`, with real
|
||||||
|
simultaneous group issues. Those protocol fixtures use a 10 ns verification
|
||||||
|
clock; product replays and timing checks use SAFE24. The banked product replay
|
||||||
|
also observes three-cycle maximum local latency and zero eligible bank-conflict
|
||||||
|
cycles. No physical frequency qualification follows from functional models.
|
||||||
|
|
||||||
|
Commands were run through `scripts/run_flow.py` for retained logs/results:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
source .cache/retrosoc/development/tiny-r2-p1/activate.sh
|
||||||
|
export BUILD_TIMESTAMP=2026-10-10-10-05
|
||||||
|
export JOBS=3
|
||||||
|
make rtl-format-check rtl-style-check-all rtl-readiness-check-all
|
||||||
|
make sw-format-check sw-policy-check sw-host-test
|
||||||
|
ruff check .
|
||||||
|
git diff --check
|
||||||
|
python3 -m pytest -q --basetemp=build/ics55-tiny-2026-10-10-10-05-b4aa59f3e5d9/meta/tiny-r2-p4/tests-full
|
||||||
|
python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55 --dry-run
|
||||||
|
python3 scripts/regress.py --root . --suite nightly --soc TINY --pdk ICS55 --dry-run
|
||||||
|
python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 --dry-run
|
||||||
|
python3 scripts/regress.py --root . --suite pr --soc MINI --pdk ICS55 --behavioral-only --dry-run
|
||||||
|
python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55
|
||||||
|
python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130
|
||||||
|
JAVA_TOOL_OPTIONS=-Dsbt.server.forcestart=true python3 scripts/regress.py \
|
||||||
|
--root . --suite pr --soc MINI --pdk ICS55 --behavioral-only
|
||||||
|
make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 SIMU=VERILATOR SOC_SIM_TIME=21600 \
|
||||||
|
TINY_P4_REFERENCE=build/ics55-tiny-performance-2026-10-09-17-10-53cc6674d532/meta/tiny-r2-p3/report.json \
|
||||||
|
TINY_P4_CASES=perf-o2-plain-flat,perf-o2-plain-banked tiny-r2-p4-replay
|
||||||
|
make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 SIMU=IVERILOG SOC_SIM_TIME=21600 \
|
||||||
|
TINY_P4_REFERENCE=build/ics55-tiny-performance-2026-10-09-17-10-53cc6674d532/meta/tiny-r2-p3/report.json \
|
||||||
|
TINY_P4_CASES=perf-o2-plain-banked tiny-r2-p4-replay
|
||||||
|
make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 tiny-r2-p4-report
|
||||||
|
make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 APP=ci_smoke SYNTH=YOSYS STA=OPENSTA metrics
|
||||||
|
make CONFIG=configs/ci/ihp130-tiny.mk SOC=TINY PDK=IHP130 APP=ci_smoke SYNTH=YOSYS STA=OPENSTA metrics
|
||||||
|
```
|
||||||
|
|
||||||
|
Tiny PR and nightly expand to the same commands; nightly was not duplicated.
|
||||||
|
Each simulation requires command success, its configured terminal marker and
|
||||||
|
no forbidden-error marker. All selected ordinary targets require SIM_TEST_PASS,
|
||||||
|
including Tiny netlist boot. UART text alone is insufficient. Development
|
||||||
|
attempts are not counted as extra qualifying cold starts.
|
||||||
|
|
||||||
|
## Measurements and remaining qualification
|
||||||
|
|
||||||
|
Raw cycles below compare each immutable binary with its own P3 baseline.
|
||||||
|
The report also retains retired instructions, CPI inputs, empty calibration,
|
||||||
|
local waits, macro issues/conflicts and external traffic accounting.
|
||||||
|
|
||||||
|
| Workload | Flat P3 → P4 cycles | Banked P3 → P4 cycles |
|
||||||
|
| --- | ---: | ---: |
|
||||||
|
| CPU | 73951 → 73911 | 73954 → 73914 |
|
||||||
|
| CPU copy | 127157 → 102568 | 114875 → 86214 |
|
||||||
|
| DMA burst 1 | 64577 → 91306 | 64583 → 89365 |
|
||||||
|
| DMA burst 16 | 16520 → 36713 | 16523 → 36489 |
|
||||||
|
| CPU/DMA contention | 310368 → 303493 | 310383 → 299633 |
|
||||||
|
| TCD contention | 310665 → 303702 | 310683 → 299645 |
|
||||||
|
| Chunk 256 | 373812 → 377705 | 374016 → 376970 |
|
||||||
|
|
||||||
|
CPU-copy cycles decrease by about 19%/25%, while isolated burst-16 DMA takes
|
||||||
|
about 2.2 times the baseline cycles. The current external frontend processes
|
||||||
|
issue, completion and bus response separately for each beat; preserving local
|
||||||
|
response isolation costs streaming throughput. This regression is retained for
|
||||||
|
review, not hidden by the CPU-copy result or a changed compiler/layout. P5's
|
||||||
|
external target concurrency alone does not remove these per-beat waits. No
|
||||||
|
fixed speedup was an acceptance gate, but the tradeoff needs explicit assessment
|
||||||
|
before phase acceptance or a throughput claim.
|
||||||
|
|
||||||
|
Actual synthesis/STA variants are
|
||||||
|
`build/ics55-tiny-2026-10-10-10-05-42d5beccb726/` and
|
||||||
|
`build/ihp130-tiny-2026-10-10-10-05-bff91e3529ba/`.
|
||||||
|
Both emit `TINY_R2_P4_BINDING_PASS` and pass netlist boot. Their raw reports are
|
||||||
|
`sta/opensta/timing_metrics.rpt`, `p4-clock-bindings.rpt`, `retrosoc_sta.log` and
|
||||||
|
`meta/metrics.json`.
|
||||||
|
|
||||||
|
| PDK | Cells | Reported library area | Max WNS / TNS (ns) | Min WNS / TNS (ns) |
|
||||||
|
| --- | ---: | ---: | ---: | ---: |
|
||||||
|
| ICS55 | 206311 | 1803352.980799 | -40.55 / -518700.31 | -0.02 / -11.85 |
|
||||||
|
| IHP130 | 216216 | 9218095.882797 | -97.46 / -1626503.88 | -0.17 / -142.67 |
|
||||||
|
|
||||||
|
Timing is not closed. Library area is not placed/routed die area. Lint baseline
|
||||||
|
comparisons remain non-blocking observations: Tiny ICS55 466 new signatures,
|
||||||
|
Tiny IHP130 410 new, Mini ICS55 1537 new / 118 resolved, all with zero increased.
|
||||||
|
Compared with the preceding P3 lint runs, newly observed signatures are intentional
|
||||||
|
empty named output connections: Tiny's unused master IDs, exclusive/power/fence
|
||||||
|
outputs, plus Mini's ignored `reset_pending_o`. Tiny is cacheless with committed
|
||||||
|
stores, no A/SBA and no Xh3Power, so these outputs do not add interfaces or clock
|
||||||
|
gating. Baselines and metric policy were not edited. The logs retain the known
|
||||||
|
generated-file clock skew and successful ABC Liberty fallback observations.
|
||||||
|
|
||||||
|
The P3 eight-configuration matrix and three-cold-start campaign were not rerun.
|
||||||
|
No place/route, extracted/PVT/CDC/RDC signoff, high-rate qualification, new PDK
|
||||||
|
rollout or PDF build/visual check was performed. Publication snapshot enforcement
|
||||||
|
remains enabled; the reviewed PDF source revision was not advanced. Mini
|
||||||
|
DMA-to-WS2812 stays unsupported under option A, and product DMA/IRQ budgets
|
||||||
|
remain for a supported integration. P5 and later features are not implemented.
|
||||||
@@ -1,5 +1,69 @@
|
|||||||
# Tiny MCU Verification Record
|
# Tiny MCU Verification Record
|
||||||
|
|
||||||
|
## TINY-R2-P4 throughput repair; validated, review pending (2026-10-10)
|
||||||
|
|
||||||
|
Following the read-only P4 review, the maintainer approved the bounded-buffer
|
||||||
|
repair plan for the P2 external-SRAM throughput regression. The changed Tiny
|
||||||
|
group/frontend keep local I/D single-operation semantics, reserve external
|
||||||
|
response capacity and overlap burst issuance/retirement; W is captured before
|
||||||
|
macro arbitration and B follows complete commit-response drain. Common FIFO
|
||||||
|
and register dependencies, ABI, clock/macro bindings and the pre-P5 external
|
||||||
|
attachment remain unchanged. No shared Mini debug code is changed by this fix.
|
||||||
|
|
||||||
|
Fresh evidence belongs under
|
||||||
|
`build/ics55-tiny-2026-10-10-15-15-b4aa59f3e5d9/meta/tiny-r2-p4/`.
|
||||||
|
The preceding P4 report below remains the pre-repair comparison, and does not
|
||||||
|
qualify these new changes. The final audit rechecks 1972 technical inputs,
|
||||||
|
six fresh regression simulations and three immutable-image executions. Full
|
||||||
|
Pytest reports 1659 passed and one optional `pypdf` skip. Both Tiny PDK PR
|
||||||
|
regressions pass, including synthesis, netlist boot and clock/macro bindings.
|
||||||
|
Streaming/backpressure/error/reset checks and response conservation/fairness
|
||||||
|
induction pass; all six SRAM fixtures retain three-cycle local completion and
|
||||||
|
one-cycle steady read/write beat spacing. Initial style failures and focused
|
||||||
|
development attempts remain separate from final qualifying results.
|
||||||
|
|
||||||
|
Matched burst16 DMA cycles fall from 36713 to 17897 (flat) and 36489 to 17317
|
||||||
|
(banked), reductions of 51.25% and 52.54%. All banked observations match across
|
||||||
|
Verilator and Icarus. CPU/copy/burst1 cycles remain unchanged from pre-repair
|
||||||
|
P4; burst1 overhead and 8.34%/4.81% burst16 overhead versus original P3 remain
|
||||||
|
explicit. The combined input digest is
|
||||||
|
`58daf68fbb9dee199b3085a73ef07d22eb3de6d00ba875c462e85b76b6091a0d`.
|
||||||
|
`evidence.json` binds the source, artifacts, comparison and reused unchanged
|
||||||
|
Mini behavioral evidence. All long gates ran once; Tiny nightly matched PR
|
||||||
|
and was not duplicated. Setup WNS remains negative (-36.88 ns ICS55,
|
||||||
|
-97.37 ns IHP130), with warning/metrics observations and baselines preserved.
|
||||||
|
The [runbook](tiny-soc-r2-local-memory.md) records exact commands, measurements
|
||||||
|
and unrun qualifications. Human review remains required; no P5 execution or
|
||||||
|
physical-frequency qualification is implied.
|
||||||
|
|
||||||
|
## TINY-R2-P4 implementation and validation; review pending (2026-10-10)
|
||||||
|
|
||||||
|
The maintainer approved P4 preflight and implementation on clean source
|
||||||
|
`4db27524a6282ca40d76b935415b89f043d6ea83`, specification blob
|
||||||
|
`7e19de49d49efaafacccfa85671e314d53dd15a4`. The [P4 runbook](tiny-soc-r2-local-memory.md)
|
||||||
|
records architecture, lifecycle, physical binding and matched-image boundaries.
|
||||||
|
P3 results retain their original source attribution; no historical phase or
|
||||||
|
physical qualification is automatically promoted.
|
||||||
|
|
||||||
|
Development evidence is under
|
||||||
|
`build/ics55-tiny-2026-10-10-10-05-b4aa59f3e5d9/meta/tiny-r2-p4/`.
|
||||||
|
All planned functional gates completed: 1659 Pytest passes and one optional
|
||||||
|
`pypdf` skip; Tiny ICS55/IHP130 PR and Mini ICS55 behavioral compatibility pass.
|
||||||
|
Both Tiny netlists pass boot and the four-group/32-macro/common-SYS binding
|
||||||
|
checks. Local uncontended latency is three cycles; group-control induction
|
||||||
|
passes. Setup and hold timing remain negative and observational.
|
||||||
|
|
||||||
|
`report.json` retains single-execution matched-binary results; the banked image
|
||||||
|
matches exactly across Verilator and Icarus. CPU-copy cycles improve about
|
||||||
|
19%/25% for flat/banked images, while isolated burst-16 DMA cycles increase
|
||||||
|
about 2.2 times. This external-frontend throughput regression requires review;
|
||||||
|
no blanket speedup or automatic phase acceptance is claimed. `evidence.json`
|
||||||
|
rechecks 1972 source inputs and binds the gate, image, model, log and report
|
||||||
|
artifacts. Exact commands, measurements and unrun qualifications are in the runbook.
|
||||||
|
|
||||||
|
External fabric stays serialized until P5, which is not authorized. Default
|
||||||
|
Tiny stays ICS55 SAFE24 with PLL off; IHP130 is explicit compatibility.
|
||||||
|
|
||||||
## TINY-R2-P3 implementation in progress (2026-10-08–10)
|
## TINY-R2-P3 implementation in progress (2026-10-08–10)
|
||||||
|
|
||||||
The retained source-bound compiler/layout matrix is complete for commit
|
The retained source-bound compiler/layout matrix is complete for commit
|
||||||
@@ -493,7 +557,7 @@ not a prerequisite to the architectural improvements in R2-P3 through R2-P5.
|
|||||||
| TINY-R2-P1 - Reproducible Baseline, Constraints and Measurements | Reviewed source/profile/lock/tool identity; current 24 MHz functional and workload baseline; clocks, reset endpoints, constraints and timing-exception audit; counters and comparison methodology. Historical WNS is a risk reference, not a refreshed measurement. | Functional/measurement baseline accepted by explicit P2 approval on 2026-10-06; timing, warning and physical gaps retained |
|
| TINY-R2-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-P2 - Reset Distribution and CPU/SRAM Clock Feasibility | Reset distribution and payload-reset semantics; locked IHP130 main-SRAM macro timing, candidate common CPU/SRAM periods and representative paths; early clock/reset feasibility with explicit gaps. No final routed/PVT pass is implied. | Implementation and evidence ready for review; all analyzed rates retain timing failures; phase acceptance pending human review |
|
||||||
| TINY-R2-P3 - Software and DMA Scheduling | Compiler and placement A/B results, finite DMA ownership, burst/chunk scheduling and available-path correctness; approved option A retains product DMA/IRQ budget qualification for the platform that supplies the route. | Matrix retained on `3e46cc8`; subsequent two P1 HAL fixes and focused/compatibility checks complete in the worktree; human review/acceptance pending |
|
| TINY-R2-P3 - Software and DMA Scheduling | Compiler and placement A/B results, finite DMA ownership, burst/chunk scheduling and available-path correctness; approved option A retains product DMA/IRQ budget qualification for the platform that supplies the route. | Matrix retained on `3e46cc8`; subsequent two P1 HAL fixes and focused/compatibility checks complete in the worktree; human review/acceptance pending |
|
||||||
| TINY-R2-P4 - Dual-Port Hazard3 and Four-Bank Local SRAM | Actual separate I/D paths, four independent 32 KiB bank frontends, SYS-clocked physical main-SRAM macros, local latency/fairness, FENCE.I, debug/reset and early inventory/SDC/macro-binding updates. | Pending |
|
| TINY-R2-P4 - Dual-Port Hazard3 and Four-Bank Local SRAM | Actual separate I/D paths, four independent 32 KiB bank frontends, SYS-clocked physical main-SRAM macros, local latency/fairness, FENCE.I, debug/reset and early inventory/SDC/macro-binding updates. | Implementation and planned functional validation complete on `4db2752` plus retained worktree snapshot; matched-image DMA throughput regression and human phase acceptance pending review; timing remains observational |
|
||||||
| TINY-R2-P5 - Per-Target Concurrent Fabric | Cross-target concurrency, one combined transaction per target, central-DMA read/write overlap, AW/W binding, errors, simultaneous faults/counters and accepted-transfer drain. | Pending |
|
| TINY-R2-P5 - Per-Target Concurrent Fabric | Cross-target concurrency, one combined transaction per target, central-DMA read/write overlap, AW/W binding, errors, simultaneous faults/counters and accepted-transfer drain. | Pending |
|
||||||
| TINY-R2-P6 - Shared IP and Eight-Channel DMA Integration | Legacy P7 obligations; shared PWM reporting, I2S slave and alternate-input-route prerequisites; common ABI/HAL parity; Crypto banks/lifecycle; truthful capabilities and affected Mini compatibility. | Pending |
|
| TINY-R2-P6 - Shared IP and Eight-Channel DMA Integration | Legacy P7 obligations; shared PWM reporting, I2S slave and alternate-input-route prerequisites; common ABI/HAL parity; Crypto banks/lifecycle; truthful capabilities and affected Mini compatibility. | Pending |
|
||||||
| TINY-R2-P7 - Tiny RCU and Dual-Mode Clock/Reset Integration | Legacy P8 obligations with main SRAM in SYS and XPI in MEM; atomic rate changes, all actual CDC/reset barriers, clock failures, WFI leaf gating and reserved DVP controls. | Pending |
|
| TINY-R2-P7 - Tiny RCU and Dual-Mode Clock/Reset Integration | Legacy P8 obligations with main SRAM in SYS and XPI in MEM; atomic rate changes, all actual CDC/reset barriers, clock failures, WFI leaf gating and reserved DVP controls. | Pending |
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ SIDE_ORDER = ("south", "east", "north", "west")
|
|||||||
EXPECTED_SIGNAL_PADS = 52
|
EXPECTED_SIGNAL_PADS = 52
|
||||||
CLOCK_PORTS = ("extclk_i_pad", "jtag_tck_i_pad")
|
CLOCK_PORTS = ("extclk_i_pad", "jtag_tck_i_pad")
|
||||||
SRAM_MASTER = "RM_IHPSG13_1P_1024x32_c2_bm_bist"
|
SRAM_MASTER = "RM_IHPSG13_1P_1024x32_c2_bm_bist"
|
||||||
SRAM_INSTANCE = "u_soc.u_sram.gen_memory.gen_bank[{index}].u_ram.u_mem"
|
SRAM_INSTANCE = "u_soc.u_sram.gen_group[{group}].u_group.gen_bank[{bank}].u_ram.u_mem"
|
||||||
SRAM_COLUMNS = 8
|
SRAM_COLUMNS = 8
|
||||||
SRAM_ORIGIN = (400, 600)
|
SRAM_ORIGIN = (400, 600)
|
||||||
# Detailed routing requires an M2 track inside each bank's 0.26 um bottom-edge
|
# Detailed routing requires an M2 track inside each bank's 0.26 um bottom-edge
|
||||||
@@ -95,7 +95,7 @@ def sram_instances(capacity_kib: int) -> dict[str, tuple[list[int], str]]:
|
|||||||
SRAM_ORIGIN[0] + column * SRAM_PITCH[0],
|
SRAM_ORIGIN[0] + column * SRAM_PITCH[0],
|
||||||
SRAM_ORIGIN[1] + row * SRAM_PITCH[1],
|
SRAM_ORIGIN[1] + row * SRAM_PITCH[1],
|
||||||
]
|
]
|
||||||
instances[SRAM_INSTANCE.format(index=index)] = (location, "N")
|
instances[SRAM_INSTANCE.format(group=index // 8, bank=index % 8)] = (location, "N")
|
||||||
return instances
|
return instances
|
||||||
|
|
||||||
|
|
||||||
@@ -122,18 +122,9 @@ def macro_config(instances: dict[str, tuple[list[int], str]], header: str) -> di
|
|||||||
def macro_hooks(instances: dict[str, tuple[list[int], str]]) -> list[str]:
|
def macro_hooks(instances: dict[str, tuple[list[int], str]]) -> list[str]:
|
||||||
hooks: list[str] = []
|
hooks: list[str] = []
|
||||||
for instance in instances:
|
for instance in instances:
|
||||||
bracket_start = instance.find("[")
|
# OpenDB and synthesis may escape array brackets differently. Handle
|
||||||
if bracket_start >= 0:
|
# every index (group and bank), retaining the exact non-index hierarchy.
|
||||||
bracket_end = instance.index("]", bracket_start)
|
instance_pattern = ".*".join(re.escape(part) for part in re.split(r"[\[\]]", instance))
|
||||||
instance_pattern = (
|
|
||||||
re.escape(instance[:bracket_start])
|
|
||||||
+ ".*"
|
|
||||||
+ instance[bracket_start + 1 : bracket_end]
|
|
||||||
+ ".*"
|
|
||||||
+ re.escape(instance[bracket_end + 1 :])
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
instance_pattern = re.escape(instance)
|
|
||||||
hooks.extend(
|
hooks.extend(
|
||||||
[
|
[
|
||||||
f"{instance_pattern} VDD VSS VDDARRAY! VSS!",
|
f"{instance_pattern} VDD VSS VDDARRAY! VSS!",
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ if {[info exists ::env(OPENSTA_TOP)] && $::env(OPENSTA_TOP) ne ""} {
|
|||||||
}
|
}
|
||||||
link_design $top_design
|
link_design $top_design
|
||||||
read_sdc $::env(OPENSTA_SDC)
|
read_sdc $::env(OPENSTA_SDC)
|
||||||
|
if {$top_design eq "retrosoc_tiny_asic"} {
|
||||||
|
source [file join [file dirname [info script]] tiny_r2_p4_checks.tcl]
|
||||||
|
}
|
||||||
if {[info exists ::env(OPENSTA_TINY_ICS55)] && $::env(OPENSTA_TINY_ICS55) eq "YES"} {
|
if {[info exists ::env(OPENSTA_TINY_ICS55)] && $::env(OPENSTA_TINY_ICS55) eq "YES"} {
|
||||||
source [file join [file dirname [info script]] tiny_ics55_checks.tcl]
|
source [file join [file dirname [info script]] tiny_ics55_checks.tcl]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# P4 structural timing prerequisites, common to both supported Tiny PDKs.
|
||||||
|
# Negative timing remains observational; missing/incorrect clock objects do not.
|
||||||
|
set p4_dir [file dirname $::env(OPENSTA_REPORT)]
|
||||||
|
set p4_ram [get_cells -hierarchical -filter {ref_name == RM_IHPSG13_1P_1024x32_c2_bm_bist}]
|
||||||
|
set p4_clock_pin A_CLK
|
||||||
|
if {[llength $p4_ram] == 0} {
|
||||||
|
set p4_ram [get_cells -hierarchical -filter {ref_name == ics55_ecos_sram_1024x32_m8}]
|
||||||
|
set p4_clock_pin CLK
|
||||||
|
}
|
||||||
|
if {[llength $p4_ram] != 32} { error "P4 requires 32 main SRAM macros" }
|
||||||
|
set p4_expected {}
|
||||||
|
for {set group 0} {$group < 4} {incr group} {
|
||||||
|
for {set bank 0} {$bank < 8} {incr bank} {
|
||||||
|
lappend p4_expected [format {u_soc.u_sram.gen_group[%d].u_group.gen_bank[%d].u_ram.u_mem} $group $bank]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set p4_seen {}
|
||||||
|
foreach cell $p4_ram {
|
||||||
|
set name [string map {/ .} [get_full_name $cell]]
|
||||||
|
foreach expected $p4_expected {
|
||||||
|
if {[string equal [string range $name end-[expr {[string length $expected]-1}] end] $expected]} {
|
||||||
|
lappend p4_seen $expected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if {[lsort $p4_seen] ne [lsort $p4_expected]} { error "P4 macro group hierarchy mismatch" }
|
||||||
|
set p4_source [get_pins -quiet {u_clock_buffer/clk_o}]
|
||||||
|
if {[llength $p4_source] != 1} { error "P4 SYS source missing" }
|
||||||
|
set p4_direct {}
|
||||||
|
foreach pin [get_fanout -from $p4_source -flat -pin_levels 1 -trace_arcs all] {
|
||||||
|
dict set p4_direct [get_full_name $pin] 1
|
||||||
|
}
|
||||||
|
set p4_clock_report [open $p4_dir/p4-clock-bindings.rpt w]
|
||||||
|
foreach cell $p4_ram {
|
||||||
|
set clocks {}
|
||||||
|
foreach pin [get_pins -of_objects $cell] {
|
||||||
|
if {[string match "*/$p4_clock_pin" [get_full_name $pin]]} { lappend clocks $pin }
|
||||||
|
}
|
||||||
|
if {[llength $clocks] != 1} { error "P4 macro clock pin missing" }
|
||||||
|
set name [get_full_name [lindex $clocks 0]]
|
||||||
|
if {![dict exists $p4_direct $name]} { error "P4 SRAM clock is not direct SYS: $name" }
|
||||||
|
puts $p4_clock_report $name
|
||||||
|
}
|
||||||
|
set p4_cpu [get_pins -hierarchical -quiet {*u_hazard3_cpu_2port*/CLK *u_hazard3_cpu_2port*/CK *u_hazard3_cpu_2port*/CKN}]
|
||||||
|
set p4_front [get_pins -hierarchical -quiet {*u_cpu_mem*/CLK *u_cpu_mem*/CK *u_cpu_mem*/CKN}]
|
||||||
|
if {[llength $p4_cpu] == 0 || [llength $p4_front] == 0} { error "P4 CPU/frontend clock endpoints missing" }
|
||||||
|
foreach pin [concat $p4_cpu $p4_front] {
|
||||||
|
set name [get_full_name $pin]
|
||||||
|
if {![dict exists $p4_direct $name]} { error "P4 CPU/frontend does not share SYS: $name" }
|
||||||
|
}
|
||||||
|
puts $p4_clock_report "CPU clock endpoints: [llength $p4_cpu]"
|
||||||
|
puts $p4_clock_report "Frontend clock endpoints: [llength $p4_front]"
|
||||||
|
close $p4_clock_report
|
||||||
|
puts "TINY_R2_P4_BINDING_PASS: four groups, 32 macros, common CPU/SRAM SYS; not timing qualification"
|
||||||
@@ -11,7 +11,7 @@ All **39 frozen entries / 14 IP chapters** are reviewed below. Register, address
|
|||||||
| 3 | Configuration and Scope / 2 | Covered. IHP130, 128 KiB, 24 MHz target, four DMA channels; hardware RV32IMC versus default RV32IM firmware. | [ihp130-tiny.mk](../../../configs/ci/ihp130-tiny.mk), [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv) | Use the committed profile and identity; keep deferred interfaces and physical claims excluded. |
|
| 3 | Configuration and Scope / 2 | Covered. IHP130, 128 KiB, 24 MHz target, four DMA channels; hardware RV32IMC versus default RV32IM firmware. | [ihp130-tiny.mk](../../../configs/ci/ihp130-tiny.mk), [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv) | Use the committed profile and identity; keep deferred interfaces and physical claims excluded. |
|
||||||
| 4 | Reading and Evidence Conventions / 2 | Covered. Product-specific organization, navigation or revision control; no additional hardware capability. | [datasheet.typ](../../../publications/datasheets/tiny/datasheet.typ), [structure-contract.json](../../../publications/datasheets/tiny/structure-contract.json) | Retain the declared scope, complete source links and independent Tiny identity when updating this section. |
|
| 4 | Reading and Evidence Conventions / 2 | Covered. Product-specific organization, navigation or revision control; no additional hardware capability. | [datasheet.typ](../../../publications/datasheets/tiny/datasheet.typ), [structure-contract.json](../../../publications/datasheets/tiny/structure-contract.json) | Retain the declared scope, complete source links and independent Tiny identity when updating this section. |
|
||||||
| 5 | System Architecture / 17 | Covered. Product-specific organization, navigation or revision control; no additional hardware capability. | [datasheet.typ](../../../publications/datasheets/tiny/datasheet.typ), [structure-contract.json](../../../publications/datasheets/tiny/structure-contract.json) | Retain the declared scope, complete source links and independent Tiny identity when updating this section. |
|
| 5 | System Architecture / 17 | Covered. Product-specific organization, navigation or revision control; no additional hardware capability. | [datasheet.typ](../../../publications/datasheets/tiny/datasheet.typ), [structure-contract.json](../../../publications/datasheets/tiny/structure-contract.json) | Retain the declared scope, complete source links and independent Tiny identity when updating this section. |
|
||||||
| 6 | Processor and Integration / 17 | Covered. One RV32IMC MCU hart, A disabled, direct AHB-Lite/AXI32 adapter and debug reset boundary. | [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv), [mgmt_core_wrapper.sv](../../../rtl/ip/core/mgmt_core_wrapper.sv) | Retain actual wrapper parameters; do not infer firmware ISA flags from the hardware C extension. |
|
| 6 | Processor and Integration / 17 | Working-source P4 update: dual-port RV32IMC hart, A disabled, independent local I/D and tagged slow adapter. Page number belongs to the earlier delivered PDF. | [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv), [tiny_cpu_wrapper.sv](../../../rtl/tiny/top/tiny_cpu_wrapper.sv) | Retain actual wrapper parameters; revalidate publication pages separately before delivery. |
|
||||||
| 7 | Clock, Reset and Operating State / 18 | Covered. System and asynchronous JTAG domains; RTC/WDG share the system clock. | [clock_reset_domains.json](../../../rtl/tiny/integration/clock_reset_domains.json), [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv) | Obtain CDC/RDC and clock/power evidence separately; do not claim independent sleep timing. |
|
| 7 | Clock, Reset and Operating State / 18 | Covered. System and asynchronous JTAG domains; RTC/WDG share the system clock. | [clock_reset_domains.json](../../../rtl/tiny/integration/clock_reset_domains.json), [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv) | Obtain CDC/RDC and clock/power evidence separately; do not claim independent sleep timing. |
|
||||||
| 8 | Bus Transactions, Arbitration and Errors / 19 | Covered. One globally active transaction, round-robin acceptance, saved ownership, bounded legal bursts and error/drain behavior. | [tiny_axi4_fabric.sv](../../../rtl/tiny/top/tiny_axi4_fabric.sv), [axi4_error_slave.sv](../../../rtl/ip/interconnect/axi4_error_slave.sv) | Keep local IP capability separate from end-to-end fabric admission; no unlimited liveness guarantee. |
|
| 8 | Bus Transactions, Arbitration and Errors / 19 | Covered. One globally active transaction, round-robin acceptance, saved ownership, bounded legal bursts and error/drain behavior. | [tiny_axi4_fabric.sv](../../../rtl/tiny/top/tiny_axi4_fabric.sv), [axi4_error_slave.sv](../../../rtl/ip/interconnect/axi4_error_slave.sv) | Keep local IP capability separate from end-to-end fabric admission; no unlimited liveness guarantee. |
|
||||||
| 9 | Memory Map and Access Conventions / 21 | Covered. Twenty canonical address windows, including alias versus physical-capacity distinctions. | [memory_map.json](../../../rtl/tiny/address_map/memory_map.json) | Reconcile every source-map change; do not use unmapped holes as scratch memory. |
|
| 9 | Memory Map and Access Conventions / 21 | Covered. Twenty canonical address windows, including alias versus physical-capacity distinctions. | [memory_map.json](../../../rtl/tiny/address_map/memory_map.json) | Reconcile every source-map change; do not use unmapped holes as scratch memory. |
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
# Tiny Gen1 v0.1 DRAFT content review
|
# Tiny Gen1 v0.1 DRAFT content review
|
||||||
|
|
||||||
The publication describes the independent IHP130 Tiny profile: one RV32IMC
|
The publication describes the independent IHP130 Tiny profile: one dual-port
|
||||||
Hazard3 with A disabled, RV32IM default firmware, 128 KiB SRAM, 24 MHz target,
|
RV32IMC Hazard3 with A disabled, RV32IM default firmware, 128 KiB SRAM in four
|
||||||
|
independently serviced 32 KiB groups, 24 MHz target,
|
||||||
four DMA channels and AXI32/APB4. Rill (清溪) is the selected prototype brand.
|
four DMA channels and AXI32/APB4. Rill (清溪) is the selected prototype brand.
|
||||||
The font/palette/grid are inherited as design rules; product facts are not
|
The font/palette/grid are inherited as design rules; product facts are not
|
||||||
inherited from Mini's descriptions or qualification records.
|
inherited from Mini's descriptions or qualification records.
|
||||||
|
|
||||||
|
The P4 working-source update changes CPU/local-SRAM bindings and the affected
|
||||||
|
draft architecture prose/figure. It does not advance `tiny.json.source_revision`,
|
||||||
|
rebuild or relabel a delivered PDF, or add hardware qualification. A later
|
||||||
|
publication release still requires source review, snapshot refresh and PDF checks.
|
||||||
|
|
||||||
| Area | Review and publication decision | Follow-up boundary |
|
| Area | Review and publication decision | Follow-up boundary |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| CPU and startup | Publish the actual C/M extensions, disabled A, CSR-enabled firmware and Tiny guard around PSRAM setup. | Keep compiler target separate from implemented ISA; a reset vector is not a completed boot. |
|
| CPU and startup | Publish the actual C/M extensions, disabled A, CSR-enabled firmware and Tiny guard around PSRAM setup. | Keep compiler target separate from implemented ISA; a reset vector is not a completed boot. |
|
||||||
|
|||||||
@@ -29,9 +29,9 @@
|
|||||||
- JTAG debug with coordinated hart reset.
|
- JTAG debug with coordinated hart reset.
|
||||||
|
|
||||||
*Memory and interconnect*
|
*Memory and interconnect*
|
||||||
- *128 KiB SRAM*, 32 banks of 4 KiB.
|
- *128 KiB SRAM*, four independently serviced 32 KiB groups backed by 32 banks of 4 KiB.
|
||||||
- Native AXI32 memory paths and one APB4 island.
|
- Native AXI32 memory paths and one APB4 island.
|
||||||
- CPU and DMA share one active transaction globally.
|
- CPU instruction/data ports are independent locally; the compatible external attachment remains serialized until P5.
|
||||||
- XPI NOR boot and SRAM-resident application layout.
|
- XPI NOR boot and SRAM-resident application layout.
|
||||||
|
|
||||||
*DMA and control*
|
*DMA and control*
|
||||||
@@ -103,16 +103,18 @@ The document uses the approved Mini visual language; all product facts and sourc
|
|||||||
|
|
||||||
= System Architecture
|
= System Architecture
|
||||||
== Processor and Integration <tiny-core>
|
== Processor and Integration <tiny-core>
|
||||||
Hazard3 is hart zero. The shared wrapper selects the C and M extensions and disables A for Tiny. Its AHB-Lite bus terminates
|
Hazard3 is hart zero. The locked dual-port wrapper selects the C and M extensions and disables A for Tiny. Instruction fetch
|
||||||
at a direct AXI32 adapter. JTAG supports the implemented halt/resume, register and system-bus debug path; debug hart reset
|
and load/store use independent AHB ports through separate local SRAM frontends; non-SRAM accesses use the tagged slow path.
|
||||||
waits for the adapter to become idle instead of abandoning an accepted transaction.
|
JTAG supports halt/resume, register access and halted-hart abstract-command memory access, without an independent system-bus master.
|
||||||
|
Debug hart reset first halts the hart and drains accepted local I/D and slow-path responses. SRAM and unrelated DMA work stay active.
|
||||||
|
|
||||||
The system has no application hart, MMU/Linux platform, hardware cache-coherent multicore organization or product extension
|
The system has no application hart, MMU/Linux platform, hardware cache-coherent multicore organization or product extension
|
||||||
slots. CPU-visible SRAM, XPI and APB routes are the implemented interconnect. The single-hart architecture does not remove
|
slots. CPU-visible SRAM, XPI and APB routes are the implemented interconnect. The single-hart architecture does not remove
|
||||||
the need for software/DMA buffer ownership and fences.
|
the need for software/DMA buffer ownership and fences.
|
||||||
#architecture()
|
#architecture()
|
||||||
#source-note("rtl/tiny/top/retrosoc_tiny.sv",title:"Tiny product integration and parameters")
|
#source-note("rtl/tiny/top/retrosoc_tiny.sv",title:"Tiny product integration and parameters")
|
||||||
#source-note("rtl/ip/core/mgmt_core_wrapper.sv",title:"CPU ISA, AHB adapter and debug wrapper")
|
#source-note("rtl/tiny/top/tiny_cpu_wrapper.sv",title:"Dual-port CPU ISA, AHB ports and debug wrapper")
|
||||||
|
#source-note("rtl/tiny/top/tiny_sram.sv",title:"Four local SRAM groups and external attachment")
|
||||||
|
|
||||||
== Clock, Reset and Operating State <tiny-clock>
|
== Clock, Reset and Operating State <tiny-clock>
|
||||||
CPU, AXI, APB, RTC and watchdog share the configured 24 MHz system clock. CLINT derives a 1 MHz tick. JTAG has its own
|
CPU, AXI, APB, RTC and watchdog share the configured 24 MHz system clock. CLINT derives a 1 MHz tick. JTAG has its own
|
||||||
@@ -130,7 +132,8 @@ advertised as a qualified voltage/frequency operating point.
|
|||||||
#source-note("rtl/tiny/integration/clock_reset_domains.json",title:"Clock/reset inventory and debug crossings")
|
#source-note("rtl/tiny/integration/clock_reset_domains.json",title:"Clock/reset inventory and debug crossings")
|
||||||
|
|
||||||
== Bus Transactions, Arbitration and Errors <tiny-bus>
|
== Bus Transactions, Arbitration and Errors <tiny-bus>
|
||||||
The two initiators are CPU and DMA. They share *one active read or write transaction globally*. The fabric saves source,
|
The two external initiators are CPU slow path and DMA. They share *one active read or write transaction globally* in this
|
||||||
|
pre-P5 attachment. CPU instruction/data local requests are independently arbitrated within each SRAM group. The fabric saves source,
|
||||||
direction, address and attributes, forwards the request, and retains ownership until the terminal B or RLAST handshake.
|
direction, address and attributes, forwards the request, and retains ownership until the terminal B or RLAST handshake.
|
||||||
Read/write request arbitration is round-robin; it is not a throughput guarantee or an unrestricted liveness guarantee.
|
Read/write request arbitration is round-robin; it is not a throughput guarantee or an unrestricted liveness guarantee.
|
||||||
|
|
||||||
|
|||||||
@@ -26,21 +26,22 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
#let architecture() = diagram("tiny-architecture",schematic((
|
#let architecture() = diagram("tiny-architecture",schematic((
|
||||||
(5,4,65,19,[Hazard3 · hart 0 \ RV32IMC · A disabled]),
|
(5,4,65,19,[Hazard3 dual-port · hart 0 \ RV32IMC · A disabled]),
|
||||||
(104,4,63,19,[JTAG / debug transport \ External TCK domain]),
|
(104,4,63,19,[JTAG / debug transport \ External TCK domain]),
|
||||||
(5,37,65,15,[AHB-Lite → AXI32 adapter]),
|
(5,37,65,15,[I/D decode + slow adapter]),
|
||||||
(104,37,63,15,[Four-channel DMA \ AXI32 master]),
|
(104,37,63,15,[Four-channel DMA \ AXI32 master]),
|
||||||
(30,69,112,19,[Tiny AXI32 fabric \ One active transaction globally]),
|
(30,69,112,19,[Tiny AXI32 fabric \ P5 external concurrency pending]),
|
||||||
(0,110,40,22,[128 KiB SRAM \ 32 × 4 KiB]),
|
(0,110,40,22,[128 KiB SRAM \ 4 × 32 KiB groups]),
|
||||||
(44,110,40,22,[XPI read path \ Flash alias / NOR]),
|
(44,110,40,22,[XPI read path \ Flash alias / NOR]),
|
||||||
(88,110,40,22,[APB4 bridge \ 16 targets]),
|
(88,110,40,22,[APB4 bridge \ 16 targets]),
|
||||||
(132,110,40,22,[Error responders \ DECERR / SLVERR]),
|
(132,110,40,22,[Error responders \ DECERR / SLVERR]),
|
||||||
),(
|
),(
|
||||||
((104,13),(70,13)),((37,23),(37,37)),
|
((104,13),(70,13)),((37,23),(37,37)),
|
||||||
|
((5,44),(0,44),(0,103),(6,103),(6,110)),
|
||||||
((37,52),(37,61),(60,61),(60,69)),((135,52),(135,61),(112,61),(112,69)),
|
((37,52),(37,61),(60,61),(60,69)),((135,52),(135,61),(112,61),(112,69)),
|
||||||
((45,88),(45,99),(20,99),(20,110)),((70,88),(70,103),(64,103),(64,110)),
|
((45,88),(45,99),(20,99),(20,110)),((70,88),(70,103),(64,103),(64,110)),
|
||||||
((103,88),(103,103),(108,103),(108,110)),((127,88),(127,99),(152,99),(152,110)),
|
((103,88),(103,103),(108,103),(108,110)),((127,88),(127,99),(152,99),(152,110)),
|
||||||
),height:134),[Tiny Gen1 integration. System logic runs from the configured 24 MHz clock; JTAG is asynchronous.])
|
),height:134),[Tiny Gen1 integration. The left branch represents independent local I/D paths bypassing the external fabric. System logic uses the configured 24 MHz clock; JTAG is asynchronous.])
|
||||||
|
|
||||||
#let ip-diagram(entry) = {
|
#let ip-diagram(entry) = {
|
||||||
let children=entry.nodes.slice(1)
|
let children=entry.nodes.slice(1)
|
||||||
|
|||||||
@@ -98,7 +98,13 @@
|
|||||||
"uart1": 26
|
"uart1": 26
|
||||||
},
|
},
|
||||||
"sources": [
|
"sources": [
|
||||||
"rtl/ip/core/mgmt_core_wrapper.sv",
|
"rtl/tiny/top/tiny_cpu_wrapper.sv",
|
||||||
|
"rtl/tiny/top/tiny_cpu_mem.sv",
|
||||||
|
"rtl/tiny/top/tiny_sram.sv",
|
||||||
|
"rtl/tiny/top/tiny_sram_group.sv",
|
||||||
|
"rtl/tiny/top/tiny_sram_axi4.sv",
|
||||||
|
"rtl/tiny/top/tiny_sram_axi4_demux.sv",
|
||||||
|
"rtl/tiny/top/tiny_sram_port_if.sv",
|
||||||
"rtl/ip/core/mgmt_debug_wrapper.sv",
|
"rtl/ip/core/mgmt_debug_wrapper.sv",
|
||||||
"rtl/ip/core/mgmt_debug_reset.sv",
|
"rtl/ip/core/mgmt_debug_reset.sv",
|
||||||
"rtl/ip/interconnect/ahbl2axi4.sv",
|
"rtl/ip/interconnect/ahbl2axi4.sv",
|
||||||
@@ -122,7 +128,7 @@
|
|||||||
"(s_last_addr[31:12] == s_selected_addr.addr[31:12])",
|
"(s_last_addr[31:12] == s_selected_addr.addr[31:12])",
|
||||||
"s_accept = (s_state_q == Idle) && s_selected_valid;",
|
"s_accept = (s_state_q == Idle) && s_selected_valid;",
|
||||||
"Response: if (s_terminal) s_state_d = Idle;",
|
"Response: if (s_terminal) s_state_d = Idle;",
|
||||||
"s_tgt_d = s_protocol_legal ? s_decoded_target : 3'd4;",
|
"s_tgt_d = !s_protocol_legal ? 3'd4 : s_fetch_denied ? ((s_decoded_target == 3'd2) ? 3'd4 : 3'd3) : s_decoded_target;",
|
||||||
"assign s_protocol_legal = !s_selected_addr.lock && (s_selected_addr.size <= 3'd2) &&\n ((s_selected_addr.addr & ((32'd1 << s_selected_addr.size) - 32'd1)) == 32'd0) &&\n ((s_selected_addr.burst == 2'b01) ||\n ((s_selected_addr.burst == 2'b00) && (s_selected_addr.len == 8'd0))) &&\n (s_selected_addr.len <= 8'd15) && !s_last_addr[32] &&\n (s_last_addr[31:12] == s_selected_addr.addr[31:12]) &&\n (decode_target(\n s_last_addr[31:0]\n ) == s_decoded_target) && ((s_decoded_target != 3'd2) || (s_selected_addr.len == 8'd0));",
|
"assign s_protocol_legal = !s_selected_addr.lock && (s_selected_addr.size <= 3'd2) &&\n ((s_selected_addr.addr & ((32'd1 << s_selected_addr.size) - 32'd1)) == 32'd0) &&\n ((s_selected_addr.burst == 2'b01) ||\n ((s_selected_addr.burst == 2'b00) && (s_selected_addr.len == 8'd0))) &&\n (s_selected_addr.len <= 8'd15) && !s_last_addr[32] &&\n (s_last_addr[31:12] == s_selected_addr.addr[31:12]) &&\n (decode_target(\n s_last_addr[31:0]\n ) == s_decoded_target) && ((s_decoded_target != 3'd2) || (s_selected_addr.len == 8'd0));",
|
||||||
"assign s_terminal = (s_state_q == Response) &&\n (s_write_q ? (s_target_bvalid[s_tgt_q] && s_bready[s_owner_q]) :\n (s_target_rvalid[s_tgt_q] && s_rready[s_owner_q] && s_target_rlast[s_tgt_q]));"
|
"assign s_terminal = (s_state_q == Response) &&\n (s_write_q ? (s_target_bvalid[s_tgt_q] && s_bready[s_owner_q]) :\n (s_target_rvalid[s_tgt_q] && s_rready[s_owner_q] && s_target_rlast[s_tgt_q]));"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ TOP = "rtl/tiny/top/retrosoc_tiny.sv"
|
|||||||
RESET_TREE = "rtl/tiny/top/tiny_reset_tree.sv"
|
RESET_TREE = "rtl/tiny/top/tiny_reset_tree.sv"
|
||||||
SYSCTRL = "rtl/tiny/top/tiny_sysctrl.sv"
|
SYSCTRL = "rtl/tiny/top/tiny_sysctrl.sv"
|
||||||
ARCHINFO = "rtl/tiny/top/tiny_archinfo.sv"
|
ARCHINFO = "rtl/tiny/top/tiny_archinfo.sv"
|
||||||
|
CPU = "rtl/tiny/top/tiny_cpu_wrapper.sv"
|
||||||
|
CPU_MEM = "rtl/tiny/top/tiny_cpu_mem.sv"
|
||||||
|
SRAM = "rtl/tiny/top/tiny_sram.sv"
|
||||||
FAMILIES = {"xpi", "sram", "gpio", "dma", "timer", "pwm", "rtc", "wdg", "uart", "i2c", "clint", "archinfo"}
|
FAMILIES = {"xpi", "sram", "gpio", "dma", "timer", "pwm", "rtc", "wdg", "uart", "i2c", "clint", "archinfo"}
|
||||||
MANAGED = {"publication_media", "hazard3", "cluster_common", "cluster_archinfo", "cluster_pwm", "cluster_rtc", "cluster_wdg"}
|
MANAGED = {"publication_media", "hazard3", "cluster_common", "cluster_archinfo", "cluster_pwm", "cluster_rtc", "cluster_wdg"}
|
||||||
SUPPORTED_SYSCTRL = {
|
SUPPORTED_SYSCTRL = {
|
||||||
@@ -65,21 +68,27 @@ def facts(root: Path = ROOT) -> dict:
|
|||||||
if any(profile.get(key) != value for key, value in required.items()):
|
if any(profile.get(key) != value for key, value in required.items()):
|
||||||
raise ValueError("Tiny publication profile changed; review configuration and ISA boundary")
|
raise ValueError("Tiny publication profile changed; review configuration and ISA boundary")
|
||||||
top = (root / TOP).read_text(encoding="utf-8")
|
top = (root / TOP).read_text(encoding="utf-8")
|
||||||
ram = instance_parameters(top, "onchip_ram", "u_sram")
|
ram_reg = instance_parameters((root / SRAM).read_text(), "onchip_ram_reg", "u_reg")
|
||||||
|
ram = {"CapacityKiB": ram_reg.get("CapacityKiB"),
|
||||||
|
"DataWidth": 8 * ram_reg.get("DataBytes", 0), "IdWidth": 1}
|
||||||
dma = instance_parameters(top, "apb4_dma", "u_dma")
|
dma = instance_parameters(top, "apb4_dma", "u_dma")
|
||||||
cpu = instance_parameters(top, "mgmt_core_wrapper", "u_cpu")
|
cpu = instance_parameters(top, "tiny_cpu_wrapper", "u_cpu")
|
||||||
if ram != {"CapacityKiB": 128, "DataWidth": 32, "IdWidth": 1, "Ics55SmallBanks": 1}:
|
if ram != {"CapacityKiB": 128, "DataWidth": 32, "IdWidth": 1} or ram_reg.get("Present") != 1:
|
||||||
raise ValueError("Tiny SRAM parameters changed")
|
raise ValueError("Tiny SRAM parameters changed")
|
||||||
# The publication remains the reviewed IHP130 compatibility profile;
|
# P4 changes local routing, not physical geometry or the book's IHP130
|
||||||
# selecting ICS55's 4 KiB geometry does not alter its reported SRAM facts.
|
# compatibility selection. Snapshot enforcement still precedes publication.
|
||||||
ram = {key: value for key, value in ram.items() if key != "Ics55SmallBanks"}
|
read_topology(root / TOPOLOGY, root / MAP)
|
||||||
|
require_snippets(root, TOP, ["tiny_sram u_sram", "tiny_sram_axi4_demux u_sram_attachment",
|
||||||
|
".ID_WIDTH(1)", ".DATA_WIDTH(32)"])
|
||||||
|
require_snippets(root, SRAM, ["group < 4", "tiny_sram_group u_group"])
|
||||||
if dma != {"NumChannels": 4, "MaxBurstBeats": 16, "FifoDepth": 32, "RequestMask": 0x7F9, "EnableStreams": 0}:
|
if dma != {"NumChannels": 4, "MaxBurstBeats": 16, "FifoDepth": 32, "RequestMask": 0x7F9, "EnableStreams": 0}:
|
||||||
raise ValueError("Tiny DMA parameters changed")
|
raise ValueError("Tiny DMA parameters changed")
|
||||||
if cpu != {"ExternalIrqCount": 30, "EnableAtomics": 0, "TwoCycleBusErrors": 1, "ResetSyncStages": 5}:
|
if cpu != {"ExternalIrqCount": 30, "EnableAtomics": 0, "ResetSyncStages": 5}:
|
||||||
raise ValueError("Tiny CPU integration changed")
|
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, 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, CPU, ["hazard3_cpu_2port", ".EXTENSION_C(1)", ".EXTENSION_A(EnableAtomics)"])
|
||||||
require_snippets(root, TOP, ["u_masters_axi4_if[2]", ".axi4(u_masters_axi4_if[0])",
|
require_snippets(root, CPU_MEM, [".TwoCycleErrors(1'b1)", "ErrorFirst", "ErrorLast"])
|
||||||
|
require_snippets(root, TOP, ["u_masters_axi4_if[2]", ".slow_axi4(u_masters_axi4_if[0])",
|
||||||
".axi4(u_masters_axi4_if[1])", ".cfg_apb4(u_sram_apb4_if)",
|
".axi4(u_masters_axi4_if[1])", ".cfg_apb4(u_sram_apb4_if)",
|
||||||
"assign s_tick = s_tick_count_q == 5'd23;"])
|
"assign s_tick = s_tick_count_q == 5'd23;"])
|
||||||
require_snippets(root, SYSCTRL, ["(apb4.paddr[1:0] != 2'd0)", "(apb4.pstrb != 4'hf)",
|
require_snippets(root, SYSCTRL, ["(apb4.paddr[1:0] != 2'd0)", "(apb4.pstrb != 4'hf)",
|
||||||
|
|||||||
@@ -72,36 +72,37 @@ module mgmt_core_wrapper #(
|
|||||||
.JtagIdcode (`SOC_JTAG_IDCODE),
|
.JtagIdcode (`SOC_JTAG_IDCODE),
|
||||||
.ResetSyncStages(ResetSyncStages)
|
.ResetSyncStages(ResetSyncStages)
|
||||||
) u_mgmt_debug_wrapper (
|
) u_mgmt_debug_wrapper (
|
||||||
.clk_i (clk_i),
|
.clk_i(clk_i),
|
||||||
.rst_n_i (rst_n_i),
|
.rst_n_i(rst_n_i),
|
||||||
.bridge_idle_i (s_ahbl_idle),
|
.bridge_idle_i(s_ahbl_idle),
|
||||||
.jtag_tck_i (jtag_tck_i),
|
.jtag_tck_i(jtag_tck_i),
|
||||||
.jtag_tms_i (jtag_tms_i),
|
.jtag_tms_i(jtag_tms_i),
|
||||||
.jtag_tdi_i (jtag_tdi_i),
|
.jtag_tdi_i(jtag_tdi_i),
|
||||||
.jtag_trst_n_i (jtag_trst_n_i),
|
.jtag_trst_n_i(jtag_trst_n_i),
|
||||||
.jtag_tdo_o (jtag_tdo_o),
|
.jtag_tdo_o(jtag_tdo_o),
|
||||||
.core_rst_n_o (s_core_rst_n),
|
.core_rst_n_o(s_core_rst_n),
|
||||||
.dbg_req_halt_o (s_dbg_req_halt),
|
.reset_pending_o(), // Mini retains its existing reset policy.
|
||||||
.dbg_req_halt_on_reset_o (s_dbg_req_halt_on_reset),
|
.dbg_req_halt_o(s_dbg_req_halt),
|
||||||
.dbg_req_resume_o (s_dbg_req_resume),
|
.dbg_req_halt_on_reset_o(s_dbg_req_halt_on_reset),
|
||||||
.dbg_halted_i (s_dbg_halted),
|
.dbg_req_resume_o(s_dbg_req_resume),
|
||||||
.dbg_running_i (s_dbg_running),
|
.dbg_halted_i(s_dbg_halted),
|
||||||
.dbg_data0_rdata_o (s_dbg_data0_rdata),
|
.dbg_running_i(s_dbg_running),
|
||||||
.dbg_data0_wdata_i (s_dbg_data0_wdata),
|
.dbg_data0_rdata_o(s_dbg_data0_rdata),
|
||||||
.dbg_data0_wen_i (s_dbg_data0_wen),
|
.dbg_data0_wdata_i(s_dbg_data0_wdata),
|
||||||
.dbg_instr_data_o (s_dbg_instr_data),
|
.dbg_data0_wen_i(s_dbg_data0_wen),
|
||||||
.dbg_instr_data_vld_o (s_dbg_instr_data_vld),
|
.dbg_instr_data_o(s_dbg_instr_data),
|
||||||
.dbg_instr_data_rdy_i (s_dbg_instr_data_rdy),
|
.dbg_instr_data_vld_o(s_dbg_instr_data_vld),
|
||||||
|
.dbg_instr_data_rdy_i(s_dbg_instr_data_rdy),
|
||||||
.dbg_instr_caught_exception_i(s_dbg_instr_caught_exception),
|
.dbg_instr_caught_exception_i(s_dbg_instr_caught_exception),
|
||||||
.dbg_instr_caught_ebreak_i (s_dbg_instr_caught_ebreak),
|
.dbg_instr_caught_ebreak_i(s_dbg_instr_caught_ebreak),
|
||||||
.dbg_sbus_addr_o (s_dbg_sbus_addr),
|
.dbg_sbus_addr_o(s_dbg_sbus_addr),
|
||||||
.dbg_sbus_write_o (s_dbg_sbus_write),
|
.dbg_sbus_write_o(s_dbg_sbus_write),
|
||||||
.dbg_sbus_size_o (s_dbg_sbus_size),
|
.dbg_sbus_size_o(s_dbg_sbus_size),
|
||||||
.dbg_sbus_vld_o (s_dbg_sbus_vld),
|
.dbg_sbus_vld_o(s_dbg_sbus_vld),
|
||||||
.dbg_sbus_rdy_i (s_dbg_sbus_rdy),
|
.dbg_sbus_rdy_i(s_dbg_sbus_rdy),
|
||||||
.dbg_sbus_err_i (s_dbg_sbus_err),
|
.dbg_sbus_err_i(s_dbg_sbus_err),
|
||||||
.dbg_sbus_wdata_o (s_dbg_sbus_wdata),
|
.dbg_sbus_wdata_o(s_dbg_sbus_wdata),
|
||||||
.dbg_sbus_rdata_i (s_dbg_sbus_rdata)
|
.dbg_sbus_rdata_i(s_dbg_sbus_rdata)
|
||||||
);
|
);
|
||||||
|
|
||||||
hazard3_cpu_1port #(
|
hazard3_cpu_1port #(
|
||||||
|
|||||||
@@ -19,12 +19,15 @@ module mgmt_debug_reset #(
|
|||||||
input logic reset_req_i,
|
input logic reset_req_i,
|
||||||
input logic bridge_idle_i,
|
input logic bridge_idle_i,
|
||||||
output logic core_rst_n_o,
|
output logic core_rst_n_o,
|
||||||
|
output logic reset_pending_o,
|
||||||
output logic reset_done_o
|
output logic reset_done_o
|
||||||
);
|
);
|
||||||
|
|
||||||
logic s_pending_d, s_pending_q;
|
logic s_pending_d, s_pending_q;
|
||||||
logic s_reset_active_d, s_reset_active_q;
|
logic s_reset_active_d, s_reset_active_q;
|
||||||
|
|
||||||
|
assign reset_pending_o = reset_req_i || s_pending_q || s_reset_active_q;
|
||||||
|
|
||||||
always_comb begin
|
always_comb begin
|
||||||
s_pending_d = s_pending_q;
|
s_pending_d = s_pending_q;
|
||||||
s_reset_active_d = s_reset_active_q;
|
s_reset_active_d = s_reset_active_q;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ module mgmt_debug_wrapper #(
|
|||||||
input logic jtag_trst_n_i,
|
input logic jtag_trst_n_i,
|
||||||
output logic jtag_tdo_o,
|
output logic jtag_tdo_o,
|
||||||
output logic core_rst_n_o,
|
output logic core_rst_n_o,
|
||||||
|
output logic reset_pending_o,
|
||||||
|
|
||||||
output logic dbg_req_halt_o,
|
output logic dbg_req_halt_o,
|
||||||
output logic dbg_req_halt_on_reset_o,
|
output logic dbg_req_halt_on_reset_o,
|
||||||
@@ -154,12 +155,13 @@ module mgmt_debug_wrapper #(
|
|||||||
mgmt_debug_reset #(
|
mgmt_debug_reset #(
|
||||||
.ResetSyncStages(ResetSyncStages)
|
.ResetSyncStages(ResetSyncStages)
|
||||||
) u_mgmt_debug_reset (
|
) u_mgmt_debug_reset (
|
||||||
.clk_i (clk_i),
|
.clk_i (clk_i),
|
||||||
.rst_n_i (rst_n_i),
|
.rst_n_i (rst_n_i),
|
||||||
.reset_req_i (s_sys_reset_req || s_hart_reset_req),
|
.reset_req_i (s_sys_reset_req || s_hart_reset_req),
|
||||||
.bridge_idle_i(bridge_idle_i),
|
.bridge_idle_i (bridge_idle_i),
|
||||||
.core_rst_n_o (core_rst_n_o),
|
.core_rst_n_o (core_rst_n_o),
|
||||||
.reset_done_o (s_reset_done)
|
.reset_pending_o(reset_pending_o),
|
||||||
|
.reset_done_o (s_reset_done)
|
||||||
);
|
);
|
||||||
|
|
||||||
endmodule
|
endmodule
|
||||||
@@ -1,6 +1,23 @@
|
|||||||
{
|
{
|
||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
"targets": [
|
"targets": [
|
||||||
|
{
|
||||||
|
"name": "tiny-r2-p4",
|
||||||
|
"paths": ["rtl/tiny/top"],
|
||||||
|
"status": "prototype",
|
||||||
|
"baseline_revision": "4db27524a6282ca40d76b935415b89f043d6ea83",
|
||||||
|
"configuration_digest": null,
|
||||||
|
"configuration_profiles": ["configs/ci/ics55-tiny.mk", "configs/ci/ihp130-tiny.mk"],
|
||||||
|
"required_evidence": [],
|
||||||
|
"synthesis_intent": {
|
||||||
|
"registers": "Common registers; independent I/D request state and per-group responses",
|
||||||
|
"reset": "five-edge release; hart halt/drain leaves SRAM and unrelated DMA active",
|
||||||
|
"clock_enable": "CPU and all 32 main-SRAM macros use direct SAFE24 SYS",
|
||||||
|
"memory": "four groups of eight tc_sram_1024x32 macros; no payload reset or extra SRAM",
|
||||||
|
"pipeline": "AHB capture, macro issue, registered response; uncontended completion within three cycles"
|
||||||
|
},
|
||||||
|
"waivers": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "owned-rtl",
|
"name": "owned-rtl",
|
||||||
"paths": [
|
"paths": [
|
||||||
|
|||||||
@@ -437,7 +437,14 @@
|
|||||||
"tiny_archinfo.sv",
|
"tiny_archinfo.sv",
|
||||||
"tiny_axi42apb4.sv",
|
"tiny_axi42apb4.sv",
|
||||||
"tiny_axi4_fabric.sv",
|
"tiny_axi4_fabric.sv",
|
||||||
|
"tiny_cpu_mem.sv",
|
||||||
|
"tiny_cpu_wrapper.sv",
|
||||||
"tiny_reset_tree.sv",
|
"tiny_reset_tree.sv",
|
||||||
|
"tiny_sram.sv",
|
||||||
|
"tiny_sram_axi4.sv",
|
||||||
|
"tiny_sram_axi4_demux.sv",
|
||||||
|
"tiny_sram_group.sv",
|
||||||
|
"tiny_sram_port_if.sv",
|
||||||
"tiny_sysctrl.sv"
|
"tiny_sysctrl.sv"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
+16
-1
@@ -200,4 +200,19 @@ tiny-r2-p3-matrix: | manifest
|
|||||||
tiny-r2-p3-report:
|
tiny-r2-p3-report:
|
||||||
$(PYTHON) $(TINY_P3_TOOL) --root $(ROOT_PATH) --variant-root $(VARIANT_ROOT) report
|
$(PYTHON) $(TINY_P3_TOOL) --root $(ROOT_PATH) --variant-root $(VARIANT_ROOT) report
|
||||||
|
|
||||||
.PHONY: tiny-r2-p3-image tiny-r2-p3-sim tiny-r2-p3-matrix tiny-r2-p3-report
|
.PHONY: tiny-r2-p3-image tiny-r2-p3-sim tiny-r2-p3-matrix tiny-r2-p3-report
|
||||||
|
|
||||||
|
# P4 preserves old image identity and replays each selected image exactly once.
|
||||||
|
TINY_P4_REFERENCE ?=
|
||||||
|
TINY_P4_CASES ?= perf-o2-plain-flat,perf-o2-plain-banked
|
||||||
|
tiny-r2-p4-replay: | manifest
|
||||||
|
$(PYTHON) $(ROOT_PATH)/scripts/tiny_r2_p4.py --root $(ROOT_PATH) --variant-root $(VARIANT_ROOT) \
|
||||||
|
--reference $(TINY_P4_REFERENCE) --cases $(TINY_P4_CASES) --simulator $(SIM_TOOL_NAME) \
|
||||||
|
--vvp $(VVP) --timeout $(SOC_SIM_TIME) -- \
|
||||||
|
make --no-print-directory -j1 -B CONFIG=$(CONFIG_PATH) BUILD_TIMESTAMP=$(BUILD_TIMESTAMP) \
|
||||||
|
$(foreach var,$(CONFIG_KEY_VARS),$(var)=$($(var))) SIMU=$(SIMU) SYNTH=NONE STA=NONE comp
|
||||||
|
|
||||||
|
tiny-r2-p4-report:
|
||||||
|
$(PYTHON) $(ROOT_PATH)/scripts/tiny_r2_p4.py --root $(ROOT_PATH) --variant-root $(VARIANT_ROOT) --report
|
||||||
|
|
||||||
|
.PHONY: tiny-r2-p4-replay tiny-r2-p4-report
|
||||||
+8
-2
@@ -9,6 +9,12 @@ explicit compatibility through `configs/ci/ihp130-tiny.mk`. See the
|
|||||||
[platform runbook](../../docs/ip/tiny-ics55-platform.md) for source-bound
|
[platform runbook](../../docs/ip/tiny-ics55-platform.md) for source-bound
|
||||||
validation; backend frequency tests are not SYS switching or timing qualification.
|
validation; backend frequency tests are not SYS switching or timing qualification.
|
||||||
|
|
||||||
|
The [R2-P4 implementation](../../docs/ip/tiny-soc-r2-local-memory.md) connects the
|
||||||
|
official dual-port CPU to four independently serviced 32 KiB local SRAM groups.
|
||||||
|
The 32 physical macros and register ABI are unchanged. Only non-SRAM CPU work
|
||||||
|
uses the tagged slow path; the external fabric stays serialized until P5.
|
||||||
|
Source presence alone is not completed phase or physical qualification.
|
||||||
|
|
||||||
`top` owns product integration, `address_map`, `pin_map` and `integration` own
|
`top` owns product integration, `address_map`, `pin_map` and `integration` own
|
||||||
canonical address, pad, IRQ and clock/reset inputs, `filelist` selects sources,
|
canonical address, pad, IRQ and clock/reset inputs, `filelist` selects sources,
|
||||||
`dv` owns product verification, and `mk` owns product configuration. Reusable
|
`dv` owns product verification, and `mk` owns product configuration. Reusable
|
||||||
@@ -43,8 +49,8 @@ final timing closure is mandatory. Software/DMA scheduling is `TINY-R2-P3`, loca
|
|||||||
`TINY-R2-P9`, system qualification `TINY-R2-P10` and complete-product physical
|
`TINY-R2-P9`, system qualification `TINY-R2-P10` and complete-product physical
|
||||||
qualification `TINY-R2-P11` (historical IHP130 title; now default ICS55).
|
qualification `TINY-R2-P11` (historical IHP130 title; now default ICS55).
|
||||||
Legacy `TINY-P0` through `TINY-P12` remain in
|
Legacy `TINY-P0` through `TINY-P12` remain in
|
||||||
the contract history. The committed RTL/profile still implements the initial
|
the contract history. Executable profiles retain SAFE24 and four DMA channels;
|
||||||
24 MHz/no-PLL/four-channel design; no faster CPU/SRAM or PLL timing support
|
ICS55's present PLL stays off. No faster CPU/SRAM or PLL timing support
|
||||||
follows from this freeze or the `HAVE_PLL` selector alone.
|
follows from this freeze or the `HAVE_PLL` selector alone.
|
||||||
|
|
||||||
The R2-P2 candidate keeps that executable clock/memory configuration and adds
|
The R2-P2 candidate keeps that executable clock/memory configuration and adds
|
||||||
|
|||||||
+107
-47
@@ -230,57 +230,117 @@ module retrosoc_tiny_tb;
|
|||||||
`undef _BASELINE_AXI
|
`undef _BASELINE_AXI
|
||||||
end
|
end
|
||||||
|
|
||||||
// The baseline has one AHB port. HPROT[0] distinguishes instruction/data;
|
// Verification-only per-group issue/conflict accounting. A bank conflict is
|
||||||
// these observations do not assert future independent local-I/D latency.
|
// two or more eligible macro clients in one cycle, not an AXI address stall.
|
||||||
longint unsigned s_ahb_cycle = 0;
|
for (genvar group = 0; group < 4; group++) begin : gen_bank_observer
|
||||||
longint unsigned s_ahb_start = 0;
|
longint unsigned s_issues[3], s_waits[3];
|
||||||
longint unsigned s_ahb_accept[2], s_ahb_done[2], s_ahb_latency[2];
|
longint unsigned s_conflicts = 0;
|
||||||
logic s_ahb_pending = 1'b0, s_ahb_measured = 1'b0;
|
logic s_previous_enable = 0;
|
||||||
logic s_ahb_data = 1'b0, s_ahb_previous_enable = 1'b0;
|
integer s_window = 0;
|
||||||
integer s_ahb_window = 0;
|
wire s_enable = u_dut.u_soc.s_perf_enable && s_baseline_enabled;
|
||||||
wire s_ahb_enable = u_dut.u_soc.s_perf_enable && s_baseline_enabled;
|
wire [2:0] s_req = u_dut.u_soc.u_sram.gen_group[group].u_group.s_req;
|
||||||
`define _BASELINE_AHB u_dut.u_soc.u_cpu.u_ahbl_if
|
wire [2:0] s_grant = u_dut.u_soc.u_sram.gen_group[group].u_group.s_grant;
|
||||||
always @(posedge s_clk) begin
|
always @(posedge s_clk) begin
|
||||||
s_ahb_cycle = s_ahb_cycle + 1;
|
if (!u_dut.u_soc.s_rst_n) begin
|
||||||
if (!u_dut.u_soc.s_rst_n) begin
|
s_previous_enable = 0;
|
||||||
s_ahb_pending = 1'b0;
|
s_window = 0;
|
||||||
s_ahb_measured = 1'b0;
|
end else if (s_baseline_enabled) begin
|
||||||
s_ahb_previous_enable = 1'b0;
|
if (s_enable && !s_previous_enable) begin
|
||||||
s_ahb_window = 0;
|
s_window = s_window + 1;
|
||||||
end else if (s_baseline_enabled) begin
|
s_conflicts = 0;
|
||||||
if (s_ahb_enable && !s_ahb_previous_enable) begin
|
for (int client = 0; client < 3; client++) begin
|
||||||
s_ahb_window = s_ahb_window + 1;
|
s_issues[client] = 0;
|
||||||
s_ahb_measured = 1'b0;
|
s_waits[client] = 0;
|
||||||
for (int kind = 0; kind < 2; kind++) begin
|
end
|
||||||
s_ahb_accept[kind] = 0;
|
|
||||||
s_ahb_done[kind] = 0;
|
|
||||||
s_ahb_latency[kind] = 0;
|
|
||||||
end
|
end
|
||||||
|
if (s_enable) begin
|
||||||
|
if ((s_req & (s_req - 3'd1)) != 3'd0) s_conflicts = s_conflicts + 1;
|
||||||
|
for (int client = 0; client < 3; client++) begin
|
||||||
|
if (s_grant[client]) s_issues[client] = s_issues[client] + 1;
|
||||||
|
if (s_req[client] && !s_grant[client]) s_waits[client] = s_waits[client] + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if (!s_enable && s_previous_enable)
|
||||||
|
$display(
|
||||||
|
"R2_P4_BANK window=%0d group=%0d issues_i=%0d issues_d=%0d issues_external=%0d conflicts=%0d wait_i=%0d wait_d=%0d wait_external=%0d",
|
||||||
|
s_window,
|
||||||
|
group,
|
||||||
|
s_issues[0],
|
||||||
|
s_issues[1],
|
||||||
|
s_issues[2],
|
||||||
|
s_conflicts,
|
||||||
|
s_waits[0],
|
||||||
|
s_waits[1],
|
||||||
|
s_waits[2]
|
||||||
|
);
|
||||||
|
s_previous_enable = s_enable;
|
||||||
end
|
end
|
||||||
if (`_BASELINE_AHB.hready) begin
|
|
||||||
if (s_ahb_pending && s_ahb_measured && s_ahb_enable) begin
|
|
||||||
s_ahb_done[s_ahb_data] = s_ahb_done[s_ahb_data] + 1;
|
|
||||||
if ((s_ahb_cycle - s_ahb_start) > s_ahb_latency[s_ahb_data])
|
|
||||||
s_ahb_latency[s_ahb_data] = s_ahb_cycle - s_ahb_start;
|
|
||||||
end
|
|
||||||
s_ahb_pending = `_BASELINE_AHB.htrans[1];
|
|
||||||
s_ahb_measured = s_ahb_pending && s_ahb_enable;
|
|
||||||
if (s_ahb_measured) begin
|
|
||||||
s_ahb_data = `_BASELINE_AHB.hprot[0];
|
|
||||||
s_ahb_start = s_ahb_cycle;
|
|
||||||
s_ahb_accept[s_ahb_data] = s_ahb_accept[s_ahb_data] + 1;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if (!s_ahb_enable && s_ahb_previous_enable) begin
|
|
||||||
for (int kind = 0; kind < 2; kind++) begin
|
|
||||||
$display("R2_AHB window=%0d kind=%0d accepted=%0d completed=%0d latency_max=%0d",
|
|
||||||
s_ahb_window, kind, s_ahb_accept[kind], s_ahb_done[kind], s_ahb_latency[kind]);
|
|
||||||
end
|
|
||||||
end
|
|
||||||
s_ahb_previous_enable = s_ahb_enable;
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
`undef _BASELINE_AHB
|
|
||||||
|
// P4 retains the original per-kind record format, now observed on two
|
||||||
|
// independent ports. External AXI counts remain external traffic only.
|
||||||
|
for (genvar port_index = 0; port_index < 2; port_index++) begin : gen_ahb_observer
|
||||||
|
longint unsigned s_cycle = 0, s_start = 0;
|
||||||
|
longint unsigned s_accepted = 0, s_completed = 0, s_latency = 0;
|
||||||
|
longint unsigned s_local_accepted = 0, s_local_completed = 0, s_local_latency = 0;
|
||||||
|
longint unsigned s_admission_wait = 0;
|
||||||
|
logic s_pending = 0, s_measured = 0, s_local = 0, s_previous_enable = 0;
|
||||||
|
integer s_window = 0;
|
||||||
|
wire s_enable = u_dut.u_soc.s_perf_enable && s_baseline_enabled;
|
||||||
|
`define _P4_AHB u_dut.u_soc.u_cpu_ahbl_if[port_index]
|
||||||
|
always @(posedge s_clk) begin
|
||||||
|
s_cycle = s_cycle + 1;
|
||||||
|
if (!u_dut.u_soc.s_core_rst_n) begin
|
||||||
|
s_pending = 0;
|
||||||
|
s_measured = 0;
|
||||||
|
s_previous_enable = 0;
|
||||||
|
s_window = 0;
|
||||||
|
end else if (s_baseline_enabled) begin
|
||||||
|
if (s_enable && !s_previous_enable) begin
|
||||||
|
s_window = s_window + 1;
|
||||||
|
s_measured = 0;
|
||||||
|
s_accepted = 0;
|
||||||
|
s_completed = 0;
|
||||||
|
s_latency = 0;
|
||||||
|
s_local_accepted = 0;
|
||||||
|
s_local_completed = 0;
|
||||||
|
s_local_latency = 0;
|
||||||
|
s_admission_wait = 0;
|
||||||
|
end
|
||||||
|
if (s_enable && `_P4_AHB.htrans[1] && !`_P4_AHB.hready)
|
||||||
|
s_admission_wait = s_admission_wait + 1;
|
||||||
|
if (`_P4_AHB.hready) begin
|
||||||
|
if (s_pending && s_measured && s_enable) begin
|
||||||
|
s_completed = s_completed + 1;
|
||||||
|
if ((s_cycle - s_start) > s_latency) s_latency = s_cycle - s_start;
|
||||||
|
if (s_local) begin
|
||||||
|
s_local_completed = s_local_completed + 1;
|
||||||
|
if ((s_cycle - s_start) > s_local_latency) s_local_latency = s_cycle - s_start;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
s_pending = `_P4_AHB.htrans[1];
|
||||||
|
s_measured = s_pending && s_enable;
|
||||||
|
if (s_measured) begin
|
||||||
|
s_start = s_cycle;
|
||||||
|
s_accepted = s_accepted + 1;
|
||||||
|
s_local = (`_P4_AHB.haddr >= 32'h30000000) && (`_P4_AHB.haddr < 32'h30020000);
|
||||||
|
if (s_local) s_local_accepted = s_local_accepted + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if (!s_enable && s_previous_enable) begin
|
||||||
|
$display("R2_AHB window=%0d kind=%0d accepted=%0d completed=%0d latency_max=%0d",
|
||||||
|
s_window, port_index, s_accepted, s_completed, s_latency);
|
||||||
|
$display(
|
||||||
|
"R2_P4_LOCAL window=%0d port=%0d accepted=%0d completed=%0d latency_max=%0d admission_wait=%0d",
|
||||||
|
s_window, port_index, s_local_accepted, s_local_completed, s_local_latency,
|
||||||
|
s_admission_wait);
|
||||||
|
end
|
||||||
|
s_previous_enable = s_enable;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
`undef _P4_AHB
|
||||||
|
end
|
||||||
`endif
|
`endif
|
||||||
|
|
||||||
retrosoc_tiny_asic u_dut (
|
retrosoc_tiny_asic u_dut (
|
||||||
|
|||||||
@@ -3,5 +3,12 @@
|
|||||||
/top/tiny_sysctrl.sv
|
/top/tiny_sysctrl.sv
|
||||||
/top/tiny_archinfo.sv
|
/top/tiny_archinfo.sv
|
||||||
/top/tiny_reset_tree.sv
|
/top/tiny_reset_tree.sv
|
||||||
|
/top/tiny_sram_port_if.sv
|
||||||
|
/top/tiny_sram_group.sv
|
||||||
|
/top/tiny_sram_axi4.sv
|
||||||
|
/top/tiny_sram_axi4_demux.sv
|
||||||
|
/top/tiny_sram.sv
|
||||||
|
/top/tiny_cpu_mem.sv
|
||||||
|
/top/tiny_cpu_wrapper.sv
|
||||||
/top/retrosoc_tiny.sv
|
/top/retrosoc_tiny.sv
|
||||||
/top/retrosoc_tiny_asic.sv
|
/top/retrosoc_tiny_asic.sv
|
||||||
@@ -1,5 +1,17 @@
|
|||||||
{
|
{
|
||||||
"schema_version": 2,
|
"schema_version": 2,
|
||||||
|
"local_memory": {
|
||||||
|
"phase": "TINY-R2-P4",
|
||||||
|
"clock_domain": "system",
|
||||||
|
"cpu_instance": "u_soc.u_cpu.u_hazard3_cpu_2port",
|
||||||
|
"frontend_instance": "u_soc.u_cpu_mem",
|
||||||
|
"macro_instance": "u_soc.u_sram.gen_group[{group}].u_group.gen_bank[{bank}].u_ram.u_mem",
|
||||||
|
"groups": 4,
|
||||||
|
"macros_per_group": 8,
|
||||||
|
"hart_reset": "halt then drain accepted I/D and slow responses; SRAM/DMA stay active",
|
||||||
|
"main_sram_payload_reset": "none",
|
||||||
|
"local_uncontended_max_cycles": 3
|
||||||
|
},
|
||||||
"inactive_macros": {
|
"inactive_macros": {
|
||||||
"ics55_pll": {
|
"ics55_pll": {
|
||||||
"cell": "PLL_TOP",
|
"cell": "PLL_TOP",
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
{
|
{
|
||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
|
"cpu": {"module": "hazard3_cpu_2port", "local_ports": 2, "data_outstanding": 1},
|
||||||
|
"main_sram": {
|
||||||
|
"groups": 4,
|
||||||
|
"group_bytes": 32768,
|
||||||
|
"macros_per_group": 8,
|
||||||
|
"macro_bytes": 4096,
|
||||||
|
"base": "0x30000000",
|
||||||
|
"macro_instance": "u_soc.u_sram.gen_group[{group}].u_group.gen_bank[{bank}].u_ram.u_mem",
|
||||||
|
"external_attachment": "serialized-pre-P5"
|
||||||
|
},
|
||||||
"gpio_pins": 32,
|
"gpio_pins": 32,
|
||||||
"irq_vector_width": 32,
|
"irq_vector_width": 32,
|
||||||
"external_irq_count": 30,
|
"external_irq_count": 30,
|
||||||
|
|||||||
@@ -23,9 +23,10 @@ module retrosoc_tiny (
|
|||||||
logic s_por_rst_n, s_rst_n;
|
logic s_por_rst_n, s_rst_n;
|
||||||
logic [16:0] s_leaf_rst_n;
|
logic [16:0] s_leaf_rst_n;
|
||||||
logic s_fabric_rst_n, s_cpu_ready_rst_n;
|
logic s_fabric_rst_n, s_cpu_ready_rst_n;
|
||||||
logic s_master_rst_n [2];
|
logic s_master_rst_n [2];
|
||||||
logic s_target_rst_n [5];
|
logic s_target_rst_n [5];
|
||||||
logic s_debug_halted;
|
logic s_debug_halted;
|
||||||
|
logic s_core_rst_n, s_cpu_bus_idle, s_cpu_quiesce;
|
||||||
logic [31:0] s_irq;
|
logic [31:0] s_irq;
|
||||||
logic s_fault_valid, s_fault_master, s_fault_write;
|
logic s_fault_valid, s_fault_master, s_fault_write;
|
||||||
logic [31:0] s_fault_addr;
|
logic [31:0] s_fault_addr;
|
||||||
@@ -38,6 +39,20 @@ module retrosoc_tiny (
|
|||||||
logic [4:0] s_tick_count_d, s_tick_count_q;
|
logic [4:0] s_tick_count_d, s_tick_count_q;
|
||||||
logic s_tick;
|
logic s_tick;
|
||||||
logic s_unused_uart1_tx_stall, s_unused_uart1_rx_stall;
|
logic s_unused_uart1_tx_stall, s_unused_uart1_rx_stall;
|
||||||
|
ahbl_if u_cpu_ahbl_if[2] (
|
||||||
|
.hclk (clk_i),
|
||||||
|
.hresetn(s_core_rst_n)
|
||||||
|
);
|
||||||
|
tiny_sram_port_if u_cpu_local_if[2] ();
|
||||||
|
axi4_if #(
|
||||||
|
.ADDR_WIDTH(32),
|
||||||
|
.DATA_WIDTH(32),
|
||||||
|
.ID_WIDTH (1),
|
||||||
|
.USER_WIDTH(1)
|
||||||
|
) u_sram_axi4_if[4] (
|
||||||
|
.aclk (clk_i),
|
||||||
|
.aresetn(s_sram_rst_n)
|
||||||
|
);
|
||||||
`include "tiny_apb_interfaces.svh"
|
`include "tiny_apb_interfaces.svh"
|
||||||
uart_if u_uart0_if ();
|
uart_if u_uart0_if ();
|
||||||
uart_if u_uart1_if ();
|
uart_if u_uart1_if ();
|
||||||
@@ -135,11 +150,10 @@ uart_if u_uart0_if ();
|
|||||||
.dat_i (s_tick_count_d),
|
.dat_i (s_tick_count_d),
|
||||||
.dat_o (s_tick_count_q)
|
.dat_o (s_tick_count_q)
|
||||||
);
|
);
|
||||||
mgmt_core_wrapper #(
|
tiny_cpu_wrapper #(
|
||||||
.ExternalIrqCount (30),
|
.ExternalIrqCount(30),
|
||||||
.EnableAtomics (1'b0),
|
.EnableAtomics (1'b0),
|
||||||
.TwoCycleBusErrors(1'b1),
|
.ResetSyncStages (5)
|
||||||
.ResetSyncStages (5)
|
|
||||||
) u_cpu (
|
) u_cpu (
|
||||||
.clk_i (clk_i),
|
.clk_i (clk_i),
|
||||||
.rst_n_i (s_cpu_ready_rst_n),
|
.rst_n_i (s_cpu_ready_rst_n),
|
||||||
@@ -150,7 +164,19 @@ uart_if u_uart0_if ();
|
|||||||
.jtag_trst_n_i (jtag_trst_n_i),
|
.jtag_trst_n_i (jtag_trst_n_i),
|
||||||
.jtag_tdo_o (jtag_tdo_o),
|
.jtag_tdo_o (jtag_tdo_o),
|
||||||
.debug_halted_o(s_debug_halted),
|
.debug_halted_o(s_debug_halted),
|
||||||
.axi4 (u_masters_axi4_if[0])
|
.bus_idle_i (s_cpu_bus_idle),
|
||||||
|
.core_rst_n_o (s_core_rst_n),
|
||||||
|
.quiesce_o (s_cpu_quiesce),
|
||||||
|
.cpu (u_cpu_ahbl_if)
|
||||||
|
);
|
||||||
|
tiny_cpu_mem u_cpu_mem (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i (s_core_rst_n),
|
||||||
|
.quiesce_i (s_cpu_quiesce),
|
||||||
|
.idle_o (s_cpu_bus_idle),
|
||||||
|
.cpu (u_cpu_ahbl_if),
|
||||||
|
.local_ports(u_cpu_local_if),
|
||||||
|
.slow_axi4 (u_masters_axi4_if[0])
|
||||||
);
|
);
|
||||||
tiny_axi4_fabric u_fabric (
|
tiny_axi4_fabric u_fabric (
|
||||||
.clk_i (clk_i),
|
.clk_i (clk_i),
|
||||||
@@ -167,18 +193,20 @@ uart_if u_uart0_if ();
|
|||||||
.cpu_wait_o (s_cpu_wait),
|
.cpu_wait_o (s_cpu_wait),
|
||||||
.dma_wait_o (s_dma_wait)
|
.dma_wait_o (s_dma_wait)
|
||||||
);
|
);
|
||||||
onchip_ram #(
|
tiny_sram_axi4_demux u_sram_attachment (
|
||||||
.CapacityKiB (128),
|
.clk_i (clk_i),
|
||||||
.DataWidth (32),
|
.rst_n_i (s_sram_rst_n),
|
||||||
.IdWidth (1),
|
.upstream(u_targets_axi4_if[0]),
|
||||||
.Ics55SmallBanks(1'b1)
|
.groups (u_sram_axi4_if)
|
||||||
) u_sram (
|
);
|
||||||
.clk_i (clk_i),
|
tiny_sram u_sram (
|
||||||
.rst_n_i (s_sram_rst_n),
|
.clk_i (clk_i),
|
||||||
.mem_axi4 (u_targets_axi4_if[0]),
|
.rst_n_i (s_sram_rst_n),
|
||||||
.cfg_apb4 (u_sram_apb4_if),
|
.local_ports (u_cpu_local_if),
|
||||||
.perf_enable_i(s_perf_enable),
|
.external_ports(u_sram_axi4_if),
|
||||||
.perf_clear_i (s_perf_clear)
|
.cfg_apb4 (u_sram_apb4_if),
|
||||||
|
.perf_enable_i (s_perf_enable),
|
||||||
|
.perf_clear_i (s_perf_clear)
|
||||||
);
|
);
|
||||||
apb4_xpi u_xpi (
|
apb4_xpi u_xpi (
|
||||||
.clk_i (clk_i),
|
.clk_i (clk_i),
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ module tiny_axi4_fabric (
|
|||||||
logic [32:0] s_last_addr;
|
logic [32:0] s_last_addr;
|
||||||
logic [ 2:0] s_decoded_target;
|
logic [ 2:0] s_decoded_target;
|
||||||
logic s_protocol_legal;
|
logic s_protocol_legal;
|
||||||
|
logic s_fetch_denied;
|
||||||
logic [1:0] s_awvalid, s_arvalid, s_wvalid, s_wlast, s_bready, s_rready;
|
logic [1:0] s_awvalid, s_arvalid, s_wvalid, s_wlast, s_bready, s_rready;
|
||||||
logic [1:0][31:0] s_wdata;
|
logic [1:0][31:0] s_wdata;
|
||||||
logic [1:0][ 3:0] s_wstrb;
|
logic [1:0][ 3:0] s_wstrb;
|
||||||
@@ -90,6 +91,11 @@ module tiny_axi4_fabric (
|
|||||||
s_last_addr[31:0]
|
s_last_addr[31:0]
|
||||||
) == s_decoded_target) && ((s_decoded_target != 3'd2) || (s_selected_addr.len == 8'd0));
|
) == s_decoded_target) && ((s_decoded_target != 3'd2) || (s_selected_addr.len == 8'd0));
|
||||||
assign s_accept = (s_state_q == Idle) && s_selected_valid;
|
assign s_accept = (s_state_q == Idle) && s_selected_valid;
|
||||||
|
// The tagged CPU slow ingress preserves instruction origin. Reject fetches
|
||||||
|
// outside the read-idempotent NOR apertures before reaching any APB target.
|
||||||
|
assign s_fetch_denied = !s_selected[1] && !s_selected[0] && s_selected_addr.prot[2] && !(
|
||||||
|
`SOC_ADDR_IS_FLASH(s_selected_addr.addr)
|
||||||
|
|| ((s_selected_addr.addr >= 32'h5000_0000) && (s_selected_addr.addr < 32'h5400_0000)));
|
||||||
assign s_terminal = (s_state_q == Response) &&
|
assign s_terminal = (s_state_q == Response) &&
|
||||||
(s_write_q ? (s_target_bvalid[s_tgt_q] && s_bready[s_owner_q]) :
|
(s_write_q ? (s_target_bvalid[s_tgt_q] && s_bready[s_owner_q]) :
|
||||||
(s_target_rvalid[s_tgt_q] && s_rready[s_owner_q] && s_target_rlast[s_tgt_q]));
|
(s_target_rvalid[s_tgt_q] && s_rready[s_owner_q] && s_target_rlast[s_tgt_q]));
|
||||||
@@ -126,8 +132,9 @@ module tiny_axi4_fabric (
|
|||||||
if (s_accept) begin
|
if (s_accept) begin
|
||||||
s_owner_d = s_selected[1];
|
s_owner_d = s_selected[1];
|
||||||
s_write_d = s_selected[0];
|
s_write_d = s_selected[0];
|
||||||
s_addr_d = s_selected_addr;
|
s_addr_d = s_selected_addr;
|
||||||
s_tgt_d = s_protocol_legal ? s_decoded_target : 3'd4;
|
s_tgt_d = !s_protocol_legal ? 3'd4 :
|
||||||
|
s_fetch_denied ? ((s_decoded_target == 3'd2) ? 3'd4 : 3'd3) : s_decoded_target;
|
||||||
s_state_d = Addr;
|
s_state_d = Addr;
|
||||||
end
|
end
|
||||||
Addr:
|
Addr:
|
||||||
|
|||||||
@@ -0,0 +1,259 @@
|
|||||||
|
// Copyright (c) 2026 Yuchi Miao
|
||||||
|
// SPDX-License-Identifier: MulanPSL-2.0
|
||||||
|
`include "mmap_define.svh"
|
||||||
|
|
||||||
|
// Independent one-operation I/D frontends. AHB write data belongs to the cycle
|
||||||
|
// following address acceptance. Local stores complete only after macro commit.
|
||||||
|
// Only non-SRAM work enters the tagged, non-posted slow-path merge.
|
||||||
|
module tiny_cpu_mem (
|
||||||
|
input logic clk_i,
|
||||||
|
input logic rst_n_i,
|
||||||
|
input logic quiesce_i,
|
||||||
|
output logic idle_o,
|
||||||
|
ahbl_if.slave cpu [2],
|
||||||
|
tiny_sram_port_if.master local_ports[2],
|
||||||
|
axi4_if.master slow_axi4
|
||||||
|
);
|
||||||
|
typedef enum logic [2:0] {
|
||||||
|
Idle,
|
||||||
|
WriteCapture,
|
||||||
|
LocalIssue,
|
||||||
|
LocalWait,
|
||||||
|
SlowIssue,
|
||||||
|
SlowWait,
|
||||||
|
ErrorFirst,
|
||||||
|
ErrorLast
|
||||||
|
} state_e;
|
||||||
|
typedef struct packed {
|
||||||
|
state_e state;
|
||||||
|
logic [31:0] addr, data;
|
||||||
|
logic [2:0] size;
|
||||||
|
logic write;
|
||||||
|
logic [3:0] strb;
|
||||||
|
} request_t;
|
||||||
|
request_t s_req_d[2], s_req_q[2];
|
||||||
|
logic [1:0] s_terminal, s_err, s_slow_req, s_slow_grant, s_front_idle;
|
||||||
|
logic s_slow_selected, s_slow_valid, s_slow_accept;
|
||||||
|
typedef enum logic [1:0] {
|
||||||
|
SlowIdle,
|
||||||
|
SlowAddress,
|
||||||
|
SlowData,
|
||||||
|
SlowResponse
|
||||||
|
} slow_state_e;
|
||||||
|
typedef struct packed {
|
||||||
|
slow_state_e state;
|
||||||
|
logic owner, write, error;
|
||||||
|
logic [2:0] size;
|
||||||
|
logic [31:0] addr, data, rdata;
|
||||||
|
} slow_t;
|
||||||
|
slow_t s_slow_d, s_slow_q;
|
||||||
|
ahbl_if u_slow_ahbl_if (
|
||||||
|
.hclk (clk_i),
|
||||||
|
.hresetn(rst_n_i)
|
||||||
|
);
|
||||||
|
axi4_if #(
|
||||||
|
.ADDR_WIDTH(32),
|
||||||
|
.DATA_WIDTH(32),
|
||||||
|
.ID_WIDTH (1),
|
||||||
|
.USER_WIDTH(1)
|
||||||
|
) u_slow_axi4_if (
|
||||||
|
.aclk (clk_i),
|
||||||
|
.aresetn(rst_n_i)
|
||||||
|
);
|
||||||
|
logic s_adapter_idle;
|
||||||
|
|
||||||
|
for (genvar port_index = 0; port_index < 2; port_index++) begin : gen_port
|
||||||
|
assign s_slow_req[port_index] = s_req_q[port_index].state == SlowIssue;
|
||||||
|
assign s_front_idle[port_index] = (s_req_q[port_index].state == Idle) &&
|
||||||
|
(quiesce_i || !cpu[port_index].htrans[1]);
|
||||||
|
assign s_err[port_index] = (s_req_q[port_index].state == ErrorFirst) ||
|
||||||
|
(s_req_q[port_index].state == ErrorLast) ||
|
||||||
|
((s_req_q[port_index].state == SlowWait) &&
|
||||||
|
(s_slow_q.state == SlowResponse) && (s_slow_q.owner == 1'(port_index)) && s_slow_q.error);
|
||||||
|
assign s_terminal[port_index] = (s_req_q[port_index].state == ErrorLast) ||
|
||||||
|
((s_req_q[port_index].state == LocalWait) && local_ports[port_index].rsp_valid) ||
|
||||||
|
((s_req_q[port_index].state == SlowWait) &&
|
||||||
|
(s_slow_q.state == SlowResponse) && (s_slow_q.owner == 1'(port_index)) && !s_slow_q.error);
|
||||||
|
assign cpu[port_index].hready =
|
||||||
|
((s_req_q[port_index].state == Idle) && !quiesce_i) || s_terminal[port_index];
|
||||||
|
assign cpu[port_index].hresp = s_err[port_index];
|
||||||
|
assign cpu[port_index].hrdata = (s_req_q[port_index].state == LocalWait) ?
|
||||||
|
local_ports[port_index].rdata : s_slow_q.rdata;
|
||||||
|
assign local_ports[port_index].req_valid = s_req_q[port_index].state == LocalIssue;
|
||||||
|
assign local_ports[port_index].addr = s_req_q[port_index].addr;
|
||||||
|
assign local_ports[port_index].write = s_req_q[port_index].write;
|
||||||
|
assign local_ports[port_index].wdata = s_req_q[port_index].data;
|
||||||
|
assign local_ports[port_index].wstrb = s_req_q[port_index].strb;
|
||||||
|
assign local_ports[port_index].rsp_ready = s_req_q[port_index].state == LocalWait;
|
||||||
|
|
||||||
|
always_comb begin
|
||||||
|
s_req_d[port_index] = s_req_q[port_index];
|
||||||
|
unique case (s_req_q[port_index].state)
|
||||||
|
WriteCapture: begin
|
||||||
|
unique case (s_req_q[port_index].size)
|
||||||
|
3'd0: begin
|
||||||
|
s_req_d[port_index].data = {24'd0, cpu[port_index].hwdata[7:0]} <<
|
||||||
|
(8 * s_req_q[port_index].addr[1:0]);
|
||||||
|
s_req_d[port_index].strb = 4'b0001 << s_req_q[port_index].addr[1:0];
|
||||||
|
end
|
||||||
|
3'd1: begin
|
||||||
|
s_req_d[port_index].data = {16'd0, cpu[port_index].hwdata[15:0]} <<
|
||||||
|
(8 * s_req_q[port_index].addr[1:0]);
|
||||||
|
s_req_d[port_index].strb = 4'b0011 << s_req_q[port_index].addr[1:0];
|
||||||
|
end
|
||||||
|
default: begin
|
||||||
|
s_req_d[port_index].data = cpu[port_index].hwdata;
|
||||||
|
s_req_d[port_index].strb = 4'b1111;
|
||||||
|
end
|
||||||
|
endcase
|
||||||
|
s_req_d[port_index].state =
|
||||||
|
`SOC_ADDR_IS_SRAM(s_req_q[port_index].addr)
|
||||||
|
? LocalIssue : SlowIssue;
|
||||||
|
end
|
||||||
|
LocalIssue: if (local_ports[port_index].req_ready) s_req_d[port_index].state = LocalWait;
|
||||||
|
SlowIssue:
|
||||||
|
if (s_slow_accept && s_slow_grant[port_index]) s_req_d[port_index].state = SlowWait;
|
||||||
|
SlowWait: if (s_err[port_index]) s_req_d[port_index].state = ErrorLast;
|
||||||
|
ErrorFirst: s_req_d[port_index].state = ErrorLast;
|
||||||
|
default: begin
|
||||||
|
end
|
||||||
|
endcase
|
||||||
|
if (cpu[port_index].hready) begin
|
||||||
|
s_req_d[port_index].state = Idle;
|
||||||
|
// Capture even a terminal-cycle overlap during quiesce: that address
|
||||||
|
// was acknowledged by HREADY and cannot be silently discarded.
|
||||||
|
if (cpu[port_index].htrans[1]) begin
|
||||||
|
s_req_d[port_index].addr = cpu[port_index].haddr;
|
||||||
|
s_req_d[port_index].size = cpu[port_index].hsize;
|
||||||
|
s_req_d[port_index].write = cpu[port_index].hwrite;
|
||||||
|
s_req_d[port_index].strb = '0;
|
||||||
|
if ((cpu[port_index].htrans != 2'b10) || cpu[port_index].hmastlock ||
|
||||||
|
(cpu[port_index].hsize > 3'd2) ||
|
||||||
|
((cpu[port_index].haddr & ((32'd1 << cpu[port_index].hsize) - 32'd1)) != 32'd0) ||
|
||||||
|
((port_index == 0) && (cpu[port_index].hwrite || (cpu[port_index].hsize != 3'd2))))
|
||||||
|
s_req_d[port_index].state = ErrorFirst;
|
||||||
|
else if (cpu[port_index].hwrite) s_req_d[port_index].state = WriteCapture;
|
||||||
|
else
|
||||||
|
s_req_d[port_index].state =
|
||||||
|
`SOC_ADDR_IS_SRAM(cpu[port_index].haddr)
|
||||||
|
? LocalIssue : SlowIssue;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
dffr #(
|
||||||
|
.DATA_WIDTH($bits(request_t))
|
||||||
|
) u_request (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i(rst_n_i),
|
||||||
|
.dat_i (s_req_d[port_index]),
|
||||||
|
.dat_o (s_req_q[port_index])
|
||||||
|
);
|
||||||
|
end
|
||||||
|
|
||||||
|
assign idle_o = (&s_front_idle) && (s_slow_q.state == SlowIdle) && s_adapter_idle;
|
||||||
|
assign s_slow_accept = (s_slow_q.state == SlowIdle) && s_slow_valid;
|
||||||
|
round_robin_arbiter #(
|
||||||
|
.CLIENTS(2)
|
||||||
|
) u_slow_arbiter (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i (rst_n_i),
|
||||||
|
.advance_i (s_slow_accept),
|
||||||
|
.request_i (s_slow_req),
|
||||||
|
.grant_o (s_slow_grant),
|
||||||
|
.selected_o(s_slow_selected),
|
||||||
|
.valid_o (s_slow_valid)
|
||||||
|
);
|
||||||
|
always_comb begin
|
||||||
|
s_slow_d = s_slow_q;
|
||||||
|
unique case (s_slow_q.state)
|
||||||
|
SlowIdle:
|
||||||
|
if (s_slow_accept) begin
|
||||||
|
s_slow_d.owner = s_slow_selected;
|
||||||
|
s_slow_d.addr = s_req_q[s_slow_selected].addr;
|
||||||
|
s_slow_d.write = s_req_q[s_slow_selected].write;
|
||||||
|
s_slow_d.size = s_req_q[s_slow_selected].size;
|
||||||
|
// The shared AHB adapter applies lane shifting itself.
|
||||||
|
s_slow_d.data = s_req_q[s_slow_selected].data >> (8 * s_req_q[s_slow_selected].addr[1:0]);
|
||||||
|
s_slow_d.state = SlowAddress;
|
||||||
|
end
|
||||||
|
SlowAddress: if (u_slow_ahbl_if.hready) s_slow_d.state = SlowData;
|
||||||
|
SlowData:
|
||||||
|
if (u_slow_ahbl_if.hready) begin
|
||||||
|
s_slow_d.rdata = u_slow_ahbl_if.hrdata;
|
||||||
|
s_slow_d.error = u_slow_ahbl_if.hresp;
|
||||||
|
s_slow_d.state = SlowResponse;
|
||||||
|
end
|
||||||
|
SlowResponse: s_slow_d.state = SlowIdle;
|
||||||
|
default: s_slow_d.state = SlowIdle;
|
||||||
|
endcase
|
||||||
|
end
|
||||||
|
dffr #(
|
||||||
|
.DATA_WIDTH($bits(slow_t))
|
||||||
|
) u_slow_state (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i(rst_n_i),
|
||||||
|
.dat_i (s_slow_d),
|
||||||
|
.dat_o (s_slow_q)
|
||||||
|
);
|
||||||
|
assign u_slow_ahbl_if.haddr = s_slow_q.addr;
|
||||||
|
assign u_slow_ahbl_if.hwrite = s_slow_q.write;
|
||||||
|
assign u_slow_ahbl_if.hsize = s_slow_q.size;
|
||||||
|
assign u_slow_ahbl_if.hwdata = s_slow_q.data;
|
||||||
|
assign u_slow_ahbl_if.htrans = (s_slow_q.state == SlowAddress) ? 2'b10 : 2'b00;
|
||||||
|
assign u_slow_ahbl_if.hburst = '0;
|
||||||
|
assign u_slow_ahbl_if.hprot = {3'b001, s_slow_q.owner};
|
||||||
|
assign u_slow_ahbl_if.hmastlock = 1'b0;
|
||||||
|
ahbl2axi4 #(
|
||||||
|
.TwoCycleErrors(1'b1)
|
||||||
|
) u_slow_adapter (
|
||||||
|
.ahbl (u_slow_ahbl_if),
|
||||||
|
.axi4 (u_slow_axi4_if),
|
||||||
|
.idle_o(s_adapter_idle)
|
||||||
|
);
|
||||||
|
// Forward every channel unchanged except the saved instruction attribute.
|
||||||
|
// Tiny's fabric uses it to reject fetches before peripheral side effects.
|
||||||
|
assign slow_axi4.awid = u_slow_axi4_if.awid;
|
||||||
|
assign slow_axi4.awaddr = u_slow_axi4_if.awaddr;
|
||||||
|
assign slow_axi4.awlen = u_slow_axi4_if.awlen;
|
||||||
|
assign slow_axi4.awsize = u_slow_axi4_if.awsize;
|
||||||
|
assign slow_axi4.awburst = u_slow_axi4_if.awburst;
|
||||||
|
assign slow_axi4.awlock = u_slow_axi4_if.awlock;
|
||||||
|
assign slow_axi4.awcache = u_slow_axi4_if.awcache;
|
||||||
|
assign slow_axi4.awprot = {!s_slow_q.owner, u_slow_axi4_if.awprot[1:0]};
|
||||||
|
assign slow_axi4.awqos = u_slow_axi4_if.awqos;
|
||||||
|
assign slow_axi4.awregion = u_slow_axi4_if.awregion;
|
||||||
|
assign slow_axi4.awuser = u_slow_axi4_if.awuser;
|
||||||
|
assign slow_axi4.awvalid = u_slow_axi4_if.awvalid;
|
||||||
|
assign slow_axi4.arid = u_slow_axi4_if.arid;
|
||||||
|
assign slow_axi4.araddr = u_slow_axi4_if.araddr;
|
||||||
|
assign slow_axi4.arlen = u_slow_axi4_if.arlen;
|
||||||
|
assign slow_axi4.arsize = u_slow_axi4_if.arsize;
|
||||||
|
assign slow_axi4.arburst = u_slow_axi4_if.arburst;
|
||||||
|
assign slow_axi4.arlock = u_slow_axi4_if.arlock;
|
||||||
|
assign slow_axi4.arcache = u_slow_axi4_if.arcache;
|
||||||
|
assign slow_axi4.arprot = {!s_slow_q.owner, u_slow_axi4_if.arprot[1:0]};
|
||||||
|
assign slow_axi4.arqos = u_slow_axi4_if.arqos;
|
||||||
|
assign slow_axi4.arregion = u_slow_axi4_if.arregion;
|
||||||
|
assign slow_axi4.aruser = u_slow_axi4_if.aruser;
|
||||||
|
assign slow_axi4.arvalid = u_slow_axi4_if.arvalid;
|
||||||
|
assign slow_axi4.wdata = u_slow_axi4_if.wdata;
|
||||||
|
assign slow_axi4.wstrb = u_slow_axi4_if.wstrb;
|
||||||
|
assign slow_axi4.wlast = u_slow_axi4_if.wlast;
|
||||||
|
assign slow_axi4.wuser = u_slow_axi4_if.wuser;
|
||||||
|
assign slow_axi4.wvalid = u_slow_axi4_if.wvalid;
|
||||||
|
assign slow_axi4.rready = u_slow_axi4_if.rready;
|
||||||
|
assign slow_axi4.bready = u_slow_axi4_if.bready;
|
||||||
|
assign u_slow_axi4_if.awready = slow_axi4.awready;
|
||||||
|
assign u_slow_axi4_if.arready = slow_axi4.arready;
|
||||||
|
assign u_slow_axi4_if.wready = slow_axi4.wready;
|
||||||
|
assign u_slow_axi4_if.rvalid = slow_axi4.rvalid;
|
||||||
|
assign u_slow_axi4_if.rid = slow_axi4.rid;
|
||||||
|
assign u_slow_axi4_if.rdata = slow_axi4.rdata;
|
||||||
|
assign u_slow_axi4_if.rresp = slow_axi4.rresp;
|
||||||
|
assign u_slow_axi4_if.rlast = slow_axi4.rlast;
|
||||||
|
assign u_slow_axi4_if.ruser = slow_axi4.ruser;
|
||||||
|
assign u_slow_axi4_if.bvalid = slow_axi4.bvalid;
|
||||||
|
assign u_slow_axi4_if.bid = slow_axi4.bid;
|
||||||
|
assign u_slow_axi4_if.bresp = slow_axi4.bresp;
|
||||||
|
assign u_slow_axi4_if.buser = slow_axi4.buser;
|
||||||
|
endmodule
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
// Copyright (c) 2023-2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
|
||||||
|
// retroSoC is licensed under Mulan PSL v2.
|
||||||
|
// You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
// You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
// http://license.coscl.org.cn/MulanPSL2
|
||||||
|
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
// See the Mulan PSL v2 for more details.
|
||||||
|
|
||||||
|
`include "mmap_define.svh"
|
||||||
|
|
||||||
|
module tiny_cpu_wrapper #(
|
||||||
|
parameter int ExternalIrqCount = 30,
|
||||||
|
parameter bit EnableAtomics = 1'b0,
|
||||||
|
parameter int ResetSyncStages = 3
|
||||||
|
) (
|
||||||
|
// verilog_format: off -- preserve reviewed column alignment
|
||||||
|
input logic clk_i,
|
||||||
|
input logic rst_n_i,
|
||||||
|
input logic [ExternalIrqCount+1:0] irq_i,
|
||||||
|
input logic jtag_tck_i,
|
||||||
|
input logic jtag_tms_i,
|
||||||
|
input logic jtag_tdi_i,
|
||||||
|
input logic jtag_trst_n_i,
|
||||||
|
output logic jtag_tdo_o,
|
||||||
|
output logic debug_halted_o,
|
||||||
|
input logic bus_idle_i,
|
||||||
|
output logic core_rst_n_o,
|
||||||
|
output logic quiesce_o,
|
||||||
|
ahbl_if.master cpu[2]
|
||||||
|
// verilog_format: on
|
||||||
|
);
|
||||||
|
|
||||||
|
logic s_pwrup_req;
|
||||||
|
logic s_reset_pending;
|
||||||
|
logic s_hart_idle;
|
||||||
|
logic s_core_rst_n;
|
||||||
|
logic s_dbg_req_halt;
|
||||||
|
logic s_dbg_req_halt_on_reset;
|
||||||
|
logic s_dbg_req_resume;
|
||||||
|
logic s_dbg_halted;
|
||||||
|
logic s_dbg_running;
|
||||||
|
logic [31:0] s_dbg_data0_rdata;
|
||||||
|
logic [31:0] s_dbg_data0_wdata;
|
||||||
|
logic s_dbg_data0_wen;
|
||||||
|
logic [31:0] s_dbg_instr_data;
|
||||||
|
logic s_dbg_instr_data_vld;
|
||||||
|
logic s_dbg_instr_data_rdy;
|
||||||
|
logic s_dbg_instr_caught_exception;
|
||||||
|
logic s_dbg_instr_caught_ebreak;
|
||||||
|
logic [31:0] s_dbg_sbus_addr;
|
||||||
|
logic s_dbg_sbus_write;
|
||||||
|
logic [ 1:0] s_dbg_sbus_size;
|
||||||
|
logic s_dbg_sbus_vld;
|
||||||
|
logic s_dbg_sbus_rdy;
|
||||||
|
logic s_dbg_sbus_err;
|
||||||
|
logic [31:0] s_dbg_sbus_wdata;
|
||||||
|
logic [31:0] s_dbg_sbus_rdata;
|
||||||
|
|
||||||
|
assign debug_halted_o = s_dbg_halted;
|
||||||
|
assign core_rst_n_o = s_core_rst_n;
|
||||||
|
// Request a normal debug halt before draining/resetting the core. Never cut
|
||||||
|
// off an accepted AHB transfer to manufacture an idle indication.
|
||||||
|
assign quiesce_o = s_reset_pending && s_dbg_halted;
|
||||||
|
assign s_hart_idle = bus_idle_i && (!s_reset_pending || s_dbg_halted);
|
||||||
|
|
||||||
|
mgmt_debug_wrapper #(
|
||||||
|
.JtagIdcode (`SOC_JTAG_IDCODE),
|
||||||
|
.ResetSyncStages(ResetSyncStages)
|
||||||
|
) u_mgmt_debug_wrapper (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i (rst_n_i),
|
||||||
|
.bridge_idle_i (s_hart_idle),
|
||||||
|
.jtag_tck_i (jtag_tck_i),
|
||||||
|
.jtag_tms_i (jtag_tms_i),
|
||||||
|
.jtag_tdi_i (jtag_tdi_i),
|
||||||
|
.jtag_trst_n_i (jtag_trst_n_i),
|
||||||
|
.jtag_tdo_o (jtag_tdo_o),
|
||||||
|
.core_rst_n_o (s_core_rst_n),
|
||||||
|
.reset_pending_o (s_reset_pending),
|
||||||
|
.dbg_req_halt_o (s_dbg_req_halt),
|
||||||
|
.dbg_req_halt_on_reset_o (s_dbg_req_halt_on_reset),
|
||||||
|
.dbg_req_resume_o (s_dbg_req_resume),
|
||||||
|
.dbg_halted_i (s_dbg_halted),
|
||||||
|
.dbg_running_i (s_dbg_running),
|
||||||
|
.dbg_data0_rdata_o (s_dbg_data0_rdata),
|
||||||
|
.dbg_data0_wdata_i (s_dbg_data0_wdata),
|
||||||
|
.dbg_data0_wen_i (s_dbg_data0_wen),
|
||||||
|
.dbg_instr_data_o (s_dbg_instr_data),
|
||||||
|
.dbg_instr_data_vld_o (s_dbg_instr_data_vld),
|
||||||
|
.dbg_instr_data_rdy_i (s_dbg_instr_data_rdy),
|
||||||
|
.dbg_instr_caught_exception_i(s_dbg_instr_caught_exception),
|
||||||
|
.dbg_instr_caught_ebreak_i (s_dbg_instr_caught_ebreak),
|
||||||
|
.dbg_sbus_addr_o (s_dbg_sbus_addr),
|
||||||
|
.dbg_sbus_write_o (s_dbg_sbus_write),
|
||||||
|
.dbg_sbus_size_o (s_dbg_sbus_size),
|
||||||
|
.dbg_sbus_vld_o (s_dbg_sbus_vld),
|
||||||
|
.dbg_sbus_rdy_i (s_dbg_sbus_rdy),
|
||||||
|
.dbg_sbus_err_i (s_dbg_sbus_err),
|
||||||
|
.dbg_sbus_wdata_o (s_dbg_sbus_wdata),
|
||||||
|
.dbg_sbus_rdata_i (s_dbg_sbus_rdata)
|
||||||
|
);
|
||||||
|
|
||||||
|
hazard3_cpu_2port #(
|
||||||
|
.RESET_VECTOR (`SOC_CPU_RESET_ADDR),
|
||||||
|
.MTVEC_INIT (32'h0000_0000),
|
||||||
|
.EXTENSION_A (EnableAtomics),
|
||||||
|
.EXTENSION_C (1),
|
||||||
|
.EXTENSION_E (0),
|
||||||
|
.EXTENSION_M (1),
|
||||||
|
.EXTENSION_ZBA (1),
|
||||||
|
.EXTENSION_ZBB (1),
|
||||||
|
.EXTENSION_ZBC (1),
|
||||||
|
.EXTENSION_ZBKB (1),
|
||||||
|
.EXTENSION_ZBKX (1),
|
||||||
|
.EXTENSION_ZBS (1),
|
||||||
|
.EXTENSION_ZCB (0),
|
||||||
|
.EXTENSION_ZCLSD (0),
|
||||||
|
.EXTENSION_ZCMP (0),
|
||||||
|
.EXTENSION_ZIFENCEI (1),
|
||||||
|
.EXTENSION_ZILSD (1),
|
||||||
|
.EXTENSION_XH3BEXTM (1),
|
||||||
|
.EXTENSION_XH3IRQ (1),
|
||||||
|
.EXTENSION_XH3PMPM (0),
|
||||||
|
.EXTENSION_XH3POWER (0),
|
||||||
|
.CSR_M_MANDATORY (1),
|
||||||
|
.CSR_M_TRAP (1),
|
||||||
|
.CSR_COUNTER (1),
|
||||||
|
.U_MODE (0),
|
||||||
|
.PMP_REGIONS (0),
|
||||||
|
.PMP_GRAIN (0),
|
||||||
|
.PMP_MATCH_NAPOT (1),
|
||||||
|
.PMP_MATCH_TOR (0),
|
||||||
|
.PMP_HARDWIRED (0),
|
||||||
|
.PMP_HARDWIRED_ADDR (0),
|
||||||
|
.PMP_HARDWIRED_CFG (0),
|
||||||
|
.DEBUG_SUPPORT (1),
|
||||||
|
.BREAKPOINT_TRIGGERS(2),
|
||||||
|
.NUM_IRQS (ExternalIrqCount),
|
||||||
|
.IRQ_PRIORITY_BITS (2),
|
||||||
|
.IRQ_INPUT_BYPASS ({ExternalIrqCount{1'b0}}),
|
||||||
|
.MVENDORID_VAL (32'h0),
|
||||||
|
.MCONFIGPTR_VAL (32'h0),
|
||||||
|
.REDUCED_BYPASS (0),
|
||||||
|
.MULDIV_UNROLL (2),
|
||||||
|
.MUL_FAST (1),
|
||||||
|
.MUL_FASTER (1),
|
||||||
|
.MULH_FAST (1),
|
||||||
|
.FAST_BRANCHCMP (1),
|
||||||
|
.RESET_REGFILE (1),
|
||||||
|
.BRANCH_PREDICTOR (1),
|
||||||
|
.MTVEC_WMASK (32'hfffffffd)
|
||||||
|
) u_hazard3_cpu_2port (
|
||||||
|
.clk (clk_i),
|
||||||
|
.clk_always_on (clk_i),
|
||||||
|
.rst_n (s_core_rst_n),
|
||||||
|
.pwrup_req (s_pwrup_req),
|
||||||
|
.pwrup_ack (s_pwrup_req),
|
||||||
|
.clk_en (),
|
||||||
|
.unblock_out (),
|
||||||
|
.unblock_in (1'b0),
|
||||||
|
.i_haddr (cpu[0].haddr),
|
||||||
|
.i_hwrite (cpu[0].hwrite),
|
||||||
|
.i_htrans (cpu[0].htrans),
|
||||||
|
.i_hsize (cpu[0].hsize),
|
||||||
|
.i_hburst (cpu[0].hburst),
|
||||||
|
.i_hprot (cpu[0].hprot),
|
||||||
|
.i_hmastlock (cpu[0].hmastlock),
|
||||||
|
.i_hmaster (),
|
||||||
|
.i_hready (cpu[0].hready),
|
||||||
|
.i_hresp (cpu[0].hresp),
|
||||||
|
.i_hwdata (cpu[0].hwdata),
|
||||||
|
.i_hrdata (cpu[0].hrdata),
|
||||||
|
.d_haddr (cpu[1].haddr),
|
||||||
|
.d_hwrite (cpu[1].hwrite),
|
||||||
|
.d_htrans (cpu[1].htrans),
|
||||||
|
.d_hsize (cpu[1].hsize),
|
||||||
|
.d_hburst (cpu[1].hburst),
|
||||||
|
.d_hprot (cpu[1].hprot),
|
||||||
|
.d_hmastlock (cpu[1].hmastlock),
|
||||||
|
.d_hmaster (),
|
||||||
|
.d_hready (cpu[1].hready),
|
||||||
|
.d_hresp (cpu[1].hresp),
|
||||||
|
.d_hwdata (cpu[1].hwdata),
|
||||||
|
.d_hrdata (cpu[1].hrdata),
|
||||||
|
.d_hexcl (),
|
||||||
|
.d_hexokay (1'b1),
|
||||||
|
.fence_i_vld (),
|
||||||
|
.fence_d_vld (),
|
||||||
|
.fence_rdy (1'b1),
|
||||||
|
.dbg_req_halt (s_dbg_req_halt || s_reset_pending),
|
||||||
|
.dbg_req_halt_on_reset (s_dbg_req_halt_on_reset),
|
||||||
|
.dbg_req_resume (s_dbg_req_resume && !s_reset_pending),
|
||||||
|
.dbg_halted (s_dbg_halted),
|
||||||
|
.dbg_running (s_dbg_running),
|
||||||
|
.dbg_data0_rdata (s_dbg_data0_rdata),
|
||||||
|
.dbg_data0_wdata (s_dbg_data0_wdata),
|
||||||
|
.dbg_data0_wen (s_dbg_data0_wen),
|
||||||
|
.dbg_instr_data (s_dbg_instr_data),
|
||||||
|
.dbg_instr_data_vld (s_dbg_instr_data_vld && !s_reset_pending),
|
||||||
|
.dbg_instr_data_rdy (s_dbg_instr_data_rdy),
|
||||||
|
.dbg_instr_caught_exception(s_dbg_instr_caught_exception),
|
||||||
|
.dbg_instr_caught_ebreak (s_dbg_instr_caught_ebreak),
|
||||||
|
.dbg_sbus_addr (s_dbg_sbus_addr),
|
||||||
|
.dbg_sbus_write (s_dbg_sbus_write),
|
||||||
|
.dbg_sbus_size (s_dbg_sbus_size),
|
||||||
|
.dbg_sbus_vld (s_dbg_sbus_vld),
|
||||||
|
.dbg_sbus_rdy (s_dbg_sbus_rdy),
|
||||||
|
.dbg_sbus_err (s_dbg_sbus_err),
|
||||||
|
.dbg_sbus_wdata (s_dbg_sbus_wdata),
|
||||||
|
.dbg_sbus_rdata (s_dbg_sbus_rdata),
|
||||||
|
.mhartid_val ('0),
|
||||||
|
.eco_version ('0),
|
||||||
|
.irq (irq_i[ExternalIrqCount+1:2]),
|
||||||
|
.soft_irq (irq_i[0]),
|
||||||
|
.timer_irq (irq_i[1])
|
||||||
|
);
|
||||||
|
endmodule
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
// Copyright (c) 2026 Yuchi Miao
|
||||||
|
// SPDX-License-Identifier: MulanPSL-2.0
|
||||||
|
|
||||||
|
module tiny_sram (
|
||||||
|
input logic clk_i,
|
||||||
|
input logic rst_n_i,
|
||||||
|
input logic perf_enable_i,
|
||||||
|
input logic perf_clear_i,
|
||||||
|
tiny_sram_port_if.slave local_ports [2],
|
||||||
|
axi4_if.slave external_ports[4],
|
||||||
|
apb4_if.slave cfg_apb4
|
||||||
|
);
|
||||||
|
logic [3:0] s_local_ready[2], s_local_valid[2];
|
||||||
|
logic [31:0] s_local_data[2] [4];
|
||||||
|
logic [ 3:0] s_events [6];
|
||||||
|
logic [31:0] s_count_d[6], s_count_q[6];
|
||||||
|
logic [ 2:0] s_increment[6];
|
||||||
|
logic [32:0] s_sum [6];
|
||||||
|
|
||||||
|
for (genvar group = 0; group < 4; group++) begin : gen_group
|
||||||
|
tiny_sram_port_if u_ports[3] ();
|
||||||
|
for (genvar client = 0; client < 2; client++) begin : gen_local
|
||||||
|
assign u_ports[client].req_valid = local_ports[client].req_valid &&
|
||||||
|
(local_ports[client].addr[16:15] == 2'(group));
|
||||||
|
assign u_ports[client].addr = local_ports[client].addr;
|
||||||
|
assign u_ports[client].write = local_ports[client].write;
|
||||||
|
assign u_ports[client].wdata = local_ports[client].wdata;
|
||||||
|
assign u_ports[client].wstrb = local_ports[client].wstrb;
|
||||||
|
assign u_ports[client].rsp_ready = local_ports[client].rsp_ready &&
|
||||||
|
(local_ports[client].addr[16:15] == 2'(group));
|
||||||
|
assign s_local_ready[client][group] = u_ports[client].req_ready;
|
||||||
|
assign s_local_valid[client][group] = u_ports[client].rsp_valid;
|
||||||
|
assign s_local_data[client][group] = u_ports[client].rdata;
|
||||||
|
end
|
||||||
|
tiny_sram_group u_group (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i(rst_n_i),
|
||||||
|
.ports (u_ports)
|
||||||
|
);
|
||||||
|
tiny_sram_axi4 #(
|
||||||
|
.GroupBase(32'h3000_0000 + 32'(group) * 32'h8000)
|
||||||
|
) u_external (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i(rst_n_i),
|
||||||
|
.axi4 (external_ports[group]),
|
||||||
|
.memory (u_ports[2])
|
||||||
|
);
|
||||||
|
assign s_events[0][group] = external_ports[group].arvalid && external_ports[group].arready;
|
||||||
|
assign s_events[1][group] = external_ports[group].awvalid && external_ports[group].awready;
|
||||||
|
assign s_events[2][group] = external_ports[group].rvalid && external_ports[group].rready;
|
||||||
|
assign s_events[3][group] = external_ports[group].wvalid && external_ports[group].wready;
|
||||||
|
assign s_events[4][group] =
|
||||||
|
(external_ports[group].arvalid && !external_ports[group].arready) ||
|
||||||
|
(external_ports[group].awvalid && !external_ports[group].awready) ||
|
||||||
|
(external_ports[group].wvalid && !external_ports[group].wready) ||
|
||||||
|
(external_ports[group].rvalid && !external_ports[group].rready) ||
|
||||||
|
(external_ports[group].bvalid && !external_ports[group].bready);
|
||||||
|
assign s_events[5][group] =
|
||||||
|
(external_ports[group].bvalid && external_ports[group].bready &&
|
||||||
|
(external_ports[group].bresp != 2'b00)) ||
|
||||||
|
(external_ports[group].rvalid && external_ports[group].rready &&
|
||||||
|
external_ports[group].rlast && (external_ports[group].rresp != 2'b00));
|
||||||
|
end
|
||||||
|
for (genvar client = 0; client < 2; client++) begin : gen_local_response
|
||||||
|
assign local_ports[client].req_ready = s_local_ready[client][local_ports[client].addr[16:15]];
|
||||||
|
assign local_ports[client].rsp_valid = s_local_valid[client][local_ports[client].addr[16:15]];
|
||||||
|
assign local_ports[client].rdata = s_local_data[client][local_ports[client].addr[16:15]];
|
||||||
|
end
|
||||||
|
// Preserve the public external-AXI counter meanings. Four simultaneous stalls
|
||||||
|
// are one stalled cycle; request/beat events are counted individually.
|
||||||
|
for (genvar counter = 0; counter < 6; counter++) begin : gen_counter
|
||||||
|
if (counter == 4) begin : gen_cycle_count
|
||||||
|
assign s_increment[counter] = {2'b0, |s_events[counter]};
|
||||||
|
end else begin : gen_event_count
|
||||||
|
assign s_increment[counter] = {2'b0, s_events[counter][0]} +
|
||||||
|
{2'b0, s_events[counter][1]} + {2'b0, s_events[counter][2]} +
|
||||||
|
{2'b0, s_events[counter][3]};
|
||||||
|
end
|
||||||
|
assign s_sum[counter] = {1'b0, s_count_q[counter]} + {30'd0, s_increment[counter]};
|
||||||
|
assign s_count_d[counter] = perf_clear_i ? 32'd0 :
|
||||||
|
perf_enable_i ? (s_sum[counter][32] ? 32'hffff_ffff : s_sum[counter][31:0]) :
|
||||||
|
s_count_q[counter];
|
||||||
|
dffr #(
|
||||||
|
.DATA_WIDTH(32)
|
||||||
|
) u_counter (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i(rst_n_i),
|
||||||
|
.dat_i (s_count_d[counter]),
|
||||||
|
.dat_o (s_count_q[counter])
|
||||||
|
);
|
||||||
|
end
|
||||||
|
onchip_ram_reg #(
|
||||||
|
.Present (1'b1),
|
||||||
|
.CapacityKiB(128),
|
||||||
|
.DataBytes (4)
|
||||||
|
) u_reg (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i (rst_n_i),
|
||||||
|
.apb4 (cfg_apb4),
|
||||||
|
.perf_read_requests_i (s_count_q[0]),
|
||||||
|
.perf_write_requests_i (s_count_q[1]),
|
||||||
|
.perf_read_beats_i (s_count_q[2]),
|
||||||
|
.perf_write_beats_i (s_count_q[3]),
|
||||||
|
.perf_stall_cycles_i (s_count_q[4]),
|
||||||
|
.perf_error_responses_i(s_count_q[5])
|
||||||
|
);
|
||||||
|
endmodule
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
// Copyright (c) 2026 Yuchi Miao
|
||||||
|
// SPDX-License-Identifier: MulanPSL-2.0
|
||||||
|
|
||||||
|
// One external transaction per group. Address ownership lasts through the
|
||||||
|
// terminal response, but the macro is arbitrated separately for every beat.
|
||||||
|
// W may precede AW on the bus; it is backpressured until its AW is accepted.
|
||||||
|
module tiny_sram_axi4 #(
|
||||||
|
parameter logic [31:0] GroupBase = 32'h3000_0000
|
||||||
|
) (
|
||||||
|
input logic clk_i,
|
||||||
|
input logic rst_n_i,
|
||||||
|
axi4_if.slave axi4,
|
||||||
|
tiny_sram_port_if.master memory
|
||||||
|
);
|
||||||
|
typedef enum logic [2:0] {
|
||||||
|
Idle,
|
||||||
|
ReadStream,
|
||||||
|
ReadError,
|
||||||
|
WriteStream,
|
||||||
|
WriteResponse,
|
||||||
|
WriteDrain
|
||||||
|
} state_e;
|
||||||
|
typedef struct packed {
|
||||||
|
state_e state;
|
||||||
|
logic prefer_write;
|
||||||
|
logic id;
|
||||||
|
logic [31:0] addr;
|
||||||
|
logic [7:0] len, beat;
|
||||||
|
logic [2:0] size;
|
||||||
|
logic [1:0] burst, resp;
|
||||||
|
logic [8:0] issued, completed;
|
||||||
|
logic last;
|
||||||
|
} transaction_t;
|
||||||
|
typedef struct packed {
|
||||||
|
logic [31:0] addr, data;
|
||||||
|
logic [3:0] strb;
|
||||||
|
} write_t;
|
||||||
|
transaction_t s_transaction_d, s_transaction_q;
|
||||||
|
write_t s_write_input, s_write_output;
|
||||||
|
logic [31:0] s_next_addr;
|
||||||
|
logic [1:0] s_ar_resp, s_aw_resp;
|
||||||
|
logic [3:0] s_lane_mask;
|
||||||
|
logic s_expected_last, s_write_legal;
|
||||||
|
logic s_write_full, s_write_empty, s_write_push, s_write_pop;
|
||||||
|
logic s_req_accept, s_resp_accept, s_write_accept;
|
||||||
|
|
||||||
|
function automatic logic [1:0] address_response(input logic [31:0] addr_i,
|
||||||
|
input logic [7:0] len_i, input logic [2:0] size_i,
|
||||||
|
input logic [1:0] burst_i, input logic lock_i);
|
||||||
|
logic [32:0] bytes_count, last_addr;
|
||||||
|
logic wrap_legal;
|
||||||
|
bytes_count = ({25'd0, len_i} + 33'd1) << size_i;
|
||||||
|
wrap_legal = (len_i == 8'd1) || (len_i == 8'd3) || (len_i == 8'd7) || (len_i == 8'd15);
|
||||||
|
last_addr = {1'b0, addr_i} + bytes_count - 33'd1;
|
||||||
|
if (burst_i == 2'b00) last_addr = {1'b0, addr_i} + (33'd1 << size_i) - 33'd1;
|
||||||
|
if (burst_i == 2'b10)
|
||||||
|
last_addr = ({1'b0, addr_i} & ~(bytes_count - 33'd1)) + bytes_count - 33'd1;
|
||||||
|
if (lock_i || (len_i > 8'd15) || (size_i > 3'd2) ||
|
||||||
|
(burst_i == 2'b11) || ((burst_i == 2'b10) && !wrap_legal) ||
|
||||||
|
((addr_i & ((32'd1 << size_i) - 32'd1)) != 32'd0) || last_addr[32] ||
|
||||||
|
(addr_i[31:12] != last_addr[31:12]))
|
||||||
|
return 2'b10;
|
||||||
|
if ((addr_i < GroupBase) || (last_addr[31:0] > (GroupBase + 32'h7fff))) return 2'b11;
|
||||||
|
return 2'b00;
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
assign s_ar_resp = address_response(
|
||||||
|
axi4.araddr, axi4.arlen, axi4.arsize, axi4.arburst, axi4.arlock
|
||||||
|
);
|
||||||
|
assign s_aw_resp = address_response(
|
||||||
|
axi4.awaddr, axi4.awlen, axi4.awsize, axi4.awburst, axi4.awlock
|
||||||
|
);
|
||||||
|
assign axi4.arready = (s_transaction_q.state == Idle) &&
|
||||||
|
!(s_transaction_q.prefer_write && axi4.awvalid);
|
||||||
|
assign axi4.awready = (s_transaction_q.state == Idle) &&
|
||||||
|
(s_transaction_q.prefer_write || !axi4.arvalid);
|
||||||
|
assign axi4.wready = ((s_transaction_q.state == WriteStream) && !s_transaction_q.last &&
|
||||||
|
(!s_write_full || s_write_pop)) || (s_transaction_q.state == WriteDrain);
|
||||||
|
assign axi4.rvalid = (s_transaction_q.state == ReadError) ||
|
||||||
|
((s_transaction_q.state == ReadStream) && memory.rsp_valid);
|
||||||
|
assign axi4.rid = s_transaction_q.id;
|
||||||
|
assign axi4.rdata = (s_transaction_q.state == ReadStream) ? memory.rdata : 32'd0;
|
||||||
|
assign axi4.rresp = s_transaction_q.resp;
|
||||||
|
assign axi4.rlast = s_transaction_q.beat == s_transaction_q.len;
|
||||||
|
assign axi4.ruser = '0;
|
||||||
|
assign axi4.bvalid = s_transaction_q.state == WriteResponse;
|
||||||
|
assign axi4.bid = s_transaction_q.id;
|
||||||
|
assign axi4.bresp = s_transaction_q.resp;
|
||||||
|
assign axi4.buser = '0;
|
||||||
|
|
||||||
|
// Read responses stay in the group's reserved queue until R handshakes.
|
||||||
|
// Issuance and retirement have independent positions within the same burst.
|
||||||
|
assign memory.req_valid = ((s_transaction_q.state == ReadStream) &&
|
||||||
|
(s_transaction_q.issued <= {1'b0, s_transaction_q.len})) ||
|
||||||
|
((s_transaction_q.state == WriteStream) && !s_write_empty);
|
||||||
|
assign memory.addr = memory.write ? s_write_output.addr : s_transaction_q.addr;
|
||||||
|
assign memory.write = s_transaction_q.state == WriteStream;
|
||||||
|
assign memory.wdata = s_write_output.data;
|
||||||
|
assign memory.wstrb = s_write_output.strb;
|
||||||
|
assign memory.rsp_ready = ((s_transaction_q.state == ReadStream) && axi4.rready) ||
|
||||||
|
(s_transaction_q.state == WriteStream);
|
||||||
|
assign s_req_accept = memory.req_valid && memory.req_ready;
|
||||||
|
assign s_resp_accept = memory.rsp_valid && memory.rsp_ready;
|
||||||
|
assign s_write_accept = axi4.wvalid && axi4.wready;
|
||||||
|
assign s_write_push = (s_transaction_q.state == WriteStream) && s_write_accept && s_write_legal;
|
||||||
|
assign s_write_pop = (s_transaction_q.state == WriteStream) && s_req_accept;
|
||||||
|
assign s_write_input = {s_transaction_q.addr, axi4.wdata, axi4.wstrb};
|
||||||
|
// Capture W before it becomes eligible at the macro. A full queue can
|
||||||
|
// replace its head on the same edge that the arbiter commits that word.
|
||||||
|
fifo #(
|
||||||
|
.DATA_WIDTH ($bits(write_t)),
|
||||||
|
.BUFFER_DEPTH(2)
|
||||||
|
) u_write_fifo (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i(rst_n_i),
|
||||||
|
.flush_i(1'b0),
|
||||||
|
.push_i (s_write_push),
|
||||||
|
.full_o (s_write_full),
|
||||||
|
.dat_i (s_write_input),
|
||||||
|
.pop_i (s_write_pop),
|
||||||
|
.empty_o(s_write_empty),
|
||||||
|
.dat_o (s_write_output),
|
||||||
|
.cnt_o () // Full/empty and accepted/completed counts cover ownership.
|
||||||
|
);
|
||||||
|
assign s_lane_mask = 4'(((32'd1 << (32'd1 << s_transaction_q.size)) - 32'd1)
|
||||||
|
<< s_transaction_q.addr[1:0]);
|
||||||
|
assign s_expected_last = s_transaction_q.beat == s_transaction_q.len;
|
||||||
|
assign s_write_legal = ((axi4.wstrb & ~s_lane_mask) == 4'd0) && (axi4.wlast == s_expected_last);
|
||||||
|
axi4_addr_gen #(
|
||||||
|
.ADDR_WIDTH(32)
|
||||||
|
) u_address (
|
||||||
|
.alen_i (s_transaction_q.len),
|
||||||
|
.asize_i (s_transaction_q.size),
|
||||||
|
.aburst_i(s_transaction_q.burst),
|
||||||
|
.addr_i (s_transaction_q.addr),
|
||||||
|
.addr_o (s_next_addr)
|
||||||
|
);
|
||||||
|
|
||||||
|
always_comb begin
|
||||||
|
s_transaction_d = s_transaction_q;
|
||||||
|
unique case (s_transaction_q.state)
|
||||||
|
Idle: begin
|
||||||
|
s_transaction_d.beat = '0;
|
||||||
|
s_transaction_d.issued = '0;
|
||||||
|
s_transaction_d.completed = '0;
|
||||||
|
s_transaction_d.last = 1'b0;
|
||||||
|
if (axi4.arvalid && axi4.arready) begin
|
||||||
|
s_transaction_d.prefer_write = 1'b1;
|
||||||
|
s_transaction_d.id = axi4.arid;
|
||||||
|
s_transaction_d.addr = axi4.araddr;
|
||||||
|
// Match the shared target's malformed-address error response length.
|
||||||
|
s_transaction_d.len = (s_ar_resp == 2'b10) ? 8'd0 : axi4.arlen;
|
||||||
|
s_transaction_d.size = axi4.arsize;
|
||||||
|
s_transaction_d.burst = axi4.arburst;
|
||||||
|
s_transaction_d.resp = s_ar_resp;
|
||||||
|
s_transaction_d.state = (s_ar_resp == 2'b00) ? ReadStream : ReadError;
|
||||||
|
end else if (axi4.awvalid && axi4.awready) begin
|
||||||
|
s_transaction_d.prefer_write = 1'b0;
|
||||||
|
s_transaction_d.id = axi4.awid;
|
||||||
|
s_transaction_d.addr = axi4.awaddr;
|
||||||
|
s_transaction_d.len = (s_aw_resp == 2'b10) ? 8'd0 : axi4.awlen;
|
||||||
|
s_transaction_d.size = axi4.awsize;
|
||||||
|
s_transaction_d.burst = axi4.awburst;
|
||||||
|
s_transaction_d.resp = s_aw_resp;
|
||||||
|
s_transaction_d.state = (s_aw_resp == 2'b00) ? WriteStream : WriteDrain;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ReadStream: begin
|
||||||
|
if (s_req_accept) begin
|
||||||
|
s_transaction_d.issued = s_transaction_q.issued + 9'd1;
|
||||||
|
s_transaction_d.addr = s_next_addr;
|
||||||
|
end
|
||||||
|
if (s_resp_accept) begin
|
||||||
|
s_transaction_d.completed = s_transaction_q.completed + 9'd1;
|
||||||
|
if (axi4.rlast) s_transaction_d.state = Idle;
|
||||||
|
else s_transaction_d.beat = s_transaction_q.beat + 8'd1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ReadError:
|
||||||
|
if (axi4.rready) begin
|
||||||
|
if (axi4.rlast) s_transaction_d.state = Idle;
|
||||||
|
else s_transaction_d.beat = s_transaction_q.beat + 8'd1;
|
||||||
|
end
|
||||||
|
WriteStream: begin
|
||||||
|
if (s_write_accept) begin
|
||||||
|
s_transaction_d.last = s_expected_last || axi4.wlast;
|
||||||
|
s_transaction_d.beat = s_transaction_q.beat + 8'd1;
|
||||||
|
s_transaction_d.addr = s_next_addr;
|
||||||
|
if (!s_write_legal) s_transaction_d.resp = 2'b10;
|
||||||
|
end
|
||||||
|
if (s_req_accept) s_transaction_d.issued = s_transaction_q.issued + 9'd1;
|
||||||
|
if (s_resp_accept) s_transaction_d.completed = s_transaction_q.completed + 9'd1;
|
||||||
|
// Errors do not discard older queued writes. B acknowledges all
|
||||||
|
// accepted legal beats only after their macro-commit responses drain.
|
||||||
|
if (s_transaction_q.last && s_write_empty &&
|
||||||
|
(s_transaction_q.issued == s_transaction_q.completed))
|
||||||
|
s_transaction_d.state = WriteResponse;
|
||||||
|
end
|
||||||
|
WriteDrain:
|
||||||
|
if (axi4.wvalid) begin
|
||||||
|
if (s_expected_last || axi4.wlast) s_transaction_d.state = WriteResponse;
|
||||||
|
else s_transaction_d.beat = s_transaction_q.beat + 8'd1;
|
||||||
|
end
|
||||||
|
WriteResponse: if (axi4.bready) s_transaction_d.state = Idle;
|
||||||
|
default: s_transaction_d.state = Idle;
|
||||||
|
endcase
|
||||||
|
end
|
||||||
|
dffr #(
|
||||||
|
.DATA_WIDTH($bits(transaction_t))
|
||||||
|
) u_transaction (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i(rst_n_i),
|
||||||
|
.dat_i (s_transaction_d),
|
||||||
|
.dat_o (s_transaction_q)
|
||||||
|
);
|
||||||
|
`ifdef HAVE_SVA
|
||||||
|
assert property (@(posedge clk_i) disable iff (!rst_n_i)
|
||||||
|
s_transaction_q.completed <= s_transaction_q.issued);
|
||||||
|
assert property (@(posedge clk_i) disable iff (!rst_n_i)
|
||||||
|
axi4.bvalid |-> s_write_empty &&
|
||||||
|
(s_transaction_q.completed == s_transaction_q.issued));
|
||||||
|
assert property (@(posedge clk_i) disable iff (!rst_n_i)
|
||||||
|
axi4.rvalid && !axi4.rready |=> axi4.rvalid &&
|
||||||
|
$stable(
|
||||||
|
{axi4.rdata, axi4.rid, axi4.rresp, axi4.rlast}
|
||||||
|
));
|
||||||
|
assert property (@(posedge clk_i) disable iff (!rst_n_i)
|
||||||
|
axi4.bvalid && !axi4.bready |=> axi4.bvalid && $stable(
|
||||||
|
{axi4.bid, axi4.bresp}
|
||||||
|
));
|
||||||
|
`endif
|
||||||
|
endmodule
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
// Copyright (c) 2026 Yuchi Miao
|
||||||
|
// SPDX-License-Identifier: MulanPSL-2.0
|
||||||
|
|
||||||
|
// Compatibility attachment to the pre-P5 fabric. Independent direction tags
|
||||||
|
// route responses; this module does not arbitrate or lock the SRAM macros.
|
||||||
|
module tiny_sram_axi4_demux (
|
||||||
|
input logic clk_i,
|
||||||
|
input logic rst_n_i,
|
||||||
|
axi4_if.slave upstream,
|
||||||
|
axi4_if.master groups [4]
|
||||||
|
);
|
||||||
|
logic s_read_busy_d, s_read_busy_q;
|
||||||
|
logic s_write_busy_d, s_write_busy_q;
|
||||||
|
logic s_read_terminal, s_write_terminal;
|
||||||
|
logic [1:0] s_read_group_d, s_read_group_q;
|
||||||
|
logic [1:0] s_write_group_d, s_write_group_q;
|
||||||
|
logic [3:0] s_arready, s_awready, s_wready, s_rvalid, s_bvalid, s_rlast;
|
||||||
|
logic [3:0] s_rid, s_bid, s_ruser, s_buser;
|
||||||
|
logic [1:0] s_rresp[4], s_bresp[4];
|
||||||
|
logic [31:0] s_rdata[4];
|
||||||
|
|
||||||
|
assign upstream.arready = !s_read_busy_q && s_arready[upstream.araddr[16:15]];
|
||||||
|
assign upstream.awready = !s_write_busy_q && s_awready[upstream.awaddr[16:15]];
|
||||||
|
assign upstream.wready = s_write_busy_q && s_wready[s_write_group_q];
|
||||||
|
assign upstream.rvalid = s_read_busy_q && s_rvalid[s_read_group_q];
|
||||||
|
assign upstream.rdata = s_rdata[s_read_group_q];
|
||||||
|
assign upstream.rresp = s_rresp[s_read_group_q];
|
||||||
|
assign upstream.rlast = s_rlast[s_read_group_q];
|
||||||
|
assign upstream.rid = s_rid[s_read_group_q];
|
||||||
|
assign upstream.ruser = s_ruser[s_read_group_q];
|
||||||
|
assign upstream.bvalid = s_write_busy_q && s_bvalid[s_write_group_q];
|
||||||
|
assign upstream.bresp = s_bresp[s_write_group_q];
|
||||||
|
assign upstream.bid = s_bid[s_write_group_q];
|
||||||
|
assign upstream.buser = s_buser[s_write_group_q];
|
||||||
|
assign s_read_terminal = upstream.rvalid && upstream.rready && upstream.rlast;
|
||||||
|
assign s_write_terminal = upstream.bvalid && upstream.bready;
|
||||||
|
assign s_read_busy_d = (s_read_busy_q && !s_read_terminal) ||
|
||||||
|
(upstream.arvalid && upstream.arready);
|
||||||
|
assign s_write_busy_d = (s_write_busy_q && !s_write_terminal) ||
|
||||||
|
(upstream.awvalid && upstream.awready);
|
||||||
|
assign s_read_group_d = upstream.araddr[16:15];
|
||||||
|
assign s_write_group_d = upstream.awaddr[16:15];
|
||||||
|
|
||||||
|
dffr #(
|
||||||
|
.DATA_WIDTH(1)
|
||||||
|
) u_read_busy (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i(rst_n_i),
|
||||||
|
.dat_i (s_read_busy_d),
|
||||||
|
.dat_o (s_read_busy_q)
|
||||||
|
);
|
||||||
|
dffr #(
|
||||||
|
.DATA_WIDTH(1)
|
||||||
|
) u_write_busy (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i(rst_n_i),
|
||||||
|
.dat_i (s_write_busy_d),
|
||||||
|
.dat_o (s_write_busy_q)
|
||||||
|
);
|
||||||
|
dffer #(
|
||||||
|
.DATA_WIDTH(2)
|
||||||
|
) u_read_group (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i(rst_n_i),
|
||||||
|
.en_i (upstream.arvalid && upstream.arready),
|
||||||
|
.dat_i (s_read_group_d),
|
||||||
|
.dat_o (s_read_group_q)
|
||||||
|
);
|
||||||
|
dffer #(
|
||||||
|
.DATA_WIDTH(2)
|
||||||
|
) u_write_group (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i(rst_n_i),
|
||||||
|
.en_i (upstream.awvalid && upstream.awready),
|
||||||
|
.dat_i (s_write_group_d),
|
||||||
|
.dat_o (s_write_group_q)
|
||||||
|
);
|
||||||
|
|
||||||
|
for (genvar group = 0; group < 4; group++) begin : gen_group
|
||||||
|
assign groups[group].arid = upstream.arid;
|
||||||
|
assign groups[group].araddr = upstream.araddr;
|
||||||
|
assign groups[group].arlen = upstream.arlen;
|
||||||
|
assign groups[group].arsize = upstream.arsize;
|
||||||
|
assign groups[group].arburst = upstream.arburst;
|
||||||
|
assign groups[group].arlock = upstream.arlock;
|
||||||
|
assign groups[group].arcache = upstream.arcache;
|
||||||
|
assign groups[group].arprot = upstream.arprot;
|
||||||
|
assign groups[group].arqos = upstream.arqos;
|
||||||
|
assign groups[group].arregion = upstream.arregion;
|
||||||
|
assign groups[group].aruser = upstream.aruser;
|
||||||
|
assign groups[group].awid = upstream.awid;
|
||||||
|
assign groups[group].awaddr = upstream.awaddr;
|
||||||
|
assign groups[group].awlen = upstream.awlen;
|
||||||
|
assign groups[group].awsize = upstream.awsize;
|
||||||
|
assign groups[group].awburst = upstream.awburst;
|
||||||
|
assign groups[group].awlock = upstream.awlock;
|
||||||
|
assign groups[group].awcache = upstream.awcache;
|
||||||
|
assign groups[group].awprot = upstream.awprot;
|
||||||
|
assign groups[group].awqos = upstream.awqos;
|
||||||
|
assign groups[group].awregion = upstream.awregion;
|
||||||
|
assign groups[group].awuser = upstream.awuser;
|
||||||
|
assign groups[group].wdata = upstream.wdata;
|
||||||
|
assign groups[group].wstrb = upstream.wstrb;
|
||||||
|
assign groups[group].wlast = upstream.wlast;
|
||||||
|
assign groups[group].wuser = upstream.wuser;
|
||||||
|
|
||||||
|
assign groups[group].arvalid = upstream.arvalid && !s_read_busy_q &&
|
||||||
|
(upstream.araddr[16:15] == 2'(group));
|
||||||
|
assign groups[group].awvalid = upstream.awvalid && !s_write_busy_q &&
|
||||||
|
(upstream.awaddr[16:15] == 2'(group));
|
||||||
|
assign groups[group].wvalid = upstream.wvalid && s_write_busy_q &&
|
||||||
|
(s_write_group_q == 2'(group));
|
||||||
|
assign groups[group].rready = upstream.rready && s_read_busy_q && (s_read_group_q == 2'(group));
|
||||||
|
assign groups[group].bready = upstream.bready && s_write_busy_q &&
|
||||||
|
(s_write_group_q == 2'(group));
|
||||||
|
assign s_arready[group] = groups[group].arready;
|
||||||
|
assign s_awready[group] = groups[group].awready;
|
||||||
|
assign s_wready[group] = groups[group].wready;
|
||||||
|
assign s_rvalid[group] = groups[group].rvalid;
|
||||||
|
assign s_bvalid[group] = groups[group].bvalid;
|
||||||
|
assign s_rlast[group] = groups[group].rlast;
|
||||||
|
assign s_rid[group] = groups[group].rid;
|
||||||
|
assign s_bid[group] = groups[group].bid;
|
||||||
|
assign s_ruser[group] = groups[group].ruser;
|
||||||
|
assign s_buser[group] = groups[group].buser;
|
||||||
|
assign s_rresp[group] = groups[group].rresp;
|
||||||
|
assign s_bresp[group] = groups[group].bresp;
|
||||||
|
assign s_rdata[group] = groups[group].rdata;
|
||||||
|
end
|
||||||
|
endmodule
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
// Copyright (c) 2026 Yuchi Miao
|
||||||
|
// SPDX-License-Identifier: MulanPSL-2.0
|
||||||
|
|
||||||
|
// One independently arbitrated 32 KiB group. Arbitration advances on the
|
||||||
|
// physical macro edge, never on an AXI address or burst reservation. Captured
|
||||||
|
// responses isolate all three clients from subsequent macro output changes.
|
||||||
|
module tiny_sram_group (
|
||||||
|
input logic clk_i,
|
||||||
|
input logic rst_n_i,
|
||||||
|
tiny_sram_port_if.slave ports [3]
|
||||||
|
);
|
||||||
|
logic [2:0] s_req, s_grant;
|
||||||
|
logic [1:0] s_selected;
|
||||||
|
logic s_issue;
|
||||||
|
logic [1:0] s_rsp_valid_d;
|
||||||
|
logic [2:0] s_rsp_valid_q;
|
||||||
|
logic [31:0] s_rsp_data_d[2], s_rsp_data_q[3];
|
||||||
|
logic s_read_pending_d, s_read_pending_q;
|
||||||
|
logic [1:0] s_read_owner_d, s_read_owner_q;
|
||||||
|
logic [2:0] s_read_bank_d, s_read_bank_q;
|
||||||
|
logic [2:0] s_write;
|
||||||
|
logic [31:0] s_addr[3], s_wdata[3];
|
||||||
|
logic [ 3:0] s_wstrb [3];
|
||||||
|
logic [31:0] s_bank_data [8];
|
||||||
|
logic [ 7:0] s_bank_select;
|
||||||
|
|
||||||
|
for (genvar client = 0; client < 3; client++) begin : gen_client
|
||||||
|
assign s_addr[client] = ports[client].addr;
|
||||||
|
assign s_write[client] = ports[client].write;
|
||||||
|
assign s_wdata[client] = ports[client].wdata;
|
||||||
|
assign s_wstrb[client] = ports[client].wstrb;
|
||||||
|
assign ports[client].req_ready = s_grant[client];
|
||||||
|
assign ports[client].rsp_valid = s_rsp_valid_q[client];
|
||||||
|
assign ports[client].rdata = s_rsp_data_q[client];
|
||||||
|
|
||||||
|
if (client < 2) begin : gen_local_response
|
||||||
|
assign s_req[client] = ports[client].req_valid && !s_rsp_valid_q[client] &&
|
||||||
|
!(s_read_pending_q && (s_read_owner_q == 2'(client)));
|
||||||
|
always_comb begin
|
||||||
|
s_rsp_valid_d[client] = s_rsp_valid_q[client] && !ports[client].rsp_ready;
|
||||||
|
s_rsp_data_d[client] = s_rsp_data_q[client];
|
||||||
|
if (s_read_pending_q && (s_read_owner_q == 2'(client))) begin
|
||||||
|
s_rsp_valid_d[client] = 1'b1;
|
||||||
|
s_rsp_data_d[client] = s_bank_data[s_read_bank_q];
|
||||||
|
end
|
||||||
|
if (s_grant[client] && s_write[client]) begin
|
||||||
|
s_rsp_valid_d[client] = 1'b1;
|
||||||
|
s_rsp_data_d[client] = '0;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
dffr #(
|
||||||
|
.DATA_WIDTH(1)
|
||||||
|
) u_response_valid (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i(rst_n_i),
|
||||||
|
.dat_i (s_rsp_valid_d[client]),
|
||||||
|
.dat_o (s_rsp_valid_q[client])
|
||||||
|
);
|
||||||
|
dffr #(
|
||||||
|
.DATA_WIDTH(32)
|
||||||
|
) u_response_data (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i(rst_n_i),
|
||||||
|
.dat_i (s_rsp_data_d[client]),
|
||||||
|
.dat_o (s_rsp_data_q[client])
|
||||||
|
);
|
||||||
|
end else begin : gen_external_response
|
||||||
|
logic s_empty, s_full, s_pop, s_push, s_pending;
|
||||||
|
logic [ 1:0] s_count;
|
||||||
|
logic [ 2:0] s_reserved;
|
||||||
|
logic [31:0] s_data;
|
||||||
|
assign s_pending = s_read_pending_q && (s_read_owner_q == 2'(client));
|
||||||
|
assign s_reserved = {1'b0, s_count} + {2'b0, s_pending};
|
||||||
|
assign s_pop = ports[client].rsp_ready && !s_empty;
|
||||||
|
// Include the macro read already in flight. A returning word must fit
|
||||||
|
// even if the consumer removes READY immediately after this issue.
|
||||||
|
// A same-client read return and write commit cannot share one FIFO push.
|
||||||
|
assign s_req[client] = ports[client].req_valid &&
|
||||||
|
((s_reserved < 3'd2) || ((s_reserved == 3'd2) && s_pop)) &&
|
||||||
|
!(s_pending && s_write[client]);
|
||||||
|
assign s_push = s_pending || (s_grant[client] && s_write[client]);
|
||||||
|
assign s_data = s_pending ? s_bank_data[s_read_bank_q] : 32'd0;
|
||||||
|
assign s_rsp_valid_q[client] = !s_empty;
|
||||||
|
fifo #(
|
||||||
|
.DATA_WIDTH (32),
|
||||||
|
.BUFFER_DEPTH(2)
|
||||||
|
) u_response_fifo (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i(rst_n_i),
|
||||||
|
.flush_i(1'b0),
|
||||||
|
.push_i (s_push),
|
||||||
|
.full_o (s_full),
|
||||||
|
.dat_i (s_data),
|
||||||
|
.pop_i (s_pop),
|
||||||
|
.empty_o(s_empty),
|
||||||
|
.dat_o (s_rsp_data_q[client]),
|
||||||
|
.cnt_o (s_count)
|
||||||
|
);
|
||||||
|
`ifdef FORMAL
|
||||||
|
always_ff @(posedge clk_i) begin
|
||||||
|
if (rst_n_i) begin
|
||||||
|
assert (s_reserved <= 3'd2);
|
||||||
|
assert (!s_push || !s_full || s_pop);
|
||||||
|
assert (!(s_pending && s_grant[client] && s_write[client]));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
`endif
|
||||||
|
`ifdef HAVE_SVA
|
||||||
|
assert property (@(posedge clk_i) disable iff (!rst_n_i) s_reserved <= 3'd2);
|
||||||
|
assert property (@(posedge clk_i) disable iff (!rst_n_i) !s_push || !s_full || s_pop);
|
||||||
|
`endif
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
round_robin_arbiter #(
|
||||||
|
.CLIENTS(3)
|
||||||
|
) u_arbiter (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i (rst_n_i),
|
||||||
|
.advance_i (s_issue),
|
||||||
|
.request_i (s_req),
|
||||||
|
.grant_o (s_grant),
|
||||||
|
.selected_o(s_selected),
|
||||||
|
.valid_o (s_issue)
|
||||||
|
);
|
||||||
|
assign s_read_pending_d = s_issue && !s_write[s_selected];
|
||||||
|
assign s_read_owner_d = s_selected;
|
||||||
|
assign s_read_bank_d = s_addr[s_selected][14:12];
|
||||||
|
dffr #(
|
||||||
|
.DATA_WIDTH(1)
|
||||||
|
) u_read_pending (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i(rst_n_i),
|
||||||
|
.dat_i (s_read_pending_d),
|
||||||
|
.dat_o (s_read_pending_q)
|
||||||
|
);
|
||||||
|
dffer #(
|
||||||
|
.DATA_WIDTH(2)
|
||||||
|
) u_read_owner (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i(rst_n_i),
|
||||||
|
.en_i (s_read_pending_d),
|
||||||
|
.dat_i (s_read_owner_d),
|
||||||
|
.dat_o (s_read_owner_q)
|
||||||
|
);
|
||||||
|
dffer #(
|
||||||
|
.DATA_WIDTH(3)
|
||||||
|
) u_read_bank (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i(rst_n_i),
|
||||||
|
.en_i (s_read_pending_d),
|
||||||
|
.dat_i (s_read_bank_d),
|
||||||
|
.dat_o (s_read_bank_q)
|
||||||
|
);
|
||||||
|
|
||||||
|
for (genvar bank = 0; bank < 8; bank++) begin : gen_bank
|
||||||
|
assign s_bank_select[bank] = s_issue && (s_addr[s_selected][14:12] == 3'(bank));
|
||||||
|
tc_sram_1024x32 u_ram (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.cs_i (s_bank_select[bank]),
|
||||||
|
.addr_i(s_addr[s_selected][11:2]),
|
||||||
|
.data_i(s_wdata[s_selected]),
|
||||||
|
.mask_i(s_wstrb[s_selected]),
|
||||||
|
.wren_i(s_write[s_selected]),
|
||||||
|
.data_o(s_bank_data[bank])
|
||||||
|
);
|
||||||
|
end
|
||||||
|
|
||||||
|
`ifdef FORMAL
|
||||||
|
// Service-count proof: only continuously eligible requests accumulate
|
||||||
|
// bypasses. No wall-clock liveness is assumed for a withheld response.
|
||||||
|
for (genvar client = 0; client < 3; client++) begin : gen_formal
|
||||||
|
logic [1:0] s_bypasses_q;
|
||||||
|
logic s_past_valid_q = 1'b0;
|
||||||
|
always_ff @(posedge clk_i) begin
|
||||||
|
s_past_valid_q <= 1'b1;
|
||||||
|
if (!rst_n_i || !s_req[client] || s_grant[client]) s_bypasses_q <= '0;
|
||||||
|
else if (s_issue) s_bypasses_q <= s_bypasses_q + 2'd1;
|
||||||
|
if (rst_n_i) begin
|
||||||
|
assert ($onehot0(s_grant));
|
||||||
|
assert (s_bypasses_q <= 2'd2);
|
||||||
|
assert (!(s_req[client] && (s_bypasses_q == 2'd2) && s_issue && !s_grant[client]));
|
||||||
|
if (client < 2)
|
||||||
|
assert (!(s_rsp_valid_q[client] && s_read_pending_q && (s_read_owner_q == 2'(client))));
|
||||||
|
end
|
||||||
|
if (s_past_valid_q && rst_n_i && $past(
|
||||||
|
rst_n_i && ports[client].rsp_valid && !ports[client].rsp_ready
|
||||||
|
)) begin
|
||||||
|
assert (ports[client].rsp_valid);
|
||||||
|
assert (ports[client].rdata == $past(ports[client].rdata));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
`endif
|
||||||
|
`ifdef HAVE_SVA
|
||||||
|
assert property (@(posedge clk_i) disable iff (!rst_n_i) $onehot0(s_grant));
|
||||||
|
assert property (@(posedge clk_i) disable iff (!rst_n_i) $onehot0(s_bank_select));
|
||||||
|
for (genvar client = 0; client < 3; client++) begin : gen_assert
|
||||||
|
assert property (@(posedge clk_i) disable iff (!rst_n_i)
|
||||||
|
ports[client].rsp_valid && !ports[client].rsp_ready |=>
|
||||||
|
ports[client].rsp_valid && $stable(
|
||||||
|
ports[client].rdata
|
||||||
|
));
|
||||||
|
end
|
||||||
|
`endif
|
||||||
|
endmodule
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (c) 2026 Yuchi Miao
|
||||||
|
// SPDX-License-Identifier: MulanPSL-2.0
|
||||||
|
|
||||||
|
// One word operation, byte strobes, one ordered response per accepted request.
|
||||||
|
// Local I/D retain their address through the response handshake (bank routing)
|
||||||
|
// and have one operation outstanding. A fixed-group external client may have
|
||||||
|
// two operations outstanding, including a reserved macro read return. It may
|
||||||
|
// advance its request address after acceptance; responses remain in order.
|
||||||
|
interface tiny_sram_port_if;
|
||||||
|
logic req_valid, req_ready;
|
||||||
|
logic [31:0] addr, wdata;
|
||||||
|
logic write;
|
||||||
|
logic [3:0] wstrb;
|
||||||
|
logic rsp_valid, rsp_ready;
|
||||||
|
logic [31:0] rdata;
|
||||||
|
|
||||||
|
modport master(
|
||||||
|
output req_valid, addr, wdata, write, wstrb, rsp_ready,
|
||||||
|
input req_ready, rsp_valid, rdata
|
||||||
|
);
|
||||||
|
modport slave(
|
||||||
|
input req_valid, addr, wdata, write, wstrb, rsp_ready,
|
||||||
|
output req_ready, rsp_valid, rdata
|
||||||
|
);
|
||||||
|
endinterface
|
||||||
@@ -23,6 +23,17 @@ def read_topology(path: Path, memory_map: Path) -> dict:
|
|||||||
regions = {r["symbol"] for r in json.loads(memory_map.read_text())["regions"]}
|
regions = {r["symbol"] for r in json.loads(memory_map.read_text())["regions"]}
|
||||||
if document.get("schema_version") != 1 or document.get("dma_channels") != 4:
|
if document.get("schema_version") != 1 or document.get("dma_channels") != 4:
|
||||||
raise ValueError("Tiny requires topology schema 1 and four DMA channels")
|
raise ValueError("Tiny requires topology schema 1 and four DMA channels")
|
||||||
|
if document.get("cpu") != {"module": "hazard3_cpu_2port", "local_ports": 2,
|
||||||
|
"data_outstanding": 1}:
|
||||||
|
raise ValueError("Tiny P4 requires independent I/D and one ordered data operation")
|
||||||
|
memory = document.get("main_sram", {})
|
||||||
|
if any(memory.get(key) != value for key, value in {
|
||||||
|
"groups": 4, "group_bytes": 32768, "macros_per_group": 8,
|
||||||
|
"macro_bytes": 4096, "base": "0x30000000",
|
||||||
|
"macro_instance": "u_soc.u_sram.gen_group[{group}].u_group.gen_bank[{bank}].u_ram.u_mem",
|
||||||
|
"external_attachment": "serialized-pre-P5",
|
||||||
|
}.items()):
|
||||||
|
raise ValueError("Tiny P4 SRAM geometry/attachment differs from its frozen contract")
|
||||||
if document.get("irq_vector_width") != 32 or document.get("external_irq_count") != 30:
|
if document.get("irq_vector_width") != 32 or document.get("external_irq_count") != 30:
|
||||||
raise ValueError("Tiny requires a 32-bit IRQ vector and 30 external inputs")
|
raise ValueError("Tiny requires a 32-bit IRQ vector and 30 external inputs")
|
||||||
names: set[str] = set()
|
names: set[str] = set()
|
||||||
|
|||||||
@@ -183,6 +183,12 @@ def inspect_netlist(document: dict) -> dict:
|
|||||||
raise ValueError("Tiny instantiated the unsupported 16 KiB SRAM interface stub")
|
raise ValueError("Tiny instantiated the unsupported 16 KiB SRAM interface stub")
|
||||||
if len(ram) != 32 or len(pll) != 1:
|
if len(ram) != 32 or len(pll) != 1:
|
||||||
raise ValueError("expected 32 main SRAM macros and one PLL")
|
raise ValueError("expected 32 main SRAM macros and one PLL")
|
||||||
|
expected_ram = {f"u_soc.u_sram.gen_group[{g}].u_group.gen_bank[{n}].u_ram.u_mem"
|
||||||
|
for g in range(4) for n in range(8)}
|
||||||
|
observed_ram = {next((name for name in expected_ram
|
||||||
|
if c["path"].replace("/", ".").endswith("." + name)), "") for c in ram}
|
||||||
|
if observed_ram != expected_ram:
|
||||||
|
raise ValueError("Tiny P4 SRAM group/macro hierarchy is missing or stale")
|
||||||
if any(c["ports"].get("CLK") != [system] for c in ram):
|
if any(c["ports"].get("CLK") != [system] for c in ram):
|
||||||
raise ValueError("main SRAM does not share direct SYS")
|
raise ValueError("main SRAM does not share direct SYS")
|
||||||
constants = {"0": 0, "1": 1}
|
constants = {"0": 0, "1": 1}
|
||||||
@@ -202,7 +208,7 @@ def inspect_netlist(document: dict) -> dict:
|
|||||||
enable = pll[0]["ports"].get("EN", [])
|
enable = pll[0]["ports"].get("EN", [])
|
||||||
if len(enable) != 1 or constants.get(enable[0]) != 0:
|
if len(enable) != 1 or constants.get(enable[0]) != 0:
|
||||||
raise ValueError("PLL enable is not proven constant zero")
|
raise ValueError("PLL enable is not proven constant zero")
|
||||||
cpu = [c for c in leaves if "u_hazard3_cpu_1port" in c["path"]
|
cpu = [c for c in leaves if "u_hazard3_cpu_2port" in c["path"]
|
||||||
and ("CK" in c["ports"] or "CKN" in c["ports"])]
|
and ("CK" in c["ports"] or "CKN" in c["ports"])]
|
||||||
if not cpu or any(c["ports"].get("CK", c["ports"].get("CKN")) != [system] for c in cpu):
|
if not cpu or any(c["ports"].get("CK", c["ports"].get("CKN")) != [system] for c in cpu):
|
||||||
raise ValueError("CPU sequential clocks do not share direct SYS")
|
raise ValueError("CPU sequential clocks do not share direct SYS")
|
||||||
|
|||||||
@@ -0,0 +1,255 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Replay immutable P3 images on P4 RTL without relabeling image provenance."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
sys.path.insert(0, str(ROOT))
|
||||||
|
from scripts import tiny_r2_baseline as b # noqa: E402
|
||||||
|
from scripts import tiny_r2_p3 as p3 # noqa: E402
|
||||||
|
|
||||||
|
PHASE = "TINY-R2-P4"
|
||||||
|
HARDWARE_KEYS = ("SOC", "PDK", "HAVE_PLL", "HAVE_SRAM_IF", "HAVE_SRAM_MACRO",
|
||||||
|
"SRAM_SIZE_KIB", "PDK_BEHAV", "HAVE_HP", "EXT_CLK_HZ", "AUD_CLK_HZ",
|
||||||
|
"CLINT_TIMEBASE_HZ", "MGMT_CPU_CLK_HZ", "JTAG_IDCODE")
|
||||||
|
LOCAL_FIELDS = {"window", "port", "accepted", "completed", "latency_max", "admission_wait"}
|
||||||
|
BANK_FIELDS = {"window", "group", "issues_i", "issues_d", "issues_external", "conflicts",
|
||||||
|
"wait_i", "wait_d", "wait_external"}
|
||||||
|
|
||||||
|
|
||||||
|
def inputs(root: Path, variant: Path) -> dict:
|
||||||
|
result = p3.inputs(root, variant)
|
||||||
|
# Publication bindings are tested source consumers, not generated PDFs.
|
||||||
|
names = subprocess.check_output(
|
||||||
|
["git", "ls-files", "--cached", "--others", "--exclude-standard", "-z", "--", "publications"],
|
||||||
|
cwd=root, text=True).split("\0")
|
||||||
|
for name in names:
|
||||||
|
path = root / name
|
||||||
|
if name and path.is_file() and path.suffix in (".py", ".json", ".typ"):
|
||||||
|
result[name] = p3.sha256(path)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def compatible_hardware(image: dict, model: dict) -> None:
|
||||||
|
a, z = (c["configuration"] for c in (image, model))
|
||||||
|
if any(key not in a or key not in z or a[key] != z[key] for key in HARDWARE_KEYS):
|
||||||
|
raise ValueError("retained image and candidate hardware configuration differ")
|
||||||
|
|
||||||
|
|
||||||
|
def parse_log(log: str, flow: dict) -> dict:
|
||||||
|
samples = p3.parse_log(log, flow)
|
||||||
|
rows = [b.parse_fields(line, LOCAL_FIELDS) for line in log.splitlines()
|
||||||
|
if line.startswith("R2_P4_LOCAL ")]
|
||||||
|
rows.sort(key=lambda row: (row["window"], row["port"]))
|
||||||
|
if [(r["window"], r["port"]) for r in rows] != [(w, p) for w in range(1, 9) for p in range(2)]:
|
||||||
|
raise ValueError("missing or duplicate P4 local observations")
|
||||||
|
for row in rows:
|
||||||
|
if row["completed"] > row["accepted"] or row["accepted"] - row["completed"] > 1:
|
||||||
|
raise ValueError("P4 local request/response accounting mismatch")
|
||||||
|
samples["local"] = rows
|
||||||
|
banks = [b.parse_fields(line, BANK_FIELDS) for line in log.splitlines()
|
||||||
|
if line.startswith("R2_P4_BANK ")]
|
||||||
|
banks.sort(key=lambda row: (row["window"], row["group"]))
|
||||||
|
if [(r["window"], r["group"]) for r in banks] != [(w, g) for w in range(1, 9) for g in range(4)]:
|
||||||
|
raise ValueError("missing or duplicate P4 bank observations")
|
||||||
|
samples["banks"] = banks
|
||||||
|
return samples
|
||||||
|
|
||||||
|
|
||||||
|
def reference_cases(path: Path, names: list[str]) -> list[dict]:
|
||||||
|
report = b.read_json(path)
|
||||||
|
if report.get("phase") != p3.PHASE or report.get("status") != "measurement_complete":
|
||||||
|
raise ValueError("reference is not a completed P3 measurement report")
|
||||||
|
matrix = b.read_json(b.check_artifact(report["matrix"]))
|
||||||
|
if matrix.get("status") != "passed":
|
||||||
|
raise ValueError("reference matrix did not pass")
|
||||||
|
if not names or len(set(names)) != len(names):
|
||||||
|
raise ValueError("select distinct retained cases")
|
||||||
|
result = []
|
||||||
|
for name in names:
|
||||||
|
matching = [c for c in report["cases"] if c["name"] == name]
|
||||||
|
original = [c for c in matrix["cases"] if c["name"] == name]
|
||||||
|
if len(matching) != 1 or len(original) != 1 or matching[0]["image"] != original[0]["image"]:
|
||||||
|
raise ValueError("unknown or mismatched retained case")
|
||||||
|
case = matching[0]
|
||||||
|
p3.validate_image(b.check_artifact(case["image"]))
|
||||||
|
# Recheck the original logs, not just a report's claimed numeric values.
|
||||||
|
samples = p3.validate_simulation(original[0]["simulations"]["verilator"],
|
||||||
|
case["image"], "verilator")
|
||||||
|
if samples != case["samples"]:
|
||||||
|
raise ValueError("reference report differs from its retained execution")
|
||||||
|
result.append(case)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def replay(args) -> None:
|
||||||
|
root, variant = args.root, args.variant_root
|
||||||
|
parent = variant / "meta/tiny-r2-p4/replay" / args.simulator
|
||||||
|
directory = b.new_attempt(parent, "attempt-")
|
||||||
|
record = {"phase": PHASE, "status": "running", "repetitions": 1, "cases": []}
|
||||||
|
b.dump(parent / "latest.json", {"path": str(directory / "record.json")})
|
||||||
|
try:
|
||||||
|
reference = b.artifact(args.reference)
|
||||||
|
cases = reference_cases(args.reference, args.cases.split(","))
|
||||||
|
configuration = p3.configuration(variant)
|
||||||
|
for case in cases:
|
||||||
|
compatible_hardware(b.read_json(b.check_artifact(case["image"]))["configuration"],
|
||||||
|
configuration)
|
||||||
|
before = inputs(root, variant)
|
||||||
|
tools = b.tools_identity(root)
|
||||||
|
command = args.command[1:] if args.command[:1] == ["--"] else args.command
|
||||||
|
if not command:
|
||||||
|
raise ValueError("fresh model compilation command required")
|
||||||
|
p3.execute_make(root, directory, "compile-model", command)
|
||||||
|
if before != inputs(root, variant):
|
||||||
|
raise ValueError("source changed while compiling candidate model")
|
||||||
|
runtime = b.runtime_inputs(root, variant, args.simulator)
|
||||||
|
model = variant / "sim" / args.simulator / ("emu" if args.simulator == "verilator" else "simv")
|
||||||
|
record.update(reference=reference, configuration=configuration, tools=tools,
|
||||||
|
model=b.artifact(model), source_digest=b.digest_map(before),
|
||||||
|
source=b.snapshot(root, directory / "inputs", before | runtime),
|
||||||
|
compilation={s: b.artifact(directory / f"compile-model.{s}") for s in ("json", "log")})
|
||||||
|
launch = [str(model)]
|
||||||
|
if args.simulator == "iverilog":
|
||||||
|
record["runtime_tool"] = b.vvp_identity(args.vvp, tools["iverilog"])
|
||||||
|
launch.insert(0, record["runtime_tool"]["executable"]["path"])
|
||||||
|
for case in cases:
|
||||||
|
image = p3.validate_image(b.check_artifact(case["image"]))
|
||||||
|
begin, end = image["layout"]["descriptor_range"]
|
||||||
|
cmd = [*launch, "+firmware=" + image["artifacts"]["tiny_p3.hex"]["path"],
|
||||||
|
"+tiny_r2_p3", f"+baseline_tcd_base={begin:x}", f"+baseline_tcd_end={end:x}",
|
||||||
|
"+max_cycles=100000000"]
|
||||||
|
label = case["name"]
|
||||||
|
flow = b.execute(root, directory, label,
|
||||||
|
["timeout", "--foreground", f"{args.timeout}s", *cmd], quiet=True)
|
||||||
|
samples = parse_log((directory / f"{label}.log").read_text(), flow)
|
||||||
|
p3.validate_image(b.check_artifact(case["image"]))
|
||||||
|
record["cases"].append({"name": label, "image": case["image"], "samples": samples,
|
||||||
|
"baseline_samples": case["samples"],
|
||||||
|
"command": cmd, "timeout_seconds": args.timeout,
|
||||||
|
"flow": b.artifact(directory / f"{label}.json"),
|
||||||
|
"log": b.artifact(directory / f"{label}.log")})
|
||||||
|
b.dump(directory / "record.json", record)
|
||||||
|
if before != inputs(root, variant) or runtime != b.runtime_inputs(root, variant, args.simulator):
|
||||||
|
raise ValueError("candidate inputs changed during execution")
|
||||||
|
b.check_artifact(record["model"])
|
||||||
|
b.check_artifact(reference)
|
||||||
|
for case in cases:
|
||||||
|
p3.validate_image(b.check_artifact(case["image"]))
|
||||||
|
for tool in tools.values():
|
||||||
|
b.check_artifact(tool["executable"])
|
||||||
|
record["status"] = "passed"
|
||||||
|
except (OSError, ValueError) as error:
|
||||||
|
record.update(status="failed", error=str(error))
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
b.dump(directory / "record.json", record)
|
||||||
|
print(f"P4 {args.simulator}: one execution per image; {directory}")
|
||||||
|
|
||||||
|
|
||||||
|
def validate_replay(path: Path, simulator: str) -> dict:
|
||||||
|
record = b.read_json(path)
|
||||||
|
if record.get("phase") != PHASE or record.get("status") != "passed" or record.get("repetitions") != 1:
|
||||||
|
raise ValueError("not a completed single-execution P4 replay")
|
||||||
|
for field in ("model", "reference"):
|
||||||
|
b.check_artifact(record[field])
|
||||||
|
for value in record["compilation"].values():
|
||||||
|
b.check_artifact(value)
|
||||||
|
for field in ("inputs", "snapshot", "patch"):
|
||||||
|
b.check_artifact(record["source"][field])
|
||||||
|
captured = b.read_json(Path(record["source"]["inputs"]["path"]))
|
||||||
|
if b.digest_map(captured) != record["source"]["input_digest"]:
|
||||||
|
raise ValueError("candidate source digest mismatch")
|
||||||
|
for tool in record["tools"].values():
|
||||||
|
b.check_artifact(tool["executable"])
|
||||||
|
b.check_artifact(tool["archive"])
|
||||||
|
prefix = [record["model"]["path"]]
|
||||||
|
if simulator == "iverilog":
|
||||||
|
runtime = b.check_artifact(record["runtime_tool"]["executable"])
|
||||||
|
compiler = Path(record["tools"]["iverilog"]["executable"]["path"])
|
||||||
|
if runtime.resolve().parent != compiler.resolve().parent:
|
||||||
|
raise ValueError("unmatched Icarus runtime")
|
||||||
|
prefix.insert(0, str(runtime))
|
||||||
|
reference = reference_cases(Path(record["reference"]["path"]), [c["name"] for c in record["cases"]])
|
||||||
|
for case, original in zip(record["cases"], reference, strict=True):
|
||||||
|
if case["image"] != original["image"] or case["baseline_samples"] != original["samples"]:
|
||||||
|
raise ValueError("comparison changed the retained image or reference measurements")
|
||||||
|
image = p3.validate_image(b.check_artifact(case["image"]))
|
||||||
|
compatible_hardware(image["configuration"], record["configuration"])
|
||||||
|
first, end = image["layout"]["descriptor_range"]
|
||||||
|
expected = [*prefix, "+firmware=" + image["artifacts"]["tiny_p3.hex"]["path"],
|
||||||
|
"+tiny_r2_p3", f"+baseline_tcd_base={first:x}", f"+baseline_tcd_end={end:x}",
|
||||||
|
"+max_cycles=100000000"]
|
||||||
|
flow = b.read_json(b.check_artifact(case["flow"]))
|
||||||
|
if case["command"] != expected or case["timeout_seconds"] <= 0 or flow["command"] != [
|
||||||
|
"timeout", "--foreground", f"{case['timeout_seconds']}s", *expected]:
|
||||||
|
raise ValueError("model/image execution provenance mismatch")
|
||||||
|
if parse_log(b.check_artifact(case["log"]).read_text(), flow) != case["samples"]:
|
||||||
|
raise ValueError("reported candidate samples differ from the log")
|
||||||
|
return record
|
||||||
|
|
||||||
|
|
||||||
|
def report(args) -> None:
|
||||||
|
base = args.variant_root / "meta/tiny-r2-p4"
|
||||||
|
runs = {}
|
||||||
|
for simulator in ("verilator", "iverilog"):
|
||||||
|
pointer = b.read_json(base / "replay" / simulator / "latest.json")
|
||||||
|
path = Path(pointer["path"])
|
||||||
|
runs[simulator] = (validate_replay(path, simulator), b.artifact(path))
|
||||||
|
before = b.digest_map(inputs(args.root, args.variant_root))
|
||||||
|
if any(run[0]["source_digest"] != before for run in runs.values()):
|
||||||
|
raise ValueError("candidate source changed after measurement")
|
||||||
|
cases = {c["name"]: c for c in runs["verilator"][0]["cases"]}
|
||||||
|
if set(cases) != {"perf-o2-plain-flat", "perf-o2-plain-banked"}:
|
||||||
|
raise ValueError("P4 comparison requires both approved placement images")
|
||||||
|
cross = runs["iverilog"][0]["cases"]
|
||||||
|
if len(cross) != 1 or cross[0]["name"] != "perf-o2-plain-banked":
|
||||||
|
raise ValueError("P4 requires one banked Icarus execution")
|
||||||
|
if any(cross[0][key] != cases["perf-o2-plain-banked"][key] for key in ("image", "samples")):
|
||||||
|
raise ValueError("cross-simulator image/results differ")
|
||||||
|
comparisons = []
|
||||||
|
for name, case in cases.items():
|
||||||
|
rows = []
|
||||||
|
for old, new in zip(case["baseline_samples"]["cases"], case["samples"]["cases"], strict=True):
|
||||||
|
rows.append({"name": new["name"], "baseline_cycles": old["cycles"],
|
||||||
|
"candidate_cycles": new["cycles"], "baseline_instructions": old["instructions"],
|
||||||
|
"candidate_instructions": new["instructions"],
|
||||||
|
"cycle_ratio": old["cycles"] / new["cycles"]})
|
||||||
|
comparisons.append({"name": name, "image": case["image"], "workloads": rows,
|
||||||
|
"local": case["samples"]["local"], "banks": case["samples"]["banks"]})
|
||||||
|
b.dump(base / "report.json", {"phase": PHASE, "status": "measurement_complete",
|
||||||
|
"source_digest": before, "comparisons": comparisons,
|
||||||
|
"executions": {key: value[1] for key, value in runs.items()},
|
||||||
|
"qualification": "matched-binary SAFE24 simulation; not phase/physical acceptance"})
|
||||||
|
print(f"P4 matched-image report: {base / 'report.json'}")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--root", type=Path, required=True)
|
||||||
|
parser.add_argument("--variant-root", type=Path, required=True)
|
||||||
|
parser.add_argument("--report", action="store_true")
|
||||||
|
parser.add_argument("--reference", type=Path)
|
||||||
|
parser.add_argument("--cases")
|
||||||
|
parser.add_argument("--simulator", choices=("verilator", "iverilog"))
|
||||||
|
parser.add_argument("--vvp", default="vvp")
|
||||||
|
parser.add_argument("--timeout", type=int, default=21600)
|
||||||
|
parser.add_argument("command", nargs=argparse.REMAINDER)
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.timeout <= 0:
|
||||||
|
parser.error("timeout must be positive")
|
||||||
|
if args.report:
|
||||||
|
report(args)
|
||||||
|
else:
|
||||||
|
if args.reference is None or not args.cases or not args.simulator:
|
||||||
|
parser.error("replay requires reference, cases and simulator")
|
||||||
|
replay(args)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -47,6 +47,7 @@ module tiny_axi4_fabric_tb;
|
|||||||
logic [1:0][ 0:0] arlock;
|
logic [1:0][ 0:0] arlock;
|
||||||
logic [1:0][ 0:0] arid;
|
logic [1:0][ 0:0] arid;
|
||||||
logic [1:0][ 0:0] arvalid;
|
logic [1:0][ 0:0] arvalid;
|
||||||
|
logic [1:0][ 2:0] arprot;
|
||||||
logic [1:0][ 0:0] rready;
|
logic [1:0][ 0:0] rready;
|
||||||
logic [1:0][ 0:0] awready;
|
logic [1:0][ 0:0] awready;
|
||||||
logic [1:0][ 0:0] arready;
|
logic [1:0][ 0:0] arready;
|
||||||
@@ -113,7 +114,7 @@ module tiny_axi4_fabric_tb;
|
|||||||
assign masters[m].awregion = '0;
|
assign masters[m].awregion = '0;
|
||||||
assign masters[m].awuser = '0;
|
assign masters[m].awuser = '0;
|
||||||
assign masters[m].arcache = '0;
|
assign masters[m].arcache = '0;
|
||||||
assign masters[m].arprot = '0;
|
assign masters[m].arprot = arprot[m];
|
||||||
assign masters[m].arqos = '0;
|
assign masters[m].arqos = '0;
|
||||||
assign masters[m].arregion = '0;
|
assign masters[m].arregion = '0;
|
||||||
assign masters[m].aruser = '0;
|
assign masters[m].aruser = '0;
|
||||||
@@ -238,6 +239,7 @@ module tiny_axi4_fabric_tb;
|
|||||||
arlock = '0;
|
arlock = '0;
|
||||||
arid = '0;
|
arid = '0;
|
||||||
arvalid = '0;
|
arvalid = '0;
|
||||||
|
arprot = '0;
|
||||||
rready = '0;
|
rready = '0;
|
||||||
completions[0] = 0;
|
completions[0] = 0;
|
||||||
completions[1] = 0;
|
completions[1] = 0;
|
||||||
@@ -267,6 +269,14 @@ module tiny_axi4_fabric_tb;
|
|||||||
target_requests[3]!=3 || target_requests[4]!=4 || cpu_wait==0 || dma_wait==0)
|
target_requests[3]!=3 || target_requests[4]!=4 || cpu_wait==0 || dma_wait==0)
|
||||||
$fatal(1, "accounting/fairness mismatch faults=%0d", faults);
|
$fatal(1, "accounting/fairness mismatch faults=%0d", faults);
|
||||||
read_request(0, 32'h30000000, 1, 2, 1, 0);
|
read_request(0, 32'h30000000, 1, 2, 1, 0);
|
||||||
|
arprot[0] = 3'b100;
|
||||||
|
read_request(0, 32'h10001000, 1, 2, 1, 2);
|
||||||
|
read_request(0, 32'h54000000, 1, 2, 1, 3);
|
||||||
|
if (target_requests[2] != 2 || target_requests[1] != 1)
|
||||||
|
$fatal(1, "instruction request reached a peripheral or non-NSS0 window");
|
||||||
|
read_request(0, 32'h00000000, 1, 2, 1, 0);
|
||||||
|
read_request(0, 32'h50000000, 1, 2, 1, 0);
|
||||||
|
if (target_requests[1] != 3) $fatal(1, "NOR instruction windows not reachable");
|
||||||
$display("SIM_TEST_PASS Tiny fabric");
|
$display("SIM_TEST_PASS Tiny fabric");
|
||||||
$finish;
|
$finish;
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
.section .text
|
||||||
|
.global _start
|
||||||
|
.option norvc
|
||||||
|
_start:
|
||||||
|
li sp, 0x3001f000
|
||||||
|
la t0, trap
|
||||||
|
csrw mtvec, t0
|
||||||
|
li s0, 0
|
||||||
|
# A C.NOP, followed by a 32-bit ADDI crossing the group boundary, C.RET.
|
||||||
|
li s1, 0x30007ffc
|
||||||
|
li s2, 3
|
||||||
|
1: li t0, 0x05130001
|
||||||
|
sw t0, 0(s1)
|
||||||
|
li t0, 0x80820090
|
||||||
|
sw t0, 4(s1)
|
||||||
|
fence.i
|
||||||
|
jalr ra, s1, 0
|
||||||
|
li t0, 9
|
||||||
|
bne a0, t0, fail
|
||||||
|
li t0, 0x8000
|
||||||
|
add s1, s1, t0
|
||||||
|
addi s2, s2, -1
|
||||||
|
bnez s2, 1b
|
||||||
|
# Instruction-side failure returns to RA via the trap handler.
|
||||||
|
li t0, 0x10001000
|
||||||
|
jalr ra, t0, 0
|
||||||
|
li t0, 1
|
||||||
|
bne s0, t0, fail
|
||||||
|
# A debugger will patch this code and release the SRAM flag while halted.
|
||||||
|
li t0, 0x30000080
|
||||||
|
li t1, 0x00100513
|
||||||
|
sw t1, 0(t0)
|
||||||
|
li t1, 0x00008067
|
||||||
|
sw t1, 4(t0)
|
||||||
|
li t1, 0x3001ffe0
|
||||||
|
sw zero, 0(t1)
|
||||||
|
fence.i
|
||||||
|
jalr ra, t0, 0
|
||||||
|
li t0, 1
|
||||||
|
bne a0, t0, fail
|
||||||
|
li t0, 0x1000b084
|
||||||
|
li t1, 1
|
||||||
|
sw t1, 0(t0)
|
||||||
|
li t0, 0x3001ffe0
|
||||||
|
2: lw t1, 0(t0)
|
||||||
|
beqz t1, 2b
|
||||||
|
fence.i
|
||||||
|
li t0, 0x30000080
|
||||||
|
jalr ra, t0, 0
|
||||||
|
li t0, 2
|
||||||
|
bne a0, t0, fail
|
||||||
|
li t0, 0x1000b084
|
||||||
|
li t1, 0x80000001
|
||||||
|
sw t1, 0(t0)
|
||||||
|
3: j 3b
|
||||||
|
trap:
|
||||||
|
csrr t2, mcause
|
||||||
|
li t3, 1
|
||||||
|
bne t2, t3, fail
|
||||||
|
addi s0, s0, 1
|
||||||
|
csrw mepc, ra
|
||||||
|
mret
|
||||||
|
fail:
|
||||||
|
li t0, 0x1000b084
|
||||||
|
li t1, 0x80000000
|
||||||
|
sw t1, 0(t0)
|
||||||
|
4: j 4b
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
ENTRY(_start)
|
||||||
|
SECTIONS {
|
||||||
|
. = 0;
|
||||||
|
.text : { *(.text*) }
|
||||||
|
/DISCARD/ : { *(.riscv.attributes) *(.comment) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,282 @@
|
|||||||
|
`timescale 1ns / 1ps
|
||||||
|
module tiny_r2_p4_cpu_tb;
|
||||||
|
logic clk_i = 0, rst_n_i = 0;
|
||||||
|
always #5 clk_i = ~clk_i;
|
||||||
|
wire s_rst_n = rst_n_i;
|
||||||
|
logic s_jtag_tck_driver = 0, s_jtag_tms_driver = 1, s_jtag_tdi_driver = 0;
|
||||||
|
wire s_jtag_tdo;
|
||||||
|
logic core_rst_n, quiesce, idle, halted;
|
||||||
|
ahbl_if cpu[2] (
|
||||||
|
.hclk (clk_i),
|
||||||
|
.hresetn(core_rst_n)
|
||||||
|
);
|
||||||
|
tiny_sram_port_if local_ports[2] ();
|
||||||
|
axi4_if #(
|
||||||
|
.ADDR_WIDTH(32),
|
||||||
|
.DATA_WIDTH(32),
|
||||||
|
.ID_WIDTH (1),
|
||||||
|
.USER_WIDTH(1)
|
||||||
|
) slow (
|
||||||
|
.aclk (clk_i),
|
||||||
|
.aresetn(rst_n_i)
|
||||||
|
);
|
||||||
|
axi4_if #(
|
||||||
|
.ADDR_WIDTH(32),
|
||||||
|
.DATA_WIDTH(32),
|
||||||
|
.ID_WIDTH (1),
|
||||||
|
.USER_WIDTH(1)
|
||||||
|
) ext[4] (
|
||||||
|
.aclk (clk_i),
|
||||||
|
.aresetn(rst_n_i)
|
||||||
|
);
|
||||||
|
apb4_if cfg (
|
||||||
|
.pclk (clk_i),
|
||||||
|
.presetn(rst_n_i)
|
||||||
|
);
|
||||||
|
logic [7:0] rom [0:4095];
|
||||||
|
string firmware;
|
||||||
|
logic read_pending = 0, aw_pending = 0, write_pending = 0, hold_read = 0;
|
||||||
|
logic [31:0] read_data = 0, write_addr = 0;
|
||||||
|
logic [ 1:0] read_resp = 0;
|
||||||
|
integer starts = 0;
|
||||||
|
logic completed = 0;
|
||||||
|
logic [31:0] status;
|
||||||
|
bit ignored;
|
||||||
|
`include "tiny_jtag_smoke.svh"
|
||||||
|
|
||||||
|
tiny_cpu_wrapper #(
|
||||||
|
.EnableAtomics (0),
|
||||||
|
.ResetSyncStages(5)
|
||||||
|
) u_cpu (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i (rst_n_i),
|
||||||
|
.irq_i (32'd0),
|
||||||
|
.jtag_tck_i (s_jtag_tck_driver),
|
||||||
|
.jtag_tms_i (s_jtag_tms_driver),
|
||||||
|
.jtag_tdi_i (s_jtag_tdi_driver),
|
||||||
|
.jtag_trst_n_i (rst_n_i),
|
||||||
|
.jtag_tdo_o (s_jtag_tdo),
|
||||||
|
.debug_halted_o(halted),
|
||||||
|
.bus_idle_i (idle),
|
||||||
|
.core_rst_n_o (core_rst_n),
|
||||||
|
.quiesce_o (quiesce),
|
||||||
|
.cpu (cpu)
|
||||||
|
);
|
||||||
|
tiny_cpu_mem u_cpu_mem (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i (core_rst_n),
|
||||||
|
.quiesce_i (quiesce),
|
||||||
|
.idle_o (idle),
|
||||||
|
.cpu (cpu),
|
||||||
|
.local_ports(local_ports),
|
||||||
|
.slow_axi4 (slow)
|
||||||
|
);
|
||||||
|
tiny_sram u_sram (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i (rst_n_i),
|
||||||
|
.perf_enable_i (1'b0),
|
||||||
|
.perf_clear_i (1'b0),
|
||||||
|
.local_ports (local_ports),
|
||||||
|
.external_ports(ext),
|
||||||
|
.cfg_apb4 (cfg)
|
||||||
|
);
|
||||||
|
// Verification-only slow memory, not a product Boot ROM. It also supplies
|
||||||
|
// the terminal mailbox and instruction fault used by this CPU fixture.
|
||||||
|
assign slow.arready = !read_pending;
|
||||||
|
assign slow.rvalid = read_pending && !hold_read;
|
||||||
|
assign slow.rdata = read_data;
|
||||||
|
assign slow.rresp = read_resp;
|
||||||
|
assign slow.rid = 0;
|
||||||
|
assign slow.rlast = 1;
|
||||||
|
assign slow.ruser = 0;
|
||||||
|
assign slow.awready = !aw_pending && !write_pending;
|
||||||
|
assign slow.wready = aw_pending;
|
||||||
|
assign slow.bvalid = write_pending;
|
||||||
|
assign slow.bresp = 0;
|
||||||
|
assign slow.bid = 0;
|
||||||
|
assign slow.buser = 0;
|
||||||
|
always @(posedge clk_i) begin
|
||||||
|
if (!rst_n_i) begin
|
||||||
|
read_pending <= 0;
|
||||||
|
aw_pending <= 0;
|
||||||
|
write_pending <= 0;
|
||||||
|
end else begin
|
||||||
|
if (slow.rvalid && slow.rready) read_pending <= 0;
|
||||||
|
if (slow.arvalid && slow.arready) begin
|
||||||
|
read_pending <= 1;
|
||||||
|
if (slow.araddr < 4096) begin
|
||||||
|
read_resp <= 0;
|
||||||
|
read_data <= {
|
||||||
|
rom[slow.araddr+3], rom[slow.araddr+2], rom[slow.araddr+1], rom[slow.araddr]
|
||||||
|
};
|
||||||
|
end else begin
|
||||||
|
read_resp <= 3;
|
||||||
|
read_data <= 0;
|
||||||
|
if (!slow.arprot[2]) $fatal(1, "unexpected slow data read %h", slow.araddr);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if (slow.awvalid && slow.awready) begin
|
||||||
|
aw_pending <= 1;
|
||||||
|
write_addr <= slow.awaddr;
|
||||||
|
end
|
||||||
|
if (slow.wvalid && slow.wready) begin
|
||||||
|
aw_pending <= 0;
|
||||||
|
write_pending <= 1;
|
||||||
|
if (write_addr != 32'h1000b084) $fatal(1, "unexpected slow write %h", write_addr);
|
||||||
|
if (slow.wdata == 1) starts <= starts + 1;
|
||||||
|
else if (slow.wdata == 32'h80000001) completed <= 1;
|
||||||
|
else $fatal(1, "SIM_TEST_FAIL CPU fixture code=%h", slow.wdata);
|
||||||
|
end
|
||||||
|
if (slow.bvalid && slow.bready) write_pending <= 0;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for (genvar g = 0; g < 4; g++) begin : gen_init
|
||||||
|
initial begin
|
||||||
|
ext[g].awid = 0;
|
||||||
|
ext[g].awaddr = 0;
|
||||||
|
ext[g].awlen = 0;
|
||||||
|
ext[g].awsize = 2;
|
||||||
|
ext[g].awburst = 1;
|
||||||
|
ext[g].awlock = 0;
|
||||||
|
ext[g].awcache = 0;
|
||||||
|
ext[g].awprot = 0;
|
||||||
|
ext[g].awqos = 0;
|
||||||
|
ext[g].awregion = 0;
|
||||||
|
ext[g].awuser = 0;
|
||||||
|
ext[g].awvalid = 0;
|
||||||
|
ext[g].wdata = 0;
|
||||||
|
ext[g].wstrb = 15;
|
||||||
|
ext[g].wlast = 1;
|
||||||
|
ext[g].wuser = 0;
|
||||||
|
ext[g].wvalid = 0;
|
||||||
|
ext[g].bready = 0;
|
||||||
|
ext[g].arid = 0;
|
||||||
|
ext[g].araddr = 0;
|
||||||
|
ext[g].arlen = 0;
|
||||||
|
ext[g].arsize = 2;
|
||||||
|
ext[g].arburst = 1;
|
||||||
|
ext[g].arlock = 0;
|
||||||
|
ext[g].arcache = 0;
|
||||||
|
ext[g].arprot = 0;
|
||||||
|
ext[g].arqos = 0;
|
||||||
|
ext[g].arregion = 0;
|
||||||
|
ext[g].aruser = 0;
|
||||||
|
ext[g].arvalid = 0;
|
||||||
|
ext[g].rready = 0;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
task automatic abstract_wait;
|
||||||
|
integer polls;
|
||||||
|
logic [31:0] value;
|
||||||
|
polls = 0;
|
||||||
|
do begin
|
||||||
|
jtag_dmi(0, 7'h16, 0, value);
|
||||||
|
polls++;
|
||||||
|
end while (value[12] && polls < 128);
|
||||||
|
if (value[12] || value[10:8] != 0) $fatal(1, "abstract command status %h", value);
|
||||||
|
endtask
|
||||||
|
task automatic set_gpr(input logic [4:0] regnum, input logic [31:0] value);
|
||||||
|
logic [31:0] unused;
|
||||||
|
jtag_dmi(1, 7'h04, value, unused);
|
||||||
|
jtag_dmi(1, 7'h17, 32'h00231000 | {27'd0, regnum}, unused);
|
||||||
|
abstract_wait();
|
||||||
|
endtask
|
||||||
|
task automatic execute_debug(input logic [31:0] instruction);
|
||||||
|
logic [31:0] unused;
|
||||||
|
jtag_dmi(1, 7'h20, instruction, unused);
|
||||||
|
jtag_dmi(1, 7'h21, 32'h00100073, unused);
|
||||||
|
jtag_dmi(1, 7'h17, 32'h00240000, unused);
|
||||||
|
abstract_wait();
|
||||||
|
endtask
|
||||||
|
task automatic debug_store(input logic [31:0] address, input logic [31:0] value);
|
||||||
|
set_gpr(28, address);
|
||||||
|
set_gpr(29, value);
|
||||||
|
execute_debug(32'h01de2023); // sw t4,0(t3)
|
||||||
|
endtask
|
||||||
|
initial begin
|
||||||
|
#10000000;
|
||||||
|
$display(
|
||||||
|
"CPU_DIAG starts=%0d completed=%b core_reset=%b halted=%b pending=%b idle=%b quiesce=%b hold=%b read=%b",
|
||||||
|
starts, completed, core_rst_n, halted, u_cpu.s_reset_pending, idle, quiesce, hold_read,
|
||||||
|
read_pending);
|
||||||
|
$display("CPU_DIAG I addr=%h trans=%b ready=%b D addr=%h trans=%b ready=%b slowstate=%0d",
|
||||||
|
cpu[0].haddr, cpu[0].htrans, cpu[0].hready, cpu[1].haddr, cpu[1].htrans,
|
||||||
|
cpu[1].hready, u_cpu_mem.s_slow_q.state);
|
||||||
|
$fatal(1, "SIM_TEST_TIMEOUT Tiny P4 CPU");
|
||||||
|
end
|
||||||
|
initial begin
|
||||||
|
if (!$value$plusargs("firmware=%s", firmware)) $fatal(1, "missing CPU fixture image");
|
||||||
|
$readmemh(firmware, rom);
|
||||||
|
cfg.paddr = 0;
|
||||||
|
cfg.pprot = 0;
|
||||||
|
cfg.psel = 0;
|
||||||
|
cfg.penable = 0;
|
||||||
|
cfg.pwrite = 0;
|
||||||
|
cfg.pwdata = 0;
|
||||||
|
cfg.pstrb = 15;
|
||||||
|
repeat (6) jtag_tick(1, 0, ignored);
|
||||||
|
@(negedge clk_i);
|
||||||
|
rst_n_i = 1;
|
||||||
|
repeat (12) jtag_tick(1, 0, ignored);
|
||||||
|
jtag_idle(1);
|
||||||
|
jtag_ir(5'h11);
|
||||||
|
wait (starts == 1);
|
||||||
|
$display("P4_CPU_STAGE boot-and-boundaries");
|
||||||
|
jtag_dmi(1, 7'h10, 1, status);
|
||||||
|
jtag_dmi(1, 7'h10, 32'h80000001, status);
|
||||||
|
wait (halted);
|
||||||
|
$display("P4_CPU_STAGE halted");
|
||||||
|
debug_store(32'h30000080, 32'h00200513);
|
||||||
|
debug_store(32'h3001ffe0, 1);
|
||||||
|
execute_debug(32'h0000100f); // FENCE.I before debugger resume
|
||||||
|
jtag_dmi(1, 7'h10, 32'h40000001, status);
|
||||||
|
jtag_dmi(1, 7'h10, 1, status);
|
||||||
|
wait (completed);
|
||||||
|
$display("P4_CPU_STAGE patched-code-executed");
|
||||||
|
// A different external owner retains a B response across hart reset.
|
||||||
|
@(negedge clk_i);
|
||||||
|
ext[2].awaddr = 32'h30010100;
|
||||||
|
ext[2].awvalid = 1;
|
||||||
|
do @(posedge clk_i); while (!ext[2].awready);
|
||||||
|
@(negedge clk_i);
|
||||||
|
ext[2].awvalid = 0;
|
||||||
|
ext[2].wdata = 32'hdecafbad;
|
||||||
|
ext[2].wvalid = 1;
|
||||||
|
do @(posedge clk_i); while (!ext[2].wready);
|
||||||
|
@(negedge clk_i);
|
||||||
|
ext[2].wvalid = 0;
|
||||||
|
wait (ext[2].bvalid);
|
||||||
|
@(negedge clk_i);
|
||||||
|
hold_read = 1;
|
||||||
|
wait (read_pending);
|
||||||
|
jtag_dmi(1, 7'h10, 3, status);
|
||||||
|
repeat (32) begin
|
||||||
|
@(posedge clk_i);
|
||||||
|
if (!core_rst_n || !ext[2].bvalid) $fatal(1, "hart reset discarded accepted work");
|
||||||
|
end
|
||||||
|
@(negedge clk_i);
|
||||||
|
hold_read = 0;
|
||||||
|
wait (!core_rst_n);
|
||||||
|
$display("P4_CPU_STAGE hart-reset-drained");
|
||||||
|
if (!ext[2].bvalid) $fatal(1, "hart reset reset external SRAM frontend");
|
||||||
|
jtag_dmi(1, 7'h10, 1, status);
|
||||||
|
wait (core_rst_n);
|
||||||
|
wait (starts == 2);
|
||||||
|
@(negedge clk_i);
|
||||||
|
ext[2].bready = 1;
|
||||||
|
@(posedge clk_i);
|
||||||
|
@(negedge clk_i);
|
||||||
|
ext[2].bready = 0;
|
||||||
|
ext[2].araddr = 32'h30010100;
|
||||||
|
ext[2].arvalid = 1;
|
||||||
|
do @(posedge clk_i); while (!ext[2].arready);
|
||||||
|
@(negedge clk_i);
|
||||||
|
ext[2].arvalid = 0;
|
||||||
|
ext[2].rready = 1;
|
||||||
|
do @(posedge clk_i); while (!ext[2].rvalid);
|
||||||
|
if (ext[2].rdata !== 32'hdecafbad || ext[2].rresp != 0)
|
||||||
|
$fatal(1, "SRAM payload lost on hart reset");
|
||||||
|
$display("SIM_TEST_PASS Tiny P4 CPU compressed-boundaries fence-i debug-patch hart-drain");
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
endmodule
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
module tiny_r2_p4_formal (
|
||||||
|
input logic clk_i,
|
||||||
|
input logic rst_n_i
|
||||||
|
);
|
||||||
|
logic s_started_q = 1'b0;
|
||||||
|
(* anyseq *) logic [2:0] s_valid, s_ready, s_write;
|
||||||
|
tiny_sram_port_if u_ports[3] ();
|
||||||
|
always_ff @(posedge clk_i) begin
|
||||||
|
s_started_q <= 1'b1;
|
||||||
|
assume (rst_n_i == s_started_q);
|
||||||
|
end
|
||||||
|
for (genvar client = 0; client < 3; client++) begin : gen_client
|
||||||
|
logic [2:0] s_outstanding_q = '0;
|
||||||
|
wire s_accept = u_ports[client].req_valid && u_ports[client].req_ready;
|
||||||
|
wire s_complete = u_ports[client].rsp_valid && u_ports[client].rsp_ready;
|
||||||
|
always_ff @(posedge clk_i) begin
|
||||||
|
if (!rst_n_i) s_outstanding_q <= '0;
|
||||||
|
else begin
|
||||||
|
case ({
|
||||||
|
s_accept, s_complete
|
||||||
|
})
|
||||||
|
2'b10: s_outstanding_q <= s_outstanding_q + 3'd1;
|
||||||
|
2'b01: s_outstanding_q <= s_outstanding_q - 3'd1;
|
||||||
|
default: begin
|
||||||
|
end
|
||||||
|
endcase
|
||||||
|
assert (s_outstanding_q <= ((client == 2) ? 3'd2 : 3'd1));
|
||||||
|
assert (!u_ports[client].rsp_valid || (s_outstanding_q != 3'd0));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
assign u_ports[client].req_valid = s_valid[client];
|
||||||
|
assign u_ports[client].rsp_ready = s_ready[client];
|
||||||
|
assign u_ports[client].write = s_write[client];
|
||||||
|
assign u_ports[client].addr = 32'h30000000 + 32'(client) * 32'h1000;
|
||||||
|
assign u_ports[client].wdata = 32'(client);
|
||||||
|
assign u_ports[client].wstrb = 4'hf;
|
||||||
|
end
|
||||||
|
tiny_sram_group u_dut (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i(rst_n_i),
|
||||||
|
.ports (u_ports)
|
||||||
|
);
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
// Abstract only the macro payload: arbitration, storage reservation, response
|
||||||
|
// capture and all control logic are the production group implementation.
|
||||||
|
module tc_sram_1024x32 (
|
||||||
|
input logic clk_i,
|
||||||
|
cs_i,
|
||||||
|
input logic [ 9:0] addr_i,
|
||||||
|
input logic [31:0] data_i,
|
||||||
|
input logic [ 3:0] mask_i,
|
||||||
|
input logic wren_i,
|
||||||
|
output logic [31:0] data_o
|
||||||
|
);
|
||||||
|
(* anyseq *) logic [31:0] s_value;
|
||||||
|
assign data_o = s_value;
|
||||||
|
endmodule
|
||||||
@@ -0,0 +1,659 @@
|
|||||||
|
`timescale 1ns / 1ps
|
||||||
|
module tiny_r2_p4_memory_tb;
|
||||||
|
logic clk_i = 0, rst_n_i = 0, quiesce = 0, idle;
|
||||||
|
logic perf_enable = 1, perf_clear = 0;
|
||||||
|
always #5 clk_i = ~clk_i;
|
||||||
|
ahbl_if cpu[2] (
|
||||||
|
.hclk (clk_i),
|
||||||
|
.hresetn(rst_n_i)
|
||||||
|
);
|
||||||
|
tiny_sram_port_if local_ports[2] ();
|
||||||
|
axi4_if #(
|
||||||
|
.ADDR_WIDTH(32),
|
||||||
|
.DATA_WIDTH(32),
|
||||||
|
.ID_WIDTH (1),
|
||||||
|
.USER_WIDTH(1)
|
||||||
|
) ext[4] (
|
||||||
|
.aclk (clk_i),
|
||||||
|
.aresetn(rst_n_i)
|
||||||
|
);
|
||||||
|
axi4_if #(
|
||||||
|
.ADDR_WIDTH(32),
|
||||||
|
.DATA_WIDTH(32),
|
||||||
|
.ID_WIDTH (1),
|
||||||
|
.USER_WIDTH(1)
|
||||||
|
) slow (
|
||||||
|
.aclk (clk_i),
|
||||||
|
.aresetn(rst_n_i)
|
||||||
|
);
|
||||||
|
apb4_if cfg (
|
||||||
|
.pclk (clk_i),
|
||||||
|
.presetn(rst_n_i)
|
||||||
|
);
|
||||||
|
logic [31:0] addr[2], data[2], rdata[2];
|
||||||
|
logic [2:0] size [2];
|
||||||
|
logic [1:0] trans[2];
|
||||||
|
logic [1:0] wr, ready, resp;
|
||||||
|
logic [31:0] awaddr[4], wdata[4], araddr[4], erdata[4];
|
||||||
|
logic [3:0] wstrb[4];
|
||||||
|
logic [7:0] awlen[4], arlen[4];
|
||||||
|
logic [2:0] awsize[4], arsize[4];
|
||||||
|
logic [1:0] awburst[4], arburst[4], bresp[4], rresp[4];
|
||||||
|
logic [3:0] awvalid, wvalid, wlast, bready, arvalid, rready;
|
||||||
|
logic [3:0] awready, wready, bvalid, arready, rvalid, rlast;
|
||||||
|
integer parallel_issues = 0;
|
||||||
|
integer local_latency_max = 0;
|
||||||
|
logic [31:0] observed;
|
||||||
|
logic [31:0] seed = 32'h12345678;
|
||||||
|
|
||||||
|
tiny_cpu_mem u_cpu_mem (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i (rst_n_i),
|
||||||
|
.quiesce_i (quiesce),
|
||||||
|
.idle_o (idle),
|
||||||
|
.cpu (cpu),
|
||||||
|
.local_ports(local_ports),
|
||||||
|
.slow_axi4 (slow)
|
||||||
|
);
|
||||||
|
tiny_sram u_sram (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i (rst_n_i),
|
||||||
|
.perf_enable_i (perf_enable),
|
||||||
|
.perf_clear_i (perf_clear),
|
||||||
|
.local_ports (local_ports),
|
||||||
|
.external_ports(ext),
|
||||||
|
.cfg_apb4 (cfg)
|
||||||
|
);
|
||||||
|
axi4_error_slave #(
|
||||||
|
.Response(2'b11)
|
||||||
|
) u_slow_error (
|
||||||
|
.clk_i (clk_i),
|
||||||
|
.rst_n_i(rst_n_i),
|
||||||
|
.axi4 (slow)
|
||||||
|
);
|
||||||
|
for (genvar p = 0; p < 2; p++) begin : gen_cpu
|
||||||
|
assign cpu[p].haddr = addr[p];
|
||||||
|
assign cpu[p].hwdata = data[p];
|
||||||
|
assign cpu[p].hsize = size[p];
|
||||||
|
assign cpu[p].htrans = trans[p];
|
||||||
|
assign cpu[p].hwrite = wr[p];
|
||||||
|
assign cpu[p].hburst = 0;
|
||||||
|
assign cpu[p].hprot = 4'(p);
|
||||||
|
assign cpu[p].hmastlock = 0;
|
||||||
|
assign ready[p] = cpu[p].hready;
|
||||||
|
assign resp[p] = cpu[p].hresp;
|
||||||
|
assign rdata[p] = cpu[p].hrdata;
|
||||||
|
end
|
||||||
|
for (genvar g = 0; g < 4; g++) begin : gen_ext
|
||||||
|
assign ext[g].awaddr = awaddr[g];
|
||||||
|
assign ext[g].awlen = awlen[g];
|
||||||
|
assign ext[g].awsize = awsize[g];
|
||||||
|
assign ext[g].awburst = awburst[g];
|
||||||
|
assign ext[g].awvalid = awvalid[g];
|
||||||
|
assign ext[g].wdata = wdata[g];
|
||||||
|
assign ext[g].wstrb = wstrb[g];
|
||||||
|
assign ext[g].wlast = wlast[g];
|
||||||
|
assign ext[g].wvalid = wvalid[g];
|
||||||
|
assign ext[g].bready = bready[g];
|
||||||
|
assign ext[g].araddr = araddr[g];
|
||||||
|
assign ext[g].arlen = arlen[g];
|
||||||
|
assign ext[g].arsize = arsize[g];
|
||||||
|
assign ext[g].arburst = arburst[g];
|
||||||
|
assign ext[g].arvalid = arvalid[g];
|
||||||
|
assign ext[g].rready = rready[g];
|
||||||
|
assign ext[g].awid = '0;
|
||||||
|
assign ext[g].awlock = '0;
|
||||||
|
assign ext[g].awcache = '0;
|
||||||
|
assign ext[g].awprot = '0;
|
||||||
|
assign ext[g].awqos = '0;
|
||||||
|
assign ext[g].awregion = '0;
|
||||||
|
assign ext[g].awuser = '0;
|
||||||
|
assign ext[g].wuser = '0;
|
||||||
|
assign ext[g].arid = '0;
|
||||||
|
assign ext[g].arlock = '0;
|
||||||
|
assign ext[g].arcache = '0;
|
||||||
|
assign ext[g].arprot = '0;
|
||||||
|
assign ext[g].arqos = '0;
|
||||||
|
assign ext[g].arregion = '0;
|
||||||
|
assign ext[g].aruser = '0;
|
||||||
|
assign awready[g] = ext[g].awready;
|
||||||
|
assign wready[g] = ext[g].wready;
|
||||||
|
assign bvalid[g] = ext[g].bvalid;
|
||||||
|
assign bresp[g] = ext[g].bresp;
|
||||||
|
assign arready[g] = ext[g].arready;
|
||||||
|
assign rvalid[g] = ext[g].rvalid;
|
||||||
|
assign rresp[g] = ext[g].rresp;
|
||||||
|
assign rlast[g] = ext[g].rlast;
|
||||||
|
assign erdata[g] = ext[g].rdata;
|
||||||
|
end
|
||||||
|
always @(posedge clk_i) begin
|
||||||
|
if ((int'(u_sram.gen_group[0].u_group.s_issue) +
|
||||||
|
int'(u_sram.gen_group[1].u_group.s_issue) +
|
||||||
|
int'(u_sram.gen_group[2].u_group.s_issue) +
|
||||||
|
int'(u_sram.gen_group[3].u_group.s_issue)) > 1)
|
||||||
|
parallel_issues++;
|
||||||
|
end
|
||||||
|
task automatic ahb(input integer p, input bit write_access, input logic [31:0] address,
|
||||||
|
input logic [2:0] width, input logic [31:0] value, input bit error_expected,
|
||||||
|
input integer max_latency, output logic [31:0] result);
|
||||||
|
time accepted;
|
||||||
|
integer error_cycles;
|
||||||
|
@(negedge clk_i);
|
||||||
|
addr[p] = address;
|
||||||
|
size[p] = width;
|
||||||
|
wr[p] = write_access;
|
||||||
|
trans[p] = 2;
|
||||||
|
do @(posedge clk_i); while (!ready[p]);
|
||||||
|
accepted = $time;
|
||||||
|
@(negedge clk_i);
|
||||||
|
trans[p] = 0;
|
||||||
|
data[p] = value;
|
||||||
|
error_cycles = 0;
|
||||||
|
do begin
|
||||||
|
@(posedge clk_i);
|
||||||
|
if (resp[p]) error_cycles++;
|
||||||
|
end while (!ready[p]);
|
||||||
|
result = rdata[p];
|
||||||
|
if (resp[p] != error_expected || (error_expected && error_cycles != 2))
|
||||||
|
$fatal(1, "AHB error protocol p=%0d addr=%h errors=%0d", p, address, error_cycles);
|
||||||
|
if (max_latency != 0 && (($time - accepted) / 10) > max_latency)
|
||||||
|
$fatal(1, "local latency p=%0d addr=%h cycles=%0d", p, address, ($time - accepted) / 10);
|
||||||
|
if (max_latency != 0 && (($time - accepted) / 10) > local_latency_max)
|
||||||
|
local_latency_max = integer'(($time - accepted) / 10);
|
||||||
|
@(negedge clk_i);
|
||||||
|
endtask
|
||||||
|
task automatic store(input logic [31:0] address, input logic [31:0] value,
|
||||||
|
input logic [2:0] width);
|
||||||
|
logic [31:0] unused;
|
||||||
|
ahb(1, 1, address, width, value, 0, 3, unused);
|
||||||
|
endtask
|
||||||
|
task automatic load(input integer p, input logic [31:0] address, input logic [31:0] value,
|
||||||
|
input integer max_latency);
|
||||||
|
logic [31:0] got;
|
||||||
|
ahb(p, 0, address, 2, 0, 0, max_latency, got);
|
||||||
|
if (got !== value)
|
||||||
|
$fatal(1, "local data p=%0d addr=%h got=%h expected=%h", p, address, got, value);
|
||||||
|
endtask
|
||||||
|
task automatic read_external(input integer g, input logic [31:0] address,
|
||||||
|
input logic [31:0] value, input integer hold_cycles,
|
||||||
|
input logic [1:0] expected_resp);
|
||||||
|
@(negedge clk_i);
|
||||||
|
araddr[g] = address;
|
||||||
|
arvalid[g] = 1;
|
||||||
|
do @(posedge clk_i); while (!arready[g]);
|
||||||
|
@(negedge clk_i);
|
||||||
|
arvalid[g] = 0;
|
||||||
|
while (!rvalid[g]) @(negedge clk_i);
|
||||||
|
repeat (hold_cycles) begin
|
||||||
|
@(posedge clk_i);
|
||||||
|
if (!rvalid[g] || erdata[g] !== value || rresp[g] != expected_resp || !rlast[g])
|
||||||
|
$fatal(1, "external response changed under backpressure");
|
||||||
|
@(negedge clk_i);
|
||||||
|
end
|
||||||
|
if (erdata[g] !== value || rresp[g] != expected_resp || !rlast[g])
|
||||||
|
$fatal(1, "external read g=%0d got=%h expected=%h resp=%b", g, erdata[g], value, rresp[g]);
|
||||||
|
rready[g] = 1;
|
||||||
|
@(posedge clk_i);
|
||||||
|
@(negedge clk_i);
|
||||||
|
rready[g] = 0;
|
||||||
|
endtask
|
||||||
|
task automatic write_external(input integer g, input logic [31:0] address,
|
||||||
|
input logic [31:0] value, input logic [3:0] mask,
|
||||||
|
input integer delay_data);
|
||||||
|
@(negedge clk_i);
|
||||||
|
awaddr[g] = address;
|
||||||
|
awvalid[g] = 1;
|
||||||
|
do @(posedge clk_i); while (!awready[g]);
|
||||||
|
@(negedge clk_i);
|
||||||
|
awvalid[g] = 0;
|
||||||
|
repeat (delay_data) @(negedge clk_i);
|
||||||
|
wdata[g] = value;
|
||||||
|
wstrb[g] = mask;
|
||||||
|
wvalid[g] = 1;
|
||||||
|
wlast[g] = 1;
|
||||||
|
do @(posedge clk_i); while (!wready[g]);
|
||||||
|
@(negedge clk_i);
|
||||||
|
wvalid[g] = 0;
|
||||||
|
bready[g] = 1;
|
||||||
|
do @(posedge clk_i); while (!bvalid[g]);
|
||||||
|
if (bresp[g] != 0) $fatal(1, "external write");
|
||||||
|
@(negedge clk_i);
|
||||||
|
bready[g] = 0;
|
||||||
|
endtask
|
||||||
|
task automatic config_read(input logic [11:0] offset, input logic [31:0] expected);
|
||||||
|
@(negedge clk_i);
|
||||||
|
cfg.paddr = {20'h10017, offset};
|
||||||
|
cfg.psel = 1;
|
||||||
|
cfg.penable = 0;
|
||||||
|
@(negedge clk_i);
|
||||||
|
cfg.penable = 1;
|
||||||
|
do @(posedge clk_i); while (!cfg.pready);
|
||||||
|
if (cfg.pslverr || cfg.prdata !== expected)
|
||||||
|
$fatal(1, "SRAM ABI/perf offset=%h got=%h expected=%h", offset, cfg.prdata, expected);
|
||||||
|
@(negedge clk_i);
|
||||||
|
cfg.psel = 0;
|
||||||
|
cfg.penable = 0;
|
||||||
|
endtask
|
||||||
|
task automatic burst_and_errors;
|
||||||
|
// Sixteen captured write beats, with W asserted before AW.
|
||||||
|
@(negedge clk_i);
|
||||||
|
wdata[0] = 32'hcafe0000;
|
||||||
|
wstrb[0] = 15;
|
||||||
|
wvalid[0] = 1;
|
||||||
|
wlast[0] = 0;
|
||||||
|
repeat (3) begin
|
||||||
|
@(posedge clk_i);
|
||||||
|
if (wready[0]) $fatal(1, "W accepted without AW ownership");
|
||||||
|
end
|
||||||
|
@(negedge clk_i);
|
||||||
|
awaddr[0] = 32'h30000fc0;
|
||||||
|
awlen[0] = 15;
|
||||||
|
awvalid[0] = 1;
|
||||||
|
do @(posedge clk_i); while (!awready[0]);
|
||||||
|
@(negedge clk_i);
|
||||||
|
awvalid[0] = 0;
|
||||||
|
for (integer beat = 0; beat < 16; beat++) begin
|
||||||
|
wdata[0] = 32'hcafe0000 + beat;
|
||||||
|
wlast[0] = (beat == 15);
|
||||||
|
do @(posedge clk_i); while (!wready[0]);
|
||||||
|
@(negedge clk_i);
|
||||||
|
end
|
||||||
|
wvalid[0] = 0;
|
||||||
|
wait (bvalid[0]);
|
||||||
|
load(0, 32'h30000ffc, 32'hcafe000f, 3); // B held while local read completes.
|
||||||
|
if (!bvalid[0] || bresp[0] != 0) $fatal(1, "B not retained");
|
||||||
|
@(negedge clk_i);
|
||||||
|
bready[0] = 1;
|
||||||
|
@(posedge clk_i);
|
||||||
|
@(negedge clk_i);
|
||||||
|
bready[0] = 0;
|
||||||
|
awlen[0] = 0;
|
||||||
|
araddr[0] = 32'h30000fc0;
|
||||||
|
arlen[0] = 15;
|
||||||
|
arvalid[0] = 1;
|
||||||
|
do @(posedge clk_i); while (!arready[0]);
|
||||||
|
@(negedge clk_i);
|
||||||
|
arvalid[0] = 0;
|
||||||
|
for (integer beat = 0; beat < 16; beat++) begin
|
||||||
|
wait (rvalid[0]);
|
||||||
|
repeat (beat % 4) @(negedge clk_i);
|
||||||
|
if (erdata[0] !== (32'hcafe0000 + beat) || rresp[0] != 0 || rlast[0] != (beat == 15))
|
||||||
|
$fatal(1, "burst read ordering/last at beat %0d", beat);
|
||||||
|
rready[0] = 1;
|
||||||
|
@(posedge clk_i);
|
||||||
|
@(negedge clk_i);
|
||||||
|
rready[0] = 0;
|
||||||
|
end
|
||||||
|
arlen[0] = 0;
|
||||||
|
// A malformed write cannot commit bytes. Test strobes and early/missing LAST.
|
||||||
|
for (integer bad = 0; bad < 3; bad++) begin
|
||||||
|
@(negedge clk_i);
|
||||||
|
awaddr[0] = 32'h30000fc0;
|
||||||
|
awvalid[0] = 1;
|
||||||
|
awsize[0] = (bad == 0) ? 1 : 2;
|
||||||
|
awlen[0] = (bad == 1) ? 3 : 0;
|
||||||
|
do @(posedge clk_i); while (!awready[0]);
|
||||||
|
@(negedge clk_i);
|
||||||
|
awvalid[0] = 0;
|
||||||
|
wvalid[0] = 1;
|
||||||
|
wdata[0] = 0;
|
||||||
|
wstrb[0] = (bad == 0) ? 4'h8 : 4'hf;
|
||||||
|
wlast[0] = (bad != 2);
|
||||||
|
do @(posedge clk_i); while (!wready[0]);
|
||||||
|
@(negedge clk_i);
|
||||||
|
wvalid[0] = 0;
|
||||||
|
bready[0] = 1;
|
||||||
|
do @(posedge clk_i); while (!bvalid[0]);
|
||||||
|
if (bresp[0] != 2) $fatal(1, "malformed write accepted");
|
||||||
|
@(negedge clk_i);
|
||||||
|
bready[0] = 0;
|
||||||
|
load(0, 32'h30000fc0, 32'hcafe0000, 3);
|
||||||
|
end
|
||||||
|
awsize[0] = 2;
|
||||||
|
awlen[0] = 0;
|
||||||
|
wlast[0] = 1;
|
||||||
|
wstrb[0] = 15;
|
||||||
|
endtask
|
||||||
|
task automatic back_to_back;
|
||||||
|
// The next address is already present when the preceding write completes.
|
||||||
|
@(negedge clk_i);
|
||||||
|
addr[1] = 32'h30000100;
|
||||||
|
size[1] = 2;
|
||||||
|
wr[1] = 1;
|
||||||
|
trans[1] = 2;
|
||||||
|
do @(posedge clk_i); while (!ready[1]);
|
||||||
|
for (integer word_index = 0; word_index < 8; word_index++) begin
|
||||||
|
@(negedge clk_i);
|
||||||
|
data[1] = 32'hf00d0000 + word_index;
|
||||||
|
addr[1] = 32'h30000104 + word_index * 4;
|
||||||
|
trans[1] = (word_index == 7) ? 0 : 2;
|
||||||
|
do @(posedge clk_i); while (!ready[1]);
|
||||||
|
if (resp[1]) $fatal(1, "pipelined write error");
|
||||||
|
end
|
||||||
|
@(negedge clk_i);
|
||||||
|
for (integer word_index = 0; word_index < 8; word_index++)
|
||||||
|
load(0, 32'h30000100 + word_index * 4, 32'hf00d0000 + word_index, 3);
|
||||||
|
endtask
|
||||||
|
task automatic streaming_burst;
|
||||||
|
time started, first_beat, previous_beat, final_write;
|
||||||
|
integer read_gap_max, write_gap_max;
|
||||||
|
read_gap_max = 0;
|
||||||
|
write_gap_max = 0;
|
||||||
|
@(negedge clk_i);
|
||||||
|
awaddr[0] = 32'h30000400;
|
||||||
|
awlen[0] = 15;
|
||||||
|
awvalid[0] = 1;
|
||||||
|
do @(posedge clk_i); while (!awready[0]);
|
||||||
|
started = $time;
|
||||||
|
@(negedge clk_i);
|
||||||
|
awvalid[0] = 0;
|
||||||
|
wvalid[0] = 1;
|
||||||
|
for (integer beat = 0; beat < 16; beat++) begin
|
||||||
|
wdata[0] = 32'hface0000 + beat;
|
||||||
|
wlast[0] = (beat == 15);
|
||||||
|
do @(posedge clk_i); while (!wready[0]);
|
||||||
|
if (beat == 0) first_beat = $time;
|
||||||
|
else begin
|
||||||
|
if (($time - previous_beat) != 10) $fatal(1, "write streaming bubble beat=%0d", beat);
|
||||||
|
write_gap_max = 1;
|
||||||
|
end
|
||||||
|
previous_beat = $time;
|
||||||
|
@(negedge clk_i);
|
||||||
|
end
|
||||||
|
final_write = previous_beat;
|
||||||
|
wvalid[0] = 0;
|
||||||
|
bready[0] = 1;
|
||||||
|
do @(posedge clk_i); while (!bvalid[0]);
|
||||||
|
if (bresp[0] != 0) $fatal(1, "streaming write response");
|
||||||
|
$display("P4_STREAM_WRITE first=%0d total=%0d b_after_last=%0d", (first_beat - started) / 10,
|
||||||
|
($time - started) / 10, ($time - final_write) / 10);
|
||||||
|
@(negedge clk_i);
|
||||||
|
bready[0] = 0;
|
||||||
|
awlen[0] = 0;
|
||||||
|
araddr[0] = 32'h30000400;
|
||||||
|
arlen[0] = 15;
|
||||||
|
arvalid[0] = 1;
|
||||||
|
rready[0] = 1;
|
||||||
|
do @(posedge clk_i); while (!arready[0]);
|
||||||
|
started = $time;
|
||||||
|
@(negedge clk_i);
|
||||||
|
arvalid[0] = 0;
|
||||||
|
for (integer beat = 0; beat < 16; beat++) begin
|
||||||
|
do @(posedge clk_i); while (!rvalid[0]);
|
||||||
|
if (erdata[0] !== (32'hface0000 + beat) || rresp[0] != 0 || rlast[0] != (beat == 15))
|
||||||
|
$fatal(1, "streaming read ordering beat=%0d", beat);
|
||||||
|
if (beat == 0) first_beat = $time;
|
||||||
|
else begin
|
||||||
|
if (($time - previous_beat) != 10) $fatal(1, "read streaming bubble beat=%0d", beat);
|
||||||
|
read_gap_max = 1;
|
||||||
|
end
|
||||||
|
previous_beat = $time;
|
||||||
|
@(negedge clk_i);
|
||||||
|
end
|
||||||
|
rready[0] = 0;
|
||||||
|
arlen[0] = 0;
|
||||||
|
$display("P4_STREAM_READ first=%0d total=%0d", (first_beat - started) / 10,
|
||||||
|
(previous_beat - started) / 10);
|
||||||
|
$display("P4_STREAM read_gap_max=%0d write_gap_max=%0d beats=16", read_gap_max, write_gap_max);
|
||||||
|
endtask
|
||||||
|
|
||||||
|
task automatic buffered_read;
|
||||||
|
// Two reserved words must survive a full queue, local writes to their
|
||||||
|
// addresses, and READY dropping again immediately after the first pop.
|
||||||
|
@(negedge clk_i);
|
||||||
|
araddr[0] = 32'h30000400;
|
||||||
|
arlen[0] = 15;
|
||||||
|
arvalid[0] = 1;
|
||||||
|
do @(posedge clk_i); while (!arready[0]);
|
||||||
|
@(negedge clk_i);
|
||||||
|
arvalid[0] = 0;
|
||||||
|
repeat (8) @(negedge clk_i);
|
||||||
|
store(32'h30000400, 32'habcd0000, 2);
|
||||||
|
store(32'h30000404, 32'habcd0001, 2);
|
||||||
|
store(32'h30000408, 32'habcd0002, 2);
|
||||||
|
for (integer beat = 0; beat < 16; beat++) begin
|
||||||
|
rready[0] = 1;
|
||||||
|
do @(posedge clk_i); while (!rvalid[0]);
|
||||||
|
if (erdata[0] !== ((beat == 2) ? 32'habcd0002 : (32'hface0000 + beat)) ||
|
||||||
|
rresp[0] != 0 || rlast[0] != (beat == 15))
|
||||||
|
$fatal(1, "reserved response lost/reordered beat=%0d", beat);
|
||||||
|
@(negedge clk_i);
|
||||||
|
rready[0] = 0;
|
||||||
|
repeat (beat % 5 + 1) @(negedge clk_i);
|
||||||
|
end
|
||||||
|
arlen[0] = 0;
|
||||||
|
endtask
|
||||||
|
|
||||||
|
task automatic buffered_write_errors;
|
||||||
|
integer beats, stalls;
|
||||||
|
// Earlier queued beats must commit before an error B. Later legal beats
|
||||||
|
// after a bad strobe keep the existing target policy; early LAST ends W.
|
||||||
|
for (integer bad = 0; bad < 3; bad++) begin
|
||||||
|
for (integer n = 0; n < 8; n++) store(32'h30000600 + n * 4, 32'h12340000 + n, 2);
|
||||||
|
beats = (bad == 1) ? 4 : 8;
|
||||||
|
stalls = 0;
|
||||||
|
@(negedge clk_i);
|
||||||
|
awaddr[0] = 32'h30000600;
|
||||||
|
awlen[0] = 7;
|
||||||
|
awsize[0] = (bad == 0) ? 1 : 2;
|
||||||
|
awvalid[0] = 1;
|
||||||
|
do @(posedge clk_i); while (!awready[0]);
|
||||||
|
@(negedge clk_i);
|
||||||
|
awvalid[0] = 0;
|
||||||
|
fork
|
||||||
|
begin
|
||||||
|
for (integer beat = 0; beat < beats; beat++) begin
|
||||||
|
wvalid[0] = 1;
|
||||||
|
wdata[0] = 32'hbeefbeef;
|
||||||
|
wstrb[0] = (bad == 0) ? ((beat == 3) ? 4'hf : ((beat % 2) == 0 ? 4'h3 : 4'hc)) : 4'hf;
|
||||||
|
wlast[0] = (bad == 1) ? (beat == 3) : ((bad == 0) && (beat == 7));
|
||||||
|
do begin
|
||||||
|
@(posedge clk_i);
|
||||||
|
if (!wready[0]) stalls++;
|
||||||
|
end while (!wready[0]);
|
||||||
|
@(negedge clk_i);
|
||||||
|
wvalid[0] = 0;
|
||||||
|
if (beat == 4) repeat (3) @(negedge clk_i);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
begin
|
||||||
|
repeat (6) load(0, 32'h30000020, 32'h98765432, 0);
|
||||||
|
end
|
||||||
|
begin
|
||||||
|
repeat (6) load(1, 32'h30000020, 32'h98765432, 0);
|
||||||
|
end
|
||||||
|
join
|
||||||
|
wait (bvalid[0]);
|
||||||
|
if (bresp[0] != 2) $fatal(1, "buffered malformed write response");
|
||||||
|
if (bad != 0 && stalls == 0) $fatal(1, "write buffer full path not exercised");
|
||||||
|
for (integer n = 0; n < 8; n++) begin
|
||||||
|
if (bad == 0) begin
|
||||||
|
load(0, 32'h30000600 + n * 4,
|
||||||
|
(n == 1) ? 32'h1234beef : ((n < 4) ? 32'hbeefbeef : (32'h12340000 + n)), 3);
|
||||||
|
end else begin
|
||||||
|
load(0, 32'h30000600 + n * 4, (n < (beats - 1)) ? 32'hbeefbeef : (32'h12340000 + n), 3);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@(negedge clk_i);
|
||||||
|
bready[0] = 1;
|
||||||
|
@(posedge clk_i);
|
||||||
|
@(negedge clk_i);
|
||||||
|
bready[0] = 0;
|
||||||
|
end
|
||||||
|
awlen[0] = 0;
|
||||||
|
awsize[0] = 2;
|
||||||
|
wlast[0] = 1;
|
||||||
|
wstrb[0] = 15;
|
||||||
|
endtask
|
||||||
|
initial begin
|
||||||
|
#20000000;
|
||||||
|
$fatal(1, "SIM_TEST_TIMEOUT Tiny P4 memory");
|
||||||
|
end
|
||||||
|
initial begin
|
||||||
|
wr = 0;
|
||||||
|
trans[0] = 0;
|
||||||
|
trans[1] = 0;
|
||||||
|
addr[0] = 0;
|
||||||
|
addr[1] = 0;
|
||||||
|
data[0] = 0;
|
||||||
|
data[1] = 0;
|
||||||
|
size[0] = 2;
|
||||||
|
size[1] = 2;
|
||||||
|
awvalid = 0;
|
||||||
|
wvalid = 0;
|
||||||
|
wlast = 0;
|
||||||
|
bready = 0;
|
||||||
|
arvalid = 0;
|
||||||
|
rready = 0;
|
||||||
|
for (integer g = 0; g < 4; g++) begin
|
||||||
|
awaddr[g] = 0;
|
||||||
|
awlen[g] = 0;
|
||||||
|
awsize[g] = 2;
|
||||||
|
awburst[g] = 1;
|
||||||
|
araddr[g] = 0;
|
||||||
|
arlen[g] = 0;
|
||||||
|
arsize[g] = 2;
|
||||||
|
arburst[g] = 1;
|
||||||
|
wdata[g] = 0;
|
||||||
|
wstrb[g] = 15;
|
||||||
|
end
|
||||||
|
cfg.paddr = 0;
|
||||||
|
cfg.pprot = 0;
|
||||||
|
cfg.psel = 0;
|
||||||
|
cfg.penable = 0;
|
||||||
|
cfg.pwrite = 0;
|
||||||
|
cfg.pwdata = 0;
|
||||||
|
cfg.pstrb = 15;
|
||||||
|
repeat (5) @(negedge clk_i);
|
||||||
|
rst_n_i = 1;
|
||||||
|
config_read(12'h00c, 131072);
|
||||||
|
config_read(12'h010, 32);
|
||||||
|
config_read(12'h014, 4096);
|
||||||
|
// First/last words of all 32 physical macros and every 32 KiB boundary.
|
||||||
|
for (integer bank = 0; bank < 32; bank++) begin
|
||||||
|
store(32'h30000000 + bank * 4096, 32'ha5000000 + bank, 2);
|
||||||
|
store(32'h30000ffc + bank * 4096, 32'h5a000000 + bank, 2);
|
||||||
|
load(0, 32'h30000000 + bank * 4096, 32'ha5000000 + bank, 3);
|
||||||
|
load(1, 32'h30000ffc + bank * 4096, 32'h5a000000 + bank, 3);
|
||||||
|
end
|
||||||
|
store(32'h30000020, 32'h11223344, 2);
|
||||||
|
store(32'h30000021, 32'haa, 0);
|
||||||
|
store(32'h30000022, 32'hbbcc, 1);
|
||||||
|
load(0, 32'h30000020, 32'hbbccaa44, 3);
|
||||||
|
for (integer lane = 0; lane < 4; lane++) begin
|
||||||
|
store(32'h30000028, 32'h11223344, 2);
|
||||||
|
store(32'h30000028 + lane, 32'haa, 0);
|
||||||
|
load(0, 32'h30000028, (32'h11223344 & ~(32'hff << (lane * 8))) | (32'haa << (lane * 8)), 3);
|
||||||
|
end
|
||||||
|
store(32'h30000028, 32'h11223344, 2);
|
||||||
|
store(32'h30000028, 32'haabb, 1);
|
||||||
|
load(0, 32'h30000028, 32'h1122aabb, 3);
|
||||||
|
back_to_back();
|
||||||
|
fork
|
||||||
|
load(0, 32'h30000000, 32'ha5000000, 3);
|
||||||
|
load(1, 32'h30008000, 32'ha5000008, 3);
|
||||||
|
join
|
||||||
|
if (parallel_issues == 0) $fatal(1, "different groups serialized");
|
||||||
|
fork
|
||||||
|
load(0, 32'h30000000, 32'ha5000000, 0);
|
||||||
|
load(1, 32'h30000000, 32'ha5000000, 0);
|
||||||
|
read_external(0, 32'h30000000, 32'ha5000000, 0, 0);
|
||||||
|
join
|
||||||
|
// A held R must retain old data while local writes use that same macro.
|
||||||
|
fork
|
||||||
|
read_external(0, 32'h30000020, 32'hbbccaa44, 31, 0);
|
||||||
|
begin
|
||||||
|
wait (rvalid[0]);
|
||||||
|
store(32'h30000020, 32'h98765432, 2);
|
||||||
|
load(0, 32'h30000020, 32'h98765432, 3);
|
||||||
|
end
|
||||||
|
join
|
||||||
|
// Waiting for external W does not lock the macro against CPU accesses.
|
||||||
|
fork
|
||||||
|
write_external(0, 32'h30000024, 32'hdeadbeef, 15, 31);
|
||||||
|
begin
|
||||||
|
wait (wready[0]);
|
||||||
|
load(0, 32'h30000020, 32'h98765432, 3);
|
||||||
|
end
|
||||||
|
join
|
||||||
|
load(1, 32'h30000024, 32'hdeadbeef, 3);
|
||||||
|
@(negedge clk_i);
|
||||||
|
perf_clear = 1;
|
||||||
|
@(negedge clk_i);
|
||||||
|
perf_clear = 0;
|
||||||
|
fork
|
||||||
|
read_external(0, 32'h30000000, 32'ha5000000, 7, 0);
|
||||||
|
read_external(1, 32'h30008000, 32'ha5000008, 7, 0);
|
||||||
|
read_external(2, 32'h30010000, 32'ha5000010, 7, 0);
|
||||||
|
read_external(3, 32'h30018000, 32'ha5000018, 7, 0);
|
||||||
|
join
|
||||||
|
config_read(12'h020, 4);
|
||||||
|
config_read(12'h024, 0);
|
||||||
|
config_read(12'h028, 4);
|
||||||
|
config_read(12'h030, 7); // Four parallel stalls count once per clock.
|
||||||
|
burst_and_errors();
|
||||||
|
streaming_burst();
|
||||||
|
buffered_read();
|
||||||
|
buffered_write_errors();
|
||||||
|
read_external(0, 32'h30008000, 0, 0, 3);
|
||||||
|
ahb(1, 0, 32'h70000000, 2, 0, 1, 0, observed);
|
||||||
|
ahb(1, 0, 32'h30000001, 2, 0, 1, 0, observed);
|
||||||
|
// Deterministic word/byte readback across all groups without touching a
|
||||||
|
// simulator memory array: every operation travels through the real ports.
|
||||||
|
for (integer g = 0; g < 4; g++) begin
|
||||||
|
for (integer n = 0; n < 4096; n++) begin
|
||||||
|
seed = seed ^ (seed << 13);
|
||||||
|
seed = seed ^ (seed >> 17);
|
||||||
|
seed = seed ^ (seed << 5);
|
||||||
|
store(32'h30000000 + g * 32768 + (n % 8192) * 4, seed, 2);
|
||||||
|
load(0, 32'h30000000 + g * 32768 + (n % 8192) * 4, seed, 3);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
store(32'h3001fff0, 32'h76543210, 2);
|
||||||
|
@(negedge clk_i);
|
||||||
|
araddr[3] = 32'h3001fff0;
|
||||||
|
arlen[3] = 3;
|
||||||
|
arvalid[3] = 1;
|
||||||
|
do @(posedge clk_i); while (!arready[3]);
|
||||||
|
@(negedge clk_i);
|
||||||
|
arvalid[3] = 0;
|
||||||
|
wait (rvalid[3]);
|
||||||
|
repeat (4) @(negedge clk_i);
|
||||||
|
@(negedge clk_i);
|
||||||
|
rst_n_i = 0;
|
||||||
|
repeat (3) @(negedge clk_i);
|
||||||
|
if (rvalid != 0 || bvalid != 0) $fatal(1, "system reset retained a stale response");
|
||||||
|
rst_n_i = 1;
|
||||||
|
arlen[3] = 0;
|
||||||
|
load(0, 32'h3001fff0, 32'h76543210, 3);
|
||||||
|
read_external(3, 32'h3001fff0, 32'h76543210, 0, 0);
|
||||||
|
// Reset after W acceptance, before the captured word reaches a macro.
|
||||||
|
// The cancelled word and its completion must not replay into a new session.
|
||||||
|
@(negedge clk_i);
|
||||||
|
awaddr[3] = 32'h3001fff0;
|
||||||
|
awlen[3] = 3;
|
||||||
|
awvalid[3] = 1;
|
||||||
|
do @(posedge clk_i); while (!awready[3]);
|
||||||
|
@(negedge clk_i);
|
||||||
|
awvalid[3] = 0;
|
||||||
|
wdata[3] = 32'hbad0bad0;
|
||||||
|
wlast[3] = 0;
|
||||||
|
wvalid[3] = 1;
|
||||||
|
do @(posedge clk_i); while (!wready[3]);
|
||||||
|
@(negedge clk_i);
|
||||||
|
wvalid[3] = 0;
|
||||||
|
rst_n_i = 0;
|
||||||
|
repeat (3) @(negedge clk_i);
|
||||||
|
rst_n_i = 1;
|
||||||
|
awlen[3] = 0;
|
||||||
|
wlast[3] = 1;
|
||||||
|
repeat (5) @(negedge clk_i);
|
||||||
|
if (bvalid != 0 || rvalid != 0) $fatal(1, "cancelled W response replayed");
|
||||||
|
read_external(3, 32'h3001fff0, 32'h76543210, 0, 0);
|
||||||
|
$display("P4_BUFFER backpressure errors reset conservation PASS");
|
||||||
|
$display("P4_MEMORY local_latency_max=%0d parallel_issue_cycles=%0d", local_latency_max,
|
||||||
|
parallel_issues);
|
||||||
|
$display("SIM_TEST_PASS Tiny P4 memory seed=12345678 random_per_group=4096");
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
endmodule
|
||||||
@@ -107,10 +107,10 @@ def test_chip_config_covers_every_onchip_sram_bank(tmp_path: Path) -> None:
|
|||||||
macro = config["MACROS"]["RM_IHPSG13_1P_1024x32_c2_bm_bist"]
|
macro = config["MACROS"]["RM_IHPSG13_1P_1024x32_c2_bm_bist"]
|
||||||
assert len(macro["instances"]) == 32
|
assert len(macro["instances"]) == 32
|
||||||
assert (
|
assert (
|
||||||
"u_soc.u_sram.gen_memory.gen_bank[0].u_ram.u_mem" in macro["instances"]
|
"u_soc.u_sram.gen_group[0].u_group.gen_bank[0].u_ram.u_mem" in macro["instances"]
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
"u_soc.u_sram.gen_memory.gen_bank[31].u_ram.u_mem" in macro["instances"]
|
"u_soc.u_sram.gen_group[3].u_group.gen_bank[7].u_ram.u_mem" in macro["instances"]
|
||||||
)
|
)
|
||||||
for instance in macro["instances"].values():
|
for instance in macro["instances"].values():
|
||||||
x, y = instance["location"]
|
x, y = instance["location"]
|
||||||
|
|||||||
@@ -71,7 +71,8 @@ def test_reset_update_does_not_bypass_reviewed_snapshot(monkeypatch):
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def source_tree(tmp_path):
|
def source_tree(tmp_path):
|
||||||
sources = [tiny.TOP, tiny.RESET_TREE, tiny.ARCHINFO, tiny.SYSCTRL, "rtl/ip/core/mgmt_core_wrapper.sv", "configs/ci/ihp130-tiny.mk",
|
sources = [tiny.TOP, tiny.RESET_TREE, tiny.ARCHINFO, tiny.SYSCTRL,
|
||||||
|
tiny.CPU, tiny.CPU_MEM, tiny.SRAM, "configs/ci/ihp130-tiny.mk",
|
||||||
tiny.MAP, tiny.TOPOLOGY, (tiny.BOOK / "source-contract.json").as_posix()]
|
tiny.MAP, tiny.TOPOLOGY, (tiny.BOOK / "source-contract.json").as_posix()]
|
||||||
for relative in sources:
|
for relative in sources:
|
||||||
target = tmp_path / relative
|
target = tmp_path / relative
|
||||||
@@ -81,9 +82,9 @@ def source_tree(tmp_path):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("path,before,after", [
|
@pytest.mark.parametrize("path,before,after", [
|
||||||
(tiny.TOP, ".CapacityKiB(128)", ".CapacityKiB(32)"),
|
(tiny.SRAM, ".CapacityKiB(128)", ".CapacityKiB(32)"),
|
||||||
(tiny.TOP, ".Ics55SmallBanks(1'b1)", ".Ics55SmallBanks(1'b0)"),
|
(tiny.SRAM, "group < 4", "group < 2"),
|
||||||
(tiny.TOP, ".DataWidth (32)", ".DataWidth (64)"),
|
(tiny.SRAM, ".DataBytes(4)", ".DataBytes(8)"),
|
||||||
(tiny.TOP, ".NumChannels (4)", ".NumChannels (8)"),
|
(tiny.TOP, ".NumChannels (4)", ".NumChannels (8)"),
|
||||||
(tiny.TOP, ".EnableStreams(1'b0)", ".EnableStreams(1'b1)"),
|
(tiny.TOP, ".EnableStreams(1'b0)", ".EnableStreams(1'b1)"),
|
||||||
(tiny.TOP, ".EnableAtomics (1'b0)", ".EnableAtomics (1'b1)"),
|
(tiny.TOP, ".EnableAtomics (1'b0)", ".EnableAtomics (1'b1)"),
|
||||||
|
|||||||
@@ -109,10 +109,11 @@ def mapped_netlist():
|
|||||||
else "input" for k in ports}}
|
else "input" for k in ports}}
|
||||||
cells = {"u_clock_buffer": cell("clock_buffer", clk_i=7, clk_o=2),
|
cells = {"u_clock_buffer": cell("clock_buffer", clk_i=7, clk_o=2),
|
||||||
"receiver": cell("P65_1233_PBMUX", PAD=1, C=7, IE="1", OE="0", CS="1"),
|
"receiver": cell("P65_1233_PBMUX", PAD=1, C=7, IE="1", OE="0", CS="1"),
|
||||||
"u_soc.u_cpu.u_hazard3_cpu_1port.core.pc_reg": cell("DFF", CK=2),
|
"u_soc.u_cpu.u_hazard3_cpu_2port.core.pc_reg": cell("DFF", CK=2),
|
||||||
"pll": cell("PLL_TOP", EN=3, CKOUT1=4, CKOUT2=5, CKTST=6),
|
"pll": cell("PLL_TOP", EN=3, CKOUT1=4, CKOUT2=5, CKTST=6),
|
||||||
"low": cell("TIELOH7R", Z=3)}
|
"low": cell("TIELOH7R", Z=3)}
|
||||||
cells.update({f"bank{i}": cell(RAM, CLK=2) for i in range(32)})
|
cells.update({f"u_soc.u_sram.gen_group[{i // 8}].u_group.gen_bank[{i % 8}].u_ram.u_mem":
|
||||||
|
cell(RAM, CLK=2) for i in range(32)})
|
||||||
return {"modules": {
|
return {"modules": {
|
||||||
"retrosoc_tiny_asic": {"ports": {"extclk_i_pad": {"direction": "inout", "bits": [1]},
|
"retrosoc_tiny_asic": {"ports": {"extclk_i_pad": {"direction": "inout", "bits": [1]},
|
||||||
"jtag_tck_i_pad": {"direction": "inout", "bits": [11]}},
|
"jtag_tck_i_pad": {"direction": "inout", "bits": [11]}},
|
||||||
@@ -201,24 +202,33 @@ def test_hierarchical_constant_output_is_resolved(mapped_netlist):
|
|||||||
"divided_ram", "wrong_cpu", "pll_sys"])
|
"divided_ram", "wrong_cpu", "pll_sys"])
|
||||||
def test_unsafe_or_incomplete_mapping_is_rejected(mapped_netlist, mutation):
|
def test_unsafe_or_incomplete_mapping_is_rejected(mapped_netlist, mutation):
|
||||||
cells = mapped_netlist["modules"]["retrosoc_tiny_asic"]["cells"]
|
cells = mapped_netlist["modules"]["retrosoc_tiny_asic"]["cells"]
|
||||||
|
first_ram = "u_soc.u_sram.gen_group[0].u_group.gen_bank[0].u_ram.u_mem"
|
||||||
if mutation == "missing_pll":
|
if mutation == "missing_pll":
|
||||||
del cells["pll"]
|
del cells["pll"]
|
||||||
elif mutation == "missing_ram":
|
elif mutation == "missing_ram":
|
||||||
del cells["bank0"]
|
del cells[first_ram]
|
||||||
elif mutation == "live_pll":
|
elif mutation == "live_pll":
|
||||||
cells["low"]["type"] = "TIEHIH7R"
|
cells["low"]["type"] = "TIEHIH7R"
|
||||||
elif mutation == "unknown_enable":
|
elif mutation == "unknown_enable":
|
||||||
cells["pll"]["connections"]["EN"] = [99]
|
cells["pll"]["connections"]["EN"] = [99]
|
||||||
elif mutation == "divided_ram":
|
elif mutation == "divided_ram":
|
||||||
cells["bank0"]["connections"]["CLK"] = [99]
|
cells[first_ram]["connections"]["CLK"] = [99]
|
||||||
elif mutation == "wrong_cpu":
|
elif mutation == "wrong_cpu":
|
||||||
cells["u_soc.u_cpu.u_hazard3_cpu_1port.core.pc_reg"]["connections"]["CK"] = [99]
|
cells["u_soc.u_cpu.u_hazard3_cpu_2port.core.pc_reg"]["connections"]["CK"] = [99]
|
||||||
else:
|
else:
|
||||||
cells["pll"]["connections"]["CKOUT1"] = [2]
|
cells["pll"]["connections"]["CKOUT1"] = [2]
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
inspect_netlist(mapped_netlist)
|
inspect_netlist(mapped_netlist)
|
||||||
|
|
||||||
|
|
||||||
|
def test_stale_p4_macro_hierarchy_is_rejected(mapped_netlist):
|
||||||
|
cells = mapped_netlist["modules"]["retrosoc_tiny_asic"]["cells"]
|
||||||
|
first_ram = "u_soc.u_sram.gen_group[0].u_group.gen_bank[0].u_ram.u_mem"
|
||||||
|
cells["u_soc.u_sram.gen_memory.gen_bank[0].u_ram.u_mem"] = cells.pop(first_ram)
|
||||||
|
with pytest.raises(ValueError, match="hierarchy"):
|
||||||
|
inspect_netlist(mapped_netlist)
|
||||||
|
|
||||||
|
|
||||||
def test_tiny_uses_32_actual_ics55_small_macros(tmp_path):
|
def test_tiny_uses_32_actual_ics55_small_macros(tmp_path):
|
||||||
assert shutil.which("verilator"), "required simulator unavailable"
|
assert shutil.which("verilator"), "required simulator unavailable"
|
||||||
generated = tmp_path / "map"
|
generated = tmp_path / "map"
|
||||||
|
|||||||
@@ -0,0 +1,191 @@
|
|||||||
|
"""Tiny dual-port local-memory protocol and source-bound replay acceptance."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from scripts.check_simulation import DEFAULT_FAILURE
|
||||||
|
from scripts.rtl.generate_memory_map import generate as generate_memory_map
|
||||||
|
from scripts import tiny_r2_p4 as p4
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
COMMON = ROOT / "rtl/managed/clusterip/common/rtl"
|
||||||
|
TINY = ROOT / "rtl/tiny/top"
|
||||||
|
|
||||||
|
|
||||||
|
def run(command, directory, label, timeout=180):
|
||||||
|
result = subprocess.run(command, text=True, stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT, timeout=timeout,
|
||||||
|
env={**os.environ, "CCACHE_DISABLE": "1"})
|
||||||
|
(directory / f"{label}.log").write_text(result.stdout)
|
||||||
|
assert result.returncode == 0, result.stdout[-12000:]
|
||||||
|
if label != "sv2v": # Converted source contains literal assertion/error strings.
|
||||||
|
assert not DEFAULT_FAILURE.search(result.stdout), result.stdout[-12000:]
|
||||||
|
return result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("simulator", ["verilator", "iverilog"])
|
||||||
|
@pytest.mark.parametrize("pdk", ["BEHAV", "ICS55", "IHP130"])
|
||||||
|
def test_four_group_local_memory(tmp_path, simulator, pdk):
|
||||||
|
for tool in (simulator, "sv2v", "vvp"):
|
||||||
|
assert shutil.which(tool), f"required tool unavailable: {tool}"
|
||||||
|
generate_memory_map(ROOT / "rtl/tiny/address_map/memory_map.json", tmp_path / "map", "YES", 128)
|
||||||
|
sources = [COMMON / p for p in (
|
||||||
|
"utils/register.sv", "utils/fifo.sv", "stream/round_robin_arbiter.sv", "interface/axi4_if.sv",
|
||||||
|
"interface/axi4_addr_gen.sv", "interface/apb4_if.sv", "interface/ahbl_if.sv")]
|
||||||
|
sources += [TINY / p for p in (
|
||||||
|
"tiny_sram_port_if.sv", "tiny_sram_group.sv", "tiny_sram_axi4.sv",
|
||||||
|
"tiny_sram.sv", "tiny_cpu_mem.sv")]
|
||||||
|
sources += [ROOT / p for p in (
|
||||||
|
"rtl/tech/tc_sram.sv", "rtl/ip/memory/onchip_ram_reg.sv",
|
||||||
|
"rtl/ip/interconnect/ahbl2axi4.sv", "rtl/ip/interconnect/axi4_error_slave.sv",
|
||||||
|
"tests/rtl/tiny_r2_p4_memory_tb.sv")]
|
||||||
|
defines = ["-DSV_ASSRT_DISABLE", f"-DPDK_{pdk}", "-DHAVE_SRAM_MACRO"]
|
||||||
|
vendor = []
|
||||||
|
if pdk == "IHP130":
|
||||||
|
base = ROOT / "physical/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_sram/verilog"
|
||||||
|
vendor = [base / "RM_IHPSG13_1P_core_behavioral_bm_bist.v",
|
||||||
|
base / "RM_IHPSG13_1P_1024x32_c2_bm_bist.v"]
|
||||||
|
defines += ["-DFUNCTIONAL", "-DSYNTHESIS"]
|
||||||
|
elif pdk == "ICS55":
|
||||||
|
vendor = [ROOT / ".cache/retrosoc/pdk/ics55/sram/ics55_ecos_sram_1024x32_m8/verilog/ics55_ecos_sram_1024x32_m8_core.v"]
|
||||||
|
assert all(p.is_file() for p in vendor), "locked SRAM models unavailable"
|
||||||
|
includes = [f"-I{p}" for p in (COMMON, COMMON / "interface", tmp_path / "map/rtl",
|
||||||
|
ROOT / "rtl/ip/memory")]
|
||||||
|
top = "tiny_r2_p4_memory_tb"
|
||||||
|
output = tmp_path / "sim"
|
||||||
|
if simulator == "verilator":
|
||||||
|
run([simulator, "--binary", "--timing", "--assert", "-Wno-fatal", "-j", "3",
|
||||||
|
"-DHAVE_SVA", "--top-module", top, *defines, *includes,
|
||||||
|
*(str(p) for p in sources + vendor), "--Mdir", str(tmp_path / "obj"), "-o", str(output)],
|
||||||
|
tmp_path, "compile")
|
||||||
|
launch = [str(output)]
|
||||||
|
else:
|
||||||
|
# Keep vendor models outside sv2v, as in the executable Tiny flow.
|
||||||
|
converted = run(["sv2v", *defines, *includes, *(str(p) for p in sources)], tmp_path, "sv2v")
|
||||||
|
(tmp_path / "converted.v").write_text(converted)
|
||||||
|
run([simulator, "-g2012", "-gno-specify", "-s", top,
|
||||||
|
*defines, "-o", str(output), str(tmp_path / "converted.v"), *(str(p) for p in vendor)],
|
||||||
|
tmp_path, "compile")
|
||||||
|
launch = ["vvp", str(output)]
|
||||||
|
content = run(launch, tmp_path, "sim")
|
||||||
|
assert "SIM_TEST_PASS Tiny P4 memory" in content
|
||||||
|
assert "P4_STREAM read_gap_max=1 write_gap_max=1 beats=16" in content
|
||||||
|
assert "P4_BUFFER backpressure errors reset conservation PASS" in content
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("simulator", ["verilator", "iverilog"])
|
||||||
|
def test_actual_dual_port_cpu_debug_and_reset(tmp_path, simulator):
|
||||||
|
for tool in (simulator, "sv2v", "vvp", "riscv32-unknown-elf-gcc", "riscv32-unknown-elf-objcopy"):
|
||||||
|
assert shutil.which(tool), f"required tool unavailable: {tool}"
|
||||||
|
generate_memory_map(ROOT / "rtl/tiny/address_map/memory_map.json", tmp_path / "map", "YES", 128)
|
||||||
|
run(["riscv32-unknown-elf-gcc", "-march=rv32imc_zicsr_zifencei", "-mabi=ilp32",
|
||||||
|
"-nostdlib", "-Wl,--build-id=none", "-T", str(ROOT / "tests/rtl/tiny_r2_p4_cpu.ld"),
|
||||||
|
str(ROOT / "tests/rtl/tiny_r2_p4_cpu.S"), "-o", str(tmp_path / "cpu.elf")], tmp_path, "firmware")
|
||||||
|
run(["riscv32-unknown-elf-objcopy", "-O", "verilog", str(tmp_path / "cpu.elf"),
|
||||||
|
str(tmp_path / "cpu.hex")], tmp_path, "objcopy")
|
||||||
|
sources = [COMMON / p for p in (
|
||||||
|
"utils/register.sv", "utils/fifo.sv", "stream/round_robin_arbiter.sv", "interface/axi4_if.sv",
|
||||||
|
"interface/axi4_addr_gen.sv", "interface/apb4_if.sv", "interface/ahbl_if.sv",
|
||||||
|
"clkrst/rst_sync.sv", "cdc/cdc_sync.sv")]
|
||||||
|
sources += [ROOT / "rtl/managed/hazard3/hdl" / line.removeprefix("/hazard3/")
|
||||||
|
for line in (ROOT / "rtl/tiny/filelist/core_hazard3.fl").read_text().splitlines()
|
||||||
|
if line.startswith("/hazard3/")]
|
||||||
|
sources += [TINY / p for p in (
|
||||||
|
"tiny_sram_port_if.sv", "tiny_sram_group.sv", "tiny_sram_axi4.sv",
|
||||||
|
"tiny_sram.sv", "tiny_cpu_mem.sv", "tiny_cpu_wrapper.sv")]
|
||||||
|
sources += [ROOT / p for p in (
|
||||||
|
"rtl/tech/tc_sram.sv", "rtl/tech/tc_clk.sv", "rtl/ip/memory/onchip_ram_reg.sv",
|
||||||
|
"rtl/ip/interconnect/ahbl2axi4.sv", "rtl/ip/core/mgmt_debug_wrapper.sv",
|
||||||
|
"rtl/ip/core/mgmt_debug_reset.sv", "tests/rtl/tiny_r2_p4_cpu_tb.sv")]
|
||||||
|
includes = [f"-I{p}" for p in (COMMON, COMMON / "interface", tmp_path / "map/rtl",
|
||||||
|
ROOT / "rtl/ip/memory", ROOT / "rtl/managed/hazard3/hdl",
|
||||||
|
ROOT / "rtl/tiny/dv")]
|
||||||
|
defines = ["-DSV_ASSRT_DISABLE", "-DPDK_BEHAV", "-DSOC_JTAG_IDCODE=32'hDEADBEEF"]
|
||||||
|
output = tmp_path / "sim"
|
||||||
|
top = "tiny_r2_p4_cpu_tb"
|
||||||
|
if simulator == "verilator":
|
||||||
|
run([simulator, "--binary", "--timing", "--assert", "-Wno-fatal", "-j", "3",
|
||||||
|
"-DHAVE_SVA", "--top-module", top, *defines, *includes,
|
||||||
|
*(str(p) for p in sources), "--Mdir", str(tmp_path / "obj"), "-o", str(output)],
|
||||||
|
tmp_path, "compile")
|
||||||
|
launch = [str(output)]
|
||||||
|
else:
|
||||||
|
converted = run(["sv2v", *defines, *includes, *(str(p) for p in sources)], tmp_path, "sv2v")
|
||||||
|
(tmp_path / "converted.v").write_text(converted)
|
||||||
|
run([simulator, "-g2012", "-s", top, "-o", str(output), str(tmp_path / "converted.v")],
|
||||||
|
tmp_path, "compile")
|
||||||
|
launch = ["vvp", str(output)]
|
||||||
|
content = run([*launch, f"+firmware={tmp_path / 'cpu.hex'}"], tmp_path, "sim")
|
||||||
|
assert "SIM_TEST_PASS Tiny P4 CPU" in content
|
||||||
|
|
||||||
|
|
||||||
|
def test_group_control_induction(tmp_path):
|
||||||
|
for tool in ("sv2v", "yosys"):
|
||||||
|
assert shutil.which(tool), f"required tool unavailable: {tool}"
|
||||||
|
sources = [COMMON / "utils/register.sv", COMMON / "utils/fifo.sv",
|
||||||
|
COMMON / "stream/round_robin_arbiter.sv",
|
||||||
|
TINY / "tiny_sram_port_if.sv", TINY / "tiny_sram_group.sv",
|
||||||
|
ROOT / "tests/rtl/tiny_r2_p4_formal.sv"]
|
||||||
|
converted = run(["sv2v", "-DFORMAL", "-DSV_ASSRT_DISABLE", f"-I{COMMON}",
|
||||||
|
*(str(p) for p in sources)], tmp_path, "sv2v")
|
||||||
|
(tmp_path / "formal.v").write_text(converted)
|
||||||
|
script = (f"read_verilog -formal {tmp_path / 'formal.v'}; "
|
||||||
|
"prep -top tiny_r2_p4_formal -flatten; memory_map; async2sync; dffunmap; opt; "
|
||||||
|
"sat -seq 12 -tempinduct -prove-asserts -verify -set-init-zero")
|
||||||
|
content = run(["yosys", "-p", script], tmp_path, "formal", timeout=180)
|
||||||
|
assert "Induction step proven: SUCCESS!" in content
|
||||||
|
|
||||||
|
|
||||||
|
def p4_log():
|
||||||
|
from test_tiny_r2_p3 import sample_log
|
||||||
|
lines = [sample_log()]
|
||||||
|
for window in range(1, 9):
|
||||||
|
for port in range(2):
|
||||||
|
lines.append(f"R2_P4_LOCAL window={window} port={port} accepted=3 completed=3 "
|
||||||
|
"latency_max=3 admission_wait=0")
|
||||||
|
for group in range(4):
|
||||||
|
lines.append(f"R2_P4_BANK window={window} group={group} issues_i=2 issues_d=1 "
|
||||||
|
"issues_external=0 conflicts=0 wait_i=0 wait_d=0 wait_external=0")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def test_p4_acceptance_retains_local_and_external_accounting():
|
||||||
|
samples = p4.parse_log(p4_log(), {"status": "passed", "exit_code": 0})
|
||||||
|
assert len(samples["local"]) == 16 and len(samples["banks"]) == 32
|
||||||
|
assert samples["cases"][2]["checksum"] == 0xc4a58dc5
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mutation", ["exit", "marker", "missing", "duplicate", "accounting"])
|
||||||
|
def test_p4_rejects_incomplete_or_failed_evidence(mutation):
|
||||||
|
text = p4_log()
|
||||||
|
flow = {"status": "passed", "exit_code": 0}
|
||||||
|
if mutation == "exit":
|
||||||
|
flow["exit_code"] = 124
|
||||||
|
elif mutation == "marker":
|
||||||
|
text += "\nSIM_TEST_TIMEOUT"
|
||||||
|
elif mutation == "missing":
|
||||||
|
text = "\n".join(line for line in text.splitlines() if not line.startswith("R2_P4_BANK window=8"))
|
||||||
|
elif mutation == "duplicate":
|
||||||
|
text += "\n" + next(line for line in text.splitlines() if line.startswith("R2_P4_LOCAL"))
|
||||||
|
else:
|
||||||
|
text = text.replace("accepted=3 completed=3", "accepted=3 completed=4")
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
p4.parse_log(text, flow)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("key", p4.HARDWARE_KEYS)
|
||||||
|
def test_p4_rejects_mismatched_hardware(key):
|
||||||
|
values = dict.fromkeys(p4.HARDWARE_KEYS, "same")
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
p4.compatible_hardware({"configuration": values}, {"configuration": values | {key: "other"}})
|
||||||
|
|
||||||
|
|
||||||
|
def test_p4_preserves_independent_image_compiler_identity():
|
||||||
|
hardware = dict.fromkeys(p4.HARDWARE_KEYS, "same")
|
||||||
|
p4.compatible_hardware({"configuration": hardware | {"SW_OPT": "O2"}},
|
||||||
|
{"configuration": hardware | {"SW_OPT": "APP"}})
|
||||||
Reference in new issue
Block a user