feat: refactor and enhance documentation for Linux runtime, boot acceptance, and NPU integration

- Updated fault reference documentation to clarify Linux termination behavior and NPU-P6 requirements.
- Enhanced Linux runtime section with emphasis on platform setup and kernel handoff processes.
- Revised overview section to include detailed descriptions of NPU capabilities and memory configurations.
- Improved product use documentation to specify Linux rootfs behavior and acceptance checkpoints.
- Clarified reset summary to differentiate between Linux and smoke/NPU-P6 acceptance processes.
- Updated software section to reflect changes in Linux image loading and readiness protocols.
- Modified build scripts to support RV64 architecture and ensure proper ELF requirements.
- Enhanced NPU compiler to support cycle counting for RV64 and RV32 architectures.
- Added tests to validate new configurations and ensure compliance with updated publication contracts.
This commit is contained in:
maksyuki committed 2026-10-01 12:41:05 +08:00
1 parent d084002bfe
commit 422f48d0cd
30 files changed
+539 -135

No files matched your search

+3 -3
View File
@@ -1020,7 +1020,7 @@ hp-smoke-sim: hp-smoke-bundle comp
python3 $(ROOT_PATH)/scripts/run_hp_sim.py --emulator $(VERILATOR_EMU) \
--image $(HP_SMOKE_BUNDLE_BIN) --output $(SIM_BUILD_ROOT)/hp-smoke \
--workload smoke --timeout $(HP_SMOKE_SIM_TIME)
$(HP_APU_STAMP): $(ROOT_PATH)/scripts/build_hp_apu.py \
$(HP_APU_STAMP): $(ROOT_PATH)/scripts/build_hp_apu.py $(ROOT_PATH)/scripts/hp_tools.py \
$(ROOT_PATH)/app/ports/hp-apu/start.S \
$(ROOT_PATH)/app/ports/hp-apu/main.c \
$(ROOT_PATH)/app/ports/hp-apu/linker.ld \
@@ -1029,7 +1029,7 @@ $(HP_APU_STAMP): $(ROOT_PATH)/scripts/build_hp_apu.py \
$(MEMORY_MAP_STAMP) $(USER_EXTENSIONS_STAMP)
python3 $(ROOT_PATH)/scripts/build_hp_apu.py \
--source-dir $(ROOT_PATH)/app/ports/hp-apu \
--output $(HP_APU_BUILD_DIR) --cross $(CROSS) \
--output $(HP_APU_BUILD_DIR) --cross $(HP_CROSS) \
--include $(MEMORY_MAP_C_DIR) \
--include $(USER_EXTENSIONS_DIR)/include \
--include $(ROOT_PATH)/crt/include \
@@ -1199,4 +1199,4 @@ regress-nightly:
python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite nightly $(if $(filter TINY,$(SOC)),--soc TINY)
sim-asm: asm
$(MAKE) SIM_FIRMWARE_NAME=$(ASM_FIRMWARE_NAME) sim
$(MAKE) SIM_FIRMWARE_NAME=$(ASM_FIRMWARE_NAME) sim
+20
View File
@@ -33,6 +33,7 @@ The build selects an application with `APP=<name>`. The supported profiles are:
| `coremark` | SRAM-resident Hazard3 CoreMark measurement; use the committed quick or standard profile. |
| `debug` | Minimal SRAM image used only by the Hazard3 OpenOCD/GDB acceptance flow. |
| `hp_boot` | SRAM-resident LP loader that validates typed V2 Linux, smoke or RT-Thread bundles, loads SDRAM, releases HP, and monitors the workload-specific mailbox verdict. |
| `apu_release` | PSRAM-resident LP/HP APU evidence flow: LP-only microcode/KWS model load, ownership handoff to RV64 HP, HP-submitted WAV/KWS jobs with Zicbom maintenance, and LP-only register fault probe. |
| `shell` | Interactive application that adds shell services, board drivers, media, FatFs, CoreMark, and UserIP integration. |
| `xpi_flash_loader` | SRAM-resident, GDB-called service image for sector-preserving JTAG programming of the qualified NSS0 NOR. |
@@ -40,6 +41,25 @@ The application manifest is loaded from `app/apps/<name>/app.mk`. It appends
`APP_SRCS` and, when necessary, `APP_INC_DIRS` to the SDK sources selected by
the central build.
NPU-P5 acceptance extends existing compositions rather than adding an APP.
`NPU_P5_ACCEPTANCE=YES` is valid only with `ci_smoke` or `hp_boot` and is part
of the reproducible variant key. The LP path links the generated KWS plan into
`ci_smoke`, uses the real LP completion interrupt, and writes the normal
SYSCTRL verdict. The RV64 HP smoke bundle assigns resource 9 to hart 1, runs
the same plan with polling and 64-byte Zicbom maintenance, then returns
ownership before LP publishes the final verdict. The option defaults to `NO`,
preserving normal PR firmware size and behavior.
NPU-P6 likewise reuses `APP=hp_boot`; it does not add an application profile
or public ABI. `NPU_P6_ACCEPTANCE=YES` selects a private qualification payload,
and `NPU_P6_WORKLOAD=kws|vww` selects one frozen generated plan. The payload
reads a CRC-protected 100-case shard from the existing HP `Image` slot, runs
the portable C and production NPU paths through CPU Softmax, measures both
with HP `rdcycle`, and emits fail-closed per-case records. The first ten global
cases also overlap an identical GA2D DMA copy with both measured paths.
Normal and P5 builds remain unchanged because P5 and P6 acceptance flags are
mutually exclusive and both default to `NO`.
Build and simulate the supported profiles from the repository root:
```sh
+2
View File
@@ -328,7 +328,9 @@ int main(void) {
}
printf("HP_LINUX_READY\n");
if (header.workload == RS_HP_BOOT_WORKLOAD_LINUX) {
#if !defined(RS_NPU_P6_ACCEPTANCE)
rs_test_finish(RS_TEST_PASSED, UINT8_C(0));
#endif
}
message.code = RS_HP_BOOT_GA2D_START_COMMAND;
message.argument = RS_HP_BOOT_GA2D_START_ARG;
+103 -11
View File
@@ -96,6 +96,12 @@ static int8_t s_npu_output[RS_NPU_P6_OUTPUT_BYTES];
static bool s_uart_ok;
static uint64_t rs_npu_p6_cycles(void) {
#if defined(__riscv_xlen) && (__riscv_xlen == 64)
uint64_t cycle;
__asm__ volatile("rdcycle %0" : "=r"(cycle));
return cycle;
#else
uint32_t high_before;
uint32_t low;
uint32_t high_after;
@@ -106,6 +112,7 @@ static uint64_t rs_npu_p6_cycles(void) {
__asm__ volatile("rdcycleh %0" : "=r"(high_after));
} while (high_before != high_after);
return ((uint64_t)high_before << 32U) | low;
#endif
}
static void rs_npu_p6_uart_character(char character) {
@@ -182,11 +189,14 @@ static bool rs_npu_p6_equal(const uint8_t *left, const uint8_t *right, uint32_t
return equal;
}
static void rs_npu_p6_invalidate_range(const void *pointer, uint32_t bytes);
static bool rs_npu_p6_parse_shard(rs_npu_p6_shard_t *shard) {
const uint8_t *image = (const uint8_t *)(uintptr_t)RS_NPU_P6_SHARD_BASE;
const uint32_t *words = (const uint32_t *)(const void *)image;
uint32_t payload_bytes;
rs_npu_p6_invalidate_range(image, RS_NPU_P6_HEADER_BYTES);
if ((shard == NULL) || (words[0] != RS_NPU_P6_SHARD_MAGIC) ||
(words[1] != RS_NPU_P6_SHARD_SCHEMA) || (words[2] != RS_NPU_P6_WORKLOAD_ID) ||
(words[4] != UINT32_C(10)) || (words[6] != RS_NPU_P6_CASES_PER_SHARD) ||
@@ -201,6 +211,7 @@ static bool rs_npu_p6_parse_shard(rs_npu_p6_shard_t *shard) {
return false;
}
payload_bytes = words[6] * words[9];
rs_npu_p6_invalidate_range(&image[RS_NPU_P6_HEADER_BYTES], payload_bytes);
if (rs_npu_p6_crc32(&image[RS_NPU_P6_HEADER_BYTES], payload_bytes) != words[10]) {
return false;
}
@@ -262,6 +273,15 @@ static void rs_npu_p6_field_u64(const char *name, uint64_t value) {
rs_npu_p6_uart_u64(value);
}
static void rs_npu_p6_stage(uint32_t case_index, const char *stage, uint64_t cycles) {
rs_npu_p6_uart_text("NPU_P6_STAGE workload=" RS_NPU_P6_WORKLOAD_NAME);
rs_npu_p6_field_u32("index", case_index);
rs_npu_p6_uart_text(" stage=");
rs_npu_p6_uart_text(stage);
rs_npu_p6_field_u64("cycles", cycles);
rs_npu_p6_uart_character('\n');
}
static bool rs_npu_p6_case_fail(uint32_t case_index, const char *stage) {
rs_npu_p6_uart_text("NPU_P6_CASE_FAIL workload=" RS_NPU_P6_WORKLOAD_NAME);
rs_npu_p6_field_u32("index", case_index);
@@ -301,7 +321,7 @@ static void rs_npu_p6_case_log(uint32_t shard_index, uint32_t case_index, uint32
}
static bool rs_npu_p6_run_case(const uint8_t *record, uint32_t expected_index, uint32_t shard_index,
bool cold_cache) {
bool cold_cache, bool trace_case) {
const uint32_t *header = (const uint32_t *)(const void *)record;
const int8_t *input = (const int8_t *)(const void *)&record[RS_NPU_P6_CASE_HEADER_BYTES];
const uint8_t *expected_terminal = &record[RS_NPU_P6_CASE_HEADER_BYTES + RS_NPU_P6_INPUT_BYTES];
@@ -313,8 +333,13 @@ static bool rs_npu_p6_run_case(const uint8_t *record, uint32_t expected_index, u
uint64_t started;
uint64_t reference_cycles;
uint64_t npu_cycles;
rs_status_t status;
bool contention = expected_index < UINT32_C(10);
if (trace_case) {
rs_npu_p6_stage(expected_index, "case-start", UINT64_C(0));
}
if ((header[0] != expected_index) ||
(rs_npu_p6_default_regions(&s_workspace, &regions) != RS_OK) ||
(rs_npu_p6_prepare(&s_workspace, &regions, input, RS_NPU_P6_INPUT_BYTES) != RS_OK)) {
@@ -341,17 +366,39 @@ static bool rs_npu_p6_run_case(const uint8_t *record, uint32_t expected_index, u
if (contention && !rs_npu_p6_start_contention()) {
return rs_npu_p6_case_fail(expected_index, "contention-reference-start");
}
if (trace_case) {
rs_npu_p6_stage(expected_index, "reference-start", UINT64_C(0));
}
started = rs_npu_p6_cycles();
if ((rs_npu_p6_reference_execute(&s_workspace.descriptors[0][0], RS_NPU_P6_DESCRIPTOR_COUNT,
&memory) != RS_OK) ||
(rs_npu_p6_softmax((const int8_t *)&s_workspace.arena[terminal_offset],
s_reference_output) != RS_OK)) {
status = rs_npu_p6_reference_execute(&s_workspace.descriptors[0][0], RS_NPU_P6_DESCRIPTOR_COUNT,
&memory);
if (status != RS_OK) {
return rs_npu_p6_case_fail(expected_index, "reference-execute");
}
if (trace_case) {
rs_npu_p6_stage(expected_index, "reference-compute-done", rs_npu_p6_cycles() - started);
}
if (rs_npu_p6_softmax((const int8_t *)&s_workspace.arena[terminal_offset],
s_reference_output) != RS_OK) {
return rs_npu_p6_case_fail(expected_index, "reference-softmax");
}
reference_cycles = rs_npu_p6_cycles() - started;
if (trace_case) {
rs_npu_p6_stage(expected_index, "softmax-done", reference_cycles);
}
if (trace_case) {
rs_npu_p6_stage(expected_index, "ga2d-reference-start", UINT64_C(0));
}
started = rs_npu_p6_cycles();
if (contention && (rs_ga2d_wait(RS_TIMEOUT_DEFAULT * UINT32_C(64)) != RS_OK)) {
if (trace_case) {
rs_npu_p6_stage(expected_index, "ga2d-reference-timeout", rs_npu_p6_cycles() - started);
}
return rs_npu_p6_case_fail(expected_index, "contention-reference-wait");
}
if (trace_case) {
rs_npu_p6_stage(expected_index, "ga2d-reference-done", rs_npu_p6_cycles() - started);
}
if (!rs_npu_p6_equal(&s_workspace.arena[terminal_offset], expected_terminal,
RS_NPU_P6_OUTPUT_BYTES) ||
!rs_npu_p6_equal((const uint8_t *)(const void *)s_reference_output, expected_softmax,
@@ -364,15 +411,31 @@ static bool rs_npu_p6_run_case(const uint8_t *record, uint32_t expected_index, u
if (contention && !rs_npu_p6_start_contention()) {
return rs_npu_p6_case_fail(expected_index, "contention-npu-start");
}
if (rs_npu_p6_execute(&s_workspace, (RS_NPU_P6_WORKLOAD_ID << 24U) | expected_index,
RS_TIMEOUT_DEFAULT * UINT32_C(64), s_npu_output, RS_NPU_P6_OUTPUT_BYTES,
&s_profile) != RS_OK) {
if (trace_case) {
rs_npu_p6_stage(expected_index, "npu-start", UINT64_C(0));
}
status = rs_npu_p6_execute(&s_workspace, (RS_NPU_P6_WORKLOAD_ID << 24U) | expected_index,
RS_TIMEOUT_DEFAULT * UINT32_C(64), s_npu_output,
RS_NPU_P6_OUTPUT_BYTES, &s_profile);
if (status != RS_OK) {
return rs_npu_p6_case_fail(expected_index, "npu-execute");
}
npu_cycles = s_profile.npu_wait_cycles + s_profile.softmax_cycles;
if (trace_case) {
rs_npu_p6_stage(expected_index, "npu-done", npu_cycles);
rs_npu_p6_stage(expected_index, "ga2d-npu-start", UINT64_C(0));
}
started = rs_npu_p6_cycles();
if (contention && (rs_ga2d_wait(RS_TIMEOUT_DEFAULT * UINT32_C(64)) != RS_OK)) {
if (trace_case) {
rs_npu_p6_stage(expected_index, "ga2d-npu-timeout", rs_npu_p6_cycles() - started);
}
return rs_npu_p6_case_fail(expected_index, "contention-npu-wait");
}
if (trace_case) {
rs_npu_p6_stage(expected_index, "ga2d-npu-done", rs_npu_p6_cycles() - started);
rs_npu_p6_stage(expected_index, "golden-start", UINT64_C(0));
}
if ((reference_cycles == 0U) || (npu_cycles == 0U) ||
(s_profile.counters.retired_descriptors != RS_NPU_P6_DESCRIPTOR_COUNT) ||
!rs_npu_p6_equal(&s_workspace.arena[terminal_offset], expected_terminal,
@@ -381,8 +444,14 @@ static bool rs_npu_p6_run_case(const uint8_t *record, uint32_t expected_index, u
RS_NPU_P6_OUTPUT_BYTES) ||
!rs_npu_p6_equal((const uint8_t *)(const void *)s_reference_output,
(const uint8_t *)(const void *)s_npu_output, RS_NPU_P6_OUTPUT_BYTES)) {
if (trace_case) {
rs_npu_p6_stage(expected_index, "golden-fail", UINT64_C(0));
}
return rs_npu_p6_case_fail(expected_index, "npu-golden");
}
if (trace_case) {
rs_npu_p6_stage(expected_index, "golden-done", UINT64_C(0));
}
rs_npu_p6_case_log(shard_index, expected_index, header[1], cold_cache, contention,
reference_cycles, npu_cycles);
return s_uart_ok;
@@ -390,10 +459,21 @@ static bool rs_npu_p6_run_case(const uint8_t *record, uint32_t expected_index, u
int rs_hp_npu_p6_acceptance(void) {
rs_npu_p6_shard_t shard;
rs_status_t status;
s_uart_ok = true;
if ((rs_npu_irq_ack(RS_NPU_IRQ_ALL) != RS_OK) || (rs_npu_irq_enable(0U) != RS_OK) ||
!rs_npu_p6_parse_shard(&shard)) {
status = rs_npu_irq_ack(RS_NPU_IRQ_ALL);
if (status != RS_OK) {
rs_npu_p6_uart_text("NPU_P6_FAIL stage=irq-ack\n");
return 0;
}
status = rs_npu_irq_enable(0U);
if (status != RS_OK) {
rs_npu_p6_uart_text("NPU_P6_FAIL stage=irq-enable\n");
return 0;
}
if (!rs_npu_p6_parse_shard(&shard)) {
rs_npu_p6_uart_text("NPU_P6_FAIL stage=shard-parse\n");
return 0;
}
rs_npu_p6_uart_text("NPU_P6_BEGIN workload=" RS_NPU_P6_WORKLOAD_NAME);
@@ -406,7 +486,8 @@ int rs_hp_npu_p6_acceptance(void) {
for (uint32_t index = 0U; index < shard.case_count; ++index) {
const uint8_t *record = &shard.payload[index * shard.record_bytes];
if (!rs_npu_p6_run_case(record, shard.first_case + index, shard.shard_index, index == 0U)) {
if (!rs_npu_p6_run_case(record, shard.first_case + index, shard.shard_index, index == 0U,
index == 0U)) {
return 0;
}
}
@@ -427,6 +508,17 @@ static void rs_npu_p6_clean_range(const void *pointer, uint32_t bytes) {
}
}
static void rs_npu_p6_invalidate_range(const void *pointer, uint32_t bytes) {
uintptr_t cursor = (uintptr_t)pointer & ~(uintptr_t)UINT32_C(63);
uintptr_t end = ((uintptr_t)pointer + bytes + UINT32_C(63)) & ~(uintptr_t)UINT32_C(63);
while (cursor < end) {
__asm__ volatile("cbo.inval 0(%0)" : : "r"(cursor) : "memory");
cursor += UINT32_C(64);
}
__asm__ volatile("fence r, r" : : : "memory");
}
void rs_hp_npu_p6_cache_clean(void) {
rs_npu_p6_clean_range(&s_workspace, sizeof(s_workspace));
rs_npu_p6_clean_range(&s_profile, sizeof(s_profile));
+13
View File
@@ -12,6 +12,14 @@ profiles. The `ihp130-hazard3-coremark.mk` profile is the automated SRAM
CoreMark quick measurement; its `-standard` counterpart is reserved for a
10-second hardware run. The nightly workflow reuses the IHP130 CI profile and
runs the quick CoreMark profile.
`ci/ihp130.mk` is the Mini PRODUCT IHP130 profile used by the NPU-P6 flow. At
root revision `d084002bfe99`, P0 numerical qualification and KWS/VWW corpus
generation passed, and the RV64 KWS/VWW bundles passed payload/header
validation. Stage-instrumented KWS shard 0 and shard 1 both stop in the pinned
portable-C reference at `reference-start`; no `NPU_P6_CASE` or P6
qualification report is claimed. See the [NPU verification contract](../docs/ip/npu-verification.md)
and retained logs under
`build/ihp130-2026-10-01-10-30-b2e490436ac7/npu/p6/`.
`ci/ihp130-hp.mk` is the asymmetric Linux application profile. It starts HP
from the external 72 MHz safe clock and LP from REF24, runs `hp_boot` entirely
from 32 KiB on-chip SRAM, enables VexiiRiscv `Zicbom` with 64-byte blocks, and
@@ -21,6 +29,11 @@ timing evidence are qualified.
`ci/ihp130-rtthread.mk` selects the same RV64 HP hardware and LP loader for
the pinned RT-Thread M-mode selftest. Run `make setup-hp-rtthread`, then
`make CONFIG=configs/ci/ihp130-rtthread.mk SIMU=VERILATOR hp-rtthread-sim`.
`ci/ihp130-apu.mk` is the RV64 APU LP/HP evidence profile. It runs
`apu_release` from PSRAM, stages the embedded APUMC/APUM/WAV/KWS assets through
SDRAM, performs the LP-only image loads, hands APU ownership to HP, and checks
HP-submitted WAV/KWS jobs plus the LP-only register fault probe in the full-SoC
Verilator simulation via `hp-apu-sim`.
All PRODUCT profiles use `rv64imafdc_zicbom_max`; LP compiler/ISA remain RV32.
`ci/ihp130-xpi-flash-loader.mk` builds the SRAM-only XPI NOR service image used
by GDB/OpenOCD; it is a programming utility, not a normal boot application.
+2 -2
View File
@@ -4,7 +4,7 @@ PDK := IHP130
HAVE_PLL := NO
HAVE_HP := YES
APU_ENABLE_P7 := YES
HP_CONFIG := rv32imafdc_zicbom_max
HP_CONFIG := rv64imafdc_zicbom_max
HAVE_SRAM_IF := YES
HAVE_SRAM_MACRO := YES
SRAM_SIZE_KIB := 32
@@ -20,4 +20,4 @@ SIM_SUCCESS_MARKER := SIM_TEST_PASS
RTL_SIM_TIMEOUT := 20000000
SOC_SIM_TIME := 3600
FIRMWARE_NAME := retrosoc_apu_release
SIM_FIRMWARE_NAME := retrosoc_apu_release
SIM_FIRMWARE_NAME := retrosoc_apu_release
+13 -2
View File
@@ -24,8 +24,19 @@ static musl/BusyBox and a small uncompressed CPIO with a dedicated `/init`.
V2 flash bundles discriminate Linux (1, four entries), smoke (2, one executable)
and RT-Thread (3, one executable). Rebuild V1 bundles. The loader checks strict
flags, workload/count/types, fixed addresses, bounds, non-overlap, unused slots
and CRC. Only smoke enters the GA2D/cache lifecycle protocol. LP alone owns
the final SYSCTRL TEST_STATUS write for all workloads.
and CRC. In ordinary builds only smoke enters the GA2D/cache lifecycle
protocol. LP alone owns the final SYSCTRL TEST_STATUS write for all workloads.
The private NPU-P6 acceptance build reuses workload 1's four-entry transport so
the frozen corpus shard occupies the existing `Image` slot. Its compile-time
`RS_NPU_P6_ACCEPTANCE` path deliberately continues beyond the Linux-ready
checkpoint into the same GA2D/cache lifecycle protocol; an ordinary Linux
build still terminates at Linux-ready. This does not add a workload number or
change the V2 header ABI.
The `ihp130-apu` evidence profile likewise uses workload 1 only as the existing
four-entry transport. Its application-specific `apu_release` LP loader owns the
APU acceptance semantics; that bundle label does not claim a Linux boot result.
## Measured runs
+25
View File
@@ -163,6 +163,31 @@ model substitution or faster reference/NPU memory placement is allowed to
obscure the comparison. Record first-run and steady-state behavior using the
same declared cache initialization policy for both paths.
### Reference Baseline Freeze Decision (2026-10-01)
The P6 reference baseline remains the pinned, independently implemented
portable-C INT8 path defined above. A host-framework implementation, a
precomputed result source, a different memory placement, or an otherwise
faster reference path MUST NOT replace it in a qualification run. Such a
replacement would change the measured denominator and requires an explicit
verification-contract refreeze before implementation.
The stage-instrumented Mini IHP130 diagnostic run at root revision
`d084002bfe99`, using `configs/ci/ihp130.mk` and the locked VexiiRiscv revision,
entered `reference-start` for both a contention KWS shard and a non-contention
KWS shard, but did not reach `reference-compute-done` within the bounded host
run. This establishes a portable-C reference runtime/evidence gap, not an NPU
completion failure: neither run reached GA2D wait or NPU submit/wait. P6 stays
incomplete until the pinned reference completes the required corpora and the
architectural cycle intervals are recorded. Host elapsed time remains
diagnostic only and cannot be substituted for the required HP cycle evidence.
The stage markers are diagnostic observability only and do not alter the NPU
ABI, case record schema, workload corpus, cache policy, or performance formula.
Any future reference optimization must preserve the independent arithmetic,
layer outputs, memory placement and cycle-accounting contract, and must be
reviewed as a separate implementation change before P6 execution resumes.
Performance uses architecturally visible HP cycle intervals and frozen NPU
counters, not Verilator host elapsed time, simulator throughput or a
peak-MAC-derived estimate. The reference and NPU intervals MUST execute in the
+6 -4
View File
@@ -36,10 +36,12 @@ Authoritative repository inputs are:
- the [verification contract](npu-verification.md), which is normative for
qualification but cannot change the architecture or ABI below.
CONFIRMED FROM REPOSITORY: the current product has nine AXI64 masters,
seven-bit global IDs, 32 KiB system SRAM, a 72 MHz initial HP fabric clock,
and no hardware cache coherence. SDRAM already has a burst-capable frontend.
The private NPU store is additional to system SRAM. The earlier
CONFIRMED FROM REPOSITORY AT THE 2026-09-16 FREEZE: the product baseline had
nine AXI64 masters, seven-bit global IDs, 32 KiB system SRAM, a 72 MHz initial
HP fabric clock, and no hardware cache coherence. The implemented NPU phases
now append the frozen master/resource index 9, producing the ten-master
topology specified below without renumbering an earlier identity. SDRAM already
has a burst-capable frontend. The private NPU store is additional to system SRAM. The earlier
[Mini NPU survey](mini-npu.md) remains historical research; its AXI32 and
128 KiB baseline and stream-first recommendation do not govern this feature.
+12 -3
View File
@@ -7,6 +7,12 @@ PCLK-to-HP AXI64/ID3 bridge as a direct single-job private-AXI64 2D engine
controlled at `APB4_GA2D`, with resource-owned IRQ. Existing hart,
memory, and source identities remain fixed.
The frozen [NPU specification](ip/npu.md) subsequently appends AXI64
master/prefix 9 and Resource Controller entry 9, expanding the implemented
PRODUCT data plane to ten masters without renumbering the preceding entries.
Its APB4 shell is at `APB4_NPU`; the resource-owned interrupt routes to LP
vector 33 or HP PLIC source 12.
## Product contract
Every committed `MINI_MODE=PRODUCT` profile instantiates two fixed harts:
@@ -79,7 +85,7 @@ same memory admission, inactive-pad, ACL, and fault path as HP and DMA.
## Native AXI64 data plane
`soc_data_plane` contains a 9-master, 6-target AXI64 crossbar. Read and write
`soc_data_plane` contains a 10-master, 6-target AXI64 crossbar. Read and write
channels progress independently, and different source IDs may be active against
the same or different targets. The same source ID is blocked until completion.
SRAM and SDRAM accept four reads and two writes; serial memories and the error
@@ -97,6 +103,7 @@ and a Common FIFO preserves write-data order where AXI4 W has no ID.
| JPEG | PCLK-to-HP AXI64 async bridge, ID prefix 6; one normal read and one normal write credit, class 8 |
| EXT-H | PCLK-to-HP AXI64 async bridge, ID prefix 7 |
| GA2D | dedicated PCLK-to-HP AXI64/ID3 async bridge; direct single-job FILL/COPY/CONVERT/BLEND engine |
| NPU | HP-native AXI64 master/prefix 9 with one read and one write outstanding; production descriptor and tensor DMA |
Targets are SRAM, SDRAM, QPI PSRAM, OPI/HyperBus PSRAM, XPI/flash, and a
finite-latency error slave. SRAM is a native AXI64, seven-bit-ID target in HP and
@@ -155,7 +162,7 @@ Its IRQ is delivered to LP IRQ 28 or HP PLIC source 3 according to owner,
never both. Software discovers it through `<retrosoc/hal/extension.h>`.
The Resource Controller at `0x2000_A000` is the central owner and IRQ authority
for DMA, USB2, SDIO0/1, SPI-SD, EXT-H, JPEG, APU, and the P5 GA2D engine.
for DMA, USB2, SDIO0/1, SPI-SD, EXT-H, JPEG, APU, the P5 GA2D engine, and NPU.
Resource 8 routes its raw IRQ exclusively to LP vector 32/external ordinal 30
or HP PLIC source 11 according to owner, while its associated AXI bridge carries
only that engine's direct FILL/COPY/CONVERT/BLEND traffic. The existing HP
@@ -163,7 +170,9 @@ smoke workload remains a FILL/COPY subset; it is not P5 composition or cache
coherency evidence.
Handoff requires idle, owner lock is sticky, and rejected handoffs raise LP IRQ
29. APU index 7 routes exclusively to LP IRQ31 or HP PLIC source10. The
controller also carries the AON cache request/clean acknowledgement used before
NPU at index 9 routes exclusively to LP vector 33/external ordinal 31 or HP
PLIC source 12; its AXI master carries only production descriptor/tensor traffic.
The controller also carries the AON cache request/clean acknowledgement used before
HP drain. See
[`ip/resource-controller.md`](ip/resource-controller.md).
Resource 8 qualifies its HP block acknowledgement with a fresh synchronized
+9 -2
View File
@@ -146,6 +146,7 @@ def split_npu_conditions(body: str) -> tuple[str, list[tuple[str, str]]]:
condition = None
allowed = {f"defined({name})" for name in NPU_DEFINES}
allowed.add("||".join(f"defined({name})" for name in NPU_DEFINES))
allowed.add(f"!defined({NPU_DEFINES[1]})")
for line in body.splitlines():
directive = line.strip()
if directive.startswith("#if "):
@@ -335,7 +336,11 @@ def collect_boot_acceptance(root: Path, function_body: Callable[[str, str], str]
)
if any(main.count(branch) != 1 for branch in workload_branches):
raise ValueError("HP boot workload selection changed")
if (main.count("rs_test_finish(RS_TEST_PASSED,") != 3 or not main.endswith(phases[-1])
linux_exit_condition = f"!defined({NPU_DEFINES[1]})"
linux_exit_blocks = [compact(body) for condition, body in gated if condition == linux_exit_condition]
if linux_exit_blocks != ["rs_test_finish(RS_TEST_PASSED,UINT8_C(0));"]:
raise ValueError("HP boot Linux-ready terminal guard changed")
if (main.count("rs_test_finish(RS_TEST_PASSED,") != 2 or not main.endswith(phases[-1])
or re.findall(r"cache_clean_completed=(true|false);", main) != ["false", "true"]
or re.findall(r"s_hp_boot_ga2d_owned_by_hp=(true|false);", main) != ["true", "false"]):
raise ValueError("HP boot terminal pass or cache completion changed")
@@ -375,5 +380,7 @@ def collect_boot_acceptance(root: Path, function_body: Callable[[str, str], str]
return {"default_poll_iterations": int(default[0]), "event_poll_multiplier": int(multiplier[0]),
"event_poll_iterations": iterations, "npu_defines": list(NPU_DEFINES),
"messages": messages, "ready_writes": ready_writes,
"rootfs_ready_only": True, "terminal_requires_cache_handoff": True,
"rootfs_ready_only": True, "linux_ready_is_terminal": True,
"smoke_terminal_requires_cache_handoff": True,
"npu_p6_reuses_linux_transport": True,
"failure_reset_requires_no_hp_ga2d_owner": True}
@@ -1,22 +1,23 @@
#import "../style.typ": *
#change-start("v05-emphasis-architecture","Selected body emphasis: architecture")
#import "../figures.typ": *
#import "../waveforms.typ": timing
#import "../diagram-packages.typ": memory-window-diagram
#import "../soc-architecture.typ": soc-functional-diagram
= System Architecture
#change-start("dev-architecture","Nine-master fabric, address map, LP IRQ and lifecycle reference")
#change-start("dev-architecture","Ten-master fabric, address map, LP IRQ and lifecycle reference")
== Introduction
=== SoC Architecture
Mini PRODUCT contains two fixed harts, an LP-owned control plane, a native HP data fabric and
two APB4 register islands. There is no hardware cache coherency. Software transfers shared
Mini PRODUCT contains *two fixed harts*, an LP-owned control plane, a native HP data fabric and
two APB4 register islands. There is *no hardware cache coherency*. Software transfers shared
buffer ownership explicitly and performs the required fences and cache maintenance.
#figure(soc-functional-diagram(), caption:[Mini PRODUCT functional architecture and clock domains.])<soc-functional-overview>
=== Management Processor
==== Hazard3
Hazard3 is hart 0 and the root-management processor. The reference profile selects RV32IM
Hazard3 is hart 0 and the *root-management processor*. The reference profile selects RV32IM
firmware. Its AHB-Lite interface is adapted to AXI32 for control access; memory transactions
are directed through the LP gateway to the shared data plane. The management JTAG path provides
halt, resume, register and system-bus access. LP retains control while HP is held in reset.
@@ -25,7 +26,7 @@ halt, resume, register and system-bus access. LP retains control while HP is hel
=== Application Processor
==== VexiiRiscv
The generated HP hart is a dual-issue RV64IMAFDC + Zicbom configuration, with supervisor/user
modes, Sv39 virtual memory and 64-byte cache-maintenance blocks. Hart 1 uses native AXI64
modes, Sv39 virtual memory and *64-byte cache-maintenance blocks*. Hart 1 uses native AXI64
instruction and data paths. Its uncached MMIO path is downsized and crosses into the LP
control plane. OpenSBI/Linux inputs and generated-core configuration are dependency-locked.
#source-note("docs/lp-hp-architecture.md", title:"LP/HP architecture and boot contract")
@@ -60,7 +61,8 @@ byte strobes, burst limits, access permissions and IP-specific side effects rema
=== Interconnect Matrix
The #data.policies.len() initiator identities access #data.targets.len() memory targets. The crossbar arbitrates reads and
writes separately for each target. I/O gateway A combines USB2, SDIO0 and the APU private
master; gateway B combines SDIO1 and SPI-SD. JPEG occupies slot 6 and GA2D slot 8; EXT-H uses slot 7.
master; gateway B combines SDIO1 and SPI-SD. JPEG occupies slot 6, EXT-H slot 7,
GA2D slot 8 and NPU slot 9.
#note[The matrix on the next page is generated from the RTL access policy. R/W permission is
subject to active memory-pad mode, resource ownership, target readiness and EXT-H address bounds.
@@ -69,7 +71,7 @@ An allowed entry does not guarantee throughput.]
#pagebreak()
#figure(matrix-diagram(), caption:[AXI64 memory access matrix: R = read, W = write, - = denied.])<bus-matrix>
The I-cache is the only instruction-permitted initiator. HP cache attributes are preserved;
DMA, I/O gateways, LP gateway, JPEG, EXT-H and GA2D require non-cacheable transactions. XPI is read-only
DMA, I/O gateways, LP gateway, JPEG, EXT-H, GA2D and NPU require non-cacheable transactions. XPI is read-only
on this data plane; indirect writes use its APB-controlled command engine.
Denied accesses return a finite error response with source attribution. EXT-H has additional
@@ -157,9 +159,11 @@ within an aggregate source. Resource-controlled interrupts are routed according
)
HP has local software/timer interrupts and a 32-source, two-context PLIC. Source 0 is
reserved; sources 1-11 are UART1, mailbox, EXT-H, DMA, USB2, SDIO0, SDIO1, SPI-SD, JPEG, APU
and GA2D. Sources 12-31 are reserved. GA2D uses LP vector bit 32 (external ordinal 30),
which is distinct from HP PLIC source 11. The contexts drive machine and supervisor external interrupts.
reserved; sources 1-12 are UART1, mailbox, EXT-H, DMA, USB2, SDIO0, SDIO1, SPI-SD, JPEG, APU,
GA2D and NPU. Sources 13-31 are reserved. GA2D uses LP vector bit 32 (external ordinal 30),
which is distinct from HP PLIC source 11; NPU uses LP vector bit 33 (external ordinal 31)
or HP PLIC source 12 according to Resource Controller ownership. The contexts drive machine
and supervisor external interrupts.
Claim/complete and priority rules are defined in the HP platform contract.
#block(above:rhythm.metadata-before,below:rhythm.metadata-after,breakable:false)[
#set text(size:9pt)
@@ -170,3 +174,5 @@ Claim/complete and priority rules are defined in the HP platform contract.
#include "register-programming.typ"
#include "interface-subsets.typ"
#change-end("dev-architecture")
#change-end("v05-emphasis-architecture")
@@ -1,15 +1,16 @@
#import "../style.typ": *
#change-start("v05-emphasis-boot-bundle-format","Selected body emphasis: boot bundle format")
#import "../diagram-packages.typ": binary-figures, storage-figures
#let bundle = data.system_reference.software.bundle_reference
#let hex(value) = code("0x"+upper(str(value,base:16)))
=== HP Boot Bundle Binary Format <boot-bundle-format>
The supplied V2 format is a typed workload bundle, serialized as little-endian 32-bit words.
The supplied V2 format is a typed workload bundle, serialized as *little-endian 32-bit words*.
Linux uses four artifacts; smoke and RT-Thread use one executable descriptor at
0x38000000 with the remaining three descriptor slots zero. Workload values are 1, 2 and 3
respectively. V1 bundles must be rebuilt.
The format version is #bundle.version; it is independent of the datasheet's v0.4 revision.
The format version is #bundle.version; it is independent of the datasheet's v0.5 revision.
The bundle starts at Flash offset #hex(bundle.bundle_offset), after the LP firmware area.
Its fixed header is #bundle.fixed_header_bytes bytes, each descriptor is #bundle.entry_bytes
bytes and the complete header including #bundle.entry_count descriptors is #bundle.header_bytes bytes.
@@ -17,8 +18,8 @@ bytes and the complete header including #bundle.entry_count descriptors is #bund
The following offsets are relative to the beginning of the named structure. The descriptor
array starts immediately after the fixed header, so descriptor n begins at
#code(str(bundle.fixed_header_bytes)+" + n * "+str(bundle.entry_bytes)), for n = 0…#(bundle.entry_count - 1).
All listed fields occupy four bytes. Use the explicit serialization, not an arbitrary host
structure layout. The existing load addresses and maximum allocations remain in the preceding
All listed fields occupy four bytes. Use the *explicit serialization, not an arbitrary host
structure layout*. The existing load addresses and maximum allocations remain in the preceding
image-layout table.
#binary-figures("software-boot","software")
@@ -91,3 +92,5 @@ The example values are recalculated from the actual packager rather than maintai
#source-note("tests/test_hp_boot_bundle.py",title:"Existing package layout/CRC test boundary")
#storage-figures("software-boot",section:"software")
#change-end("v05-emphasis-boot-bundle-format")
@@ -64,8 +64,9 @@ one application. Preserve application identity, execution order and preceding lo
These result codes belong to the supplied HP boot application. TEST_STATUS's result byte is
application-defined; other firmware can assign different meanings. Code 1 can be written
before a reliable console exists. The ready, result and cache-clean waits have bounded polling
budgets and distinct failure stages. The initial ready marker precedes the remaining GA2D
acceptance/cache handoff; it does not itself write a successful TEST_STATUS value.
budgets and distinct failure stages. Ordinary Linux terminates after the checked ready message;
for smoke and the dedicated NPU-P6 build, that marker precedes the remaining GA2D
acceptance/cache handoff and does not itself write a successful TEST_STATUS value.
#ds-table("boot-result-codes",[HP boot application terminal result codes],
([Code],[Meaning / producing stage]),
@@ -74,8 +75,9 @@ acceptance/cache handoff; it does not itself write a successful TEST_STATUS valu
The #code("HP_BOOT_FAILED:<code>") diagnostic is emitted by the failure helper before its
terminal status write; UART initialization failure uses the direct terminal path. Entry-copy
failure is reported only after both the DMA attempt and software copy/CRC fallback fail.
After copying, HP release, initial readiness, job result and cache-clean handoff are distinct
checkpoints. NPU resource handoff is conditional on its acceptance build options. The failure
After copying, HP release and workload-specific terminal checkpoints remain distinct. Smoke and
NPU-P6 additionally require job result and cache-clean handoff. NPU resource handoff is
conditional on its acceptance build options. The failure
helper preserves HP state after GA2D ownership moves to HP; a failed verdict does not guarantee
a completed reset or DMA drain. See @boot-configuration for prerequisites/recovery and
@image-maintenance for separate programming-tool result semantics.
@@ -1,16 +1,17 @@
#import "../style.typ": *
#change-start("v05-emphasis-linux-runtime","Selected body emphasis: linux runtime")
#import "../system-figures.typ": sequence-diagram
#let platform = data.system_reference.software.platform
=== OpenSBI platform and console path <linux-runtime>
The supplied OpenSBI platform exposes one application hart, with hart ID #platform.hart_id.
The supplied OpenSBI platform exposes *one application hart*, with hart ID #platform.hart_id.
LP remains the separate management firmware processor. The platform's ACLINT descriptor covers
two hart-indexed register positions beginning at zero so it can address hart 1; that storage
range is not a declaration that Linux runs on both harts. The kernel configuration disables SMP.
range is not a declaration that Linux runs on both harts. The *kernel configuration disables SMP*.
The early platform setup programs UART1, registers its polled console and initializes machine
software-interrupt support. Timer initialization uses the ACLINT machine timer. Console writes
wait while TX is full; this loop has no local timeout. These implemented callbacks do not
wait while TX is full; this loop has *no local timeout*. These implemented callbacks do not
establish that all optional SBI services or all peripherals have native Linux drivers.
#figure(sequence-diagram((
@@ -57,6 +58,7 @@ apply the generic OpenSBI FDT fixups.
#source-note("scripts/build_hp_linux.py",title:"Actual initrd-end patch and FW_JUMP build arguments")
=== Kernel and rootfs ready handoff
#change-start("v05-refresh-linux-mailbox","HAL-derived mailbox purposes and RV64 ready-only userspace boundary")
The dedicated /init mounts procfs, sysfs, devtmpfs and tmpfs, checks file I/O and child execution,
then runs the static hp-ready helper. It prints its ready message and publishes through 32-bit
mailbox writes with an I/O fence before the doorbell. This is a userspace checkpoint, not proof of all drivers, networking,
@@ -66,7 +68,7 @@ alone is not the LP's readiness condition.
#ds-table("linux-ready-writes",[Userspace helper mailbox publication order],
([Order],[Address],[Written value],[Purpose]),
platform.ready_writes.enumerate().map(((i,row))=>(str(i+1),code(row.address),code(row.value),
([Linux-ready event],[Argument / ready state],[Sequence],[Interrupt request]).at(i))),
[#code(row.register) \ #row.purpose])),
widths:(0.4fr,1.1fr,1.1fr,1.85fr))
The helper checks open/mmap errors for /dev/mem and reports init failures through event 3
@@ -79,4 +81,8 @@ Resource ownership and non-coherent buffer rules still apply after Linux starts.
transactions. This description adds no new successful boot or board-validation claim.
#source-note("app/ports/linux/hp_ready.c",title:"Ready message and actual mailbox write order")
#source-note("app/ports/linux/init",title:"Minimal userspace acceptance checks")
#source-note("crt/src/hal/hp_mailbox.c",title:"Event, argument and sequence register purposes")
#source-note("app/apps/hp_boot/main.c",title:"LP sequence/event checks and final terminal result")
#change-end("v05-refresh-linux-mailbox")
#change-end("v05-emphasis-linux-runtime")
+26 -11
View File
@@ -3,13 +3,16 @@
// Publication status is explicit; successful build/tests do not change these marks.
#let artifact-status = (
(name:"Available",selected:true),
(name:"Functional",selected:false),
(name:"Reproduced",selected:false),
(name:"Tapedout",selected:false),
(name:"Available",fill:"full"),
(name:"Functional",fill:"left-half"),
(name:"Reproduced",fill:"empty"),
(name:"Tapedout",fill:"empty"),
)
#cover(evaluation:artifact-status)[
#change-start("v05-rill-cover","Rill bilingual prototype brand in the existing cover metadata row")
#change-start("v05-emphasis-cover","Cover contact, partial Functional mark and selected feature emphasis")
#change-start("v05-product-brief","v0.5 document identity and implemented accelerator features")
#text(9pt, weight: "semibold", fill: gold)[PRODUCT DATASHEET / #doc.status]
#v(rhythm.cover-gap)
#text(30pt, weight: "semibold")[retroSoC Mini]
@@ -18,9 +21,10 @@
#v(rhythm.cover-gap)
#text(13pt)[An Open-Source, Linux-Capable Asymmetric Dual-Core SoC]
#v(rhythm.cover-meta-gap)
// Inter's 1490/2048 em cap edge keeps CJK fallback from increasing this row.
#grid(columns: (1fr, 1fr),
[*#doc.document_id* · v#doc.version \ #doc.date],
align(right)[#doc.author \ #doc.maintainer],
[#text(font:("Inter","Noto Sans CJK SC"),weight:"bold",top-edge:0.7275390625em)[#doc.brand_name(#doc.brand_name_zh)] · v#doc.version \ #doc.date],
align(right)[#doc.author#link("mailto:"+doc.author_email)[(#doc.author_email)] \ #doc.maintainer],
)
#v(rhythm.cover-rule-gap)
#line(length:100%, stroke:1.1pt + gold)
@@ -33,14 +37,15 @@
*Compute and control*
- Hazard3 LP: boot, clocks, resource ownership and fault recovery.
- Dual-issue VexiiRiscv HP: RV64IMAFDC + Zicbom, Sv39.
- NPU: *64 dense MACs*, eight INT8 operator classes and 64 KiB private SRAM.
- JTAG debug; fixed EXT-L control and EXT-H AXI64 slots.
*Memory and interconnect*
- 32 KiB banked SRAM in this profile; 4/16/32/64/128 KiB build options.
- *32 KiB banked SRAM* in this profile; 4/16/32/64/128 KiB build options.
- AXI32 control, native AXI64 data paths and APB4 registers.
- SDRAM, QPI PSRAM and OPI/HyperBus-style external-memory controllers.
- XPI: four chip selects, 16 LUT sequences, 1/2/4-bit SDR transfers.
- Eight DMA channels; linked-list TCDs, 16-beat bursts and stream endpoints.
- *Eight DMA channels*; linked-list TCDs, 16-beat bursts and stream endpoints.
*Platform services*
- Two 32-bit timers with 16-bit prescalers; four-channel PWM.
@@ -51,8 +56,8 @@
#colbreak()
*Connectivity*
- 32 GPIOs: atomic outputs, pin interrupts and two alternate-function selections.
- Two UARTs with 64-byte TX/RX FIFOs; UART0 DMA and RTS/CTS.
- *32 GPIOs*: atomic outputs, pin interrupts and two alternate-function selections.
- Two UARTs with *64-byte TX/RX FIFOs*; UART0 DMA and RTS/CTS.
- Two I2C controllers: 7/10-bit addressing and 16-entry command/RX FIFOs.
- SPI-SD, two 1/4-bit SDR SDIO hosts and bidirectional PS/2.
- USB2: 8 endpoints, 16 host channels, 16 KiB packet RAM; external ULPI PHY.
@@ -62,7 +67,7 @@
- Stereo 16/24-bit I2S master; 8-bit RGB565/YUV422 DVP with crop/snapshot capture.
- Baseline JPEG encode/decode up to 2048 × 2048, with private DMA.
- GA2D fill/copy/convert/opaque blend and A8 masks.
- APU WAV/FLAC decode; MP3/KWS not advertised.
- APU WAV/FLAC decode; fixed KWS in the P7 configuration; MP3 unsupported.
- AES-128/192/256, SHA-224/256 and raw RSA-2048; programmable CRC.
- RNG source remains unqualified for production entropy.
@@ -74,10 +79,15 @@
#note(below: 0pt)[This draft describes a reviewed RTL/configuration snapshot. It does not establish silicon
speed grades, electrical limits, production availability or certification. Gen2/Gen2+ is the
retained document title; no separate derivative specifications are inferred.]
#change-end("v05-product-brief")
#change-end("v05-emphasis-cover")
#change-end("v05-rill-cover")
]
#pagebreak()
== Overview
#change-start("v05-emphasis-overview","Cover-adjacent overview and evidence context")
#change-start("v05-overview","NPU-inclusive functional inventory and configuration reference")
retroSoC Mini combines a small management processor with an application processor and shared
memory and I/O. Hazard3 retains authority over startup, clock transitions, resource ownership
and fault recovery. VexiiRiscv supplies the application-side compute and Linux integration.
@@ -86,6 +96,7 @@ experimentation, education and ASIC prototyping.
#figure(product-diagram(), caption:[Integrated Mini PRODUCT IP inventory, organized by function.])<product-diagram>
#change-start("v05-refresh-identity","Reviewed source identity for this same-version revision")
#ds-table("profile", [Document reference configuration],
([Property], [Reference]),
(
@@ -98,3 +109,7 @@ experimentation, education and ASIC prototyping.
([MPW compatibility], code(doc.mpw_profile)),
), widths:(0.8fr,1.9fr),
)
#change-end("v05-overview")
#change-end("v05-refresh-identity")
#change-end("v05-emphasis-overview")
@@ -98,11 +98,12 @@ SBI-based, even though OpenSBI accesses UART1 underneath. Native custom peripher
and an application resource-handoff service are separate work.
Before boot, validate the external memory configuration and the exact bundle generated for
the selected snapshot. The Linux rootfs publishes an initial ready event. The current HP
acceptance loader also requires GA2D result, cache-clean and resource-return checkpoints;
the rootfs ready service does not implement those later exchanges. Follow @boot-configuration
for the bounded polling budgets, matching acceptance payload and failure/ownership policy.
Neither an initial event nor a complete acceptance pass establishes all Linux peripheral drivers.
the selected snapshot. The Linux rootfs publishes a ready event that terminates the ordinary
Linux workload. Smoke and the dedicated NPU-P6 build additionally require GA2D result,
cache-clean and resource-return checkpoints; the Linux ready service does not implement those
later exchanges. Follow @boot-configuration for the bounded polling budgets, matching
acceptance payload and failure/ownership policy. Neither the Linux-ready verdict nor a complete
freestanding acceptance pass establishes all Linux peripheral drivers.
=== Camera and audio data paths
#change-start("system-media", "Camera and audio circuit composition")
@@ -42,8 +42,9 @@ separate universal IRQ-enable policy. Check each producer's local reset, cause a
An initialization dependency is not a fixed delay specification. Prefer documented ready,
idle, lock and error observations with bounded software waits. The HP loader bounds its
ready/result/cache-clean polls by iteration counts, not fixed elapsed time; stalled MMIO can
still prevent progress. Its initial ready event is separate from complete acceptance and
ownership return. The generic LP CRT's earlier PSRAM-ready loop remains unbounded; see
still prevent progress. Linux-ready is terminal for the ordinary Linux workload, while smoke
and NPU-P6 continue through acceptance and ownership return. The generic LP CRT's earlier
PSRAM-ready loop remains unbounded; see
@lp-runtime and @boot-configuration for the distinct startup stages.
#change-end("v05-refresh-reset-waits")
+14 -12
View File
@@ -15,15 +15,16 @@ The separate APU P9 acceptance profile performs LP APUMC/APUC/APUM loading and b
audio/KWS work; it is not the Linux boot profile. NPU compiler output and HP smoke payloads
likewise do not supply a native Linux driver. See @apu and @npu for their software boundaries.
#figure(boot-diagram(), caption:[Linux image loading and initial ready checkpoint. The loader's complete acceptance protocol continues below.])<boot-flow>
#figure(boot-diagram(), caption:[Linux image loading and ready checkpoint. Smoke and NPU-P6 acceptance continue through the later protocol.])<boot-flow>
The HP bundle contains locked OpenSBI, Linux, device-tree and initramfs inputs. LP validates
the header and payload bounds, attempts transfer with the boot DMA context and CRC, and
falls back to a software copy/CRC if that attempt fails. It fences memory, clears the LP
mailbox interrupt and requests HP release. It then waits for the expected readiness event;
it does not publish entry/DTB addresses through a boot mailbox before release. The same loader
also serves freestanding HP acceptance payloads. Its later GA2D/cache-clean protocol is required
before a successful TEST_STATUS write; the *initial ready message alone does not complete it*.
it does not publish entry/DTB addresses through a boot mailbox before release. For an ordinary
Linux workload the checked userspace ready event is terminal. The same loader also serves
freestanding HP acceptance payloads: smoke and the dedicated NPU-P6 build continue through the
later GA2D/cache-clean protocol, where the *initial ready message alone does not complete it*.
#source-note("docs/lp-hp-architecture.md", title:"Boot-bundle ABI, lifecycle and failure handling")
=== Prerequisites and image layout
@@ -62,7 +63,8 @@ port guide for the generated output layout and boot packaging commands.
+ Apply the memory fence, clear the LP mailbox interrupt, request release, and check the
immediate HP status result. Observe #code("HP_BOOT_RELEASED") only as a release checkpoint.
+ Wait for the exact ready event, argument and sequence. #code("HP_LINUX_READY") marks this
checkpoint; a freestanding acceptance payload uses the same marker without booting Linux.
checkpoint. Ordinary Linux terminates successfully here; smoke continues, and the dedicated
NPU-P6 build reuses the four-entry Linux transport but also continues.
+ Send the GA2D start command and wait for the result message with its distinct sequence.
Confirm GA2D safe idle before requesting HP hold.
+ Observe the cache-clean request, receive the cache message, acknowledge clean state and
@@ -79,11 +81,10 @@ The budget is *not a duration in milliseconds*, and a stalled MMIO access can pr
progress. A different sequence remains pending until the budget expires; matching the sequence
with a wrong event or argument fails immediately. A mailbox API error also fails the wait.
The supplied Linux rootfs service sends *only the initial ready message*. It contains no GA2D
command responder or cache-clean service. A Linux init checkpoint therefore does not satisfy
the complete current acceptance loader. A matching HP acceptance payload supplies those later
responses; deploying a Linux service that does so is additional integration work, not a native
graphics-driver capability established by this publication. See @linux-runtime.
The supplied Linux rootfs service sends *only the ready message*. That is sufficient for the
ordinary Linux workload's terminal checkpoint. It contains no GA2D command responder or
cache-clean service and therefore cannot substitute for the smoke or NPU-P6 acceptance payloads,
which supply those later responses. See @linux-runtime.
The software fallback computes CRC from bytes read from the source while copying. It performs
full destination readback only for entries up to the loader's small-entry threshold; it must
@@ -101,8 +102,9 @@ or buffer reuse. No automatic image retry or signed recovery image selection is
A board supervisor/watchdog remains a separate protection against stalled accesses or software.
For target acceptance, capture profile/image hashes, enabled acceptance options, LP load/release
checkpoints, ready/result/cache messages, final ownership and the simulator verdict. Do not treat
UART startup or the ready marker alone as success. Linux peripheral qualification requires
checkpoints, ready/result/cache messages where applicable, final ownership and the simulator
verdict. Do not treat UART startup as success or apply the Linux-ready terminal rule to smoke or
NPU-P6. Linux peripheral qualification requires
the separate support matrix and matching platform evidence.
#block(above:rhythm.metadata-before,below:rhythm.metadata-after,breakable:false)[
+9 -19
View File
@@ -8,10 +8,17 @@ from pathlib import Path
from publications.implementation_reference import without_comments
from publications.api_bundle_reference import bundle_layout, validate_api, dependencies as api_bundle_dependencies
from publications.boot_acceptance_reference import (
SOURCES as BOOT_SOURCES,
collect_boot_acceptance,
ready_mailbox,
)
def dependencies(spec: dict) -> set[str]:
paths = set(spec.get("sources", []))
if spec:
paths.update(BOOT_SOURCES)
paths.update(api_bundle_dependencies(spec))
paths.update(spec.get("runtime_profiles", []))
for row in spec.get("bindings", []):
@@ -174,29 +181,11 @@ def linux_platform(root: Path) -> dict:
raise ValueError("CPU and UART device-tree clock declarations disagree")
if dt_value("linux,initrd-start") != dt_value("linux,initrd-end"):
raise ValueError("reviewed initrd template placeholder changed")
ready = (root / "app/ports/linux/hp_ready.c").read_text(encoding="utf-8")
ordered = ['puts("retroSoC HP Linux ready")', 'mailbox[8] = (error == 0U) ? 1U : 3U;',
'mailbox[9] = (error == 0U) ? UINT32_C(0x4C4E5801) : error;',
'mailbox[10] = 1U;', '"fence iorw, iorw"', 'mailbox[11] = 1U;']
if any(text not in ready for text in ordered) or [ready.index(text) for text in ordered] != sorted(ready.index(text) for text in ordered):
raise ValueError("Linux ready message/publication order changed")
writes = [(0x10019020, 1), (0x10019024, 0x4C4E5801), (0x10019028, 1), (0x1001902C, 1)]
if "descriptor, 0x10019000)" not in ready:
raise ValueError("Linux mailbox mapping changed")
registers = ("HP_EVENT", "HP_ARG0", "HP_SEQUENCE", "HP_DOORBELL")
purposes = ("Linux-ready event", "Ready-state argument", "Publication sequence",
"LP interrupt request")
return {"hart_id": 1, "timebase_hz": timebase, "clock_hz": int(clocks[0]),
"cbom_bytes": dt_value("riscv,cbom-block-size"), "memory_base": memory[0][0],
"memory_bytes": int(memory[0][1], 0), "initrd_start": dt_value("linux,initrd-start"),
"initrd_template_end": dt_value("linux,initrd-end"), "bootargs": bootargs[0],
"ready_writes": [
{"address": f"0x{address:08X}", "value": f"0x{value:08X}",
"register": register, "purpose": purpose}
for (address, value), register, purpose in zip(
writes, registers, purposes, strict=True
)
]}
"ready_writes": ready_mailbox(root)}
def collect_software(root: Path, spec: dict) -> dict:
@@ -206,6 +195,7 @@ def collect_software(root: Path, spec: dict) -> dict:
raise ValueError("software diagnostic application coverage changed")
return {**copy.deepcopy(spec), "sources": sorted(dependencies(spec)), "profiles": runtime_profiles(root, spec),
"irq": irq_support(root), "platform": linux_platform(root),
"boot_acceptance": collect_boot_acceptance(root, function_body),
"api_reference": validate_api(root, spec["api_semantics"], function_body),
"bundle_reference": bundle_layout(root, spec["boot_bundle"], function_body),
"applications": [application_diagnostics(root, app) for app in spec["applications"]]}
+10 -4
View File
@@ -7,6 +7,11 @@ import argparse
import subprocess
from pathlib import Path
try:
from scripts.hp_tools import require_rv64_elf
except ModuleNotFoundError:
from hp_tools import require_rv64_elf
def build(args: argparse.Namespace) -> None:
output = args.output.resolve()
@@ -20,8 +25,8 @@ def build(args: argparse.Namespace) -> None:
subprocess.run(
[
compiler,
"-march=rv32imafdc_zicbom_zicsr_zifencei",
"-mabi=ilp32d",
"-march=rv64imafdc_zicbom_zicsr_zifencei",
"-mabi=lp64d",
"-nostdlib",
"-nostartfiles",
"-ffreestanding",
@@ -38,20 +43,21 @@ def build(args: argparse.Namespace) -> None:
],
check=True,
)
require_rv64_elf(elf)
subprocess.run(
[objcopy, "-O", "binary", str(elf), str(images / "fw_jump.bin")],
check=True,
)
(images / "retrosoc_hp.dtb").write_bytes(b"SMOK")
(images / "Image").write_bytes(b"SMOK")
(images / "rootfs.cpio.gz").write_bytes(b"SMOK")
(images / "rootfs.cpio").write_bytes(b"SMOK")
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--source-dir", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--cross", default="riscv32-unknown-elf-")
parser.add_argument("--cross", default="riscv64-unknown-elf-")
parser.add_argument("--include", type=Path, action="append", default=[])
build(parser.parse_args())
+24 -21
View File
@@ -7,6 +7,11 @@ import argparse
import subprocess
from pathlib import Path
try:
from scripts.hp_tools import require_rv64_elf
except ModuleNotFoundError:
from hp_tools import require_rv64_elf
def build(args: argparse.Namespace) -> None:
output = args.output.resolve()
@@ -16,35 +21,33 @@ def build(args: argparse.Namespace) -> None:
compiler = f"{args.cross}gcc"
objcopy = f"{args.cross}objcopy"
command = [
compiler,
"-march=rv32imafdc_zicbom_zicsr_zifencei",
"-mabi=ilp32d",
"-nostdlib",
"-nostartfiles",
"-ffreestanding",
"-O3",
"-Wall",
"-Wextra",
"-Werror=implicit-function-declaration",
"-Werror=return-type",
"-Wl,--build-id=none",
f"-Wl,-T,{args.linker.resolve()}",
"-o",
str(elf),
str(args.source.resolve()),
compiler,
"-march=rv64imafdc_zicbom_zicsr_zifencei",
"-mabi=lp64d",
"-nostdlib",
"-nostartfiles",
"-ffreestanding",
"-O3",
"-Wall",
"-Wextra",
"-Werror=implicit-function-declaration",
"-Werror=return-type",
"-Wl,--build-id=none",
f"-Wl,-T,{args.linker.resolve()}",
"-o",
str(elf),
str(args.source.resolve()),
]
for include in args.include:
command.append(f"-I{include.resolve()}")
command.extend(str(source.resolve()) for source in args.extra_source)
command.extend(args.define)
subprocess.run(command, check=True)
require_rv64_elf(elf)
subprocess.run(
[objcopy, "-O", "binary", str(elf), str(images / "fw_jump.bin")],
[objcopy, "-O", "binary", str(elf), str(images / "hp_smoke.bin")],
check=True,
)
(images / "retrosoc_hp.dtb").write_bytes(b"SMOK")
(images / "Image").write_bytes(b"SMOK")
(images / "rootfs.cpio.gz").write_bytes(b"SMOK")
def main() -> None:
@@ -52,7 +55,7 @@ def main() -> None:
parser.add_argument("--source", type=Path, required=True)
parser.add_argument("--linker", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--cross", default="riscv32-unknown-elf-")
parser.add_argument("--cross", default="riscv64-unknown-elf-")
parser.add_argument("--extra-source", type=Path, action="append", default=[])
parser.add_argument("--include", type=Path, action="append", default=[])
parser.add_argument("--define", action="append", default=[])
+5 -1
View File
@@ -261,7 +261,11 @@ static const rs_{prefix}_reloc_t rs_{prefix}_npu_relocations[{len(job.relocation
{_c_bytes(f'rs_{prefix}_npu_params', job.params)}
static uint64_t rs_{prefix}_cycles(void) {{
#if defined(__riscv)
#if defined(__riscv_xlen) && (__riscv_xlen == 64)
uint64_t cycle;
__asm__ volatile("rdcycle %0" : "=r"(cycle));
return cycle;
#elif defined(__riscv)
uint32_t high0; uint32_t low; uint32_t high1;
do {{ __asm__ volatile("rdcycleh %0" : "=r"(high0));
__asm__ volatile("rdcycle %0" : "=r"(low));
+30 -9
View File
@@ -110,6 +110,32 @@ def build_variants(timestamp: str, log_dir: Path) -> dict[str, Path]:
return roots
def qualification_configuration(variants: dict[str, Path]) -> dict[str, object]:
selected = ("MINI_MODE", "APP", "SIMU", "HP_CONFIG", "EXT_CLK_HZ")
configurations: dict[str, dict[str, str]] = {}
for workload, variant in variants.items():
manifest = json.loads((variant / "meta/manifest.json").read_text(encoding="utf-8"))
configuration = manifest.get("configuration")
if not isinstance(configuration, dict) or any(
not isinstance(configuration.get(name), str) for name in selected
):
raise ValueError(f"P6 {workload} manifest lacks its PRODUCT configuration")
configurations[workload] = {name: configuration[name] for name in selected}
if set(configurations) != {"kws", "vww"} or configurations["kws"] != configurations["vww"]:
raise ValueError("P6 workload manifests use different PRODUCT configurations")
configuration = configurations["kws"]
expected = {
"MINI_MODE": "PRODUCT",
"APP": "hp_boot",
"SIMU": "VERILATOR",
"HP_CONFIG": "rv64imafdc_zicbom_max",
"EXT_CLK_HZ": "72000000",
}
if configuration != expected:
raise ValueError(f"P6 manifest configuration differs from the frozen target: {configuration}")
return {**configuration, "EXT_CLK_HZ": int(configuration["EXT_CLK_HZ"])}
def parse_case_line(line: str) -> dict[str, Any]:
if not line.startswith(CASE_PREFIX):
raise ValueError("not an NPU-P6 case record")
@@ -168,10 +194,10 @@ def package_shard(
shard_dir = output / workload / f"shard-{shard_index:03d}"
images = shard_dir / "images"
images.mkdir(parents=True, exist_ok=True)
shutil.copy2(variant / "hp-smoke/images/fw_jump.bin", images / "fw_jump.bin")
shutil.copy2(variant / "hp-smoke/images/hp_smoke.bin", images / "fw_jump.bin")
shutil.copy2(Path(shard["file"]["path"]), images / "Image")
(images / "retrosoc_hp.dtb").write_bytes(b"SMOK")
(images / "rootfs.cpio.gz").write_bytes(b"SMOK")
(images / "rootfs.cpio").write_bytes(b"SMOK")
bundle = shard_dir / "retrosoc_npu_p6.bin"
manifest = shard_dir / "bundle.json"
completed = run(
@@ -396,6 +422,7 @@ def main() -> int:
output = args.output_dir.resolve()
output.mkdir(parents=True, exist_ok=True)
variants = build_variants(args.build_timestamp, output / "build-logs")
configuration = qualification_configuration(variants)
emulator = variants["kws"] / "sim/verilator/emu"
if not emulator.is_file():
raise ValueError("P6 PRODUCT Verilator emulator is missing")
@@ -441,13 +468,7 @@ def main() -> int:
["git", "-C", str(ROOT), "rev-parse", "HEAD"], text=True
).strip(),
"profile": "configs/ci/ihp130.mk",
"configuration": {
"MINI_MODE": "PRODUCT",
"APP": "hp_boot",
"SIMU": "VERILATOR",
"HP_CONFIG": "rv32imafdc_zicbom_max",
"EXT_CLK_HZ": 72_000_000,
},
"configuration": configuration,
"measurement": {
"counter": "HP rdcycle",
"reference": "portable INT8 C through CPU Softmax",
+44
View File
@@ -128,6 +128,40 @@ def test_hp_smoke_p5_npu_payload_has_stack_cache_and_polling_plan() -> None:
assert "rs_npu_irq_enable(0U)" in acceptance
assert "rs_npu_irq_ack(RS_NPU_IRQ_ALL)" in acceptance
assert '"--extra-source"' in builder and '"--include"' in builder
assert '"-march=rv64imafdc_zicbom_zicsr_zifencei"' in builder
assert '"-mabi=lp64d"' in builder
assert "require_rv64_elf(elf)" in builder
assert 'images / "hp_smoke.bin"' in builder
assert 'images / "fw_jump.bin"' not in builder
def test_hp_apu_profile_and_payload_use_the_rv64_product_contract() -> None:
profile = (ROOT / "configs/ci/ihp130-apu.mk").read_text(encoding="utf-8")
builder = (ROOT / "scripts/build_hp_apu.py").read_text(encoding="utf-8")
makefile = (ROOT / "Makefile").read_text(encoding="utf-8")
assert "HP_CONFIG := rv64imafdc_zicbom_max" in profile
assert '"-march=rv64imafdc_zicbom_zicsr_zifencei"' in builder
assert '"-mabi=lp64d"' in builder
assert "require_rv64_elf(elf)" in builder
assert 'images / "rootfs.cpio"' in builder
assert "--output $(HP_APU_BUILD_DIR) --cross $(HP_CROSS)" in makefile
completed = subprocess.run(
[
"make",
"-s",
"CONFIG=configs/ci/ihp130-apu.mk",
"BUILD_TIMESTAMP=2026-09-30-18-40",
"config",
],
cwd=ROOT,
capture_output=True,
text=True,
check=False,
)
assert completed.returncode == 0, completed.stdout + completed.stderr
assert "HP_CONFIG rv64imafdc_zicbom_max" in completed.stdout
def test_hp_smoke_simulation_requires_ga2d_result_and_cache_lifecycle_markers() -> None:
@@ -146,6 +180,16 @@ def test_hp_smoke_simulation_requires_ga2d_result_and_cache_lifecycle_markers()
assert marker in (*MARKERS["smoke"], "SIM_TEST_PASS code=0")
def test_npu_p6_linux_transport_does_not_take_the_normal_ready_exit() -> None:
source = (ROOT / "app/apps/hp_boot/main.c").read_text(encoding="utf-8")
linux_branch = source.split(
"if (header.workload == RS_HP_BOOT_WORKLOAD_LINUX) {", 1
)[1].split("}", 1)[0]
assert "#if !defined(RS_NPU_P6_ACCEPTANCE)" in linux_branch
assert "rs_test_finish(RS_TEST_PASSED" in linux_branch
def test_hp_cache_handshake_budget_covers_cbo_and_mailbox_round_trip() -> None:
source = (ROOT / "rtl/mini/top/retrosoc.sv").read_text(encoding="utf-8")
+4
View File
@@ -257,6 +257,10 @@ def test_reproducible_package_and_generated_c(tmp_path, name, model, digest, log
for entry in a["report"]["operators"])
assert a["report"]["totals"]["descriptor_read_bytes"] == descriptor_bytes
assert a["report"]["totals"]["dma_read_bytes"] == descriptor_bytes + operator_reads
generated_source = (first / f"{name}_npu.c").read_text(encoding="utf-8")
assert "#if defined(__riscv_xlen) && (__riscv_xlen == 64)" in generated_source
assert '__asm__ volatile("rdcycle %0" : "=r"(cycle));' in generated_source
assert '#elif defined(__riscv)\n uint32_t high0;' in generated_source
compiler = shutil.which("cc")
if compiler is None:
pytest.fail("host C compiler is required")
+94
View File
@@ -21,6 +21,36 @@ import run_npu_p6_netlist as netlist # noqa: E402
import run_npu_p6_verilator as verilator # noqa: E402
def test_p6_cycle_counter_supports_rv64_and_rv32() -> None:
runner = (ROOT / "app/benchmark/npu/npu_p6_runner.c").read_text(encoding="utf-8")
assert "#if defined(__riscv_xlen) && (__riscv_xlen == 64)" in runner
assert '__asm__ volatile("rdcycle %0" : "=r"(cycle));' in runner
assert '__asm__ volatile("rdcycleh %0" : "=r"(high_before));' in runner
def test_p6_shard_parser_invalidates_image_before_header_and_payload_reads() -> None:
runner = (ROOT / "app/benchmark/npu/npu_p6_runner.c").read_text(encoding="utf-8")
assert "rs_npu_p6_invalidate_range(image, RS_NPU_P6_HEADER_BYTES);" in runner
assert "rs_npu_p6_invalidate_range(&image[RS_NPU_P6_HEADER_BYTES], payload_bytes);" in runner
assert '"cbo.inval 0(%0)"' in runner
def test_p6_first_case_has_bounded_stage_markers_without_changing_case_schema() -> None:
runner = (ROOT / "app/benchmark/npu/npu_p6_runner.c").read_text(encoding="utf-8")
assert '"NPU_P6_STAGE workload="' in runner
for stage in (
'"reference-start"',
'"softmax-done"',
'"ga2d-reference-start"',
'"npu-start"',
'"npu-done"',
'"golden-done"',
):
assert stage in runner
assert "index == 0U))" in runner
assert '"NPU_P6_CASE workload="' in runner
def _case(index: int, workload: str = "kws") -> corpus.CorpusCase:
input_data = bytes([index & 0xFF]) * corpus.INPUT_BYTES[workload]
terminal_data = bytes([(index + 1) & 0xFF]) * corpus.OUTPUT_BYTES[workload]
@@ -120,6 +150,70 @@ def test_verilator_case_parser_is_exact_and_cycle_accounted() -> None:
verilator.parse_case_line(broken)
def test_verilator_report_uses_matching_variant_manifests(tmp_path: Path) -> None:
variants = {name: tmp_path / name for name in ("kws", "vww")}
configuration = {
"MINI_MODE": "PRODUCT",
"APP": "hp_boot",
"SIMU": "VERILATOR",
"HP_CONFIG": "rv64imafdc_zicbom_max",
"EXT_CLK_HZ": "72000000",
}
for variant in variants.values():
(variant / "meta").mkdir(parents=True)
(variant / "meta/manifest.json").write_text(
json.dumps({"configuration": configuration}), encoding="utf-8"
)
assert verilator.qualification_configuration(variants) == {
**configuration,
"EXT_CLK_HZ": 72_000_000,
}
mismatched = {**configuration, "HP_CONFIG": "rv32imafdc_zicbom_max"}
(variants["vww"] / "meta/manifest.json").write_text(
json.dumps({"configuration": mismatched}), encoding="utf-8"
)
with pytest.raises(ValueError, match="different PRODUCT configurations"):
verilator.qualification_configuration(variants)
(variants["kws"] / "meta/manifest.json").write_text(
json.dumps({"configuration": mismatched}), encoding="utf-8"
)
with pytest.raises(ValueError, match="frozen target"):
verilator.qualification_configuration(variants)
def test_verilator_shard_reuses_v2_linux_transport(tmp_path: Path) -> None:
variant = tmp_path / "variant"
(variant / "hp-smoke/images").mkdir(parents=True)
(variant / "sw").mkdir()
hp_payload = b"rv64-hp-payload"
shard_payload = b"npu-p6-shard"
(variant / "hp-smoke/images/hp_smoke.bin").write_bytes(hp_payload)
(variant / "sw/retrosoc_fw.bin").write_bytes(b"lp")
shard_file = tmp_path / "shard.bin"
shard_file.write_bytes(shard_payload)
bundle, manifest = verilator.package_shard(
"kws",
{"index": 0, "file": {"path": str(shard_file)}},
variant,
tmp_path / "packages",
)
record = json.loads(manifest.read_text(encoding="utf-8"))
assert bundle.is_file()
assert record["workload"] == "linux"
assert record["xlen"] == 64
assert record["artifacts"]["fw_jump.bin"]["sha256"] == hashlib.sha256(
hp_payload
).hexdigest()
assert record["artifacts"]["Image"]["sha256"] == hashlib.sha256(
shard_payload
).hexdigest()
def test_compiler_comparison_explains_material_cycle_difference() -> None:
estimate = {
"artifact": {"path": "npu.json", "bytes": 1, "sha256": "0" * 64},
+3 -1
View File
@@ -37,7 +37,9 @@ def test_actual_boot_requires_bounded_complete_acceptance():
assert [(row["event"], row["argument"], row["sequence"]) for row in result["messages"]] == [
(1, 0x4C4E5801, 1), (2, 0x47413244, 2), (4, 0x43424F4B, 3)]
assert result["rootfs_ready_only"]
assert result["terminal_requires_cache_handoff"]
assert result["linux_ready_is_terminal"]
assert result["smoke_terminal_requires_cache_handoff"]
assert result["npu_p6_reuses_linux_transport"]
assert result["failure_reset_requires_no_hp_ga2d_owner"]
+4 -1
View File
@@ -42,6 +42,9 @@ def test_real_software_records_separate_startup_and_irq_selections():
assert rows["xpi_flash_loader"]["startup"] == "app/apps/xpi_flash_loader/startup.S"
assert result["irq"]["enabled_core_causes"] == ["IRQ_M_SOFT", "IRQ_M_TIMER"]
assert result["irq"]["counts"]["RS_EXTERNAL_IRQ_COUNT"] == 62
assert result["boot_acceptance"]["event_poll_iterations"] == 64000000
assert result["boot_acceptance"]["linux_ready_is_terminal"]
assert result["boot_acceptance"]["smoke_terminal_requires_cache_handoff"]
assert sr.dependencies(result) == sr.dependencies(SPEC)
@@ -161,7 +164,7 @@ def test_ready_request_cannot_move_before_payload(source_tree):
last = next(i for i, line in enumerate(lines) if "mailbox[11]" in line)
lines[first], lines[last] = lines[last], lines[first]
path.write_text("\n".join(lines))
with pytest.raises(ValueError, match="publication"):
with pytest.raises(ValueError, match="mailbox sequence"):
sr.linux_platform(source_tree)
+16
View File
@@ -38,6 +38,22 @@ def test_apu_capability_and_digest_remain_profile_specific(data):
assert all(r["capability"] & 2 == 0 for r in rows) # MP3 remains clear.
def test_merge_recovery_preserves_publication_contracts(data):
root = ROOT / "publications/datasheets"
overview = (root / "sections/overview.typ").read_text(encoding="utf-8")
architecture = (root / "sections/architecture.typ").read_text(encoding="utf-8")
software = data["system_reference"]["software"]
assert '(name:"Functional",fill:"left-half")' in overview
assert "v05-rill-cover" in overview
assert "64 dense MACs" in overview
assert "fixed KWS in the P7 configuration" in overview
assert "Ten-master fabric" in architecture
assert "GA2D slot 8 and NPU slot 9" in architecture
assert software["boot_acceptance"]["linux_ready_is_terminal"]
assert software["boot_acceptance"]["smoke_terminal_requires_cache_handoff"]
@pytest.mark.parametrize("kind", ["npu-shell", "apu-blanket", "npu-counter", "npu-qualified", "npu-profile"])
def test_stale_live_accelerator_prose_is_not_accepted(data, kind):
catalog, features, content, annotations, profiles = [