diff --git a/Makefile b/Makefile index 0847cdbe..2685de26 100644 --- a/Makefile +++ b/Makefile @@ -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 \ No newline at end of file + $(MAKE) SIM_FIRMWARE_NAME=$(ASM_FIRMWARE_NAME) sim diff --git a/app/README.md b/app/README.md index ccda5e6d..fae63e58 100644 --- a/app/README.md +++ b/app/README.md @@ -33,6 +33,7 @@ The build selects an application with `APP=`. 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//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 diff --git a/app/apps/hp_boot/main.c b/app/apps/hp_boot/main.c index 5b2162ed..f2ac9461 100644 --- a/app/apps/hp_boot/main.c +++ b/app/apps/hp_boot/main.c @@ -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; diff --git a/app/benchmark/npu/npu_p6_runner.c b/app/benchmark/npu/npu_p6_runner.c index df663e3d..46ff64f9 100644 --- a/app/benchmark/npu/npu_p6_runner.c +++ b/app/benchmark/npu/npu_p6_runner.c @@ -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, ®ions) != RS_OK) || (rs_npu_p6_prepare(&s_workspace, ®ions, 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)); diff --git a/configs/README.md b/configs/README.md index 251b6212..5ec86a58 100644 --- a/configs/README.md +++ b/configs/README.md @@ -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. diff --git a/configs/ci/ihp130-apu.mk b/configs/ci/ihp130-apu.mk index 5060b100..43a2f706 100644 --- a/configs/ci/ihp130-apu.mk +++ b/configs/ci/ihp130-apu.mk @@ -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 \ No newline at end of file +SIM_FIRMWARE_NAME := retrosoc_apu_release diff --git a/docs/hp-rv64-validation.md b/docs/hp-rv64-validation.md index 861d0aa1..b809cb1b 100644 --- a/docs/hp-rv64-validation.md +++ b/docs/hp-rv64-validation.md @@ -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 diff --git a/docs/ip/npu-verification.md b/docs/ip/npu-verification.md index 72938b59..fe23599c 100644 --- a/docs/ip/npu-verification.md +++ b/docs/ip/npu-verification.md @@ -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 diff --git a/docs/ip/npu.md b/docs/ip/npu.md index 3fe54dfe..719389b7 100644 --- a/docs/ip/npu.md +++ b/docs/ip/npu.md @@ -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. diff --git a/docs/lp-hp-architecture.md b/docs/lp-hp-architecture.md index a67a31e8..a255e3df 100644 --- a/docs/lp-hp-architecture.md +++ b/docs/lp-hp-architecture.md @@ -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 ``. 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 diff --git a/publications/boot_acceptance_reference.py b/publications/boot_acceptance_reference.py index de42d8ec..cbe59d46 100644 --- a/publications/boot_acceptance_reference.py +++ b/publications/boot_acceptance_reference.py @@ -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} diff --git a/publications/datasheets/sections/architecture.typ b/publications/datasheets/sections/architecture.typ index 0077bcce..b4434b34 100644 --- a/publications/datasheets/sections/architecture.typ +++ b/publications/datasheets/sections/architecture.typ @@ -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.]) === 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.]) 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") diff --git a/publications/datasheets/sections/boot-bundle-format.typ b/publications/datasheets/sections/boot-bundle-format.typ index b4ad66b0..7f7bd9c7 100644 --- a/publications/datasheets/sections/boot-bundle-format.typ +++ b/publications/datasheets/sections/boot-bundle-format.typ @@ -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 -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") diff --git a/publications/datasheets/sections/fault-reference.typ b/publications/datasheets/sections/fault-reference.typ index 634ad2bb..2096deee 100644 --- a/publications/datasheets/sections/fault-reference.typ +++ b/publications/datasheets/sections/fault-reference.typ @@ -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:") 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. diff --git a/publications/datasheets/sections/linux-runtime.typ b/publications/datasheets/sections/linux-runtime.typ index 428ae6c3..bb6c6a24 100644 --- a/publications/datasheets/sections/linux-runtime.typ +++ b/publications/datasheets/sections/linux-runtime.typ @@ -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 -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") diff --git a/publications/datasheets/sections/overview.typ b/publications/datasheets/sections/overview.typ index 80aa44a0..ce5b638e 100644 --- a/publications/datasheets/sections/overview.typ +++ b/publications/datasheets/sections/overview.typ @@ -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.]) +#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") diff --git a/publications/datasheets/sections/product-use.typ b/publications/datasheets/sections/product-use.typ index ff59f38f..4e14f5fc 100644 --- a/publications/datasheets/sections/product-use.typ +++ b/publications/datasheets/sections/product-use.typ @@ -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") diff --git a/publications/datasheets/sections/reset-summary.typ b/publications/datasheets/sections/reset-summary.typ index 74e34420..7fba0988 100644 --- a/publications/datasheets/sections/reset-summary.typ +++ b/publications/datasheets/sections/reset-summary.typ @@ -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") diff --git a/publications/datasheets/sections/software.typ b/publications/datasheets/sections/software.typ index a9861338..8573a02a 100644 --- a/publications/datasheets/sections/software.typ +++ b/publications/datasheets/sections/software.typ @@ -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.]) +#figure(boot-diagram(), caption:[Linux image loading and ready checkpoint. Smoke and NPU-P6 acceptance continue through the later protocol.]) 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)[ diff --git a/publications/software_reference.py b/publications/software_reference.py index ab8f03be..fd3e7cdd 100644 --- a/publications/software_reference.py +++ b/publications/software_reference.py @@ -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"]]} diff --git a/scripts/build_hp_apu.py b/scripts/build_hp_apu.py index 1f3d1ea3..afc87d05 100644 --- a/scripts/build_hp_apu.py +++ b/scripts/build_hp_apu.py @@ -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()) diff --git a/scripts/build_hp_smoke.py b/scripts/build_hp_smoke.py index 4dbb96aa..68500422 100644 --- a/scripts/build_hp_smoke.py +++ b/scripts/build_hp_smoke.py @@ -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=[]) diff --git a/scripts/npu_compiler.py b/scripts/npu_compiler.py index f31bfd04..1bf7e947 100644 --- a/scripts/npu_compiler.py +++ b/scripts/npu_compiler.py @@ -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)); diff --git a/scripts/run_npu_p6_verilator.py b/scripts/run_npu_p6_verilator.py index d401a960..9dfda92a 100644 --- a/scripts/run_npu_p6_verilator.py +++ b/scripts/run_npu_p6_verilator.py @@ -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", diff --git a/tests/test_hp_platform.py b/tests/test_hp_platform.py index 0f57020d..b8be842a 100644 --- a/tests/test_hp_platform.py +++ b/tests/test_hp_platform.py @@ -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") diff --git a/tests/test_npu_compiler.py b/tests/test_npu_compiler.py index 44de64f9..d2fd4284 100644 --- a/tests/test_npu_compiler.py +++ b/tests/test_npu_compiler.py @@ -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") diff --git a/tests/test_npu_p6.py b/tests/test_npu_p6.py index 205499b6..d91bc324 100644 --- a/tests/test_npu_p6.py +++ b/tests/test_npu_p6.py @@ -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}, diff --git a/tests/test_publication_boot_acceptance.py b/tests/test_publication_boot_acceptance.py index 9ad5fe98..7c115600 100644 --- a/tests/test_publication_boot_acceptance.py +++ b/tests/test_publication_boot_acceptance.py @@ -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"] diff --git a/tests/test_publication_software.py b/tests/test_publication_software.py index 5fad7a17..87e8dec7 100644 --- a/tests/test_publication_software.py +++ b/tests/test_publication_software.py @@ -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) diff --git a/tests/test_publication_v05.py b/tests/test_publication_v05.py index 6159b6ef..ee5305c6 100644 --- a/tests/test_publication_v05.py +++ b/tests/test_publication_v05.py @@ -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 = [