mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
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:
30 files changed
+539
-135
No files matched your search
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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)[
|
||||
|
||||
@@ -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
@@ -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
@@ -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=[])
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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"]
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
Reference in new issue
Block a user