feat: replace the rom of apu into sram

This commit is contained in:
maksyuki committed 2026-09-25 08:35:07 +08:00
1 parent 9298a582a4
commit 92830d963d
74 files changed
+4726 -297

No files matched your search

+1 -1
View File
@@ -115,7 +115,7 @@ jobs:
tools: verilator sv2v iverilog yosys
- name: Set up locked accelerator references
run: make setup-apu-reference setup-npu-reference
run: make setup-apu-reference setup-apu-kws-reference setup-npu-reference
- name: Test scripts and RTL fixtures
run: python3 -m pytest -q
+37 -3
View File
@@ -136,6 +136,11 @@ APU_P5_CORPUS_RTL_DIR := $(APU_P5_DIR)/corpus-rtl
APU_P7_DIR := $(VARIANT_ROOT)/apu/kws
APU_P7_MODEL := $(APU_P7_DIR)/apu-p7.apum
APU_P7_MODEL_MANIFEST := $(APU_P7_DIR)/apu-p7-manifest.json
APU_P9_COEFFICIENT_DIR := $(VARIANT_ROOT)/apu/coefficients
APU_P9_APUC := $(APU_P9_COEFFICIENT_DIR)/apu-p9.apuc
APU_P9_LAYOUT_MANIFEST := $(APU_P9_COEFFICIENT_DIR)/coefficient-layout.json
APU_P9_EVIDENCE_DIR := $(VARIANT_ROOT)/apu/p9/evidence
APU_P9_EVIDENCE_LAYOUT := $(APU_P9_EVIDENCE_DIR)/coefficient-layout.json
APU_P7_KWS_TFLITE := $(CACHE_ROOT)/sources/apu-mlperf-tiny/benchmark/training/keyword_spotting/trained_models/kws_ref_model.tflite
NPU_P0_DIR := $(VARIANT_ROOT)/npu/p0
NPU_P5_DIR := $(VARIANT_ROOT)/npu/p5
@@ -416,7 +421,7 @@ endif
include physical/librelane/Makefile
include physical/ecc/Makefile
.PHONY: help config doctor setup setup-regression setup-mpw setup-vexiiriscv setup-clusterip setup-ip setup-pdk setup-app setup-apu-reference setup-apu-kws-reference apu-p5-bundle apu-p5-corpus apu-p7-model setup-hp-linux hp-linux hp-bundle hp-linux-sim hp-smoke-bundle hp-smoke-sim hp-apu-bundle hp-apu-sim \
.PHONY: help config doctor setup setup-regression setup-mpw setup-vexiiriscv setup-clusterip setup-ip setup-pdk setup-app setup-apu-reference setup-apu-kws-reference apu-p5-bundle apu-p5-corpus apu-p7-model apu-p9-coefficients apu-p9-evidence apu-p9-memory-ab setup-hp-linux hp-linux hp-bundle hp-linux-sim hp-smoke-bundle hp-smoke-sim hp-apu-bundle hp-apu-sim \
clean-all purge-cache manifest check-warnings metrics check-metrics package commercial-package \
regress-smoke regress-rtl regress-pr regress-nightly sim-asm format format-check sw-format sw-format-check mk-format \
mk-format-check rtl-format rtl-format-check rtl-style-check rtl-migrate-connections rtl-migrate-names sw-policy-check sw-host-test \
@@ -425,7 +430,7 @@ include physical/ecc/Makefile
apu-block-filelist apu-block-synth apu-block-sta apu-block-report apu-block-evidence apu-block-clean \
pin-map check-pin-map soc-topology check-soc-topology user-extensions check-user-extensions \
check-clock-reset-domains tech-cell-test rtl-lint check-rtl-lint \
formal formal-bus formal-rib-adapter formal-rib2apb formal-gpio formal-ws2812 formal-uart formal-i2c formal-timer formal-dvp formal-i2s formal-onchip-ram formal-opipsram formal-dma formal-apu formal-apu-kws formal-gateway-a formal-sdio formal-clean formal-doctor \
formal formal-bus formal-rib-adapter formal-rib2apb formal-gpio formal-ws2812 formal-uart formal-i2c formal-timer formal-dvp formal-i2s formal-onchip-ram formal-opipsram formal-dma formal-apu formal-apu-kws formal-apu-p9 formal-gateway-a formal-sdio formal-clean formal-doctor \
rtl-style-check-all rtl-readiness-check rtl-readiness-check-all vexii-generate
.NOTPARALLEL: setup
@@ -458,6 +463,9 @@ help:
' apu-p5-bundle build the deterministic WAV/FLAC APUMC bundle' \
' apu-p5-corpus qualify pinned FLAC with BAM/libFLAC and production RTL' \
' apu-p7-model convert the locked MLPerf Tiny KWS model to APUM' \
' apu-p9-coefficients build the frozen APUC image and 15-bank manifest' \
' apu-p9-evidence initialize the six fail-closed P9 evidence reports' \
' apu-p9-memory-ab compare explicit baseline/candidate synthesis roots' \
' setup-npu-reference install/verify locked NPU models, corpora, and oracle' \
' npu-p0-qualify qualify full corpora against the independent oracle' \
' npu-p5-deployments build deterministic KWS/VWW ABI-1 packages' \
@@ -497,7 +505,7 @@ help:
' check-clock-reset-domains validate the root clock/reset and CDC inventory' \
' rtl-lint | check-rtl-lint run/check strict Verilator RTL lint warnings' \
' formal | formal-bus | formal-rib-adapter | formal-rib2apb run SBY protocol proofs' \
'formal-sysctrl | formal-pll-rcu | formal-gpio | formal-ws2812 | formal-uart | formal-i2c | formal-timer | formal-clint | formal-dvp | formal-i2s | formal-onchip-ram | formal-opipsram | formal-dma | formal-ga2d | formal-apu | formal-apu-kws | formal-gateway-a | formal-sdio run peripheral proofs' \
'formal-sysctrl | formal-pll-rcu | formal-gpio | formal-ws2812 | formal-uart | formal-i2c | formal-timer | formal-clint | formal-dvp | formal-i2s | formal-onchip-ram | formal-opipsram | formal-dma | formal-ga2d | formal-apu | formal-apu-kws | formal-apu-p9 | formal-gateway-a | formal-sdio run peripheral proofs' \
' formal-doctor check the SBY, Yosys, sv2v, and Bitwuzla formal toolchain' \
' benchmark-report run the memory/DMA profile and write meta/performance.json' \
' coremark-report run the quick CoreMark profile and write meta/coremark.json' \
@@ -758,6 +766,32 @@ $(APU_P7_MODEL): $(ROOT_PATH)/scripts/apu_kws_convert.py \
apu-p7-model: $(APU_P7_MODEL)
$(APU_P9_APUC) $(APU_P9_LAYOUT_MANIFEST) &: $(APU_P7_MODEL) \
$(ROOT_PATH)/scripts/generate_apu_kws_rtl_constants.py \
$(ROOT_PATH)/scripts/apu_kws_coeff.py
python3 $(ROOT_PATH)/scripts/generate_apu_kws_rtl_constants.py \
--apum $(APU_P7_MODEL) \
--output $(APU_P9_COEFFICIENT_DIR)/apu_kws_rom.svh \
--profile-output $(APU_P9_COEFFICIENT_DIR)/apu_kws_apum_profile.svh \
--apuc-output $(APU_P9_APUC) --manifest-output $(APU_P9_LAYOUT_MANIFEST)
apu-p9-coefficients: $(APU_P9_APUC) $(APU_P9_LAYOUT_MANIFEST)
$(APU_P9_EVIDENCE_LAYOUT): $(APU_P9_LAYOUT_MANIFEST) $(APU_P9_APUC) \
$(ROOT_PATH)/scripts/apu_p9_evidence.py
python3 $(ROOT_PATH)/scripts/apu_p9_evidence.py initialize \
--layout $(APU_P9_LAYOUT_MANIFEST) --apuc $(APU_P9_APUC) \
--profile $(CONFIG) \
--output-dir $(APU_P9_EVIDENCE_DIR)
apu-p9-evidence: $(APU_P9_EVIDENCE_LAYOUT)
apu-p9-memory-ab:
@test -n '$(APU_P9_BASELINE_ROOT)' -a -n '$(APU_P9_CANDIDATE_ROOT)'
python3 $(ROOT_PATH)/scripts/apu_p9_memory_ab.py \
--baseline-root $(APU_P9_BASELINE_ROOT) --candidate-root $(APU_P9_CANDIDATE_ROOT) \
--output $(APU_P9_EVIDENCE_DIR)/memory-synthesis-ab.json
apu-p5-corpus: setup-apu-reference $(APU_P5_BUNDLE)
python3 $(ROOT_PATH)/scripts/qualify_apu_p5_corpus.py \
--flac $(APU_P5_REFERENCE_DIR)/src/flac/flac \
+3 -2
View File
@@ -4,12 +4,13 @@ APP_INC_DIRS += $(ROOT_PATH)/app/apps/apu_release $(ROOT_PATH)/app/apps/hp_boot
APU_RELEASE_ASSETS_H := $(SW_BUILD_DIR)/include/apu_release_assets.h
$(APU_RELEASE_ASSETS_H): $(APU_P5_BUNDLE) $(APU_P7_MODEL) \
$(APU_RELEASE_ASSETS_H): $(APU_P5_BUNDLE) $(APU_P7_MODEL) $(APU_P9_APUC) \
$(ROOT_PATH)/scripts/build_apu_release_assets.py \
$(ROOT_PATH)/scripts/apu_codecs.py $(ROOT_PATH)/scripts/apu_kws.py \
$(ROOT_PATH)/scripts/apu_kws_coeff.py \
$(ROOT_PATH)/scripts/apu_p5_coefficients.py
python3 $(ROOT_PATH)/scripts/build_apu_release_assets.py \
--apumc $(APU_P5_BUNDLE) --apum $(APU_P7_MODEL) \
--apumc $(APU_P5_BUNDLE) --apuc $(APU_P9_APUC) --apum $(APU_P7_MODEL) \
--kws-corpus $(CACHE_ROOT)/sources/apu-kws-pcm \
--output $@
+2
View File
@@ -15,6 +15,8 @@
#define RS_APU_RELEASE_MODEL_CAPACITY UINT32_C(0x00008000)
#define RS_APU_RELEASE_WAV_ADDRESS UINT32_C(0x3BC20000)
#define RS_APU_RELEASE_WAV_CAPACITY UINT32_C(0x00010000)
#define RS_APU_RELEASE_COEFF_ADDRESS UINT32_C(0x3BC30000)
#define RS_APU_RELEASE_COEFF_CAPACITY UINT32_C(0x00010000)
#define RS_APU_RELEASE_PCM_ADDRESS UINT32_C(0x3BC40000)
#define RS_APU_RELEASE_PCM_CAPACITY UINT32_C(0x00010000)
#define RS_APU_RELEASE_KWS_ADDRESS UINT32_C(0x3BC60000)
+50 -2
View File
@@ -39,6 +39,17 @@ static rs_dma_tcd_t s_apu_release_tcd __attribute__((aligned(64)));
_Static_assert(sizeof(rs_hp_boot_header_t) == UINT32_C(128), "HP bundle header ABI mismatch");
static uint32_t rs_apu_release_cycle(void) {
uint32_t cycle;
__asm__ volatile("csrr %0, mcycle" : "=r"(cycle));
return cycle;
}
static void rs_apu_release_milestone(const char *name) {
printf("APU_RELEASE_CYCLE:%s:%08lx\n", name, (unsigned long)rs_apu_release_cycle());
}
static uint32_t rs_apu_release_crc32_byte(uint32_t crc, uint8_t value) {
crc ^= value;
for (uint32_t bit = 0U; bit < 8U; ++bit) {
@@ -232,6 +243,11 @@ static bool rs_apu_release_stage_assets(void) {
RS_APU_RELEASE_APUMC_SIZE, RS_APU_RELEASE_APUMC_CRC)) {
return false;
}
if (!rs_apu_release_stage((uint32_t)(uintptr_t)rs_apu_release_apuc,
RS_APU_RELEASE_COEFF_ADDRESS, RS_APU_RELEASE_APUC_SIZE,
RS_APU_RELEASE_APUC_CRC)) {
return false;
}
if (!rs_apu_release_stage((uint32_t)(uintptr_t)rs_apu_release_apum,
RS_APU_RELEASE_MODEL_ADDRESS, RS_APU_RELEASE_APUM_SIZE,
RS_APU_RELEASE_APUM_CRC)) {
@@ -272,6 +288,11 @@ static uint32_t rs_apu_release_load_images(void) {
.bytes = RS_APU_RELEASE_APUM_SIZE,
.expected_crc = RS_APU_RELEASE_APUM_PAYLOAD_CRC,
};
const rs_apu_image_t coefficient_image = {
.address = RS_APU_RELEASE_COEFF_ADDRESS,
.bytes = RS_APU_RELEASE_APUC_SIZE,
.expected_crc = RS_APU_RELEASE_APUC_PAYLOAD_CRC,
};
uint32_t status;
rs_status_t load_status;
@@ -290,20 +311,37 @@ static uint32_t rs_apu_release_load_images(void) {
return 2U;
}
printf("APU_RELEASE_MC:%08lx\n", (unsigned long)RS_APU_REG(RS_APU_ABI_MC_ACTUAL_CRC));
rs_apu_release_milestone("MC");
__asm__ volatile("fence rw, rw" ::: "memory");
load_status = rs_apu_kws_coeff_load(&coefficient_image, RS_APU_RELEASE_LOAD_TIMEOUT);
if (load_status != RS_OK) {
printf("APU_RELEASE_COEFFERR:%d\n", (int)load_status);
rs_apu_release_dump_apu();
return 3U;
}
status = RS_APU_REG(RS_APU_ABI_KWS_COEFF_STATUS);
if (((status & UINT32_C(6)) != UINT32_C(6)) ||
(RS_APU_REG(RS_APU_ABI_KWS_COEFF_ACTUAL_CRC) != RS_APU_RELEASE_APUC_PAYLOAD_CRC)) {
rs_apu_release_dump_apu();
return 4U;
}
printf("APU_RELEASE_COEFF:%08lx\n", (unsigned long)RS_APU_REG(RS_APU_ABI_KWS_COEFF_ACTUAL_CRC));
rs_apu_release_milestone("COEFF");
__asm__ volatile("fence rw, rw" ::: "memory");
load_status = rs_apu_kws_model_load(&model_image, RS_APU_RELEASE_LOAD_TIMEOUT);
if (load_status != RS_OK) {
printf("APU_RELEASE_MODELERR:%d\n", (int)load_status);
rs_apu_release_dump_apu();
return 3U;
return 5U;
}
status = RS_APU_REG(RS_APU_ABI_KWS_MODEL_STATUS);
if (((status & UINT32_C(6)) != UINT32_C(6)) ||
(RS_APU_REG(RS_APU_ABI_KWS_MODEL_ACTUAL_CRC) != RS_APU_RELEASE_APUM_PAYLOAD_CRC)) {
rs_apu_release_dump_apu();
return 4U;
return 6U;
}
printf("APU_RELEASE_MODEL:%08lx\n", (unsigned long)RS_APU_REG(RS_APU_ABI_KWS_MODEL_ACTUAL_CRC));
rs_apu_release_milestone("MODEL");
return 0U;
}
@@ -361,6 +399,7 @@ static bool rs_apu_release_handoff(void) {
((owner & (UINT32_C(1) << RS_APU_ABI_OWNER_STATUS_QUIESCE)) == 0U) &&
((owner & (UINT32_C(1) << RS_APU_ABI_OWNER_STATUS_RESET)) == 0U)) {
printf("APU_RELEASE_HANDOFF:%u\n", (unsigned int)after.handoff_count);
rs_apu_release_milestone("HANDOFF");
return true;
}
}
@@ -418,6 +457,7 @@ static void rs_apu_release_run(void) {
rs_test_finish(RS_TEST_FAILED, UINT8_C(1));
}
printf("APU_RELEASE_BOOT\n");
rs_apu_release_milestone("BOOT");
if ((rs_sysctrl_set_hp_release(false) != RS_OK) ||
(rs_sysctrl_select_hp_debug(true) != RS_OK) ||
(rs_sysctrl_get_hp_status(&hp_status) != RS_OK) || !hp_status.present ||
@@ -425,14 +465,17 @@ static void rs_apu_release_run(void) {
rs_apu_release_fail(UINT8_C(2));
}
printf("APU_RELEASE_HP_HELD\n");
rs_apu_release_milestone("HP_HELD");
if (!rs_apu_release_wait_sdram()) {
rs_apu_release_fail(UINT8_C(3));
}
printf("APU_RELEASE_SDRAM\n");
rs_apu_release_milestone("SDRAM");
if (!rs_apu_release_load_bundle()) {
rs_apu_release_fail(UINT8_C(4));
}
printf("APU_RELEASE_BUNDLE\n");
rs_apu_release_milestone("BUNDLE");
if ((rs_apu_probe(&info) != RS_OK) || (info.capability0 != RS_APU_CAPABILITY0_P7_IMPLEMENTED) ||
(info.abi_digest != RS_APU_DIGEST_P7_IMPLEMENTED)) {
rs_apu_release_fail(UINT8_C(5));
@@ -443,10 +486,12 @@ static void rs_apu_release_run(void) {
rs_apu_release_fail(UINT8_C(6));
}
printf("APU_RELEASE_QUIESCED\n");
rs_apu_release_milestone("QUIESCED");
if (!rs_apu_release_stage_assets()) {
rs_apu_release_fail(UINT8_C(7));
}
printf("APU_RELEASE_STAGED\n");
rs_apu_release_milestone("STAGED");
if (rs_apu_set_acl(RS_APU_RELEASE_ACL_READ_BASE, RS_APU_RELEASE_ACL_READ_LIMIT,
RS_APU_RELEASE_ACL_WRITE_BASE, RS_APU_RELEASE_ACL_WRITE_LIMIT) != RS_OK) {
rs_apu_release_fail(UINT8_C(8));
@@ -471,6 +516,7 @@ static void rs_apu_release_run(void) {
rs_apu_release_fail(UINT8_C(12));
}
printf("APU_RELEASE_HP_RUN\n");
rs_apu_release_milestone("HP_RUN");
/* Wait for the HP done event; the bounded poll only exits early on a stuck
* hart because every hardware failure path publishes its own event. */
@@ -484,6 +530,7 @@ static void rs_apu_release_run(void) {
(event.argument != 0U)) {
rs_apu_release_fail(UINT8_C(13));
}
rs_apu_release_milestone("HP_EVENT");
for (budget = 0U; budget < RS_APU_RELEASE_SETTLE_SPINS; ++budget) {
__asm__ volatile("nop" ::: "memory");
}
@@ -501,6 +548,7 @@ static void rs_apu_release_run(void) {
printf("APU_RELEASE_TRAP:%lu:%lu\n", (unsigned long)page->fault_mcause,
(unsigned long)page->fault_mtval);
printf("APU_RELEASE_PASS\n");
rs_apu_release_milestone("PASS");
rs_test_finish(RS_TEST_PASSED, UINT8_C(0));
}
+21
View File
@@ -309,6 +309,21 @@ static void hp_report(const char *marker, uint32_t value) {
hp_uart_puts("\n");
}
static uint32_t hp_cycle(void) {
uint32_t cycle;
__asm__ volatile("csrr %0, mcycle" : "=r"(cycle));
return cycle;
}
static void hp_milestone(const char *name) {
hp_uart_puts("HP_APU_CYCLE:");
hp_uart_puts(name);
hp_uart_puts(":");
hp_uart_puthex(hp_cycle());
hp_uart_puts("\n");
}
void main(void) {
volatile rs_apu_release_page_t *page =
(volatile rs_apu_release_page_t *)(uintptr_t)RS_APU_RELEASE_PAGE_ADDRESS;
@@ -318,11 +333,13 @@ void main(void) {
hp_uart_init();
hp_uart_puts("HP_APU_BOOT\n");
hp_milestone("BOOT");
if (!hp_mailbox_wait(&code, &argument)) {
hp_uart_puts("HP_APU_NO_MAILBOX\n");
for (;;) {
}
}
hp_milestone("MAILBOX");
hp_cbo_inval(RS_APU_RELEASE_PAGE_ADDRESS, RS_APU_RELEASE_PAGE_BYTES);
page->hp_steps = RS_APU_RELEASE_STEP_MAILBOX;
if ((code != RS_APU_RELEASE_JOB_RUN) || (argument != RS_APU_RELEASE_PAGE_ADDRESS) ||
@@ -343,6 +360,7 @@ void main(void) {
}
}
hp_report("HP_APU_WAV_DONE", page->wav_output_bytes);
hp_milestone("WAV_DONE");
if (!hp_submit_kws(page)) {
hp_report("HP_APU_KWS_STOP", page->kws_status);
page->hp_error = 4U;
@@ -351,12 +369,15 @@ void main(void) {
}
}
hp_report("HP_APU_KWS_DONE", page->kws_class_id);
hp_milestone("KWS_DONE");
hp_probe_lp_only(page);
hp_report("HP_APU_TRAP_SEEN", page->fault_mcause);
hp_milestone("TRAP");
summary = (page->hp_steps == RS_APU_RELEASE_STEP_ALL) ? 0U : 5U;
page->hp_error = summary;
hp_publish(page, summary);
hp_uart_puts("HP_APU_DONE\n");
hp_milestone("DONE");
for (;;) {
}
}
+9
View File
@@ -112,6 +112,13 @@ typedef struct {
uint32_t overrun_count;
} rs_apu_kws_status_t;
typedef struct {
uint32_t status;
uint32_t actual_crc;
uint32_t coefficient_id[2];
uint32_t capacity_bytes;
} rs_apu_kws_coeff_status_t;
typedef struct {
uint32_t status;
uint32_t address;
@@ -199,6 +206,8 @@ rs_status_t rs_apu_irq_read(uint32_t *state);
rs_status_t rs_apu_irq_enable(uint32_t mask);
rs_status_t rs_apu_irq_ack(uint32_t mask);
rs_status_t rs_apu_kws_model_load(const rs_apu_image_t *image, rs_timeout_t timeout);
rs_status_t rs_apu_kws_coeff_load(const rs_apu_image_t *image, rs_timeout_t timeout);
rs_status_t rs_apu_kws_coeff_status_read(rs_apu_kws_coeff_status_t *status);
rs_status_t rs_apu_kws_configure(const rs_apu_kws_config_t *config);
rs_status_t rs_apu_kws_enable(uint32_t memory_window);
rs_status_t rs_apu_kws_disable(rs_timeout_t timeout);
+32 -5
View File
@@ -81,6 +81,15 @@
#define RS_APU_ABI_KWS_MODEL_STATUS UINT32_C(0x234)
#define RS_APU_ABI_KWS_MODEL_ACTUAL_CRC UINT32_C(0x238)
#define RS_APU_ABI_KWS_INPUT_CONFIG UINT32_C(0x23C)
#define RS_APU_ABI_KWS_COEFF_ADDRESS UINT32_C(0x240)
#define RS_APU_ABI_KWS_COEFF_SIZE UINT32_C(0x244)
#define RS_APU_ABI_KWS_COEFF_EXPECTED_CRC UINT32_C(0x248)
#define RS_APU_ABI_KWS_COEFF_COMMAND UINT32_C(0x24C)
#define RS_APU_ABI_KWS_COEFF_STATUS UINT32_C(0x250)
#define RS_APU_ABI_KWS_COEFF_ACTUAL_CRC UINT32_C(0x254)
#define RS_APU_ABI_KWS_COEFF_ID_LO UINT32_C(0x258)
#define RS_APU_ABI_KWS_COEFF_ID_HI UINT32_C(0x25C)
#define RS_APU_ABI_KWS_COEFF_CAPACITY UINT32_C(0x260)
#define RS_APU_ABI_PERF_CONTROL UINT32_C(0x300)
#define RS_APU_ABI_PERF_STATUS UINT32_C(0x304)
#define RS_APU_ABI_PERF_ACTIVE_CYCLES_LO UINT32_C(0x308)
@@ -128,6 +137,7 @@
#define RS_APU_ABI_CAPABILITY0_KWS 6U
#define RS_APU_ABI_CAPABILITY0_SEQUENCER 7U
#define RS_APU_ABI_CAPABILITY0_RESAMPLER 8U
#define RS_APU_ABI_CAPABILITY0_COEFFICIENT_LOAD 9U
#define RS_APU_ABI_CAPABILITY1_CONTROL_KIB 0U
#define RS_APU_ABI_CAPABILITY1_DATA_KIB 8U
#define RS_APU_ABI_CAPABILITY1_MAX_CHANNELS 16U
@@ -172,6 +182,14 @@
#define RS_APU_ABI_IRQ_FIRST_ERROR 8U
#define RS_APU_ABI_IRQ_STREAM_XRUN 9U
#define RS_APU_ABI_IRQ_SEQUENCER_TRAP 10U
#define RS_APU_ABI_IRQ_COEFFICIENT_LOAD_DONE 11U
#define RS_APU_ABI_KWS_COEFF_STATUS_BUSY 0U
#define RS_APU_ABI_KWS_COEFF_STATUS_VALID 1U
#define RS_APU_ABI_KWS_COEFF_STATUS_LOCKED 2U
#define RS_APU_ABI_KWS_COEFF_STATUS_SIZE_ERROR 8U
#define RS_APU_ABI_KWS_COEFF_STATUS_HEADER_ERROR 9U
#define RS_APU_ABI_KWS_COEFF_STATUS_CRC_ERROR 10U
#define RS_APU_ABI_KWS_COEFF_STATUS_PADDING_ERROR 11U
#define RS_APU_ABI_ERROR_VALID 0U
#define RS_APU_ABI_ERROR_CODE 1U
#define RS_APU_ABI_ERROR_STAGE 7U
@@ -325,6 +343,14 @@
#define RS_APU_ABI_APUM_ABI UINT32_C(0x00010000)
#define RS_APU_ABI_APUM_IMAGE_BYTES 32768U
#define RS_APU_ABI_APUM_PAYLOAD_CRC UINT32_C(0xB9034B22)
#define RS_APU_ABI_APUC_MAGIC UINT32_C(0x43555041)
#define RS_APU_ABI_APUC_ABI UINT32_C(0x00010000)
#define RS_APU_ABI_APUC_IMAGE_BYTES 61504U
#define RS_APU_ABI_APUC_HEADER_BYTES 64U
#define RS_APU_ABI_APUC_PAYLOAD_BYTES 61440U
#define RS_APU_ABI_APUC_PAYLOAD_CRC UINT32_C(0x25E7C27D)
#define RS_APU_ABI_APUC_COEFFICIENT_ID_LO UINT32_C(0x8A0B038D)
#define RS_APU_ABI_APUC_COEFFICIENT_ID_HI UINT32_C(0x806C780D)
#define RS_APU_ABI_APUMC_HEADER_MAGIC 0U
#define RS_APU_ABI_APUMC_HEADER_ABI 1U
#define RS_APU_ABI_APUMC_HEADER_TOTAL_BYTES 2U
@@ -479,21 +505,22 @@
#define RS_APU_ABI_MC_TRAP_EXPLICIT 10U
#define RS_APU_IP_ID_VALUE UINT32_C(0x41505530)
#define RS_APU_IP_VERSION_VALUE UINT32_C(0x00010001)
#define RS_APU_IP_VERSION_VALUE UINT32_C(0x00010002)
#define RS_APU_IP_VERSION_MAJOR_MASK UINT32_C(0xFFFF0000)
#define RS_APU_CAPABILITY0_P7_IMPLEMENTED UINT32_C(0x000001FD)
#define RS_APU_CAPABILITY0_P7_IMPLEMENTED UINT32_C(0x000003FD)
#define RS_APU_CAPABILITY0_P5_IMPLEMENTED UINT32_C(0x000001BD)
#define RS_APU_CAPABILITY1_P7_IMPLEMENTED UINT32_C(0x01827020)
#define RS_APU_CAPABILITY1_IMPLEMENTED UINT32_C(0x01827020)
#define RS_APU_DIGEST_P7_IMPLEMENTED UINT32_C(0xF5005D7C)
#define RS_APU_DIGEST_P9_IMPLEMENTED UINT32_C(0x63E96066)
#define RS_APU_DIGEST_P7_IMPLEMENTED RS_APU_DIGEST_P9_IMPLEMENTED
#if defined(RS_APU_RELEASE_P7)
#define RS_APU_CAPABILITY0_IMPLEMENTED RS_APU_CAPABILITY0_P7_IMPLEMENTED
#define RS_APU_DIGEST_IMPLEMENTED RS_APU_DIGEST_P7_IMPLEMENTED
#define RS_APU_DIGEST_IMPLEMENTED RS_APU_DIGEST_P9_IMPLEMENTED
#else
#define RS_APU_CAPABILITY0_IMPLEMENTED RS_APU_CAPABILITY0_P5_IMPLEMENTED
#define RS_APU_DIGEST_IMPLEMENTED UINT32_C(0x00000000)
#endif
#define RS_APU_IRQ_ALL UINT32_C(0x000007FF)
#define RS_APU_IRQ_ALL UINT32_C(0x00000FFF)
#define RS_APU_REG_IP_ID RS_APU_ABI_IP_ID
#define RS_APU_REG_IP_VERSION RS_APU_ABI_IP_VERSION
+95 -3
View File
@@ -202,7 +202,7 @@ rs_status_t rs_apu_probe(rs_apu_info_t *info) {
if ((info->ip_id != RS_APU_IP_ID_VALUE) ||
((info->version & RS_APU_IP_VERSION_MAJOR_MASK) !=
(RS_APU_IP_VERSION_VALUE & RS_APU_IP_VERSION_MAJOR_MASK)) ||
((info->version & UINT32_C(0xFFFF)) > 1U)) {
((info->version & UINT32_C(0xFFFF)) > 2U)) {
return RS_ENOTSUP;
}
return RS_OK;
@@ -620,6 +620,26 @@ static rs_status_t rs_apu_kws_discover(void) {
return (rs_apu_bit(info.capability0, RS_APU_ABI_CAPABILITY0_KWS) != 0U) ? RS_OK : RS_ENOTSUP;
}
static rs_status_t rs_apu_kws_coeff_ready_if_required(void) {
rs_apu_info_t info;
uint32_t status;
if (rs_apu_probe(&info) != RS_OK) {
return RS_ENOTSUP;
}
if (rs_apu_bit(info.capability0, RS_APU_ABI_CAPABILITY0_COEFFICIENT_LOAD) == 0U) {
return RS_OK;
}
status = RS_APU_REG(RS_APU_ABI_KWS_COEFF_STATUS);
if (((status & UINT32_C(6)) != UINT32_C(6)) ||
(RS_APU_REG(RS_APU_ABI_KWS_COEFF_ACTUAL_CRC) != RS_APU_ABI_APUC_PAYLOAD_CRC) ||
(RS_APU_REG(RS_APU_ABI_KWS_COEFF_ID_LO) != RS_APU_ABI_APUC_COEFFICIENT_ID_LO) ||
(RS_APU_REG(RS_APU_ABI_KWS_COEFF_ID_HI) != RS_APU_ABI_APUC_COEFFICIENT_ID_HI)) {
return RS_EINVAL;
}
return RS_OK;
}
static rs_status_t rs_apu_kws_admission(const rs_apu_kws_job_t *job) {
uint32_t model_status;
@@ -629,7 +649,8 @@ static rs_status_t rs_apu_kws_admission(const rs_apu_kws_job_t *job) {
return RS_EINVAL;
}
model_status = RS_APU_REG(RS_APU_ABI_KWS_MODEL_STATUS);
if ((model_status & UINT32_C(6)) != UINT32_C(6) ||
if ((rs_apu_kws_coeff_ready_if_required() != RS_OK) ||
(model_status & UINT32_C(6)) != UINT32_C(6) ||
(RS_APU_REG(RS_APU_ABI_KWS_CONTROL) != UINT32_C(3)) ||
(rs_apu_bit(RS_APU_REG(RS_APU_ABI_KWS_STATUS), RS_APU_ABI_KWS_STATUS_LISTENING) != 0U)) {
return RS_EINVAL;
@@ -655,6 +676,9 @@ rs_status_t rs_apu_kws_model_load(const rs_apu_image_t *image, rs_timeout_t time
if (rs_apu_kws_discover() != RS_OK) {
return RS_ENOTSUP;
}
if (rs_apu_kws_coeff_ready_if_required() != RS_OK) {
return RS_EINVAL;
}
if ((rs_apu_lp_quiesced() == 0U) ||
(rs_apu_bit(RS_APU_REG(RS_APU_ABI_STATUS), RS_APU_ABI_STATUS_IDLE) == 0U) ||
(rs_apu_range_in_acl(image->address, image->bytes, RS_APU_REG(RS_APU_ABI_READ_BASE),
@@ -682,6 +706,73 @@ rs_status_t rs_apu_kws_model_load(const rs_apu_image_t *image, rs_timeout_t time
return RS_ETIMEOUT;
}
rs_status_t rs_apu_kws_coeff_load(const rs_apu_image_t *image, rs_timeout_t timeout) {
rs_apu_info_t info;
uint32_t polls;
uint32_t status;
if ((image == NULL) || ((image->address & UINT32_C(63)) != 0U) ||
(image->bytes != RS_APU_ABI_APUC_IMAGE_BYTES) ||
(image->expected_crc != RS_APU_ABI_APUC_PAYLOAD_CRC) ||
(image->address > (UINT32_MAX - (RS_APU_ABI_APUC_IMAGE_BYTES - 1U)))) {
return RS_EINVAL;
}
if ((rs_apu_probe(&info) != RS_OK) ||
(rs_apu_bit(info.capability0, RS_APU_ABI_CAPABILITY0_COEFFICIENT_LOAD) == 0U)) {
return RS_ENOTSUP;
}
status = RS_APU_REG(RS_APU_ABI_KWS_COEFF_STATUS);
if ((rs_apu_lp_quiesced() == 0U) ||
(rs_apu_bit(RS_APU_REG(RS_APU_ABI_STATUS), RS_APU_ABI_STATUS_IDLE) == 0U) ||
(rs_apu_range_in_acl(image->address, image->bytes, RS_APU_REG(RS_APU_ABI_READ_BASE),
RS_APU_REG(RS_APU_ABI_READ_LIMIT)) == 0U) ||
((RS_APU_REG(RS_APU_ABI_KWS_CONTROL) & UINT32_C(1)) != 0U) ||
((status & UINT32_C(4)) != 0U) ||
((RS_APU_REG(RS_APU_ABI_KWS_MODEL_STATUS) & UINT32_C(2)) != 0U)) {
return RS_EINVAL;
}
RS_APU_REG(RS_APU_ABI_KWS_COEFF_ADDRESS) = image->address;
RS_APU_REG(RS_APU_ABI_KWS_COEFF_SIZE) = image->bytes;
RS_APU_REG(RS_APU_ABI_KWS_COEFF_EXPECTED_CRC) = image->expected_crc;
rs_apu_fence();
RS_APU_REG(RS_APU_ABI_KWS_COEFF_COMMAND) = UINT32_C(1);
polls = rs_apu_poll_count(timeout);
while (polls-- != 0U) {
status = RS_APU_REG(RS_APU_ABI_KWS_COEFF_STATUS);
if ((status & UINT32_C(1)) == 0U) {
if (((status & UINT32_C(6)) == UINT32_C(6)) &&
(RS_APU_REG(RS_APU_ABI_KWS_COEFF_ACTUAL_CRC) == image->expected_crc) &&
(RS_APU_REG(RS_APU_ABI_KWS_COEFF_ID_LO) == RS_APU_ABI_APUC_COEFFICIENT_ID_LO) &&
(RS_APU_REG(RS_APU_ABI_KWS_COEFF_ID_HI) == RS_APU_ABI_APUC_COEFFICIENT_ID_HI)) {
return RS_OK;
}
if ((status & UINT32_C(0x0B00)) != 0U) {
return RS_EFORMAT;
}
return RS_EIO;
}
}
return RS_ETIMEOUT;
}
rs_status_t rs_apu_kws_coeff_status_read(rs_apu_kws_coeff_status_t *status) {
rs_apu_info_t info;
if (status == NULL) {
return RS_EINVAL;
}
if ((rs_apu_probe(&info) != RS_OK) ||
(rs_apu_bit(info.capability0, RS_APU_ABI_CAPABILITY0_COEFFICIENT_LOAD) == 0U)) {
return RS_ENOTSUP;
}
status->status = RS_APU_REG(RS_APU_ABI_KWS_COEFF_STATUS);
status->actual_crc = RS_APU_REG(RS_APU_ABI_KWS_COEFF_ACTUAL_CRC);
status->coefficient_id[0] = RS_APU_REG(RS_APU_ABI_KWS_COEFF_ID_LO);
status->coefficient_id[1] = RS_APU_REG(RS_APU_ABI_KWS_COEFF_ID_HI);
status->capacity_bytes = RS_APU_REG(RS_APU_ABI_KWS_COEFF_CAPACITY);
return RS_OK;
}
rs_status_t rs_apu_kws_configure(const rs_apu_kws_config_t *config) {
uint32_t input_config;
@@ -716,7 +807,8 @@ rs_status_t rs_apu_kws_enable(uint32_t memory_window) {
return RS_ENOTSUP;
}
model_status = RS_APU_REG(RS_APU_ABI_KWS_MODEL_STATUS);
if ((rs_apu_owner_unblocked() == 0U) || ((model_status & UINT32_C(6)) != UINT32_C(6)) ||
if ((rs_apu_owner_unblocked() == 0U) || (rs_apu_kws_coeff_ready_if_required() != RS_OK) ||
((model_status & UINT32_C(6)) != UINT32_C(6)) ||
(RS_APU_REG(RS_APU_ABI_KWS_CONTROL) != 0U)) {
return RS_EINVAL;
}
+20 -11
View File
@@ -564,7 +564,15 @@ Unlisted offsets are reserved and return `PSLVERR`.
| `0x234` | `KWS_MODEL_STATUS` | RO | `0` | Busy, valid/lock, header/range/size/CRC/operator errors. |
| `0x238` | `KWS_MODEL_ACTUAL_CRC` | RO | `0` | Observed payload CRC32. |
| `0x23c` | `KWS_INPUT_CONFIG` | RW disabled/idle | `0x0104bb80` | P7 append: rate `[16:0]`, physical channels `[18:17]`, precision `[25:20]`; see P7 capture rules. |
| `0x240..0x260` | `KWS_COEFF_*` | P9 only | see P9 | Additive coefficient DMA/CRC/readback/lock interface; exact fields and resets below. |
| `0x240` | `KWS_COEFF_ADDRESS` | RW idle/LP | `0` | P9: 64-byte-aligned APUC DMA source. |
| `0x244` | `KWS_COEFF_SIZE` | RW idle/LP | `0` | P9: exact 61504-byte APUC image size. |
| `0x248` | `KWS_COEFF_EXPECTED_CRC` | RW idle/LP | `0` | P9: expected APUC payload CRC32. |
| `0x24c` | `KWS_COEFF_COMMAND` | WO | `0` | P9: full-word value 1 starts coefficient loading. |
| `0x250` | `KWS_COEFF_STATUS` | RO | `0` | P9: busy, valid, lock and terminal validation errors. |
| `0x254` | `KWS_COEFF_ACTUAL_CRC` | RO | `0` | P9: finalized SRAM-readback CRC32. |
| `0x258` | `KWS_COEFF_ID_LO` | RO | `0` | P9: published coefficient ID bits 31:0. |
| `0x25c` | `KWS_COEFF_ID_HI` | RO | `0` | P9: published coefficient ID bits 63:32. |
| `0x260` | `KWS_COEFF_CAPACITY` | RO | `0x0000f000` | P9: auxiliary coefficient payload capacity in bytes. |
| `0x300` | `PERF_CONTROL` | RW | `0` | Enable 0, clear pulse 1, snapshot pulse 2. |
| `0x304` | `PERF_STATUS` | RO | `0` | Snapshot valid and overflow summary. |
| `0x308..0x354` | `PERF_*` | RO snapshot | `0` | Ten 64-bit pairs: active cycles, input/output bytes, decoded frames, DMA read/write stalls, stream stalls, sequencer instructions, KWS cycles, and faults. |
@@ -2815,16 +2823,17 @@ offsets, descriptor, APUM/APUMC versions, MP3 stub and system IRQ/resource/DMA/
clock/reset allocations are unchanged. Earlier profiles reject these new
offsets and bit11 IRQ writes. P9-only software tests discovery before access.
| Offset | Register | Access / reset | Contract |
| ---: | --- | --- | --- |
| `0x240` | KWS_COEFF_ADDRESS | RW idle/LP / 0 | 64-byte-aligned DMA source. |
| `0x244` | KWS_COEFF_SIZE | RW idle/LP / 0 | Exact 61504 bytes. |
| `0x248` | KWS_COEFF_EXPECTED_CRC | RW idle/LP / 0 | Expected APUC payload CRC. |
| `0x24c` | KWS_COEFF_COMMAND | WO / 0 | Full-word value 1 starts load; all other values reject. |
| `0x250` | KWS_COEFF_STATUS | RO / 0 | Busy0, valid1, locked2; header-declared size8, other header9, CRC10, padding11 error flags. |
| `0x254` | KWS_COEFF_ACTUAL_CRC | RO / 0 | Finalized SRAM-readback CRC, zero until a complete readback. |
| `0x258`, `0x25c` | KWS_COEFF_ID_LO/HI | RO / 0 | Published coefficient ID; zero until success. |
| `0x260` | KWS_COEFF_CAPACITY | RO / `0x0000f000` | Auxiliary payload bytes, not APUM scratch bytes. |
| Offset | Register | Access | Reset | Contract |
| ---: | --- | --- | --- | --- |
| `0x240` | KWS_COEFF_ADDRESS | RW idle/LP | `0` | 64-byte-aligned DMA source. |
| `0x244` | KWS_COEFF_SIZE | RW idle/LP | `0` | Exact 61504 bytes. |
| `0x248` | KWS_COEFF_EXPECTED_CRC | RW idle/LP | `0` | Expected APUC payload CRC. |
| `0x24c` | KWS_COEFF_COMMAND | WO | `0` | Full-word value 1 starts load; all other values reject. |
| `0x250` | KWS_COEFF_STATUS | RO | `0` | Busy0, valid1, locked2; header-declared size8, other header9, CRC10, padding11 error flags. |
| `0x254` | KWS_COEFF_ACTUAL_CRC | RO | `0` | Finalized SRAM-readback CRC, zero until a complete readback. |
| `0x258` | KWS_COEFF_ID_LO | RO | `0` | Published coefficient ID bits 31:0; zero until success. |
| `0x25c` | KWS_COEFF_ID_HI | RO | `0` | Published coefficient ID bits 63:32; zero until success. |
| `0x260` | KWS_COEFF_CAPACITY | RO | `0x0000f000` | Auxiliary payload bytes, not APUM scratch bytes. |
RW/WO writes require full-word strobes; reserved status bits read zero and
RO writes reject. Load admission requires LP owner, globally idle/quiesced,
+50 -20
View File
@@ -33,15 +33,18 @@ SRAM_WRAPPER_PREFIX = "tc_sram"
CLOCK_GATE_RE = re.compile(r"^sg13g2_(s)?lgcp_\d+$")
MACRO_CLASS_BY_ANCESTOR = {
"u_proof_memo": "verifier_memo",
"u_kws_coeff_store": "coefficient_store",
"u_kws_sram_client": "kws_model_scratch",
"u_control_store": "control_store",
"u_local_sram": "local_data",
"u_local_sram": "codec_common_data",
"u_microcode_loader": "loader_path_stack",
}
LOCAL_SRAM_RTL = REPO_ROOT / "rtl/ip/multimedia/apu_local_sram.sv"
APU_TOP_RTL = REPO_ROOT / "rtl/ip/multimedia/apb4_apu.sv"
APU_DEFINE_RTL = REPO_ROOT / "rtl/ip/multimedia/apu_define.svh"
DOC_REFERENCE = "docs/ip/apu.md:3536-3541"
DOC_REFERENCE = "docs/ip/apu.md:2698-2714"
MODULE_DEF_RE = re.compile(r"^module\s+\\?([^\s(]+)", re.MULTILINE)
WORST_BLOCK_RE = re.compile(r"(?=^Startpoint: )", re.MULTILINE)
@@ -502,6 +505,15 @@ def storage_accounting(synths: dict[str, dict[str, Any]]) -> dict[str, Any]:
"wrappers": control_kib // wrapper_kib + constants["local_data_logical_banks"],
"kib": control_kib + data_kib,
},
"p9_physical": {
"control_store": {"wrappers": 8, "kib": 32},
"codec_common_data": {"wrappers": 12, "kib": 48},
"kws_model_scratch": {"wrappers": 16, "kib": 64},
"loader_path_stack": {"wrappers": 8, "kib": 32},
"verifier_memo": {"wrappers": 17, "kib": 68},
"coefficient_store": {"wrappers": 15, "kib": 60},
"total": {"wrappers": 76, "kib": 304},
},
"rtl_artifacts": [str(LOCAL_SRAM_RTL), str(APU_TOP_RTL), str(APU_DEFINE_RTL)],
"per_configuration": {},
}
@@ -513,6 +525,22 @@ def storage_accounting(synths: dict[str, dict[str, Any]]) -> dict[str, Any]:
"by_type": macros["by_type"],
"unclassified": macros["by_class"].get("unclassified", 0),
}
expected_p9 = {
"control_store": 8,
"codec_common_data": 12,
"kws_model_scratch": 16,
"loader_path_stack": 8,
"verifier_memo": 17,
"coefficient_store": 15,
}
expanded = result["per_configuration"]["expanded"]
observed = {name: expanded["by_class"].get(name, 0) for name in expected_p9}
if observed != expected_p9 or expanded["physical_macro_instances"] != 76:
raise SystemExit(
"P9 expanded macro inventory mismatch: "
f"observed {observed}, total {expanded['physical_macro_instances']}; "
f"expected {expected_p9}, total 76"
)
return result
@@ -597,31 +625,31 @@ def render_markdown(report: dict[str, Any]) -> str:
f"{storage['control_store']['wrappers']} wrappers, {storage['control_store']['kib']} KiB",
f"- local data: {storage['local_data']['logical_banks']} logical banks -> "
f"{storage['local_data']['kib']} KiB "
f"(macro-backed {storage['local_data']['macro_backed_kib']} KiB + "
f"KWS register-file window {storage['local_data']['kws_window_kib']} KiB)",
f"(codec/common {storage['local_data']['macro_backed_kib']} KiB + "
f"KWS model/scratch {storage['local_data']['kws_window_kib']} KiB)",
f"- combined logical: {storage['combined_logical']['wrappers']} wrappers, "
f"{storage['combined_logical']['kib']} KiB",
"",
"| configuration | control-store macros | local-data macros | loader path-stack macros | total physical macros |",
"| --- | --- | --- | --- | --- |",
"| configuration | control | codec/common | KWS | path stack | memo/bitmap | coefficients | total physical |",
"| --- | --- | --- | --- | --- | --- | --- | --- |",
]
for name in ("baseline", "expanded"):
per = storage["per_configuration"][name]
lines.append(
f"| {name} | {per['by_class'].get('control_store', 0)} | "
f"{per['by_class'].get('local_data', 0)} | "
f"{per['by_class'].get('codec_common_data', 0)} | "
f"{per['by_class'].get('kws_model_scratch', 0)} | "
f"{per['by_class'].get('loader_path_stack', 0)} | "
f"{per['by_class'].get('verifier_memo', 0)} | "
f"{per['by_class'].get('coefficient_store', 0)} | "
f"{per['physical_macro_instances']} |"
)
lines += [
"",
f"Capacity reference ({DOC_REFERENCE}): 8 control-store + 28 local-data = 36 logical",
"wrappers (144 KiB combined) vs the pre-expansion P5 baseline of 4 + 28 = 32",
"(128 KiB). The loader path-stack macros above are the verifier workspace and",
"are reported separately as required. Note: on the current RTL the control-store",
"Depth is fixed at 4096 for both EnableP7 values, so both configurations measure",
"36 logical wrappers; the 32-wrapper P5 baseline is the pre-expansion RTL, not",
"EnableP7=0 of the current RTL.",
f"P9 physical reference ({DOC_REFERENCE}): 76 wrappers / 304 KiB. The software",
"capacity remains 8 control-store + 28 local-data wrappers / 144 KiB; path stack,",
"memo/bitmap and coefficient banks are auxiliary physical storage. The inspected",
"pre-P9 source inventory was 44 wrappers, so P9 adds exactly 32 wrappers.",
"",
"## Per-block cell/macro/area accounting (hierarchy subtree totals)",
"",
@@ -641,8 +669,8 @@ def render_markdown(report: dict[str, Any]) -> str:
"",
"Cells are leaf standard cells plus macro cells inside the kept hierarchy;",
"areas are the sums of the Liberty cell areas inside each subtree. The",
"KWS register-file window lives in `u_kws_sram_client` flops, not in",
"SRAM macros, so it appears as standard-cell area above.",
"All six P9 storage classes above must resolve to SRAM macros in the IHP130 flow;",
"a register-file or giant mux implementation is not accepted as physical storage.",
"",
"## Worst max-delay paths (top 5 per configuration)",
"",
@@ -727,10 +755,12 @@ def main() -> int:
"storage_accounting": storage_accounting(synths),
"documentation_reference": {
"source": DOC_REFERENCE,
"expanded_logical_wrappers": 36,
"baseline_logical_wrappers": 32,
"expanded_combined_kib": 144,
"baseline_combined_kib": 128,
"p9_physical_wrappers": 76,
"p9_physical_kib": 304,
"pre_p9_source_wrappers": 44,
"added_wrappers": 32,
"advertised_capacity_wrappers": 36,
"advertised_capacity_kib": 144,
},
}
report["blocks"] = block_table(synths)
+5 -4
View File
@@ -45,10 +45,11 @@ measurement evidence. APU's checked-in WAV/FLAC image is statically assembled by
The [109-entry chapter review](datasheets/chapter-review.md) records final page
numbers, implementation status, document completeness, sources and recommendations
for the delivered PDF snapshot.
MP3 remains a reserved trap entry. Default PRODUCT keeps KWS gated off; the P7
acceptance profile advertises capability `0x1FD` and digest `0xF5005D7C`.
APU KWS storage is described from the inferred implementation, not an unimplemented
macro claim. NPU has eight executable operator classes, 64 KiB private SRAM and
MP3 remains a reserved trap entry. Default PRODUCT keeps KWS gated off; the P9
acceptance profile advertises capability `0x3FD` and digest `0x63E96066`.
APU KWS storage includes 15 coefficient and 17 proof-memo macro wrappers in the
qualified configuration; physical replacement remains evidence-bound. NPU has
eight executable operator classes, 64 KiB private SRAM and
an integrated eight-beat AXI64 DMA limit; its reusable DMA default is not the
deployed limit. Native Linux NPU/ASoC support is not inferred from HP smoke code.
CI workflow outcomes are a separately scoped snapshot, including failures and
+1 -1
View File
@@ -66,7 +66,7 @@ See [content review](content-review.md) for the bounded commercial comparison, [
| 51 | Digital Video Port (DVP) / 469-479 | Covered and retained. Integrated: 8-bit parallel RGB565/YUV422 capture. Tests available; 0 attached per-IP report records. Workflow outcomes do not replace scoped reports. | Selected feature, programming and restriction phrases emphasized; shared register/ABI descriptions retained. | [dvp.h](../../crt/include/retrosoc/hal/dvp.h), [dvp.md](../../docs/ip/dvp.md), [dvp.c](../../publications/examples/dvp.c). Retain the documented subset and prerequisites. Parallel camera, sensor configuration and pixel clock. Attach a matching scoped report before qualification. |
| 52 | JPEG Encoder / Decoder / 480-503 | Covered and retained. Partial: 8-bit Baseline Sequential JPEG encode or decode. Tests available; 0 attached per-IP report records. Workflow outcomes do not replace scoped reports. | Selected feature, programming and restriction phrases emphasized; shared register/ABI descriptions retained. | [jpeg.h](../../crt/include/retrosoc/hal/jpeg.h), [jpeg.md](../../docs/ip/jpeg.md), [jpeg.c](../../publications/examples/jpeg.c). Retain the documented subset and prerequisites. Native slot-6 payload admission has one normal read and one normal write credit at class 8; standalone codec behavior and end-to-end system qualification remain separate scopes. Attach a matching scoped report before qualification. |
| 53 | 2D Graphics Accelerator (GA2D) / 504-523 | Covered and retained. Integrated: P5 direct single-job FILL, COPY, CONVERT, opaque alpha BLEND, A8 masks and exact background/destination in-place composition. Tests available; 0 attached per-IP report records. Workflow outcomes do not replace scoped reports. | Selected feature, programming and restriction phrases emphasized; shared register/ABI descriptions retained. | [ga2d.md](../../docs/ip/ga2d.md), [apb4_ga2d.sv](../../rtl/ip/multimedia/apb4_ga2d.sv), [ga2d_core.sv](../../rtl/ip/multimedia/ga2d_core.sv). P6 test sources and dated records already exist. Attach current-commit full-product contention, workload throughput and physical closure reports; preserve the documented subset. |
| 54 | Audio Processing Unit (APU, partial) / 524-569 | Covered and retained. Partial: WAV/FLAC image and job/stream execution; fixed KWS is enabled only in the P7 acceptance configuration. MP3 remains unsupported. Tests available; 0 attached per-IP report records. Workflow outcomes do not replace scoped reports. | Selected feature, programming and restriction phrases emphasized; shared register/ABI descriptions retained. | [Makefile](../../Makefile), [main.c](../../app/apps/apu_release/main.c), [main.c](../../app/ports/hp-apu/main.c). Keep default/P7 capability and digest distinct, MP3 unsupported and KWS configuration-dependent. A shipped WAV/FLAC image or smoke wrapper is not full corpus or sustained-audio qualification. Keep inferred KWS storage distinct from the specified technology-macro target. |
| 54 | Audio Processing Unit (APU, partial) / 524-569 | Covered and retained. Partial: WAV/FLAC image and job/stream execution; APUC-backed KWS is enabled only in the P9 acceptance configuration. MP3 remains unsupported. Tests available; 0 attached per-IP report records. Workflow outcomes do not replace scoped reports. | Selected feature, programming and restriction phrases emphasized; shared register/ABI descriptions retained. | [Makefile](../../Makefile), [main.c](../../app/apps/apu_release/main.c), [main.c](../../app/ports/hp-apu/main.c). Keep default/P9 capability and digest distinct, MP3 unsupported and KWS configuration-dependent. A shipped image or smoke wrapper is not full corpus or sustained-audio qualification. Keep wrapper inventory distinct from revision-qualified physical replacement evidence. |
| 55 | Neural Processing Unit (NPU) / 570-594 | Covered and retained. Integrated: Eight INT8 operators with native HP descriptor/DMA execution and private local SRAM. Tests available; 0 attached per-IP report records. Workflow outcomes do not replace scoped reports. | Selected feature, programming and restriction phrases emphasized; shared register/ABI descriptions retained. | [npu.h](../../crt/include/retrosoc/hal/npu.h), [npu_regs.h](../../crt/include/retrosoc/hal/npu_regs.h), [npu.c](../../crt/src/hal/npu.c). Attach matching KWS/VWW raw reports including CPU preprocessing/Softmax and cache costs. Keep unknown-operator rejection, INT32 limits, abort/drain and private SRAM boundaries explicit; no arbitrary TFLite or certification claim. |
| 56 | Crypto Controller / 595-616 | Covered and retained. Integrated: AES-128/192/256, SHA-224/256 and raw RSA-2048. Tests available; 0 attached per-IP report records. Workflow outcomes do not replace scoped reports. | Selected feature, programming and restriction phrases emphasized; shared register/ABI descriptions retained. | [crypto.h](../../crt/include/retrosoc/hal/crypto.h), [crypto.md](../../docs/ip/crypto.md), [crypto.c](../../publications/examples/crypto.c). Retain the documented subset and prerequisites. LP-controlled buffers and key lifecycle; no secure key-store claim. Attach a matching scoped report before qualification. |
| 57 | Cyclic Redundancy Check (CRC) / 617-623 | Covered and retained. Integrated: Programmable 7-, 8-, 16- or 32-bit CRC. Source reviewed; 0 attached per-IP report records. Workflow outcomes do not replace scoped reports. | Selected feature, programming and restriction phrases emphasized; shared register/ABI descriptions retained. | [crc.h](../../crt/include/retrosoc/hal/crc.h), [crc.c](../../publications/examples/crc.c), [datasheet.md](../../rtl/managed/clusterip/crc/doc/datasheet.md). Retain the documented subset and prerequisites. No additional off-chip component implied; use the selected clock/reset and access policy. Attach a matching scoped report before qualification. |
+6 -7
View File
@@ -33,7 +33,7 @@ remain. Strict source checks remain enabled.
The preceding technical refresh retained **v0.5 DRAFT / 2026-09-22** and reviewed commit
`b209ccedfe6f9c33078ef082a4d31580d4d1a845`. The main reference remains
IHP130 PRODUCT / 32 KiB SRAM; the APU P7 acceptance profile is separate.
IHP130 PRODUCT / 32 KiB SRAM; the APU P9 acceptance profile is separate.
This is a publication refresh, not a hardware change or a new qualification campaign.
That technical refresh compared against the 777-page baseline bound to
@@ -62,7 +62,7 @@ release-status policies are not retroSoC specifications.
| --- | --- | --- | --- |
| NPU | The existing chapter adequately covers the software-launchable eight-operator engine, private DMA/SRAM, compiler/HAL, descriptor/parameter layouts and normal/abort behavior. | Already covered; retain the chapter, geometry, static graph subset and numeric profile. No new NPU capability is introduced by this refresh. | Attach matching full-corpus and physical reports before claiming speedup, accuracy or timing closure. Distinguish the deployed burst limit from the reusable DMA default. |
| APU formats and base programming | APUMC V1/V2, 4096-word current store, seven classes/62 opcodes, APUM model and 128-byte job format are already covered. | Already covered; retain the formats, register/HAL reference and default/P7 identity distinction. | Preserve format compatibility and separate the fixed APUM engine from NPU deployment. MP3 remains unsupported. |
| APU summary, configuration and acceptance | Default KWS is off; P7 enables it with digest `0xF5005D7C`. The checked-in WAV/FLAC image is distinct from full production-job qualification. | Correct summary/body/table consistency while retaining the detailed formats and bare-metal acceptance reference. No native Linux ASoC support is claimed. | A smoke wrapper or missing-data early return is not the full accuracy or 60-second-per-scenario campaign. Keep actual workload, clock, source and result artifacts with any pass. |
| APU summary, configuration and acceptance | Default KWS is off; the P9 acceptance profile enables KWS and APUC loading with digest `0x63E96066`. The checked-in WAV/FLAC image is distinct from full production-job qualification. | Correct summary/body/table consistency while retaining the detailed formats and bare-metal acceptance reference. No native Linux ASoC support is claimed. | A smoke wrapper or missing-data early return is not the full accuracy or 60-second-per-scenario campaign. Keep actual workload, clock, source and result artifacts with any pass. |
| GA2D | Existing FILL/COPY/CONVERT/opaque BLEND/A8 programming coverage and P6 workload/evidence boundaries are adequate. | Already covered; retain functional ABI and schema-3 performance-method descriptions. | The dated composition target and 48 MHz slow-corner timing were not met. Full-product closure and current-source measurements still require matching artifacts. |
| Fabric, memory conversion and resources | Ten masters/resources, NPU master/resource 9, Resource ABI 1.2 and fragmented 64-to-32 long-burst conversion are already described. | Already covered; retain source-bound topology, matrices, circuits, credits and lifecycle references. | Preserve permission, source-ID and boundary rules. Burst admission is not a throughput guarantee. |
| Address/register/IRQ/pad inventories | Existing generation and field-reference mechanisms cover their declared scope, including the independent NPU chapter. | Already covered; retain inventories, instance distinctions and stable anchors. Correct mailbox-purpose summaries from the actual register offsets and HAL/RTL producer paths. | Keep chapter, feature, register, diagram and API coverage synchronized. NPU has no implied new external pins. |
@@ -76,11 +76,10 @@ release-status policies are not retroSoC specifications.
## Source/specification discrepancies and follow-up
- [APU specification](../../docs/ip/apu.md) describes sixteen KWS technology-macro
banks, while [the SRAM client](../../rtl/ip/multimedia/apu_kws_sram_client.sv)
uses inferred storage with combinational reads. Preserve the logical/physical
distinction in the datasheet. Reconcile the engineering contract or implement
and verify the macro change as a separate hardware task.
- [APU specification](../../docs/ip/apu.md) defines synchronous macro-backed KWS,
coefficient and proof-memo storage. The RTL exposes the frozen wrapper inventory,
but physical replacement, synthesis-time and peak-RSS claims remain conditional on
the revision-qualified P9 A/B evidence rather than on source structure alone.
- Older NPU phase comments and some descriptions predate the enabled scheduler
and capability returns. Keep publication claims tied to instantiated parameters,
register logic and the compute/DMA path. Clean up stale engineering comments
+1 -1
View File
@@ -633,7 +633,7 @@
"rtl/ip/multimedia/apu_local_sram.sv"
],
"bindings": [],
"note": "Offsets address the 112 KiB logical local-data space, distinct from the 32 KiB control store and SoC SRAM. The current codec/internal macro branch uses 12 four-KiB banks; the separate 64 KiB KWS client uses inferred storage and is gated off by default. Logical capacity does not establish a 28-macro physical implementation or completed PPA qualification.",
"note": "Offsets address the 112 KiB logical local-data space, distinct from the 32 KiB control store and SoC SRAM. The codec/internal branch uses 12 four-KiB banks and the separate 64 KiB KWS client uses 16 banks. P9 adds 15 coefficient and 17 proof-memo wrappers outside that logical data window. Wrapper inventory does not establish physical replacement or completed PPA qualification.",
"derived": "apu-local"
},
{
+1 -1
View File
@@ -87,7 +87,7 @@
"apu": [
"**Coreless microcode sequencer** with private DMA and local storage.",
"WAV/FLAC jobs and streams are enabled in the deployed P5 path; their format, image and buffer requirements still apply.",
"Fixed KWS is disabled in default PRODUCT and enabled by the P7 acceptance profile with its own ABI digest.",
"Fixed KWS is disabled in default PRODUCT and enabled with APUC loading by the P9 acceptance profile with its own ABI digest.",
"MP3 is unsupported. Advertised capabilities do not establish full-corpus, sustained-audio or production qualification."
],
"npu": [
+3 -3
View File
@@ -1713,9 +1713,9 @@
"function_sections": [],
"software_sections": [],
"notes": [
"The deployed P5 register, loader and sequencer paths enable WAV/FLAC jobs. Default PRODUCT advertises 0x000001BD with KWS disabled; the dedicated P7 acceptance profile advertises 0x000001FD with fixed-model KWS and ABI digest 0xF5005D7C. MP3 remains unsupported in both configurations.",
"Capability and ABI identities describe the selected implementation. Full-corpus codec/KWS accuracy, sustained-audio acceptance and production qualification require their own matching reports; none is implied by enabling the P7 configuration.",
"LP loads a bounded APUMC image before submitting work. Expanded P5 RTL has 4096 64-bit control-store words (32 KiB), CAPABILITY1=0x01827020 and APB version 1.1 while retaining APUMC V1 image compatibility.",
"The deployed P5 register, loader and sequencer paths enable WAV/FLAC jobs. Default PRODUCT advertises 0x000001BD with KWS disabled; the dedicated P9 acceptance profile advertises 0x000003FD with APUC-backed KWS and ABI digest 0x63E96066. MP3 remains unsupported in both configurations.",
"Capability and ABI identities describe the selected implementation. Full-corpus codec/KWS accuracy, sustained-audio acceptance and production qualification require their own matching reports; none is implied by enabling the P9 configuration.",
"LP loads bounded APUMC, APUC and APUM images before submitting KWS work. Expanded P5 RTL retains 4096 64-bit control-store words (32 KiB), CAPABILITY1=0x01827020 and APUMC V1 compatibility; P9 reports APB version 1.2.",
"The loader checks header, ABI, bounds, entry descriptors and CRC before publishing valid/lock state. Rejected or aborted loads must leave stale instructions unexecutable.",
"Direct jobs use JOB_* staging registers; ring jobs use descriptor ownership and producer/consumer rules. Buffer limits, codec entry bounds, ownership, quiesce and transport completion participate in admission and termination.",
"The codec controller coordinates parser microcode, transport and processing primitives. Private DMA moves input/output while the stream router connects permitted audio paths. Capabilities do not waive rate, format, microcode or lifecycle checks.",
+3 -3
View File
@@ -42,8 +42,8 @@
#implementation.kws_tensors tensor records and #implementation.kws_parameter_bytes parameter bytes.
It is not a general TFLite interpreter or a qualified MLPerf result.
The 112 KiB logical data map includes a 64 KiB KWS region; the codec/internal macro branch
uses 12 four-KiB banks, while the separate KWS client has inferred storage. Do not infer a
completed 28-macro physical implementation from the logical map.
uses 12 four-KiB banks. The P9 KWS implementation adds 15 coefficient and 17 proof-memo
macro wrappers; physical replacement remains subject to the exact synthesis evidence gate.
#source-note("rtl/ip/multimedia/apb4_apu.sv",title:"Default P7 gate and implemented internal connections")
#source-note("rtl/ip/multimedia/apu_kws_sram_client.sv",title:"Current KWS storage implementation")
#source-note("scripts/apu_kws.py",title:"Fixed APUM model-container definitions")
@@ -58,7 +58,7 @@
]
#apu-family-figures()
],legacy:[
Default PRODUCT advertises capability word `0x000001BD`, APB version `0x00010001`, and a 32 KiB control store through `CAPABILITY1=0x01827020`. The P7 acceptance configuration instead advertises `0x000001FD` and ABI digest `0xF5005D7C`. WAV/FLAC transport, job submission and stream paths are integrated; APUMC V1 images remain loadable, while new P5 release images use APUMC V2. MP3 remains unsupported; KWS is configuration-dependent. The checked-in P5 image can be assembled and fits the current control store. Complete corpus,
Default PRODUCT advertises capability word `0x000001BD`, while the P9 acceptance configuration advertises APB version `0x00010002`, capability `0x000003FD` and ABI digest `0x63E96066`. WAV/FLAC transport, job submission and stream paths are integrated; APUMC V1 images remain loadable, while new P5 release images use APUMC V2. MP3 remains unsupported; KWS and APUC loading are configuration-dependent. The checked-in P5 image can be assembled and fits the current control store. Complete corpus,
sustained real-time and physical qualification require their own reports; they are not inferred from
image availability. KWS implementation exists but EnableP7 defaults to false in PRODUCT.
@@ -1828,6 +1828,14 @@
},
{
"lsb": 11,
"msb": 11,
"name": "COEFFICIENT_LOAD_DONE",
"description": "Coefficient load reached a terminal success or error state.",
"reset": "Register reset applies",
"expression": "`APB4_APU__IRQ_COEFFICIENT_LOAD_DONE"
},
{
"lsb": 12,
"msb": 31,
"name": "Reserved",
"description": "Reserved; write zero.",
@@ -1838,7 +1846,7 @@
"reset": "Not retained (command/data port)"
},
"main.IP_VERSION": {
"description": "Expanded P5 uses APB interface V1.1 (0x00010001) and retains the frozen V1.0 register offsets and accepted fields."
"description": "P9 uses additive APB interface V1.2 (0x00010002), preserving the frozen V1.0/V1.1 offsets and accepted fields while adding coefficient discovery and loading."
},
"main.CAPABILITY1": {
"description": "Expanded P5 geometry is 0x01827020: 32 KiB control store, 112 KiB local data, two channels, and 96 kHz maximum source-rate encoding."
@@ -2,14 +2,14 @@
#let apu-release-reference() = [
#change-start("v05-refresh-apu","Source-bound APU availability and acceptance order")
#minor-title("Default and P7 acceptance configurations")
#minor-title("Default and P9 acceptance configurations")
#ds-table("apu-profile-capabilities",[APU capability and digest by committed configuration],
([Configuration],[CAPABILITY0],[ABI_DIGEST],[Software boundary]),
data.system_reference.apu_implementation.profiles.map(r=>([#r.name \ #code(r.profile)],
code("0x"+upper(str(r.capability,base:16))),code("0x"+upper(str(r.digest,base:16))),
if r.enabled {[Fixed APUM KWS is enabled; APP=apu_release and CSR-enabled IRQ acceptance.]} else {[WAV/FLAC enabled; KWS discovery returns RS_ENOTSUP.]})),
widths:(1.45fr,0.65fr,0.65fr,1.25fr))
The P7 digest is an *ABI identity derived from the frozen register, model and microcode*
The P9 digest is an *ABI identity derived from the frozen register, coefficient, model and microcode*
interfaces. It is not a runtime accuracy result, firmware authentication or a physical
qualification certificate. *MP3 capability remains clear* in both configurations.
#source-note("rtl/ip/multimedia/apu_reg.sv",title:"Actual configured capability and digest values")
@@ -15,7 +15,7 @@ board wiring, a technology macro or additional software. A reserved address rang
([Role],[Committed profile],[Boundary]),
(([PRODUCT reference],code(doc.profile),[Fixed LP/HP topology; reference for the main register and pad inventories.]),
([HP boot acceptance],code(doc.hp_profile),[Same PRODUCT organization; SRAM-resident LP loader and an external HP bundle.]),
([APU P7 acceptance],code("configs/ci/ihp130-apu.mk"),[32 KiB SRAM, CSR-enabled LP orchestration and a bare-metal HP audio/KWS payload.]),
([APU P9 acceptance],code("configs/ci/ihp130-apu.mk"),[32 KiB SRAM, CSR-enabled LP orchestration, APUC loading and a bare-metal HP audio/KWS payload.]),
([MPW compatibility],code(doc.mpw_profile),[Selectable legacy user core/IP; use only the separate MPW appendix.])),
widths:(0.75fr,1.35fr,1.6fr))
The source revision printed in Document Control identifies the hardware/software contract.
@@ -11,7 +11,7 @@ The management hart starts from the reset flash alias. Normal firmware compositi
placement depend on the committed application and linker profile. The HP acceptance example
instead uses the *SRAM-resident `hp_boot` image* and validates an external boot bundle before
releasing HP. It must not be assumed that every bringup image automatically boots Linux.
The separate APU P7 acceptance profile performs LP image/model loading and bare-metal HP
The separate APU P9 acceptance profile performs LP APUMC/APUC/APUM loading and bare-metal HP
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.
@@ -774,8 +774,8 @@
"id": "apu",
"title": "Audio Processing Unit",
"implementation": "Partial",
"scope": "WAV/FLAC image and job/stream execution; fixed KWS is enabled only in the P7 acceptance configuration. MP3 remains unsupported.",
"dependencies": "Load the compatible checked-in P5 APUMC V2 image. MP3 is unsupported and KWS is gated off by default; corpus, real-time and physical qualification remain separate.",
"scope": "WAV/FLAC image and job/stream execution; APUC-backed KWS is enabled only in the P9 acceptance configuration. MP3 remains unsupported.",
"dependencies": "Load compatible APUMC V2, APUC and APUM images in order. MP3 is unsupported and KWS is gated off by default; corpus, real-time and physical qualification remain separate.",
"hal": "Published API and syntax example; see the IP chapter.",
"linux": "No delivered Linux ASoC integration. The LP/HP APU release image is bare-metal acceptance firmware, not a kernel driver.",
"verification": "Tests available",
@@ -791,13 +791,17 @@
"publications/examples/apu.c",
"rtl/ip/multimedia/apb4_apu.sv",
"rtl/ip/multimedia/apu_define.svh",
"rtl/ip/multimedia/apu_kws_coeff_loader.sv",
"rtl/ip/multimedia/apu_kws_coeff_store.sv",
"rtl/ip/multimedia/apu_kws_sram_client.sv",
"rtl/ip/multimedia/apu_local_sram.sv",
"rtl/ip/multimedia/apu_p5_codecs.apus",
"rtl/ip/multimedia/apu_reg.sv",
"rtl/ip/multimedia/apu_proof_memo.sv",
"scripts/apu_abi_digest.py",
"scripts/apu_isa.py",
"scripts/apu_kws.py",
"scripts/apu_kws_coeff.py",
"scripts/apu_mcasm.py",
"scripts/apu_p5_coefficients.py",
"scripts/build_apu_p5_bundle.py"
+13 -12
View File
@@ -21,10 +21,11 @@ APU_CONFIGURATION_SOURCES = (
APU_SOURCES = (
"scripts/apu_mcasm.py", "scripts/apu_isa.py", "scripts/apu_p5_coefficients.py",
"scripts/apu_kws.py", "scripts/build_apu_p5_bundle.py",
"scripts/apu_kws.py", "scripts/apu_kws_coeff.py", "scripts/build_apu_p5_bundle.py",
"rtl/ip/multimedia/apu_p5_codecs.apus", "rtl/ip/multimedia/apu_define.svh",
"rtl/ip/multimedia/apb4_apu.sv", "rtl/ip/multimedia/apu_local_sram.sv",
"rtl/ip/multimedia/apu_kws_sram_client.sv",
"rtl/ip/multimedia/apu_kws_sram_client.sv", "rtl/ip/multimedia/apu_kws_coeff_store.sv",
"rtl/ip/multimedia/apu_kws_coeff_loader.sv", "rtl/ip/multimedia/apu_proof_memo.sv",
"rtl/ip/multimedia/apu_reg.sv", "crt/include/retrosoc/hal/apu_regs.h",
"scripts/apu_abi_digest.py", "app/apps/apu_release/main.c",
*APU_CONFIGURATION_SOURCES,
@@ -124,7 +125,7 @@ def apu_configurations(root: Path) -> list[dict]:
bits = constants(root, "rtl/ip/multimedia/apu_define.svh")
rows = []
for name, profile in (("Default PRODUCT", "configs/ci/ihp130.mk"),
("P7 acceptance", "configs/ci/ihp130-apu.mk")):
("P9 acceptance", "configs/ci/ihp130-apu.mk")):
source = (root / profile).read_text(encoding="utf-8")
assignments = re.findall(r"^APU_ENABLE_P7\s*[:?+]?=\s*([^\n#]+)", source, re.M)
if len(assignments) > 1 or (assignments and assignments[0].strip() not in {"YES", "NO"}):
@@ -137,11 +138,11 @@ def apu_configurations(root: Path) -> list[dict]:
for codec in ("WAV", "FLAC", "MP3", "KWS")}})
if rows[-1]["formats"] != {"WAV": True, "FLAC": True, "MP3": False, "KWS": enabled}:
raise ValueError("APU deployed format capability meanings changed")
if name == "P7 acceptance" and (re.findall(r"^APP\s*:=\s*(\w+)\s*$", source, re.M) != ["apu_release"]
if name == "P9 acceptance" and (re.findall(r"^APP\s*:=\s*(\w+)\s*$", source, re.M) != ["apu_release"]
or re.findall(r"^HAVE_CSR\s*:=\s*(\w+)\s*$", source, re.M) != ["YES"]):
raise ValueError("APU P7 acceptance application or IRQ configuration changed")
raise ValueError("APU P9 acceptance application or IRQ configuration changed")
if [(r["enabled"], r["capability"], r["digest"]) for r in rows] != [
(False, 0x1BD, 0), (True, 0x1FD, 0xF5005D7C)]:
(False, 0x1BD, 0), (True, 0x3FD, 0x63E96066)]:
raise ValueError("review APU configuration identities")
values = constants(root, "crt/include/retrosoc/hal/apu_regs.h")
if (values.get("RS_APU_DIGEST_P7_IMPLEMENTED"), values.get("RS_APU_CAPABILITY0_P5_IMPLEMENTED"),
@@ -156,11 +157,11 @@ def apu_acceptance_steps(root: Path) -> list[dict]:
text = (root / source).read_text(encoding="utf-8")
body = function_body(text, "rs_apu_release_run")
stages = (
("probe", "rs_apu_probe", 5, "After holding HP in reset, waiting for SDRAM and loading the HP bundle, LP checks the P7 capability and ABI digest."),
("probe", "rs_apu_probe", 5, "After holding HP in reset, waiting for SDRAM and loading the HP bundle, LP checks the P9 capability and ABI digest."),
("quiesce", "rs_apu_release_quiesce", 6, "LP quiesces the APU resource and waits for LP ownership, asserted quiesce, reset released and engine idle."),
("stage", "rs_apu_release_stage_assets", 7, "With the resource quiesced, LP stages the APUMC image, APUM model, WAV input and KWS PCM window in SDRAM."),
("acl", "rs_apu_set_acl", 8, "LP configures the permitted read and write address ranges before requesting either image load."),
("load", "rs_apu_release_load_images", 9, "LP loads the microcode, checks its valid/lock state and CRC, then loads the model and checks its status and CRC."),
("stage", "rs_apu_release_stage_assets", 7, "With the resource quiesced, LP stages the APUMC, APUC and APUM images plus WAV input and the KWS PCM window in SDRAM."),
("acl", "rs_apu_set_acl", 8, "LP configures the permitted read and write address ranges before requesting the three image loads."),
("load", "rs_apu_release_load_images", 9, "LP loads and verifies APUMC, APUC and APUM in that order before publishing any KWS work."),
("handoff", "rs_apu_release_handoff", 10, "LP hands ownership to HP after successful image loading, then publishes the shared page and mailbox request before releasing HP."),
)
calls = list(re.finditer(r"\b(" + "|".join(row[1] for row in stages) + r")\s*\(", body))
@@ -172,8 +173,8 @@ def apu_acceptance_steps(root: Path) -> list[dict]:
if failures != [str(stage[2])]:
raise ValueError("APU acceptance failure checkpoint changed")
loads = function_body(text, "rs_apu_release_load_images")
if re.findall(r"\b(rs_apu_(?:microcode|kws_model)_load)\s*\(", loads) != [
"rs_apu_microcode_load", "rs_apu_kws_model_load"]:
if re.findall(r"\b(rs_apu_(?:microcode|kws_coeff|kws_model)_load)\s*\(", loads) != [
"rs_apu_microcode_load", "rs_apu_kws_coeff_load", "rs_apu_kws_model_load"]:
raise ValueError("APU acceptance image-load order changed")
tail = body[calls[-1].end():]
if re.findall(r"\b(rs_apu_release_fill_page|rs_hp_mailbox_clear_lp_interrupt|rs_hp_mailbox_send_to_hp|"
+4 -2
View File
@@ -164,8 +164,10 @@ retained, not an enabled third codec. The P9 refreeze requires macro-backed
coefficient/profile storage and microcode memoization instead of large inferred
arrays/case ROMs. Its full IHP130 inventory is 76 4 KiB wrappers: the existing
44 (including proof stack), 17 memo/bitmap and 15 coefficient/profile wrappers.
APUC startup loading and additive APB V1.2 are future P9 implementation
obligations; this refreeze does not claim that they are already implemented.
The implementation provides LP APUC startup loading, bounded synchronous
coefficient access, macro-backed proof memoization and additive APB V1.2.
Release qualification still requires the frozen P9 evidence rather than source
presence alone.
The model, fixed-point results, 32/112 KiB advertised capacities and existing
system clock/reset/IRQ/resource assignments remain fixed. Macro/init/latency
changes must re-pass P5/P7 functional/concurrency tests and the P9 synthesis
+194 -16
View File
@@ -40,21 +40,26 @@ module apb4_apu #(
logic [31:0] s_stream_stat, s_job_stat, s_scheduler_job_stat, s_ring_stat, s_ring_completed;
logic [ 7:0][31:0] s_job_config;
logic [1023:0] s_direct_descriptor;
logic [ 10:0] s_irq_set;
logic [ 11:0] s_irq_set;
logic [ 7:0] s_mc_status;
logic [31:0] s_mc_abi, s_mc_actual_crc, s_mc_load_count;
logic [31:0] s_kws_status, s_kws_result, s_kws_timestamp_lo, s_kws_timestamp_hi;
logic [31:0] s_kws_frame_count, s_kws_inference_count, s_kws_hit_count, s_kws_overrun_count;
logic [31:0] s_kws_model_status, s_kws_model_actual_crc;
logic [31:0] s_kws_model_addr, s_kws_model_size, s_kws_model_crc;
logic [31:0] s_kws_coeff_addr, s_kws_coeff_size, s_kws_coeff_crc;
logic [31:0] s_kws_coeff_status, s_kws_coeff_actual_crc;
logic [63:0] s_kws_coefficient_id;
logic [15:0] s_kws_config;
logic [31:0] s_kws_input_config;
logic [ 1:0] s_kws_control;
logic s_kws_clear_history;
logic s_kws_disable_request;
logic [10:0] s_kws_irq_set;
logic s_kws_model_load, s_kws_rx_ready, s_kws_busy, s_kws_idle, s_kws_disable_done;
logic s_kws_model_load, s_kws_coeff_load, s_kws_rx_ready, s_kws_busy, s_kws_idle;
logic s_kws_disable_done;
logic s_kws_loader_busy;
logic s_kws_coeff_loader_busy, s_kws_coeff_valid, s_kws_coeff_lock;
logic s_kws_config_publish;
logic [15:0] s_kws_config_default, s_codec_kws_config;
logic s_codec_kws_config_valid;
@@ -78,6 +83,34 @@ module apb4_apu #(
logic [3:0] s_kws_loader_fault_stage;
logic [1:0] s_kws_loader_fault_resp;
logic [31:0] s_kws_loader_fault_addr, s_kws_loader_fault_detail;
logic s_kws_coeff_loader_done, s_kws_coeff_loader_abort_done, s_kws_coeff_loader_fault_v;
logic [5:0] s_kws_coeff_loader_fault_code;
logic [3:0] s_kws_coeff_loader_fault_stage;
logic [1:0] s_kws_coeff_loader_fault_resp;
logic [31:0] s_kws_coeff_loader_fault_addr, s_kws_coeff_loader_fault_detail;
logic s_kws_coeff_dma_req, s_kws_coeff_dma_rdy, s_kws_coeff_dma_data_rdy;
logic [31:0] s_kws_coeff_dma_addr, s_kws_coeff_dma_bytes;
logic s_kws_coeff_store_active, s_kws_coeff_store_req, s_kws_coeff_store_write;
logic [13:0] s_kws_coeff_store_addr;
logic [31:0] s_kws_coeff_store_wdata;
logic s_kws_coeff_store_ready, s_kws_coeff_store_valid, s_kws_coeff_store_fault;
logic [31:0] s_kws_coeff_store_rdata;
logic s_kws_coeff_front_req, s_kws_coeff_front_ready;
logic [ 3:0] s_kws_coeff_front_kind;
logic [13:0] s_kws_coeff_front_index;
logic s_kws_coeff_front_resp_valid, s_kws_coeff_front_resp_ready;
logic [63:0] s_kws_coeff_front_resp_data;
logic s_kws_coeff_front_resp_fault;
logic s_kws_coeff_infer_req, s_kws_coeff_infer_ready;
logic [6:0] s_kws_coeff_infer_index;
logic s_kws_coeff_infer_resp_valid, s_kws_coeff_infer_resp_ready;
logic [31:0] s_kws_coeff_infer_resp_data;
logic s_kws_coeff_infer_resp_fault;
logic s_kws_coeff_profile_req, s_kws_coeff_profile_ready;
logic [10:0] s_kws_coeff_profile_index;
logic s_kws_coeff_profile_resp_valid, s_kws_coeff_profile_resp_ready;
logic [31:0] s_kws_coeff_profile_resp_data;
logic s_kws_coeff_profile_resp_fault;
logic s_kws_engine_model_v_unused, s_kws_engine_model_lock_unused;
logic [31:0] s_kws_engine_model_stat_unused, s_kws_engine_model_crc_unused;
logic s_direct_start_allowed;
@@ -238,6 +271,7 @@ module apb4_apu #(
logic s_transport_frame_commit, s_transport_cancel;
logic s_transport_input_pending, s_transport_output_pending, s_codec_transport_idle;
logic s_dma_admission_block;
logic s_dma_quiesce, s_kws_memory_start;
logic s_system_idle, s_resource_reset_request, s_resource_reset_apply_q;
logic s_resource_reset_pending_q, s_resource_reset_seen_q;
logic s_tx_route_apu, s_rx_route_apu;
@@ -331,7 +365,8 @@ module apb4_apu #(
((s_dma_owner_q == 3'd1) ? u_scheduler_read_axis.tready :
((s_dma_owner_q == 3'd2) ? u_transport_read_axis.tready :
((s_dma_owner_q == 3'd3) ? s_kws_loader_dma_rdy_data :
s_kws_mem_dma_data_rdy)));
((s_dma_owner_q == 3'd4) ? s_kws_mem_dma_data_rdy :
s_kws_coeff_dma_data_rdy))));
assign u_dma_write_axis.tdata = (s_dma_owner_q == 3'd2) ?
u_transport_write_axis.tdata : u_scheduler_write_axis.tdata;
assign u_dma_write_axis.tkeep = (s_dma_owner_q == 3'd2) ?
@@ -348,12 +383,14 @@ module apb4_apu #(
assign u_transport_write_axis.tready = (s_dma_owner_q == 3'd2) && u_dma_write_axis.tready;
assign u_scheduler_write_axis.tready = (s_dma_owner_q == 3'd1) && u_dma_write_axis.tready;
assign s_irq_set = {
s_kws_coeff_loader_done && (s_kws_coeff_loader_fault_code != 6'd0 || s_kws_coeff_valid),
s_seq_trap_event,
s_xrun_evt || s_kws_irq_set[`APB4_APU__IRQ_STREAM_XRUN],
s_fault_valid || s_kws_loader_fault_v || s_kws_fault_valid,
s_output_watermark_evt || s_transport_event_output,
s_input_watermark_evt || s_transport_event_input,
s_abort_done || s_mc_abort_done || s_seq_abort_done || s_kws_loader_abort_done,
s_abort_done || s_mc_abort_done || s_seq_abort_done || s_kws_loader_abort_done ||
s_kws_coeff_loader_abort_done,
s_kws_loader_done,
s_mc_load_done,
s_kws_irq_set[`APB4_APU__IRQ_KWS_HIT],
@@ -362,13 +399,15 @@ module apb4_apu #(
};
assign s_tx_route_apu = s_stream_route[1:0] == 2'd1;
assign s_rx_route_apu = s_stream_route[3:2] == 2'd1;
assign s_direct_start_allowed = !s_kws_loader_busy && !s_kws_disable_request &&
assign s_direct_start_allowed = !s_kws_loader_busy && !s_kws_coeff_loader_busy &&
!s_kws_disable_request &&
s_codec_direct_allowed &&
((s_job_config[0][3:0] != 4'd1) ||
(s_kws_model_valid && s_kws_model_lock && (s_kws_control == 2'd3)));
(s_kws_model_valid && s_kws_model_lock && s_kws_coeff_valid && s_kws_coeff_lock &&
(s_kws_control == 2'd3)));
assign s_system_idle = s_scheduler_idle && !s_dma_busy && s_stream_idle && s_mc_idle &&
s_seq_idle && !s_primitive_busy && s_codec_idle && s_codec_transport_idle &&
s_kws_idle && !s_kws_busy && !s_kws_loader_busy;
s_kws_idle && !s_kws_busy && !s_kws_loader_busy && !s_kws_coeff_loader_busy;
assign s_codec_kws_active = s_codec_busy && (s_codec_descriptor[3:0] == 4'd1);
assign s_kws_disable_done = s_kws_idle && !s_codec_kws_active;
assign idle_o = s_system_idle;
@@ -379,10 +418,15 @@ module apb4_apu #(
assign s_dma_admission_block = (quiesce_i || s_resource_reset_request) &&
!s_job_stat[`APB4_APU__JOB_STATUS_BUSY] &&
!s_ring_stat[`APB4_APU__RING_STATUS_WRITEBACK_PENDING];
assign s_dma_quiesce = s_dma_admission_block && s_mc_idle && !s_kws_loader_busy &&
!s_kws_coeff_loader_busy;
assign s_kws_memory_start = s_codec_kws_start && s_kws_coeff_valid && s_kws_coeff_lock &&
!s_resource_reset_request;
assign s_dma_select = !s_mc_idle ? 3'd0 :
(s_kws_coeff_dma_req ? 3'd5 :
(s_kws_loader_dma_req ? 3'd3 :
(s_kws_mem_dma_req ? 3'd4 :
(s_ring_dma_req_valid ? 3'd1 : 3'd2)));
(s_ring_dma_req_valid ? 3'd1 : 3'd2))));
assign s_dma_req_valid = (s_dma_select == 3'd0) ?
(s_mc_dma_req_valid && !s_resource_reset_request) :
((s_dma_select == 3'd1) ? s_ring_dma_req_valid :
@@ -392,17 +436,20 @@ module apb4_apu #(
assign s_dma_req_addr = (s_dma_select == 3'd0) ? s_mc_dma_req_addr :
((s_dma_select == 3'd1) ? s_ring_dma_req_addr :
((s_dma_select == 3'd2) ? s_transport_dma_req_addr :
((s_dma_select == 3'd3) ? s_kws_loader_dma_addr : s_kws_mem_dma_addr)));
((s_dma_select == 3'd3) ? s_kws_loader_dma_addr :
((s_dma_select == 3'd4) ? s_kws_mem_dma_addr : s_kws_coeff_dma_addr))));
assign s_dma_req_bytes = (s_dma_select == 3'd0) ? s_mc_dma_req_bytes :
((s_dma_select == 3'd1) ? s_ring_dma_req_bytes :
((s_dma_select == 3'd2) ? s_transport_dma_req_bytes :
((s_dma_select == 3'd3) ? s_kws_loader_dma_bytes : s_kws_mem_dma_bytes)));
((s_dma_select == 3'd3) ? s_kws_loader_dma_bytes :
((s_dma_select == 3'd4) ? s_kws_mem_dma_bytes : s_kws_coeff_dma_bytes))));
assign s_ring_dma_req_ready = (s_dma_select == 3'd1) && s_dma_req_ready;
assign s_mc_dma_req_ready = (s_dma_select == 3'd0) && s_dma_req_ready &&
!s_resource_reset_request;
assign s_transport_dma_req_ready = (s_dma_select == 3'd2) && s_dma_req_ready;
assign s_kws_loader_dma_rdy = (s_dma_select == 3'd3) && s_dma_req_ready;
assign s_kws_mem_dma_rdy = (s_dma_select == 3'd4) && s_dma_req_ready;
assign s_kws_coeff_dma_rdy = (s_dma_select == 3'd5) && s_dma_req_ready;
assign s_kws_mem_data_v = u_dma_read_axis.tvalid && (s_dma_owner_q == 3'd4);
assign s_kws_mem_dma_done = s_dma_done && (s_dma_owner_q == 3'd4);
assign s_kws_mem_dma_error = s_dma_err && (s_dma_owner_q == 3'd4);
@@ -492,6 +539,14 @@ module apb4_apu #(
s_fault_index = 8'd0;
s_fault_addr = s_kws_loader_fault_addr;
s_fault_detail = s_kws_loader_fault_detail;
end else if (!s_fault_valid && s_kws_coeff_loader_fault_v) begin
s_fault_valid = 1'b1;
s_fault_code = s_kws_coeff_loader_fault_code;
s_fault_stage = s_kws_coeff_loader_fault_stage;
s_fault_resp = s_kws_coeff_loader_fault_resp;
s_fault_index = 8'd0;
s_fault_addr = s_kws_coeff_loader_fault_addr;
s_fault_detail = s_kws_coeff_loader_fault_detail;
end else if (!s_fault_valid && s_kws_fault_valid) begin
s_fault_valid = 1'b1;
s_fault_code = s_kws_fault_code;
@@ -532,7 +587,7 @@ module apb4_apu #(
.core_idle_i(idle_o),
.finite_idle_i (s_scheduler_idle && s_codec_idle && s_codec_transport_idle &&
!s_dma_busy && s_mc_idle && s_seq_idle && !s_primitive_busy &&
!s_kws_loader_busy),
!s_kws_loader_busy && !s_kws_coeff_loader_busy),
.core_busy_i(!idle_o),
.core_aborting_i(s_dma_aborting || s_scheduler_aborting || s_resource_reset_request),
.direct_start_allowed_i(s_direct_start_allowed),
@@ -575,6 +630,9 @@ module apb4_apu #(
.kws_overrun_count_i(s_kws_overrun_count),
.kws_model_status_i(s_kws_model_status),
.kws_model_actual_crc_i(s_kws_model_actual_crc),
.kws_coeff_status_i(s_kws_coeff_status),
.kws_coeff_actual_crc_i(s_kws_coeff_actual_crc),
.kws_coefficient_id_i(s_kws_coefficient_id),
.kws_config_publish_i(s_kws_config_publish),
.kws_config_default_i(s_kws_config_default),
.kws_disable_done_i(s_kws_disable_done),
@@ -593,6 +651,7 @@ module apb4_apu #(
.ring_kick_o(s_ring_kick),
.microcode_load_o(s_microcode_load),
.model_load_o(s_kws_model_load),
.coefficient_load_o(s_kws_coeff_load),
.counter_clear_o(s_cnt_clear),
.perf_enable_o(s_perf_enable),
.xrun_clear_o(s_xrun_clear),
@@ -616,6 +675,9 @@ module apb4_apu #(
.kws_model_address_o(s_kws_model_addr),
.kws_model_size_o(s_kws_model_size),
.kws_model_expected_crc_o(s_kws_model_crc),
.kws_coeff_address_o(s_kws_coeff_addr),
.kws_coeff_size_o(s_kws_coeff_size),
.kws_coeff_expected_crc_o(s_kws_coeff_crc),
.kws_control_o(s_kws_control),
.kws_config_o(s_kws_config),
.kws_input_config_o(s_kws_input_config),
@@ -626,6 +688,99 @@ module apb4_apu #(
);
assign s_kws_loader_fault_v = s_kws_loader_done && (s_kws_loader_fault_code != 6'd0);
assign s_kws_coeff_loader_fault_v = s_kws_coeff_loader_done &&
(s_kws_coeff_loader_fault_code != 6'd0);
apu_kws_coeff_loader u_kws_coeff_loader (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.start_i (s_kws_coeff_load),
.abort_i (s_abort),
.soft_reset_i (s_soft_reset),
.resource_reset_request_i(s_resource_reset_request),
.resource_reset_i (s_resource_reset_apply_q),
.quiesce_i (quiesce_i),
.address_i (s_kws_coeff_addr),
.size_i (s_kws_coeff_size),
.expected_crc_i (s_kws_coeff_crc),
.acl_base_i (s_read_base),
.acl_limit_i (s_read_limit),
.dma_request_valid_o (s_kws_coeff_dma_req),
.dma_request_ready_i (s_kws_coeff_dma_rdy),
.dma_request_address_o (s_kws_coeff_dma_addr),
.dma_request_bytes_o (s_kws_coeff_dma_bytes),
.dma_data_i (u_dma_read_axis.tdata),
.dma_keep_i (u_dma_read_axis.tkeep),
.dma_last_i (u_dma_read_axis.tlast),
.dma_valid_i (u_dma_read_axis.tvalid && (s_dma_owner_q == 3'd5)),
.dma_ready_o (s_kws_coeff_dma_data_rdy),
.dma_done_i (s_dma_done && (s_dma_owner_q == 3'd5)),
.dma_error_i (s_dma_err && (s_dma_owner_q == 3'd5)),
.dma_error_code_i (s_dma_err_code),
.dma_error_stage_i (s_dma_err_stage),
.dma_error_resp_i (s_dma_err_resp),
.dma_error_address_i (s_dma_err_addr),
.store_active_o (s_kws_coeff_store_active),
.store_req_o (s_kws_coeff_store_req),
.store_write_o (s_kws_coeff_store_write),
.store_addr_o (s_kws_coeff_store_addr),
.store_data_o (s_kws_coeff_store_wdata),
.store_ready_i (s_kws_coeff_store_ready),
.store_valid_i (s_kws_coeff_store_valid),
.store_data_i (s_kws_coeff_store_rdata),
.store_fault_i (s_kws_coeff_store_fault),
.busy_o (s_kws_coeff_loader_busy),
.valid_o (s_kws_coeff_valid),
.lock_o (s_kws_coeff_lock),
.actual_crc_o (s_kws_coeff_actual_crc),
.coefficient_id_o (s_kws_coefficient_id),
.status_o (s_kws_coeff_status),
.error_code_o (s_kws_coeff_loader_fault_code),
.error_stage_o (s_kws_coeff_loader_fault_stage),
.error_resp_o (s_kws_coeff_loader_fault_resp),
.error_address_o (s_kws_coeff_loader_fault_addr),
.error_detail_o (s_kws_coeff_loader_fault_detail),
.done_o (s_kws_coeff_loader_done),
.abort_done_o (s_kws_coeff_loader_abort_done)
);
apu_kws_coeff_store u_kws_coeff_store (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.abort_i (s_abort || s_soft_reset || s_resource_reset_apply_q),
.valid_i (s_kws_coeff_valid),
.loader_active_i (s_kws_coeff_store_active),
.loader_req_i (s_kws_coeff_store_req),
.loader_write_i (s_kws_coeff_store_write),
.loader_addr_i (s_kws_coeff_store_addr),
.loader_data_i (s_kws_coeff_store_wdata),
.loader_ready_o (s_kws_coeff_store_ready),
.loader_valid_o (s_kws_coeff_store_valid),
.loader_data_o (s_kws_coeff_store_rdata),
.loader_fault_o (s_kws_coeff_store_fault),
.frontend_req_valid_i (s_kws_coeff_front_req),
.frontend_req_ready_o (s_kws_coeff_front_ready),
.frontend_kind_i (s_kws_coeff_front_kind),
.frontend_index_i (s_kws_coeff_front_index),
.frontend_resp_valid_o (s_kws_coeff_front_resp_valid),
.frontend_resp_ready_i (s_kws_coeff_front_resp_ready),
.frontend_resp_data_o (s_kws_coeff_front_resp_data),
.frontend_resp_fault_o (s_kws_coeff_front_resp_fault),
.inference_req_valid_i (s_kws_coeff_infer_req),
.inference_req_ready_o (s_kws_coeff_infer_ready),
.inference_index_i (s_kws_coeff_infer_index),
.inference_resp_valid_o(s_kws_coeff_infer_resp_valid),
.inference_resp_ready_i(s_kws_coeff_infer_resp_ready),
.inference_resp_data_o (s_kws_coeff_infer_resp_data),
.inference_resp_fault_o(s_kws_coeff_infer_resp_fault),
.profile_req_valid_i (s_kws_coeff_profile_req),
.profile_req_ready_o (s_kws_coeff_profile_ready),
.profile_index_i (s_kws_coeff_profile_index),
.profile_resp_valid_o (s_kws_coeff_profile_resp_valid),
.profile_resp_ready_i (s_kws_coeff_profile_resp_ready),
.profile_resp_data_o (s_kws_coeff_profile_resp_data),
.profile_resp_fault_o (s_kws_coeff_profile_resp_fault)
);
apu_kws_model_loader u_kws_model_loader (
.clk_i (clk_i),
@@ -661,6 +816,13 @@ module apb4_apu #(
.local_data_o (s_kws_loader_local_data),
.local_strb_o (s_kws_loader_local_strb),
.local_ready_i (s_kws_loader_local_rdy),
.profile_request_valid_o (s_kws_coeff_profile_req),
.profile_request_ready_i (s_kws_coeff_profile_ready),
.profile_index_o (s_kws_coeff_profile_index),
.profile_response_valid_i(s_kws_coeff_profile_resp_valid),
.profile_response_ready_o(s_kws_coeff_profile_resp_ready),
.profile_data_i (s_kws_coeff_profile_resp_data),
.profile_fault_i (s_kws_coeff_profile_resp_fault),
.busy_o (s_kws_loader_busy),
.valid_o (s_kws_model_valid),
.lock_o (s_kws_model_lock),
@@ -694,7 +856,7 @@ module apb4_apu #(
.model_valid_i(s_kws_model_valid),
.model_lock_i(s_kws_model_lock),
.sequencer_timeout_i(s_sequencer_timeout),
.memory_start_i (s_codec_kws_start && !s_resource_reset_request),
.memory_start_i(s_kws_memory_start),
.memory_start_ready_o(s_kws_mem_start_ready),
.memory_address_i(s_codec_descriptor[(2*32)+:32]),
.memory_dma_request_valid_o(s_kws_mem_dma_req),
@@ -739,6 +901,21 @@ module apb4_apu #(
.scratch_write_data_o(s_kws_scratch_write_data),
.scratch_write_strb_o(s_kws_scratch_write_strb),
.scratch_access_err_i(s_kws_scratch_err),
.coeff_frontend_req_valid_o(s_kws_coeff_front_req),
.coeff_frontend_req_ready_i(s_kws_coeff_front_ready),
.coeff_frontend_kind_o(s_kws_coeff_front_kind),
.coeff_frontend_index_o(s_kws_coeff_front_index),
.coeff_frontend_resp_valid_i(s_kws_coeff_front_resp_valid),
.coeff_frontend_resp_ready_o(s_kws_coeff_front_resp_ready),
.coeff_frontend_data_i(s_kws_coeff_front_resp_data),
.coeff_frontend_fault_i(s_kws_coeff_front_resp_fault),
.coeff_inference_req_valid_o(s_kws_coeff_infer_req),
.coeff_inference_req_ready_i(s_kws_coeff_infer_ready),
.coeff_inference_index_o(s_kws_coeff_infer_index),
.coeff_inference_resp_valid_i(s_kws_coeff_infer_resp_valid),
.coeff_inference_resp_ready_o(s_kws_coeff_infer_resp_ready),
.coeff_inference_data_i(s_kws_coeff_infer_resp_data),
.coeff_inference_fault_i(s_kws_coeff_infer_resp_fault),
.stream_i(u_apu_rx_axis),
.rx_ready_o(s_kws_rx_ready),
.status_o(s_kws_status),
@@ -769,7 +946,7 @@ module apb4_apu #(
.clk_i(clk_i),
.rst_n_i(rst_n_i),
.abort_i(s_dma_abort),
.quiesce_i (s_dma_admission_block && s_mc_idle && !s_kws_loader_busy),
.quiesce_i(s_dma_quiesce),
.bridge_epoch_i(bridge_epoch_i),
.perf_enable_i(s_perf_enable),
.counter_clear_i(s_cnt_clear),
@@ -1137,9 +1314,10 @@ module apb4_apu #(
.ring_result_kws_o(s_codec_ring_result_kws),
.microcode_valid_i(s_mc_status[`APB4_APU__MC_STATUS_VALID]),
.microcode_lock_i(s_mc_lock),
.kws_model_valid_i(s_kws_model_valid),
.kws_model_lock_i(s_kws_model_lock),
.kws_memory_armed_i((s_kws_control == 2'd3) && !s_kws_disable_request),
.kws_model_valid_i(s_kws_model_valid && s_kws_coeff_valid),
.kws_model_lock_i(s_kws_model_lock && s_kws_coeff_lock),
.kws_memory_armed_i((s_kws_control == 2'd3) && s_kws_coeff_valid &&
s_kws_coeff_lock && !s_kws_disable_request),
.entry_scratch_base_i(s_mc_entry_scratch_base),
.entry_scratch_bytes_i(s_mc_entry_scratch_bytes),
.sequencer_launch_o(s_codec_seq_launch),
+27
View File
@@ -84,6 +84,15 @@
`define APB4_APU__KWS_MODEL_STATUS 12'h234
`define APB4_APU__KWS_MODEL_ACTUAL_CRC 12'h238
`define APB4_APU__KWS_INPUT_CONFIG 12'h23c
`define APB4_APU__KWS_COEFF_ADDRESS 12'h240
`define APB4_APU__KWS_COEFF_SIZE 12'h244
`define APB4_APU__KWS_COEFF_EXPECTED_CRC 12'h248
`define APB4_APU__KWS_COEFF_COMMAND 12'h24c
`define APB4_APU__KWS_COEFF_STATUS 12'h250
`define APB4_APU__KWS_COEFF_ACTUAL_CRC 12'h254
`define APB4_APU__KWS_COEFF_ID_LO 12'h258
`define APB4_APU__KWS_COEFF_ID_HI 12'h25c
`define APB4_APU__KWS_COEFF_CAPACITY 12'h260
`define APB4_APU__PERF_CONTROL 12'h300
`define APB4_APU__PERF_STATUS 12'h304
@@ -117,6 +126,7 @@
`define APB4_APU__CAPABILITY0_KWS 6
`define APB4_APU__CAPABILITY0_SEQUENCER 7
`define APB4_APU__CAPABILITY0_RESAMPLER 8
`define APB4_APU__CAPABILITY0_COEFFICIENT_LOAD 9
`define APB4_APU__KWS_INPUT_CONFIG_RATE_MASK 32'h0001ffff
`define APB4_APU__KWS_INPUT_CONFIG_CHANNELS_SHIFT 17
`define APB4_APU__KWS_INPUT_CONFIG_PRECISION_SHIFT 20
@@ -180,6 +190,15 @@
`define APB4_APU__IRQ_FIRST_ERROR 8
`define APB4_APU__IRQ_STREAM_XRUN 9
`define APB4_APU__IRQ_SEQUENCER_TRAP 10
`define APB4_APU__IRQ_COEFFICIENT_LOAD_DONE 11
`define APB4_APU__KWS_COEFF_STATUS_BUSY 0
`define APB4_APU__KWS_COEFF_STATUS_VALID 1
`define APB4_APU__KWS_COEFF_STATUS_LOCKED 2
`define APB4_APU__KWS_COEFF_STATUS_SIZE_ERROR 8
`define APB4_APU__KWS_COEFF_STATUS_HEADER_ERROR 9
`define APB4_APU__KWS_COEFF_STATUS_CRC_ERROR 10
`define APB4_APU__KWS_COEFF_STATUS_PADDING_ERROR 11
`define APB4_APU__ERROR_VALID 0
`define APB4_APU__ERROR_CODE 1
@@ -359,6 +378,14 @@
`define APB4_APU__APUM_ABI 32'h0001_0000
`define APB4_APU__APUM_IMAGE_BYTES 32768
`define APB4_APU__APUM_PAYLOAD_CRC 32'hb903_4b22
`define APB4_APU__APUC_MAGIC 32'h4355_5041
`define APB4_APU__APUC_ABI 32'h0001_0000
`define APB4_APU__APUC_IMAGE_BYTES 61504
`define APB4_APU__APUC_HEADER_BYTES 64
`define APB4_APU__APUC_PAYLOAD_BYTES 61440
`define APB4_APU__APUC_PAYLOAD_CRC 32'h25e7_c27d
`define APB4_APU__APUC_COEFFICIENT_ID_LO 32'h8a0b_038d
`define APB4_APU__APUC_COEFFICIENT_ID_HI 32'h806c_780d
`define APB4_APU__APUMC_HEADER_MAGIC 0
`define APB4_APU__APUMC_HEADER_ABI 1
`define APB4_APU__APUMC_HEADER_TOTAL_BYTES 2
+501
View File
@@ -0,0 +1,501 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// SPDX-License-Identifier: MulanPSL-2.0
`include "apu_define.svh"
module apu_kws_coeff_loader (
// verilog_format: off -- preserve the private-DMA and coefficient-store columns
input logic clk_i,
input logic rst_n_i,
input logic start_i,
input logic abort_i,
input logic soft_reset_i,
input logic resource_reset_request_i,
input logic resource_reset_i,
input logic quiesce_i,
input logic [31:0] address_i,
input logic [31:0] size_i,
input logic [31:0] expected_crc_i,
input logic [31:0] acl_base_i,
input logic [31:0] acl_limit_i,
output logic dma_request_valid_o,
input logic dma_request_ready_i,
output logic [31:0] dma_request_address_o,
output logic [31:0] dma_request_bytes_o,
input logic [31:0] dma_data_i,
input logic [ 3:0] dma_keep_i,
input logic dma_last_i,
input logic dma_valid_i,
output logic dma_ready_o,
input logic dma_done_i,
input logic dma_error_i,
input logic [ 5:0] dma_error_code_i,
input logic [ 3:0] dma_error_stage_i,
input logic [ 1:0] dma_error_resp_i,
input logic [31:0] dma_error_address_i,
output logic store_active_o,
output logic store_req_o,
output logic store_write_o,
output logic [13:0] store_addr_o,
output logic [31:0] store_data_o,
input logic store_ready_i,
input logic store_valid_i,
input logic [31:0] store_data_i,
input logic store_fault_i,
output logic busy_o,
output logic valid_o,
output logic lock_o,
output logic [31:0] actual_crc_o,
output logic [63:0] coefficient_id_o,
output logic [31:0] status_o,
output logic [ 5:0] error_code_o,
output logic [ 3:0] error_stage_o,
output logic [ 1:0] error_resp_o,
output logic [31:0] error_address_o,
output logic [31:0] error_detail_o,
output logic done_o,
output logic abort_done_o
// verilog_format: on
);
localparam logic [31:0] ImageBytes = 32'd61504;
localparam logic [31:0] PayloadBytes = 32'd61440;
localparam logic [31:0] PayloadCrc = 32'h25e7_c27d;
localparam logic [31:0] CoefficientIdLow = 32'h8a0b_038d;
localparam logic [31:0] CoefficientIdHigh = 32'h806c_780d;
typedef enum logic [3:0] {
Idle,
RequestHeader,
ReceiveHeader,
WaitHeader,
CheckHeader,
RequestPayload,
ReceivePayload,
WaitPayload,
ReadbackRequest,
Validate,
Complete,
CancelWait
} loader_state_e;
loader_state_e s_state_q;
logic [15:0][31:0] s_header_q;
logic [31:0] s_addr_q, s_size_q, s_expected_crc_q, s_acl_base_q, s_acl_limit_q;
logic [31:0] s_received_q;
logic [31:0] s_crc_q, s_readback_crc_q;
logic [13:0] s_readback_word_q;
logic [13:0] s_readback_received_q;
logic s_transfer_err_q, s_padding_err_q, s_store_pending_q;
logic [31:0] s_padding_addr_q;
logic s_valid_q, s_lock_q, s_done_q, s_abort_done_q, s_abort_pending_q;
logic [31:0] s_actual_crc_q, s_stat_q;
logic [63:0] s_coefficient_id_q;
logic [ 5:0] s_err_code_q;
logic [ 3:0] s_err_stage_q;
logic [ 1:0] s_err_resp_q;
logic [31:0] s_err_addr_q, s_err_detail_q;
logic [32:0] s_last_addr, s_snapshot_last_addr;
logic s_admit_ok, s_snapshot_range_ok, s_data_accept;
logic [31:0] s_crc_after_word, s_readback_crc_after_word;
logic s_header_size_err, s_header_err;
logic [31:0] s_header_err_addr;
function automatic logic [31:0] crc32_byte(input logic [31:0] crc_i, input logic [7:0] byte_i);
logic [31:0] s_crc;
begin
s_crc = crc_i ^ {24'd0, byte_i};
for (int unsigned bit_index = 0; bit_index < 8; bit_index++) begin
s_crc = s_crc[0] ? ((s_crc >> 1) ^ 32'hedb8_8320) : (s_crc >> 1);
end
return s_crc;
end
endfunction
function automatic logic [31:0] crc32_word(input logic [31:0] crc_i, input logic [31:0] data_i);
logic [31:0] s_crc;
begin
s_crc = crc_i;
for (int unsigned byte_index = 0; byte_index < 4; byte_index++) begin
s_crc = crc32_byte(s_crc, data_i[(byte_index*8)+:8]);
end
return s_crc;
end
endfunction
function automatic logic payload_word_is_padding(input logic [13:0] word_i);
logic [3:0] s_bank;
logic [9:0] s_row;
begin
s_bank = word_i[13:10];
s_row = word_i[9:0];
unique case (s_bank)
4'd0: payload_word_is_padding = s_row >= 10'd1006;
4'd1: payload_word_is_padding = s_row >= 10'd769;
4'd2: payload_word_is_padding = s_row >= 10'd768;
4'd14: payload_word_is_padding = s_row >= 10'd637;
default: payload_word_is_padding = 1'b0;
endcase
end
endfunction
assign s_last_addr = {1'b0, address_i} + {1'b0, size_i} - 1'b1;
assign s_admit_ok = (size_i == ImageBytes) && (address_i[5:0] == 6'd0) &&
(address_i >= acl_base_i) && !s_last_addr[32] && (s_last_addr[31:0] <= acl_limit_i) &&
quiesce_i && !s_lock_q;
assign s_snapshot_last_addr = {1'b0, s_addr_q} + {1'b0, s_size_q} - 1'b1;
assign s_snapshot_range_ok = (s_size_q == ImageBytes) && (s_addr_q[5:0] == 6'd0) &&
(s_addr_q >= s_acl_base_q) && !s_snapshot_last_addr[32] &&
(s_snapshot_last_addr[31:0] <= s_acl_limit_q);
assign s_data_accept = dma_valid_i && dma_ready_o;
assign s_crc_after_word = crc32_word(s_crc_q, dma_data_i);
assign s_readback_crc_after_word = crc32_word(s_readback_crc_q, store_data_i);
always_comb begin
s_header_size_err = 1'b0;
s_header_err = 1'b0;
s_header_err_addr = s_addr_q;
if (s_header_q[2] != ImageBytes) begin
s_header_size_err = 1'b1;
s_header_err_addr = s_addr_q + 32'h08;
end else if (s_header_q[4] != PayloadBytes) begin
s_header_size_err = 1'b1;
s_header_err_addr = s_addr_q + 32'h10;
end else if (s_header_q[0] != 32'h4355_5041) begin
s_header_err = 1'b1;
end else if (s_header_q[1] != 32'h0001_0000) begin
s_header_err = 1'b1;
s_header_err_addr = s_addr_q + 32'h04;
end else if (s_header_q[3] != 32'd64) begin
s_header_err = 1'b1;
s_header_err_addr = s_addr_q + 32'h0c;
end else if (s_header_q[5] != 32'd1) begin
s_header_err = 1'b1;
s_header_err_addr = s_addr_q + 32'h14;
end else if (s_header_q[6] != 32'd1) begin
s_header_err = 1'b1;
s_header_err_addr = s_addr_q + 32'h18;
end else if (s_header_q[7] != 32'd15) begin
s_header_err = 1'b1;
s_header_err_addr = s_addr_q + 32'h1c;
end else if (s_header_q[8] != 32'd10) begin
s_header_err = 1'b1;
s_header_err_addr = s_addr_q + 32'h20;
end else if (s_header_q[9] != PayloadCrc) begin
s_header_err = 1'b1;
s_header_err_addr = s_addr_q + 32'h24;
end else if (s_header_q[10] != CoefficientIdLow) begin
s_header_err = 1'b1;
s_header_err_addr = s_addr_q + 32'h28;
end else if (s_header_q[11] != CoefficientIdHigh) begin
s_header_err = 1'b1;
s_header_err_addr = s_addr_q + 32'h2c;
end else if (s_header_q[12] != 32'hb903_4b22) begin
s_header_err = 1'b1;
s_header_err_addr = s_addr_q + 32'h30;
end else if (s_header_q[13] != 32'd11629080) begin
s_header_err = 1'b1;
s_header_err_addr = s_addr_q + 32'h34;
end else if (s_header_q[14] != 32'd0) begin
s_header_err = 1'b1;
s_header_err_addr = s_addr_q + 32'h38;
end else if (s_header_q[15] != 32'd0) begin
s_header_err = 1'b1;
s_header_err_addr = s_addr_q + 32'h3c;
end
end
assign dma_request_valid_o = (s_state_q inside {RequestHeader, RequestPayload}) &&
s_snapshot_range_ok;
assign dma_request_address_o = (s_state_q == RequestPayload) ? s_addr_q + 32'd64 : s_addr_q;
assign dma_request_bytes_o = (s_state_q == RequestPayload) ? PayloadBytes : 32'd64;
assign dma_ready_o = ((s_state_q == ReceiveHeader) || (s_state_q == ReceivePayload)) &&
((s_state_q == ReceiveHeader) || store_ready_i);
assign store_active_o = s_state_q inside {ReceivePayload, WaitPayload, ReadbackRequest, Validate};
assign store_req_o = ((s_state_q == ReceivePayload) && s_data_accept) ||
((s_state_q == ReadbackRequest) && (s_readback_word_q < 14'd15360));
assign store_write_o = s_state_q == ReceivePayload;
assign store_addr_o = (s_state_q == ReceivePayload) ?
14'((s_received_q - 32'd64) >> 2) : s_readback_word_q;
assign store_data_o = dma_data_i;
assign busy_o = s_state_q != Idle;
assign valid_o = s_valid_q;
assign lock_o = s_lock_q;
assign actual_crc_o = s_actual_crc_q;
assign coefficient_id_o = s_coefficient_id_q;
assign status_o = {20'd0, s_stat_q[11:8], 5'd0, s_lock_q, s_valid_q, busy_o};
assign error_code_o = s_err_code_q;
assign error_stage_o = s_err_stage_q;
assign error_resp_o = s_err_resp_q;
assign error_address_o = s_err_addr_q;
assign error_detail_o = s_err_detail_q;
assign done_o = s_done_q;
assign abort_done_o = s_abort_done_q;
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
s_state_q <= Idle;
s_header_q <= '0;
s_addr_q <= 32'd0;
s_size_q <= 32'd0;
s_expected_crc_q <= 32'd0;
s_acl_base_q <= 32'd0;
s_acl_limit_q <= 32'd0;
s_received_q <= 32'd0;
s_crc_q <= 32'hffff_ffff;
s_readback_crc_q <= 32'hffff_ffff;
s_readback_word_q <= 14'd0;
s_readback_received_q <= 14'd0;
s_transfer_err_q <= 1'b0;
s_padding_err_q <= 1'b0;
s_store_pending_q <= 1'b0;
s_padding_addr_q <= 32'd0;
s_valid_q <= 1'b0;
s_lock_q <= 1'b0;
s_done_q <= 1'b0;
s_abort_done_q <= 1'b0;
s_abort_pending_q <= 1'b0;
s_actual_crc_q <= 32'd0;
s_stat_q <= 32'd0;
s_coefficient_id_q <= 64'd0;
s_err_code_q <= 6'd0;
s_err_stage_q <= 4'd0;
s_err_resp_q <= 2'd0;
s_err_addr_q <= 32'd0;
s_err_detail_q <= 32'd0;
end else begin
s_done_q <= 1'b0;
s_abort_done_q <= 1'b0;
if (store_valid_i) s_store_pending_q <= 1'b0;
if ((soft_reset_i || resource_reset_i) && (s_state_q == Idle)) begin
if (!s_lock_q) begin
s_stat_q <= 32'd0;
s_actual_crc_q <= 32'd0;
s_coefficient_id_q <= 64'd0;
end
end
unique case (s_state_q)
Idle: begin
if (start_i && s_admit_ok) begin
s_state_q <= RequestHeader;
s_header_q <= '0;
s_addr_q <= address_i;
s_size_q <= size_i;
s_expected_crc_q <= expected_crc_i;
s_acl_base_q <= acl_base_i;
s_acl_limit_q <= acl_limit_i;
s_received_q <= 32'd0;
s_crc_q <= 32'hffff_ffff;
s_readback_crc_q <= 32'hffff_ffff;
s_readback_word_q <= 14'd0;
s_readback_received_q <= 14'd0;
s_transfer_err_q <= 1'b0;
s_padding_err_q <= 1'b0;
s_store_pending_q <= 1'b0;
s_stat_q <= 32'd0;
s_actual_crc_q <= 32'd0;
s_coefficient_id_q <= 64'd0;
s_err_code_q <= 6'd0;
s_err_stage_q <= 4'd0;
s_err_resp_q <= 2'd0;
s_err_addr_q <= 32'd0;
s_err_detail_q <= 32'd0;
end
end
RequestHeader: begin
if (abort_i || resource_reset_request_i || resource_reset_i || soft_reset_i) begin
s_abort_done_q <= abort_i && !resource_reset_request_i && !resource_reset_i;
s_state_q <= Idle;
end else if (dma_request_ready_i) begin
s_received_q <= 32'd0;
s_state_q <= ReceiveHeader;
end
end
ReceiveHeader: begin
if (abort_i || resource_reset_request_i || resource_reset_i || soft_reset_i) begin
s_abort_pending_q <= abort_i && !resource_reset_request_i && !resource_reset_i;
s_state_q <= CancelWait;
end else begin
if (s_data_accept) begin
if ((dma_keep_i != 4'hf) || (s_received_q >= 32'd64) ||
(dma_last_i != (s_received_q == 32'd60))) begin
s_transfer_err_q <= 1'b1;
end else begin
s_header_q[s_received_q[5:2]] <= dma_data_i;
end
s_received_q <= s_received_q + 32'd4;
end
if (dma_error_i) begin
s_err_code_q <= dma_error_code_i;
s_err_stage_q <= dma_error_stage_i;
s_err_resp_q <= dma_error_resp_i;
s_err_addr_q <= dma_error_address_i;
s_state_q <= Complete;
end else if (dma_done_i) begin
s_state_q <= WaitHeader;
end
end
end
WaitHeader: begin
if (s_transfer_err_q || (s_received_q != 32'd64)) begin
s_err_code_q <= `APB4_APU__ERROR_CODE_AXI_READ;
s_err_stage_q <= `APB4_APU__ERROR_STAGE_DMA_READ;
s_err_addr_q <= s_addr_q + s_received_q;
s_state_q <= Complete;
end else begin
s_state_q <= CheckHeader;
end
end
CheckHeader: begin
if (s_header_size_err) begin
s_stat_q[8] <= 1'b1;
s_err_code_q <= `APB4_APU__ERROR_CODE_KWS_MODEL;
s_err_stage_q <= `APB4_APU__ERROR_STAGE_LOADER;
s_err_addr_q <= s_header_err_addr;
s_err_detail_q <= 32'h0900_0001;
s_state_q <= Complete;
end else if (s_header_err) begin
s_stat_q[9] <= 1'b1;
s_err_code_q <= `APB4_APU__ERROR_CODE_KWS_MODEL;
s_err_stage_q <= `APB4_APU__ERROR_STAGE_LOADER;
s_err_addr_q <= s_header_err_addr;
s_err_detail_q <= 32'h0900_0002;
s_state_q <= Complete;
end else begin
s_received_q <= 32'd64;
s_crc_q <= 32'hffff_ffff;
s_transfer_err_q <= 1'b0;
s_state_q <= RequestPayload;
end
end
RequestPayload: begin
if (abort_i || resource_reset_request_i || resource_reset_i || soft_reset_i) begin
s_abort_done_q <= abort_i && !resource_reset_request_i && !resource_reset_i;
s_state_q <= Idle;
end else if (dma_request_ready_i) begin
s_state_q <= ReceivePayload;
end
end
ReceivePayload: begin
if (abort_i || resource_reset_request_i || resource_reset_i || soft_reset_i) begin
s_abort_pending_q <= abort_i && !resource_reset_request_i && !resource_reset_i;
s_state_q <= CancelWait;
end else begin
if (s_data_accept) begin
if ((dma_keep_i != 4'hf) || (s_received_q >= ImageBytes) ||
(dma_last_i != (s_received_q == (ImageBytes - 32'd4)))) begin
s_transfer_err_q <= 1'b1;
end else begin
s_crc_q <= s_crc_after_word;
s_store_pending_q <= 1'b1;
if (payload_word_is_padding(
14'((s_received_q - 32'd64) >> 2)
) && (dma_data_i != 32'd0) && !s_padding_err_q) begin
s_padding_err_q <= 1'b1;
s_padding_addr_q <= s_addr_q + s_received_q;
end
end
s_received_q <= s_received_q + 32'd4;
end
if (dma_error_i) begin
s_err_code_q <= dma_error_code_i;
s_err_stage_q <= dma_error_stage_i;
s_err_resp_q <= dma_error_resp_i;
s_err_addr_q <= dma_error_address_i;
s_state_q <= Complete;
end else if (dma_done_i) begin
s_state_q <= WaitPayload;
end
end
end
WaitPayload: begin
if (!s_store_pending_q) begin
if (s_transfer_err_q || (s_received_q != ImageBytes) || store_fault_i) begin
s_err_code_q <= `APB4_APU__ERROR_CODE_AXI_READ;
s_err_stage_q <= `APB4_APU__ERROR_STAGE_DMA_READ;
s_err_addr_q <= s_addr_q + s_received_q;
s_state_q <= Complete;
end else if (s_padding_err_q) begin
s_stat_q[11] <= 1'b1;
s_err_code_q <= `APB4_APU__ERROR_CODE_KWS_MODEL;
s_err_stage_q <= `APB4_APU__ERROR_STAGE_LOADER;
s_err_addr_q <= s_padding_addr_q;
s_err_detail_q <= 32'h0900_0004;
s_state_q <= Complete;
end else if ((~s_crc_q != PayloadCrc) || (s_header_q[9] != ~s_crc_q) ||
(s_expected_crc_q != ~s_crc_q)) begin
s_stat_q[10] <= 1'b1;
s_err_code_q <= `APB4_APU__ERROR_CODE_KWS_CRC;
s_err_stage_q <= `APB4_APU__ERROR_STAGE_LOADER;
s_err_addr_q <= s_addr_q + 32'h24;
s_err_detail_q <= 32'h0900_0003;
s_state_q <= Complete;
end else begin
s_readback_crc_q <= 32'hffff_ffff;
s_readback_word_q <= 14'd0;
s_readback_received_q <= 14'd0;
s_state_q <= ReadbackRequest;
end
end
end
ReadbackRequest: begin
if (abort_i || resource_reset_request_i || resource_reset_i || soft_reset_i) begin
s_abort_done_q <= abort_i && !resource_reset_request_i && !resource_reset_i;
s_state_q <= Idle;
end else begin
if (store_req_o && store_ready_i) begin
s_readback_word_q <= s_readback_word_q + 1'b1;
end
if (store_valid_i) begin
if (store_fault_i) begin
s_stat_q[10] <= 1'b1;
s_err_code_q <= `APB4_APU__ERROR_CODE_KWS_CRC;
s_err_stage_q <= `APB4_APU__ERROR_STAGE_LOADER;
s_err_addr_q <= s_addr_q + 32'h24;
s_err_detail_q <= 32'h0900_0005;
s_state_q <= Complete;
end else begin
s_readback_crc_q <= s_readback_crc_after_word;
if (s_readback_received_q == 14'd15359) begin
s_actual_crc_q <= ~s_readback_crc_after_word;
s_state_q <= Validate;
end else begin
s_readback_received_q <= s_readback_received_q + 1'b1;
end
end
end
end
end
Validate: begin
if (s_actual_crc_q != PayloadCrc) begin
s_stat_q[10] <= 1'b1;
s_err_code_q <= `APB4_APU__ERROR_CODE_KWS_CRC;
s_err_stage_q <= `APB4_APU__ERROR_STAGE_LOADER;
s_err_addr_q <= s_addr_q + 32'h24;
s_err_detail_q <= 32'h0900_0005;
end else begin
s_valid_q <= 1'b1;
s_lock_q <= 1'b1;
s_coefficient_id_q <= {CoefficientIdHigh, CoefficientIdLow};
end
s_state_q <= Complete;
end
Complete: begin
s_done_q <= 1'b1;
s_state_q <= Idle;
end
CancelWait: begin
if (dma_error_i || dma_done_i) begin
s_abort_done_q <= s_abort_pending_q;
s_abort_pending_q <= 1'b0;
s_state_q <= Idle;
end
end
default: s_state_q <= Idle;
endcase
end
end
endmodule
+314
View File
@@ -0,0 +1,314 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// SPDX-License-Identifier: MulanPSL-2.0
module apu_kws_coeff_store (
// verilog_format: off -- preserve the four coefficient-client columns
input logic clk_i,
input logic rst_n_i,
input logic abort_i,
input logic valid_i,
input logic loader_active_i,
input logic loader_req_i,
input logic loader_write_i,
input logic [13:0] loader_addr_i,
input logic [31:0] loader_data_i,
output logic loader_ready_o,
output logic loader_valid_o,
output logic [31:0] loader_data_o,
output logic loader_fault_o,
input logic frontend_req_valid_i,
output logic frontend_req_ready_o,
input logic [ 3:0] frontend_kind_i,
input logic [13:0] frontend_index_i,
output logic frontend_resp_valid_o,
input logic frontend_resp_ready_i,
output logic [63:0] frontend_resp_data_o,
output logic frontend_resp_fault_o,
input logic inference_req_valid_i,
output logic inference_req_ready_o,
input logic [ 6:0] inference_index_i,
output logic inference_resp_valid_o,
input logic inference_resp_ready_i,
output logic [31:0] inference_resp_data_o,
output logic inference_resp_fault_o,
input logic profile_req_valid_i,
output logic profile_req_ready_o,
input logic [10:0] profile_index_i,
output logic profile_resp_valid_o,
input logic profile_resp_ready_i,
output logic [31:0] profile_resp_data_o,
output logic profile_resp_fault_o
// verilog_format: on
);
localparam int unsigned BankCount = 15;
logic [BankCount-1:0] s_bank_cs;
logic [BankCount-1:0] s_bank_wren;
logic [BankCount-1:0][ 9:0] s_bank_addr;
logic [BankCount-1:0][31:0] s_bank_wdata;
logic [BankCount-1:0][31:0] s_bank_rdata;
logic s_front_legal;
logic [3:0] s_front_bank_low, s_front_bank_high;
logic [9:0] s_front_row_low, s_front_row_high;
logic s_front_pair;
logic s_front_accept;
logic s_front_pending_q;
logic s_front_fault_q;
logic [3:0] s_front_bank_low_q, s_front_bank_high_q;
logic s_front_pair_q;
logic s_infer_legal;
logic s_infer_accept;
logic s_infer_pending_q;
logic s_infer_fault_q;
logic s_profile_legal;
logic [3:0] s_profile_bank;
logic [9:0] s_profile_row;
logic s_profile_accept;
logic s_profile_pending_q;
logic s_profile_fault_q;
logic [3:0] s_profile_bank_q;
logic s_loader_accept;
logic s_loader_pending_q;
logic s_loader_fault_q;
logic [3:0] s_loader_bank_q;
always_comb begin
s_front_legal = 1'b1;
s_front_bank_low = 4'd0;
s_front_bank_high = 4'd0;
s_front_row_low = 10'd0;
s_front_row_high = 10'd0;
s_front_pair = 1'b0;
unique case (frontend_kind_i)
4'd0: begin
s_front_legal = frontend_index_i < 14'd480;
s_front_row_low = frontend_index_i[9:0];
end
4'd1: begin
s_front_legal = frontend_index_i < 14'd256;
s_front_bank_low = 4'd1;
s_front_bank_high = 4'd2;
s_front_row_low = frontend_index_i[9:0];
s_front_row_high = frontend_index_i[9:0];
s_front_pair = 1'b1;
end
4'd2: begin
s_front_legal = frontend_index_i < 14'd10280;
if (frontend_index_i < 14'd10240) begin
s_front_bank_low = 4'd3 + frontend_index_i[13:10];
s_front_row_low = frontend_index_i[9:0];
end else begin
s_front_bank_low = 4'd13;
s_front_row_low = 10'(frontend_index_i - 14'd10240);
end
end
4'd3: begin
s_front_legal = frontend_index_i < 14'd400;
s_front_row_low = 10'd480 + frontend_index_i[9:0];
end
4'd4: begin
s_front_legal = frontend_index_i < 14'd1024;
s_front_pair = 1'b1;
if (!frontend_index_i[0]) begin
s_front_bank_low = 4'd1;
s_front_bank_high = 4'd2;
s_front_row_low = 10'd256 + frontend_index_i[10:1];
s_front_row_high = 10'd256 + frontend_index_i[10:1];
end else begin
s_front_bank_low = 4'd2;
s_front_bank_high = 4'd1;
s_front_row_low = 10'd256 + frontend_index_i[10:1];
s_front_row_high = 10'd257 + frontend_index_i[10:1];
end
end
4'd5: begin
s_front_legal = frontend_index_i < 14'd63;
s_front_row_low = 10'd880 + frontend_index_i[9:0];
end
4'd6: begin
s_front_legal = frontend_index_i < 14'd63;
s_front_row_low = 10'd943 + frontend_index_i[9:0];
end
default: s_front_legal = 1'b0;
endcase
end
assign s_infer_legal = inference_index_i < 7'd125;
always_comb begin
s_profile_legal = profile_index_i < 11'd1496;
if (profile_index_i < 11'd984) begin
s_profile_bank = 4'd13;
s_profile_row = 10'd40 + profile_index_i[9:0];
end else begin
s_profile_bank = 4'd14;
s_profile_row = 10'(profile_index_i - 11'd984);
end
end
assign loader_ready_o = loader_active_i;
assign frontend_req_ready_o = !loader_active_i && !profile_req_valid_i &&
!s_front_pending_q && (!frontend_resp_valid_o || frontend_resp_ready_i);
assign inference_req_ready_o = !loader_active_i && !profile_req_valid_i &&
!s_infer_pending_q && (!inference_resp_valid_o || inference_resp_ready_i);
assign profile_req_ready_o = !loader_active_i && !frontend_req_valid_i &&
!inference_req_valid_i && !s_profile_pending_q &&
(!profile_resp_valid_o || profile_resp_ready_i);
assign s_loader_accept = loader_req_i && loader_ready_o;
assign s_front_accept = frontend_req_valid_i && frontend_req_ready_o;
assign s_infer_accept = inference_req_valid_i && inference_req_ready_o;
assign s_profile_accept = profile_req_valid_i && profile_req_ready_o;
always_comb begin
s_bank_cs = BankCount'(0);
s_bank_wren = BankCount'(0);
s_bank_addr = '0;
s_bank_wdata = '0;
if (s_loader_accept && (loader_addr_i < 14'd15360)) begin
s_bank_cs[loader_addr_i[13:10]] = 1'b1;
s_bank_wren[loader_addr_i[13:10]] = loader_write_i;
s_bank_addr[loader_addr_i[13:10]] = loader_addr_i[9:0];
s_bank_wdata[loader_addr_i[13:10]] = loader_data_i;
end else begin
if (s_profile_accept && s_profile_legal && valid_i) begin
s_bank_cs[s_profile_bank] = 1'b1;
s_bank_addr[s_profile_bank] = s_profile_row;
end else begin
if (s_front_accept && s_front_legal && valid_i) begin
s_bank_cs[s_front_bank_low] = 1'b1;
s_bank_addr[s_front_bank_low] = s_front_row_low;
if (s_front_pair) begin
s_bank_cs[s_front_bank_high] = 1'b1;
s_bank_addr[s_front_bank_high] = s_front_row_high;
end
end
if (s_infer_accept && s_infer_legal && valid_i) begin
s_bank_cs[14] = 1'b1;
s_bank_addr[14] = 10'd512 + {3'd0, inference_index_i};
end
end
end
end
for (genvar bank = 0; bank < BankCount; bank++) begin : gen_coefficient_bank
`ifdef HAVE_SRAM_MACRO
tc_sram_1024x32 u_coefficient_sram (
.clk_i (clk_i),
.cs_i (s_bank_cs[bank]),
.addr_i(s_bank_addr[bank]),
.data_i(s_bank_wdata[bank]),
.mask_i(4'hf),
.wren_i(s_bank_wren[bank]),
.data_o(s_bank_rdata[bank])
);
`else
logic [31:0] s_storage_q[0:1023];
always_ff @(posedge clk_i) begin
if (s_bank_cs[bank]) begin
if (s_bank_wren[bank]) begin
s_storage_q[s_bank_addr[bank]] <= s_bank_wdata[bank];
end else begin
s_bank_rdata[bank] <= s_storage_q[s_bank_addr[bank]];
end
end
end
`endif
end
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
s_front_pending_q <= 1'b0;
s_front_fault_q <= 1'b0;
s_front_bank_low_q <= 4'd0;
s_front_bank_high_q <= 4'd0;
s_front_pair_q <= 1'b0;
frontend_resp_valid_o <= 1'b0;
frontend_resp_data_o <= 64'd0;
frontend_resp_fault_o <= 1'b0;
s_infer_pending_q <= 1'b0;
s_infer_fault_q <= 1'b0;
inference_resp_valid_o <= 1'b0;
inference_resp_data_o <= 32'd0;
inference_resp_fault_o <= 1'b0;
s_profile_pending_q <= 1'b0;
s_profile_fault_q <= 1'b0;
s_profile_bank_q <= 4'd0;
profile_resp_valid_o <= 1'b0;
profile_resp_data_o <= 32'd0;
profile_resp_fault_o <= 1'b0;
s_loader_pending_q <= 1'b0;
s_loader_fault_q <= 1'b0;
s_loader_bank_q <= 4'd0;
loader_valid_o <= 1'b0;
loader_data_o <= 32'd0;
loader_fault_o <= 1'b0;
end else if (abort_i) begin
s_front_pending_q <= 1'b0;
frontend_resp_valid_o <= 1'b0;
s_infer_pending_q <= 1'b0;
inference_resp_valid_o <= 1'b0;
s_profile_pending_q <= 1'b0;
profile_resp_valid_o <= 1'b0;
s_loader_pending_q <= 1'b0;
loader_valid_o <= 1'b0;
end else begin
if (frontend_resp_valid_o && frontend_resp_ready_i) frontend_resp_valid_o <= 1'b0;
if (inference_resp_valid_o && inference_resp_ready_i) inference_resp_valid_o <= 1'b0;
if (profile_resp_valid_o && profile_resp_ready_i) profile_resp_valid_o <= 1'b0;
loader_valid_o <= s_loader_pending_q || (s_loader_accept && loader_write_i);
if (s_loader_pending_q) begin
loader_fault_o <= s_loader_fault_q;
loader_data_o <= s_loader_fault_q ? 32'd0 : s_bank_rdata[s_loader_bank_q];
end else if (s_loader_accept && loader_write_i) begin
loader_fault_o <= loader_addr_i >= 14'd15360;
end
if (s_front_accept) begin
s_front_pending_q <= 1'b1;
s_front_fault_q <= !s_front_legal || !valid_i;
s_front_bank_low_q <= s_front_bank_low;
s_front_bank_high_q <= s_front_bank_high;
s_front_pair_q <= s_front_pair;
end else if (s_front_pending_q) begin
s_front_pending_q <= 1'b0;
frontend_resp_valid_o <= 1'b1;
frontend_resp_fault_o <= s_front_fault_q;
frontend_resp_data_o <= s_front_fault_q ? 64'd0 :
{s_front_pair_q ? s_bank_rdata[s_front_bank_high_q] : 32'd0,
s_bank_rdata[s_front_bank_low_q]};
end
if (s_infer_accept) begin
s_infer_pending_q <= 1'b1;
s_infer_fault_q <= !s_infer_legal || !valid_i;
end else if (s_infer_pending_q) begin
s_infer_pending_q <= 1'b0;
inference_resp_valid_o <= 1'b1;
inference_resp_fault_o <= s_infer_fault_q;
inference_resp_data_o <= s_infer_fault_q ? 32'd0 : s_bank_rdata[14];
end
if (s_profile_accept) begin
s_profile_pending_q <= 1'b1;
s_profile_fault_q <= !s_profile_legal || !valid_i;
s_profile_bank_q <= s_profile_bank;
end else if (s_profile_pending_q) begin
s_profile_pending_q <= 1'b0;
profile_resp_valid_o <= 1'b1;
profile_resp_fault_o <= s_profile_fault_q;
profile_resp_data_o <= s_profile_fault_q ? 32'd0 : s_bank_rdata[s_profile_bank_q];
end
if (s_loader_accept) begin
s_loader_fault_q <= loader_addr_i >= 14'd15360;
s_loader_bank_q <= loader_addr_i[13:10];
s_loader_pending_q <= !loader_write_i;
end else begin
s_loader_pending_q <= 1'b0;
end
end
end
endmodule
+216 -46
View File
@@ -66,6 +66,21 @@ module apu_kws_engine (
output logic [ 5:0][31:0] scratch_write_data_o,
output logic [ 5:0][ 3:0] scratch_write_strb_o,
input logic scratch_access_err_i,
output logic coeff_frontend_req_valid_o,
input logic coeff_frontend_req_ready_i,
output logic [ 3:0] coeff_frontend_kind_o,
output logic [13:0] coeff_frontend_index_o,
input logic coeff_frontend_resp_valid_i,
output logic coeff_frontend_resp_ready_o,
input logic [63:0] coeff_frontend_data_i,
input logic coeff_frontend_fault_i,
output logic coeff_inference_req_valid_o,
input logic coeff_inference_req_ready_i,
output logic [ 6:0] coeff_inference_index_o,
input logic coeff_inference_resp_valid_i,
output logic coeff_inference_resp_ready_o,
input logic [31:0] coeff_inference_data_i,
input logic coeff_inference_fault_i,
axi4_stream_if.sink stream_i,
output logic rx_ready_o,
output logic [31:0] status_o,
@@ -92,11 +107,10 @@ module apu_kws_engine (
output logic idle_o
// verilog_format: on
);
`include "apu_kws_rom.svh"
localparam logic [15:0] ScratchFftRealBase = `RETROSOC_APU_KWS__SCRATCH_FFT_REAL_BASE;
localparam logic [15:0] ScratchFftImagBase = `RETROSOC_APU_KWS__SCRATCH_FFT_IMAG_BASE;
localparam logic [15:0] ScratchSoftmaxBase = `RETROSOC_APU_KWS__SCRATCH_SOFTMAX_BASE;
localparam logic signed [31:0] ApuKwsLn2Q24 = 32'sd11629080;
typedef enum logic [3:0] {
FrontIdle,
@@ -122,6 +136,12 @@ module apu_kws_engine (
InferComplete
} infer_state_e;
typedef struct packed {
logic signed [7:0] exponent;
logic [10:0] index;
logic [16:0] fraction;
} log_lookup_t;
front_state_e s_front_state_q;
infer_state_e s_infer_state_q;
@@ -182,9 +202,15 @@ module apu_kws_engine (
logic [31:0] s_fault_addr_q, s_fault_detail_q;
logic [31:0] s_front_watchdog_q, s_infer_watchdog_q;
logic s_front_progress, s_infer_progress;
logic s_storage_wait_q, s_storage_write_phase_q, s_storage_state_owned_q;
logic s_storage_wait_q, s_storage_complete_q, s_storage_write_phase_q;
logic s_storage_state_owned_q;
logic s_storage_read_access, s_storage_write_access, s_storage_has_access;
logic s_storage_final_done, s_storage_state_step;
logic s_front_coeff_needed, s_infer_coeff_needed, s_coeff_progress, s_coeff_operand_ready;
logic s_front_coeff_pending_q, s_front_coeff_have_q, s_front_coeff_fault_q;
logic [63:0] s_front_coeff_data_q;
logic s_infer_coeff_pending_q, s_infer_coeff_have_q, s_infer_coeff_fault_q;
logic [31:0] s_infer_coeff_data_q;
logic s_config_valid, s_continuous_active, s_listening, s_capture_available;
logic s_stream_accept, s_stream_legal, s_window_deadline, s_mel_overwrite;
@@ -248,6 +274,7 @@ module apu_kws_engine (
logic signed [31:0] s_fft_left_real_data, s_fft_left_imag_data;
logic signed [31:0] s_fft_right_real_data, s_fft_right_imag_data;
logic signed [31:0] s_log_data;
log_lookup_t s_log_lookup;
logic [ 7:0] s_dct_output_value;
logic signed [7:0] s_logit_data, s_softmax_data[0:11];
logic s_fft_overflow;
@@ -449,25 +476,22 @@ module apu_kws_engine (
end
endfunction
function automatic logic signed [31:0] log_mel_q24(input logic [31:0] mel_i,
function automatic log_lookup_t log_mel_lookup(input logic [31:0] mel_i,
input logic [15:0] peak_i);
logic [95:0] numerator, denominator, normalized_denominator;
logic [95:0] scaled, remainder;
logic signed [ 7:0] exponent;
logic [10:0] index;
logic [16:0] fraction;
log_lookup_t s_lookup;
logic [95:0] fraction_scaled, fraction_quotient, fraction_remainder;
logic signed [63:0] interpolation;
begin
numerator = 96'd1000000 * mel_i + 96'd16 * peak_i;
denominator = 96'd16000000 * peak_i;
exponent = 0;
s_lookup.exponent = 0;
if (numerator >= denominator) begin
normalized_denominator = denominator;
for (int iteration = 0; iteration < 95; iteration++) begin
if (numerator >= (normalized_denominator << 1)) begin
normalized_denominator = normalized_denominator << 1;
exponent = exponent + 1'b1;
s_lookup.exponent = s_lookup.exponent + 1'b1;
end
end
end else begin
@@ -475,13 +499,13 @@ module apu_kws_engine (
for (int iteration = 0; iteration < 95; iteration++) begin
if (numerator < normalized_denominator) begin
numerator = numerator << 1;
exponent = exponent - 1'b1;
s_lookup.exponent = s_lookup.exponent - 1'b1;
end
end
end
scaled = (numerator - normalized_denominator) * 96'd1024;
index = 11'(scaled / normalized_denominator);
remainder = scaled - index * normalized_denominator;
s_lookup.index = 11'(scaled / normalized_denominator);
remainder = scaled - s_lookup.index * normalized_denominator;
fraction_scaled = remainder * 96'd65536;
fraction_quotient = fraction_scaled / normalized_denominator;
fraction_remainder = fraction_scaled - fraction_quotient * normalized_denominator;
@@ -489,15 +513,22 @@ module apu_kws_engine (
((fraction_remainder * 2 == normalized_denominator) && fraction_quotient[0])) begin
fraction_quotient = fraction_quotient + 1'b1;
end
fraction = fraction_quotient[16:0];
interpolation = 64'($signed(apu_kws_log_q24(index + 11'd1))) -
64'($signed(apu_kws_log_q24(index)));
interpolation = rne_shift(interpolation * $signed({1'b0, fraction}), 16);
s_lookup.fraction = fraction_quotient[16:0];
return s_lookup;
end
endfunction
function automatic logic signed [31:0] log_mel_result(input log_lookup_t lookup_i,
input logic [63:0] values_i);
logic signed [63:0] s_interpolation;
begin
s_interpolation = 64'($signed(values_i[63:32])) - 64'($signed(values_i[31:0]));
s_interpolation = rne_shift(s_interpolation * $signed({1'b0, lookup_i.fraction}), 6'd16);
return 32'(64'($signed(
exponent
lookup_i.exponent
)) * 64'(ApuKwsLn2Q24) + 64'($signed(
apu_kws_log_q24(index)
)) + interpolation);
values_i[31:0]
)) + s_interpolation);
end
endfunction
@@ -642,7 +673,7 @@ module apu_kws_engine (
assign s_fir_term = $signed(
(s_fir_tap_q == 0) ? s_fir_sample_q : s_fir_history_data
) * $signed(
(s_decimation == 3'd3) ? apu_kws_fir3_q30(s_fir_tap_q) : apu_kws_fir6_q30(s_fir_tap_q)
s_front_coeff_data_q[31:0]
);
assign s_fir_total = s_fir_acc_q + s_fir_term;
assign s_fir_output = saturate_s16(rne_shift(s_fir_total, 30));
@@ -680,25 +711,25 @@ module apu_kws_engine (
assign s_windowed = (s_front_index_q < 10'd480) ? 64'($signed(
s_ingress_data
)) * 64'($signed(
apu_kws_hann_q30(s_front_index_q[8:0])
s_front_coeff_data_q[31:0]
)) : 64'sd0;
assign s_fft_product_real = $signed(
s_fft_right_real_data
) * $signed(
apu_kws_twiddle_real_q30(s_twiddle_index)
s_front_coeff_data_q[31:0]
) - $signed(
s_fft_right_imag_data
) * $signed(
apu_kws_twiddle_imag_q30(s_twiddle_index)
s_front_coeff_data_q[63:32]
);
assign s_fft_product_imag = $signed(
s_fft_right_real_data
) * $signed(
apu_kws_twiddle_imag_q30(s_twiddle_index)
s_front_coeff_data_q[63:32]
) + $signed(
s_fft_right_imag_data
) * $signed(
apu_kws_twiddle_real_q30(s_twiddle_index)
s_front_coeff_data_q[31:0]
);
assign s_fft_twiddled_real = rne_shift_s32(s_fft_product_real, 6'd30);
assign s_fft_twiddled_imag = rne_shift_s32(s_fft_product_imag, 6'd30);
@@ -721,15 +752,9 @@ module apu_kws_engine (
) + $unsigned(
64'($signed(s_fft_left_imag_data)) * 64'($signed(s_fft_left_imag_data))
);
assign s_mel_product = s_magnitude_data * apu_kws_mel_q30(
14'(s_mel_band_q) * 14'd257 + 14'(s_mel_bin_q)
);
assign s_mel_product = s_magnitude_data * s_front_coeff_data_q[31:0];
assign s_mel_total = s_mel_acc_q + s_mel_product;
assign s_dct_product = $signed(
s_log_data
) * $signed(
apu_kws_dct_q30(9'(s_mfcc_coefficient_q) * 9'd40 + 9'(s_mfcc_band_q))
);
assign s_dct_product = $signed(s_log_data) * $signed(s_front_coeff_data_q[31:0]);
assign s_dct_total = s_dct_acc_q + s_dct_product;
assign s_log_row_sum = {1'b0, s_snapshot_start_q} + {1'b0, s_mfcc_row_q};
assign s_log_row_index = (s_log_row_sum >= 7'd50) ? 6'(s_log_row_sum - 7'd50) : 6'(s_log_row_sum);
@@ -978,12 +1003,76 @@ module apu_kws_engine (
end
assign s_logit_data = scratch_s8(scratch_read_data_i[4], scratch_read_addr_o[4][1:0]);
assign s_log_lookup = log_mel_lookup(s_mel_data, s_window_peak_q);
for (genvar class_index = 0; class_index < 12; class_index++) begin : gen_softmax_read
assign s_softmax_data[class_index] = scratch_s8(
scratch_read_data_i[class_index+4], scratch_read_addr_o[class_index+4][1:0]
);
end
always_comb begin
s_front_coeff_needed = 1'b0;
coeff_frontend_kind_o = 4'd0;
coeff_frontend_index_o = 14'd0;
unique case (s_front_state_q)
FrontFir: begin
s_front_coeff_needed = 1'b1;
coeff_frontend_kind_o = (s_decimation == 3'd3) ? 4'd5 : 4'd6;
coeff_frontend_index_o = {8'd0, s_fir_tap_q};
end
FrontFftLoad: begin
s_front_coeff_needed = s_front_index_q < 10'd480;
coeff_frontend_kind_o = 4'd0;
coeff_frontend_index_o = {5'd0, s_front_index_q[8:0]};
end
FrontFftRun: begin
s_front_coeff_needed = 1'b1;
coeff_frontend_kind_o = 4'd1;
coeff_frontend_index_o = {6'd0, s_twiddle_index};
end
FrontMel: begin
s_front_coeff_needed = 1'b1;
coeff_frontend_kind_o = 4'd2;
coeff_frontend_index_o = 14'(s_mel_band_q) * 14'd257 + 14'(s_mel_bin_q);
end
FrontLog: begin
s_front_coeff_needed = 1'b1;
coeff_frontend_kind_o = 4'd4;
coeff_frontend_index_o = {3'd0, s_log_lookup.index};
end
FrontDct: begin
s_front_coeff_needed = 1'b1;
coeff_frontend_kind_o = 4'd3;
coeff_frontend_index_o = 14'(s_mfcc_coefficient_q) * 14'd40 + 14'(s_mfcc_band_q);
end
default: begin
end
endcase
s_infer_coeff_needed = 1'b0;
coeff_inference_index_o = 7'd0;
if (s_infer_state_q == InferSoftmaxSum) begin
s_infer_coeff_needed = !s_coeff_operand_ready || (s_softmax_sum_diff >= -9'sd124);
coeff_inference_index_o = 7'(-s_softmax_sum_diff);
end else if ((s_infer_state_q == InferSoftmaxScale) && (s_softmax_index_q != 4'd0)) begin
s_infer_coeff_needed = !s_coeff_operand_ready || (s_softmax_scale_diff >= -9'sd124);
coeff_inference_index_o = 7'(-s_softmax_scale_diff);
end
end
assign s_coeff_operand_ready = s_storage_write_phase_q || s_storage_complete_q;
assign coeff_frontend_req_valid_o = s_front_coeff_needed && !s_front_coeff_pending_q &&
!s_front_coeff_have_q && ((s_front_state_q != FrontLog) || s_coeff_operand_ready);
assign coeff_frontend_resp_ready_o = s_front_coeff_pending_q;
assign coeff_inference_req_valid_o = s_infer_coeff_needed && !s_infer_coeff_pending_q &&
!s_infer_coeff_have_q && s_coeff_operand_ready;
assign coeff_inference_resp_ready_o = s_infer_coeff_pending_q;
assign s_coeff_progress =
(coeff_frontend_req_valid_o && coeff_frontend_req_ready_i) ||
(coeff_frontend_resp_valid_i && coeff_frontend_resp_ready_o) ||
(coeff_inference_req_valid_o && coeff_inference_req_ready_i) ||
(coeff_inference_resp_valid_i && coeff_inference_resp_ready_o);
always_comb begin
s_softmax_sum_diff = 9'($signed(s_logit_data)) - 9'($signed(s_softmax_max_q));
s_softmax_scale_diff = 9'sd0;
@@ -992,8 +1081,8 @@ module apu_kws_engine (
end
s_softmax_headroom = count_leading_zero(s_softmax_sum_q);
s_softmax_shifted_sum = 32'($unsigned(s_softmax_sum_q) << s_softmax_headroom) - 32'h8000_0000;
s_softmax_exp_value = (s_softmax_scale_diff >= -9'sd124) ?
apu_kws_softmax_exp_q31(-s_softmax_scale_diff[6:0]) : 32'sd0;
s_softmax_exp_value = (s_softmax_scale_diff >= -9'sd124) ? $signed(s_infer_coeff_data_q) :
32'sd0;
s_softmax_prob = rounding_divide_pot(
saturating_high_mul(s_softmax_scale_q, s_softmax_exp_value), s_softmax_bits_q + 6'd23);
if (s_softmax_prob > 32'sd255) s_softmax_prob = 32'sd255;
@@ -1117,7 +1206,7 @@ module apu_kws_engine (
scratch_write_valid_o[0] = 1'b1;
scratch_write_addr_o[0] =
`RETROSOC_APU_KWS__SCRATCH_FFT_IMAG_BASE + 16'(s_mfcc_band_q * 4);
scratch_write_data_o[0] = log_mel_q24(s_mel_data, s_window_peak_q);
scratch_write_data_o[0] = log_mel_result(s_log_lookup, s_front_coeff_data_q);
scratch_write_strb_o[0] = 4'hf;
end
FrontDct: begin
@@ -1169,36 +1258,96 @@ module apu_kws_engine (
assign s_storage_write_access = |scratch_write_valid_o;
assign s_storage_has_access = s_storage_read_access || s_storage_write_access;
assign storage_write_o = s_storage_write_phase_q || !s_storage_read_access;
assign storage_req_o = !s_storage_wait_q && s_storage_has_access;
assign storage_req_o = !s_storage_wait_q && !s_storage_complete_q && s_storage_has_access &&
(!s_storage_write_phase_q ||
((!s_front_coeff_needed || s_front_coeff_have_q) &&
(!s_infer_coeff_needed || s_infer_coeff_have_q)));
assign s_storage_final_done = storage_done_i &&
(s_storage_write_phase_q || !s_storage_read_access || !s_storage_write_access);
assign s_storage_state_step = (!s_storage_wait_q && !s_storage_has_access) ||
(s_storage_final_done && s_storage_state_owned_q);
assign s_storage_state_step = ((!s_storage_wait_q && !s_storage_has_access) ||
((s_storage_complete_q || s_storage_final_done) &&
s_storage_state_owned_q)) &&
(!s_front_coeff_needed || s_front_coeff_have_q) &&
(!s_infer_coeff_needed || s_infer_coeff_have_q);
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
s_storage_wait_q <= 1'b0;
s_storage_complete_q <= 1'b0;
s_storage_write_phase_q <= 1'b0;
s_storage_state_owned_q <= 1'b0;
end else if (soft_reset_i || resource_reset_i || abort_i) begin
s_storage_wait_q <= 1'b0;
s_storage_complete_q <= 1'b0;
s_storage_write_phase_q <= 1'b0;
s_storage_state_owned_q <= 1'b0;
end else begin
if (storage_req_o && storage_ready_i) begin
s_storage_wait_q <= 1'b1;
s_storage_complete_q <= 1'b0;
s_storage_state_owned_q <= (s_front_state_q != FrontIdle) || (s_infer_state_q != InferIdle);
end
if (storage_done_i && s_storage_wait_q) begin
s_storage_wait_q <= 1'b0;
if (!s_storage_write_phase_q && s_storage_read_access && s_storage_write_access) begin
s_storage_wait_q <= 1'b0;
s_storage_write_phase_q <= 1'b1;
end else begin
s_storage_wait_q <= 1'b0;
s_storage_complete_q <= s_storage_state_owned_q;
s_storage_write_phase_q <= 1'b0;
if (!s_storage_state_owned_q) s_storage_state_owned_q <= 1'b0;
end
end
if (s_storage_state_step) begin
s_storage_complete_q <= 1'b0;
s_storage_state_owned_q <= 1'b0;
end
end
end
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
s_front_coeff_pending_q <= 1'b0;
s_front_coeff_have_q <= 1'b0;
s_front_coeff_fault_q <= 1'b0;
s_front_coeff_data_q <= 64'd0;
s_infer_coeff_pending_q <= 1'b0;
s_infer_coeff_have_q <= 1'b0;
s_infer_coeff_fault_q <= 1'b0;
s_infer_coeff_data_q <= 32'd0;
end else if (soft_reset_i || resource_reset_i || abort_i) begin
s_front_coeff_pending_q <= 1'b0;
s_front_coeff_have_q <= 1'b0;
s_front_coeff_fault_q <= 1'b0;
s_infer_coeff_pending_q <= 1'b0;
s_infer_coeff_have_q <= 1'b0;
s_infer_coeff_fault_q <= 1'b0;
end else begin
if (coeff_frontend_req_valid_o && coeff_frontend_req_ready_i) begin
s_front_coeff_pending_q <= 1'b1;
end
if (coeff_frontend_resp_valid_i && coeff_frontend_resp_ready_o) begin
s_front_coeff_pending_q <= 1'b0;
s_front_coeff_have_q <= 1'b1;
s_front_coeff_fault_q <= coeff_frontend_fault_i;
s_front_coeff_data_q <= coeff_frontend_data_i;
end
if (coeff_inference_req_valid_o && coeff_inference_req_ready_i) begin
s_infer_coeff_pending_q <= 1'b1;
end
if (coeff_inference_resp_valid_i && coeff_inference_resp_ready_o) begin
s_infer_coeff_pending_q <= 1'b0;
s_infer_coeff_have_q <= 1'b1;
s_infer_coeff_fault_q <= coeff_inference_fault_i;
s_infer_coeff_data_q <= coeff_inference_data_i;
end
if (s_storage_state_step) begin
s_front_coeff_have_q <= 1'b0;
s_front_coeff_fault_q <= 1'b0;
s_infer_coeff_have_q <= 1'b0;
s_infer_coeff_fault_q <= 1'b0;
end
end
end
assign memory_dma_request_valid_o = s_mem_req_q;
@@ -1207,7 +1356,8 @@ module apu_kws_engine (
(s_infer_state_q == InferIdle) && (s_front_state_q == FrontIdle);
assign memory_dma_request_address_o = memory_address_i;
assign memory_dma_request_bytes_o = 32'd32000;
assign memory_dma_ready_o = s_mem_dma_active_q && !s_mem_word_valid_q;
assign memory_dma_ready_o = s_mem_dma_active_q && !s_mem_word_valid_q &&
!s_storage_wait_q && !s_storage_complete_q && storage_ready_i;
assign memory_done_o = s_mem_done_q;
assign memory_error_o = s_mem_err_q;
assign memory_error_code_o = s_mem_err_code_q;
@@ -1405,13 +1555,13 @@ module apu_kws_engine (
s_mem_err_detail_q <= 32'd0;
end
end else begin
if (s_front_state_q == FrontIdle || storage_progress_i ||
if (s_front_state_q == FrontIdle || storage_progress_i || s_coeff_progress ||
(s_storage_state_step && s_front_progress)) begin
s_front_watchdog_q <= 32'd0;
end else if (!(&s_front_watchdog_q)) begin
s_front_watchdog_q <= s_front_watchdog_q + 1'b1;
end
if (s_infer_state_q == InferIdle || storage_progress_i ||
if (s_infer_state_q == InferIdle || storage_progress_i || s_coeff_progress ||
(s_storage_state_step && s_infer_progress)) begin
s_infer_watchdog_q <= 32'd0;
end else if (!(&s_infer_watchdog_q)) begin
@@ -1562,7 +1712,9 @@ module apu_kws_engine (
s_s24_pending_q <= 1'b0;
end
if (s_commit_valid && ((s_front_state_q == FrontIdle) || s_storage_state_step)) begin
if (s_commit_valid &&
(((s_front_state_q == FrontIdle) && storage_req_o && storage_ready_i) ||
s_storage_state_step)) begin
s_ingress_write_q <= s_next_write;
s_epoch_samples_q <= s_next_samples;
s_timestamp_q <= s_timestamp_q + 1'b1;
@@ -1598,6 +1750,15 @@ module apu_kws_engine (
end
if (s_storage_state_step) begin
if (s_front_coeff_needed && s_front_coeff_fault_q) begin
s_fault_q <= 1'b1;
s_fault_code_q <= `APB4_APU__ERROR_CODE_KWS_ARITHMETIC;
s_fault_stage_q <= `APB4_APU__ERROR_STAGE_KWS_FRONTEND;
s_fault_addr_q <= {16'd0, coeff_frontend_index_o, 2'd0};
s_fault_detail_q <= 32'h0900_0100 | {28'd0, coeff_frontend_kind_o};
s_stop_latched_q <= 1'b1;
s_front_state_q <= FrontIdle;
end else
unique case (s_front_state_q)
FrontIdle: begin
if (s_frame_pending_q) begin
@@ -1751,6 +1912,15 @@ module apu_kws_engine (
default: s_front_state_q <= FrontIdle;
endcase
if (s_infer_coeff_needed && s_infer_coeff_fault_q) begin
s_fault_q <= 1'b1;
s_fault_code_q <= `APB4_APU__ERROR_CODE_KWS_ARITHMETIC;
s_fault_stage_q <= `APB4_APU__ERROR_STAGE_KWS_INFERENCE;
s_fault_addr_q <= {23'd0, coeff_inference_index_o, 2'd0};
s_fault_detail_q <= 32'h0900_0107;
s_stop_latched_q <= 1'b1;
s_infer_state_q <= InferIdle;
end else
unique case (s_infer_state_q)
InferIdle: begin
end
@@ -1838,7 +2008,7 @@ module apu_kws_engine (
InferSoftmaxSum: begin
if (s_softmax_sum_diff >= -9'sd124) begin
s_softmax_sum_q <= s_softmax_sum_q +
rounding_divide_pot(apu_kws_softmax_exp_q31(-s_softmax_sum_diff[6:0]), 12);
rounding_divide_pot($signed(s_infer_coeff_data_q), 12);
end
if (s_softmax_index_q == 4'd11) begin
s_softmax_index_q <= 4'd0;
+77 -19
View File
@@ -40,6 +40,13 @@ module apu_kws_model_loader (
output logic [31:0] local_data_o,
output logic [ 3:0] local_strb_o,
input logic local_ready_i,
output logic profile_request_valid_o,
input logic profile_request_ready_i,
output logic [10:0] profile_index_o,
input logic profile_response_valid_i,
output logic profile_response_ready_o,
input logic [31:0] profile_data_i,
input logic profile_fault_i,
output logic busy_o,
output logic valid_o,
output logic lock_o,
@@ -56,8 +63,6 @@ module apu_kws_model_loader (
output logic abort_done_o
// verilog_format: on
);
`include "apu_kws_apum_profile.svh"
localparam logic [31:0] ModelBytes = 32'd32768;
localparam logic [31:0] ModelCrc = 32'hb903_4b22;
localparam logic [31:0] ModelAddress = 32'hce4f_7000;
@@ -88,9 +93,12 @@ module apu_kws_model_loader (
logic [15:0][31:0] s_header_q;
logic s_padding_err_q;
logic s_profile_err_q;
logic s_profile_pending_q;
logic s_dma_complete_q;
logic [31:0] s_profile_word_q;
logic [31:0] s_profile_offset_q;
logic s_receive_err_q;
logic [31:0] s_profile_err_addr_q;
logic [ 1:0] s_profile_word_check;
logic s_profile_candidate;
logic [32:0] s_last_addr;
logic [32:0] s_snapshot_last_addr;
@@ -154,6 +162,33 @@ module apu_kws_model_loader (
end
endfunction
function automatic logic [10:0] profile_word_index(input logic [31:0] offset_i);
begin
if (offset_i <= 32'h04fc) begin
return 11'(offset_i >> 2);
end else if ((offset_i >= 32'h1000) && (offset_i <= 32'h11fc)) begin
return 11'd320 + 11'((offset_i - 32'h1000) >> 2);
end else if ((offset_i >= 32'h1540) && (offset_i <= 32'h173c)) begin
return 11'd448 + 11'((offset_i - 32'h1540) >> 2);
end else if ((offset_i >= 32'h2840) && (offset_i <= 32'h2a3c)) begin
return 11'd576 + 11'((offset_i - 32'h2840) >> 2);
end else if ((offset_i >= 32'h2d80) && (offset_i <= 32'h2f7c)) begin
return 11'd704 + 11'((offset_i - 32'h2d80) >> 2);
end else if ((offset_i >= 32'h4080) && (offset_i <= 32'h427c)) begin
return 11'd832 + 11'((offset_i - 32'h4080) >> 2);
end else if ((offset_i >= 32'h45c0) && (offset_i <= 32'h47bc)) begin
return 11'd960 + 11'((offset_i - 32'h45c0) >> 2);
end else if ((offset_i >= 32'h58c0) && (offset_i <= 32'h5abc)) begin
return 11'd1088 + 11'((offset_i - 32'h58c0) >> 2);
end else if ((offset_i >= 32'h5e00) && (offset_i <= 32'h5ffc)) begin
return 11'd1216 + 11'((offset_i - 32'h5e00) >> 2);
end else if ((offset_i >= 32'h7100) && (offset_i <= 32'h72fc)) begin
return 11'd1344 + 11'((offset_i - 32'h7100) >> 2);
end
return 11'd1472 + 11'((offset_i - 32'h7630) >> 2);
end
endfunction
assign s_last_addr = {1'b0, address_i} + {1'b0, size_i} - 1'b1;
assign s_admit_ok = (size_i == ModelBytes) && (address_i[5:0] == 6'd0) &&
(address_i >= acl_base_i) && !s_last_addr[32] && (s_last_addr[31:0] <= acl_limit_i) &&
@@ -165,16 +200,18 @@ module apu_kws_model_loader (
assign s_data_accept = dma_valid_i && dma_ready_o;
assign s_crc_after_word = crc32_word(s_crc_q, dma_data_i, dma_keep_i);
assign s_profile_candidate = profile_word_candidate(s_received_q);
assign s_profile_word_check = s_profile_candidate ? apu_kws_apum_fixed_word_check(
s_received_q[14:0], dma_data_i
) : 2'b00;
assign profile_request_valid_o = (s_state_q == ReceivePayload) && dma_valid_i &&
local_ready_i && s_profile_candidate && !s_profile_pending_q;
assign profile_index_o = profile_word_index(s_received_q);
assign profile_response_ready_o = s_profile_pending_q;
assign dma_request_valid_o = ((s_state_q == RequestHeader) ||
(s_state_q == RequestPayload)) && s_snapshot_range_ok;
assign dma_request_address_o = (s_state_q == RequestPayload) ? s_addr_q + 32'd64 : s_addr_q;
assign dma_request_bytes_o = (s_state_q == RequestPayload) ? s_size_q - 32'd64 : 32'd64;
assign dma_ready_o = ((s_state_q == ReceiveHeader) ||
(s_state_q == ReceivePayload)) && local_ready_i;
assign dma_ready_o = (s_state_q == ReceiveHeader) ||
((s_state_q == ReceivePayload) && local_ready_i && !s_profile_pending_q &&
(!s_profile_candidate || profile_request_ready_i));
assign local_request_o = s_data_accept;
assign local_address_o = KwsBase[16:0] + s_received_q[16:0];
assign local_data_o = dma_data_i;
@@ -217,6 +254,10 @@ module apu_kws_model_loader (
s_header_q <= '0;
s_padding_err_q <= 1'b0;
s_profile_err_q <= 1'b0;
s_profile_pending_q <= 1'b0;
s_dma_complete_q <= 1'b0;
s_profile_word_q <= 32'd0;
s_profile_offset_q <= 32'd0;
s_receive_err_q <= 1'b0;
s_profile_err_addr_q <= 32'd0;
s_addr_q <= 32'd0;
@@ -245,6 +286,10 @@ module apu_kws_model_loader (
s_received_q <= 32'd0;
s_padding_err_q <= 1'b0;
s_profile_err_q <= 1'b0;
s_profile_pending_q <= 1'b0;
s_dma_complete_q <= 1'b0;
s_profile_word_q <= 32'd0;
s_profile_offset_q <= 32'd0;
s_receive_err_q <= 1'b0;
s_profile_err_addr_q <= 32'd0;
s_addr_q <= 32'd0;
@@ -253,6 +298,13 @@ module apu_kws_model_loader (
s_acl_base_q <= 32'd0;
s_acl_limit_q <= 32'd0;
end else begin
if (profile_response_valid_i && profile_response_ready_o) begin
s_profile_pending_q <= 1'b0;
if ((profile_data_i != s_profile_word_q) || profile_fault_i) begin
s_profile_err_q <= 1'b1;
s_profile_err_addr_q <= s_addr_q + s_profile_offset_q;
end
end
unique case (s_state_q)
Idle: begin
if (start_i && !s_lock_q) begin
@@ -270,6 +322,10 @@ module apu_kws_model_loader (
s_header_q <= '0;
s_padding_err_q <= 1'b0;
s_profile_err_q <= 1'b0;
s_profile_pending_q <= 1'b0;
s_dma_complete_q <= 1'b0;
s_profile_word_q <= 32'd0;
s_profile_offset_q <= 32'd0;
s_receive_err_q <= 1'b0;
s_profile_err_addr_q <= 32'd0;
s_addr_q <= address_i;
@@ -475,6 +531,7 @@ module apu_kws_model_loader (
end
ReceivePayload: begin
if (dma_done_i) begin
s_dma_complete_q <= 1'b1;
if (dma_error_i) begin
s_stat_q <= 32'd0;
s_err_code_q <= dma_error_code_i;
@@ -501,15 +558,14 @@ module apu_kws_model_loader (
s_padding_err_q <= 1'b1;
s_profile_err_addr_q <= s_addr_q + s_received_q;
end
if (s_profile_candidate && !s_profile_err_q && s_profile_word_check[1] &&
!s_profile_word_check[0]) begin
s_profile_err_q <= 1'b1;
s_profile_err_addr_q <= s_addr_q + s_received_q;
if (s_profile_candidate && !s_profile_err_q) begin
s_profile_pending_q <= 1'b1;
s_profile_word_q <= dma_data_i;
s_profile_offset_q <= s_received_q;
end
s_crc_q <= s_crc_after_word;
s_actual_crc_q <= ~s_crc_after_word;
s_received_q <= s_received_q + 32'd4;
s_state_q <= Validate;
s_state_q <= WaitPayload;
end else begin
s_stat_q <= 32'h0000_0200;
s_err_code_q <= `APB4_APU__ERROR_CODE_KWS_MODEL;
@@ -539,10 +595,9 @@ module apu_kws_model_loader (
s_profile_err_addr_q <= s_addr_q + s_received_q;
end
if (s_profile_candidate && !s_profile_err_q) begin
if (s_profile_word_check[1] && !s_profile_word_check[0]) begin
s_profile_err_q <= 1'b1;
s_profile_err_addr_q <= s_addr_q + s_received_q;
end
s_profile_pending_q <= 1'b1;
s_profile_word_q <= dma_data_i;
s_profile_offset_q <= s_received_q;
end
s_crc_q <= s_crc_after_word;
s_received_q <= s_received_q + 32'd4;
@@ -554,7 +609,9 @@ module apu_kws_model_loader (
end
WaitPayload: begin
if (abort_i && !resource_reset_request_i) s_abort_pending_q <= 1'b1;
if (dma_done_i) begin
if (dma_done_i) s_dma_complete_q <= 1'b1;
if ((dma_done_i || s_dma_complete_q) && !s_profile_pending_q &&
!profile_response_valid_i) begin
if (dma_error_i) begin
s_stat_q <= 32'd0;
s_err_code_q <= dma_error_code_i;
@@ -585,6 +642,7 @@ module apu_kws_model_loader (
s_state_q <= Complete;
end else begin
s_actual_crc_q <= ~s_crc_q;
s_dma_complete_q <= 1'b0;
s_state_q <= Validate;
end
end
+44 -38
View File
@@ -110,9 +110,13 @@ module apu_microcode_loader #(
PathPop,
PathPopLoad,
PathMemoClear,
PathMemoClearWait,
PathMemoStart,
PathMemoRead,
PathMemoReadWait,
PathMemoCheck,
PathMemoInsert,
PathMemoInsertWait,
Finish,
CancelWait,
DescriptorDmaRequest,
@@ -148,9 +152,9 @@ module apu_microcode_loader #(
logic s_path_stack_cs, s_path_stack_write, s_path_stack_push;
logic [11:0] s_path_stack_addr;
logic [63:0] s_path_stack_write_data, s_path_stack_read_data;
logic [12:0] s_path_memo_addr_q, s_path_memo_probe_q, s_path_memo_clear_q;
logic [63:0] s_path_memo_key, s_path_memo_read_key_q;
logic s_path_memo_read_valid_q;
logic [12:0] s_path_memo_addr_q, s_path_memo_probe_q;
logic [63:0] s_path_memo_key, s_path_memo_read_key;
logic s_path_memo_ready, s_path_memo_done, s_path_memo_read_valid;
logic [ 2:0][63:0] s_entry_instruction_q;
logic s_path_stop_valid_q;
logic [11:0] s_path_stop_pc_q;
@@ -258,8 +262,8 @@ module apu_microcode_loader #(
assign actual_crc_o = s_actual_crc_q;
assign load_count_o = s_load_count_q;
assign proof_visit_count_o = s_path_visit_q;
assign proof_memo_full_o = (s_state_q == PathMemoCheck) && s_path_memo_read_valid_q &&
(s_path_memo_read_key_q != s_path_memo_key) && (s_path_memo_probe_q == PathMemoLast);
assign proof_memo_full_o = (s_state_q == PathMemoCheck) && s_path_memo_read_valid &&
(s_path_memo_read_key != s_path_memo_key) && (s_path_memo_probe_q == PathMemoLast);
for (genvar entry = 0; entry < 3; entry++) begin : gen_entry_outputs
assign entry_pc_o[entry] = s_header_v2 ?
@@ -522,27 +526,27 @@ module apu_microcode_loader #(
assign s_path_visit_limit = s_header_v2 ? 19'(PathTraversalLimitV2) : 19'(PathTraversalLimitV1);
if (EnableP5) begin : gen_path_memo
logic [63:0] s_key_mem [0:PathMemoDepth-1];
logic s_valid_mem[0:PathMemoDepth-1];
always_ff @(posedge clk_i) begin
if (s_state_q == PathMemoClear) begin
s_valid_mem[s_path_memo_clear_q] <= 1'b0;
end
if (s_state_q == PathMemoRead) begin
s_path_memo_read_key_q <= s_key_mem[s_path_memo_addr_q];
s_path_memo_read_valid_q <= s_valid_mem[s_path_memo_addr_q];
end
if ((s_state_q == PathMemoCheck) && !s_path_memo_read_valid_q) begin
s_key_mem[s_path_memo_addr_q] <= s_path_memo_key;
s_valid_mem[s_path_memo_addr_q] <= 1'b1;
end
end
apu_proof_memo #(
.Depth(PathMemoDepth)
) u_proof_memo (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.abort_i (resource_reset_i || abort_i),
.clear_i (s_state_q == PathMemoClear),
.lookup_i (s_state_q == PathMemoRead),
.insert_i (s_state_q == PathMemoInsert),
.addr_i (s_path_memo_addr_q),
.key_i (s_path_memo_key),
.ready_o (s_path_memo_ready),
.done_o (s_path_memo_done),
.read_valid_o(s_path_memo_read_valid),
.read_key_o (s_path_memo_read_key)
);
end else begin : gen_no_path_memo
always_comb begin
s_path_memo_read_key_q = 64'd0;
s_path_memo_read_valid_q = 1'b0;
end
assign s_path_memo_ready = 1'b1;
assign s_path_memo_done = 1'b1;
assign s_path_memo_read_valid = 1'b0;
assign s_path_memo_read_key = 64'd0;
end
`ifdef HAVE_SRAM_MACRO
@@ -777,7 +781,6 @@ module apu_microcode_loader #(
s_path_visit_q <= 19'd0;
s_path_memo_addr_q <= 13'd0;
s_path_memo_probe_q <= 13'd0;
s_path_memo_clear_q <= 13'd0;
s_path_stop_valid_q <= 1'b0;
s_path_stop_pc_q <= 12'd0;
s_entry_instruction_q <= '0;
@@ -815,7 +818,8 @@ module apu_microcode_loader #(
if ((s_state_q inside {
HeaderValidate, DescriptorValidate, Validate, ScanRequest, ScanWait, ScanCheck,
PathRequest, PathWait, PathCheck, PathPop, PathPopLoad, PathMemoClear,
PathMemoStart, PathMemoRead, PathMemoCheck, Finish
PathMemoClearWait, PathMemoStart, PathMemoRead, PathMemoReadWait,
PathMemoCheck, PathMemoInsert, PathMemoInsertWait, Finish
}) &&
(resource_reset_i || abort_i)) begin
s_state_q <= Idle;
@@ -1119,7 +1123,6 @@ module apu_microcode_loader #(
s_path_visit_q <= 19'd0;
s_path_memo_addr_q <= 13'd0;
s_path_memo_probe_q <= 13'd0;
s_path_memo_clear_q <= 13'd0;
s_path_stop_valid_q <= 1'b0;
s_state_q <= ScanRequest;
end
@@ -1190,13 +1193,9 @@ module apu_microcode_loader #(
end
end
PathMemoClear: begin
if (s_path_memo_clear_q == PathMemoLast) begin
s_path_memo_clear_q <= 13'd0;
s_state_q <= PathMemoStart;
end else begin
s_path_memo_clear_q <= s_path_memo_clear_q + 1'b1;
end
if (s_path_memo_ready) s_state_q <= PathMemoClearWait;
end
PathMemoClearWait: if (s_path_memo_done) s_state_q <= PathMemoStart;
PathMemoStart: begin
if (s_call_depth_q > 3'd2) begin
s_state_q <= PathRequest;
@@ -1206,12 +1205,15 @@ module apu_microcode_loader #(
s_state_q <= PathMemoRead;
end
end
PathMemoRead: s_state_q <= PathMemoCheck;
PathMemoRead: begin
if (s_path_memo_ready) s_state_q <= PathMemoReadWait;
end
PathMemoReadWait: if (s_path_memo_done) s_state_q <= PathMemoCheck;
PathMemoCheck: begin
if (s_path_memo_read_valid_q && (s_path_memo_read_key_q == s_path_memo_key)) begin
if (s_path_memo_read_valid && (s_path_memo_read_key == s_path_memo_key)) begin
s_state_q <= PathPop;
end else if (!s_path_memo_read_valid_q) begin
s_state_q <= PathRequest;
end else if (!s_path_memo_read_valid) begin
s_state_q <= PathMemoInsert;
end else if (s_path_memo_probe_q == PathMemoLast) begin
// A full table cannot weaken or reject the proof; traverse this state normally.
s_state_q <= PathRequest;
@@ -1221,6 +1223,10 @@ module apu_microcode_loader #(
s_state_q <= PathMemoRead;
end
end
PathMemoInsert: begin
if (s_path_memo_ready) s_state_q <= PathMemoInsertWait;
end
PathMemoInsertWait: if (s_path_memo_done) s_state_q <= PathRequest;
PathRequest: begin
if (s_path_visit_q >= s_path_visit_limit) begin
if (!s_scan_control_err_q || (entry_pc_o[s_scan_entry_q] < s_scan_control_pc_q)) begin
+207
View File
@@ -0,0 +1,207 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// SPDX-License-Identifier: MulanPSL-2.0
module apu_proof_memo #(
parameter int unsigned Depth = 8192
) (
input logic clk_i,
input logic rst_n_i,
input logic abort_i,
input logic clear_i,
input logic lookup_i,
input logic insert_i,
input logic [12:0] addr_i,
input logic [63:0] key_i,
output logic ready_o,
output logic done_o,
output logic read_valid_o,
output logic [63:0] read_key_o
);
localparam int unsigned BitmapRows = (Depth + 31) / 32;
localparam logic [7:0] BitmapLast = 8'(BitmapRows - 1);
typedef enum logic [2:0] {
Idle,
LookupWait,
InsertBitmapWrite,
ClearBitmap,
ClearDrain
} memo_state_e;
memo_state_e s_state_q;
logic [12:0] s_addr_q;
logic [63:0] s_key_q;
logic [ 7:0] s_clear_row_q;
logic [ 2:0] s_read_bank_q;
logic [ 7:0] s_key_cs;
logic [ 7:0] s_key_wren;
logic [ 7:0][31:0] s_key_low_rdata;
logic [ 7:0][31:0] s_key_high_rdata;
logic s_bitmap_cs;
logic s_bitmap_wren;
logic [ 9:0] s_bitmap_addr;
logic [31:0] s_bitmap_wdata;
logic [31:0] s_bitmap_rdata;
`ifndef SYNTHESIS
initial begin
if ((Depth == 0) || (Depth > 8192) || ((Depth & (Depth - 1)) != 0)) begin
$fatal(1, "apu_proof_memo: depth must be a power of two up to 8192");
end
end
`endif
assign ready_o = s_state_q == Idle;
always_comb begin
s_key_cs = 8'd0;
s_key_wren = 8'd0;
s_bitmap_cs = 1'b0;
s_bitmap_wren = 1'b0;
s_bitmap_addr = 10'd0;
s_bitmap_wdata = 32'd0;
if ((s_state_q == Idle) && clear_i) begin
s_bitmap_cs = 1'b1;
s_bitmap_wren = 1'b1;
s_bitmap_addr = 10'd0;
s_bitmap_wdata = 32'd0;
end else if ((s_state_q == Idle) && lookup_i) begin
s_key_cs[addr_i[12:10]] = 1'b1;
s_bitmap_cs = 1'b1;
s_bitmap_addr = {2'd0, addr_i[12:5]};
end else if ((s_state_q == Idle) && insert_i) begin
s_key_cs[addr_i[12:10]] = 1'b1;
s_key_wren[addr_i[12:10]] = 1'b1;
s_bitmap_cs = 1'b1;
s_bitmap_addr = {2'd0, addr_i[12:5]};
end else if (s_state_q == InsertBitmapWrite) begin
s_bitmap_cs = 1'b1;
s_bitmap_wren = 1'b1;
s_bitmap_addr = {2'd0, s_addr_q[12:5]};
s_bitmap_wdata = s_bitmap_rdata | (32'd1 << s_addr_q[4:0]);
end else if (s_state_q == ClearBitmap) begin
s_bitmap_cs = 1'b1;
s_bitmap_wren = 1'b1;
s_bitmap_addr = {2'd0, s_clear_row_q};
s_bitmap_wdata = 32'd0;
end
end
`ifdef HAVE_SRAM_MACRO
for (genvar bank = 0; bank < 8; bank++) begin : gen_key_bank
tc_sram_1024x32 u_key_low_sram (
.clk_i (clk_i),
.cs_i (s_key_cs[bank]),
.addr_i((s_state_q == Idle) ? addr_i[9:0] : s_addr_q[9:0]),
.data_i((s_state_q == Idle) ? key_i[31:0] : s_key_q[31:0]),
.mask_i(4'hf),
.wren_i(s_key_wren[bank]),
.data_o(s_key_low_rdata[bank])
);
tc_sram_1024x32 u_key_high_sram (
.clk_i (clk_i),
.cs_i (s_key_cs[bank]),
.addr_i((s_state_q == Idle) ? addr_i[9:0] : s_addr_q[9:0]),
.data_i((s_state_q == Idle) ? key_i[63:32] : s_key_q[63:32]),
.mask_i(4'hf),
.wren_i(s_key_wren[bank]),
.data_o(s_key_high_rdata[bank])
);
end
tc_sram_1024x32 u_valid_bitmap_sram (
.clk_i (clk_i),
.cs_i (s_bitmap_cs),
.addr_i(s_bitmap_addr),
.data_i(s_bitmap_wdata),
.mask_i(4'hf),
.wren_i(s_bitmap_wren),
.data_o(s_bitmap_rdata)
);
`else
logic [63:0] s_key_mem [0:Depth-1];
logic [31:0] s_bitmap_mem[0:BitmapRows-1];
logic [12:0] s_key_access_addr;
logic [63:0] s_key_access_data;
assign s_key_access_addr = (s_state_q == Idle) ? addr_i : s_addr_q;
assign s_key_access_data = (s_state_q == Idle) ? key_i : s_key_q;
always_ff @(posedge clk_i) begin
if (|s_key_cs) begin
if (|s_key_wren) begin
s_key_mem[s_key_access_addr] <= s_key_access_data;
end else begin
s_key_low_rdata[s_key_access_addr[12:10]] <= s_key_mem[s_key_access_addr][31:0];
s_key_high_rdata[s_key_access_addr[12:10]] <= s_key_mem[s_key_access_addr][63:32];
end
end
if (s_bitmap_cs) begin
if (s_bitmap_wren) begin
s_bitmap_mem[s_bitmap_addr[7:0]] <= s_bitmap_wdata;
end else begin
s_bitmap_rdata <= s_bitmap_mem[s_bitmap_addr[7:0]];
end
end
end
`endif
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
s_state_q <= Idle;
s_addr_q <= 13'd0;
s_key_q <= 64'd0;
s_clear_row_q <= 8'd0;
s_read_bank_q <= 3'd0;
done_o <= 1'b0;
read_valid_o <= 1'b0;
read_key_o <= 64'd0;
end else begin
done_o <= 1'b0;
if (abort_i) begin
s_state_q <= Idle;
s_clear_row_q <= 8'd0;
end else begin
unique case (s_state_q)
Idle: begin
if (clear_i) begin
s_clear_row_q <= 8'd1;
s_state_q <= (BitmapRows == 1) ? ClearDrain : ClearBitmap;
end else if (lookup_i) begin
s_addr_q <= addr_i;
s_key_q <= key_i;
s_read_bank_q <= addr_i[12:10];
s_state_q <= LookupWait;
end else if (insert_i) begin
s_addr_q <= addr_i;
s_key_q <= key_i;
s_state_q <= InsertBitmapWrite;
end
end
LookupWait: begin
read_key_o <= {s_key_high_rdata[s_read_bank_q], s_key_low_rdata[s_read_bank_q]};
read_valid_o <= s_bitmap_rdata[s_addr_q[4:0]];
done_o <= 1'b1;
s_state_q <= Idle;
end
InsertBitmapWrite: begin
done_o <= 1'b1;
s_state_q <= Idle;
end
ClearBitmap: begin
if (s_clear_row_q == BitmapLast) begin
s_clear_row_q <= 8'd0;
s_state_q <= ClearDrain;
end else begin
s_clear_row_q <= s_clear_row_q + 1'b1;
end
end
ClearDrain: begin
done_o <= 1'b1;
s_state_q <= Idle;
end
default: s_state_q <= Idle;
endcase
end
end
end
endmodule
+112 -25
View File
@@ -41,7 +41,7 @@ module apu_reg #(
input logic [31:0] sequencer_status_i,
input logic [31:0] sequencer_retired_i,
input logic sequencer_trapped_i,
input logic [10:0] irq_set_i,
input logic [11:0] irq_set_i,
input logic fault_valid_i,
input logic [5:0] fault_code_i,
input logic [3:0] fault_stage_i,
@@ -59,6 +59,9 @@ module apu_reg #(
input logic [31:0] kws_overrun_count_i,
input logic [31:0] kws_model_status_i,
input logic [31:0] kws_model_actual_crc_i,
input logic [31:0] kws_coeff_status_i,
input logic [31:0] kws_coeff_actual_crc_i,
input logic [63:0] kws_coefficient_id_i,
input logic kws_config_publish_i,
input logic [15:0] kws_config_default_i,
input logic kws_disable_done_i,
@@ -77,6 +80,7 @@ module apu_reg #(
output logic ring_kick_o,
output logic microcode_load_o,
output logic model_load_o,
output logic coefficient_load_o,
output logic counter_clear_o,
output logic perf_enable_o,
output logic xrun_clear_o,
@@ -100,6 +104,9 @@ module apu_reg #(
output logic [31:0] kws_model_address_o,
output logic [31:0] kws_model_size_o,
output logic [31:0] kws_model_expected_crc_o,
output logic [31:0] kws_coeff_address_o,
output logic [31:0] kws_coeff_size_o,
output logic [31:0] kws_coeff_expected_crc_o,
output logic [1:0] kws_control_o,
output logic [15:0] kws_config_o,
output logic [31:0] kws_input_config_o,
@@ -110,14 +117,15 @@ module apu_reg #(
// verilog_format: on
);
localparam logic [31:0] IpId = 32'h4150_5530;
localparam logic [31:0] IpVersion = (EnableP5 || EnableP7) ? 32'h0001_0001 : 32'h0001_0000;
localparam logic [31:0] IpVersion = EnableP7 ? 32'h0001_0002 :
(EnableP5 ? 32'h0001_0001 : 32'h0001_0000);
localparam logic [31:0] P5Capability0 = EnableP5 ? 32'h0000_01bd : 32'h0000_0198;
// EnableP7 advertises the qualified KWS capability after the frozen release
// gates pass; the MP3 bit 1 stays zero for the deferred P6.
localparam logic [31:0] Capability0 = EnableP7 ? 32'h0000_01fd : P5Capability0;
localparam logic [31:0] Capability0 = EnableP7 ? 32'h0000_03fd : P5Capability0;
localparam logic [31:0] Capability1 = (EnableP5 || EnableP7) ? 32'h0182_7020 : 32'h0182_7010;
localparam logic [31:0] AbiDigest = EnableP7 ? 32'hf500_5d7c : 32'd0;
localparam logic [10:0] IrqMask = 11'h7ff;
localparam logic [31:0] AbiDigest = EnableP7 ? 32'h63e9_6066 : 32'd0;
localparam logic [11:0] IrqMask = EnableP7 ? 12'hfff : 12'h7ff;
localparam logic [31:0] TimeoutReset = 32'h0000_ffff;
localparam logic [31:0] RingCoalesceReset = 32'h0001_0001;
localparam logic [31:0] KwsConfigReset = 32'h0000_0380;
@@ -143,13 +151,14 @@ module apu_reg #(
logic s_ring_kick;
logic s_microcode_load;
logic s_model_load;
logic s_coefficient_load;
logic s_kws_clear_history;
logic s_cnt_clear;
logic [10:0] s_irq_clear;
logic [10:0] s_irq_set;
logic [11:0] s_irq_clear;
logic [11:0] s_irq_set;
logic [10:0] s_irq_state_d, s_irq_state_q;
logic [10:0] s_irq_en_d, s_irq_en_q;
logic [11:0] s_irq_state_d, s_irq_state_q;
logic [11:0] s_irq_en_d, s_irq_en_q;
logic [31:0] s_err_stat_d, s_err_stat_q;
logic [31:0] s_err_addr_d, s_err_addr_q;
logic [31:0] s_err_detail_d, s_err_detail_q;
@@ -160,6 +169,7 @@ module apu_reg #(
logic [7:0][31:0] s_job_cfg_d, s_job_cfg_q;
logic [4:0][31:0] s_ring_cfg_d, s_ring_cfg_q;
logic [2:0][31:0] s_kws_model_cfg_d, s_kws_model_cfg_q;
logic [2:0][31:0] s_kws_coeff_cfg_d, s_kws_coeff_cfg_q;
logic [15:0] s_kws_cfg_d, s_kws_cfg_q;
logic [31:0] s_kws_input_cfg_d, s_kws_input_cfg_q;
logic [1:0] s_kws_control_d, s_kws_control_q;
@@ -171,6 +181,7 @@ module apu_reg #(
logic s_err_clear;
logic s_unused_snapshot;
logic [32:0] s_mc_image_end;
logic [32:0] s_kws_coeff_image_end;
logic s_kws_disable_write;
function automatic logic [31:0] apply_wstrb(
@@ -201,13 +212,14 @@ module apu_reg #(
assign apb4.prdata = s_apb4_rdata_q;
assign apb4.pslverr = s_apb4_resp_err_q;
assign idle_o = core_idle_i;
assign irq_o = (s_irq_state_q & s_irq_en_q) != 11'd0;
assign irq_o = (s_irq_state_q & s_irq_en_q) != 12'd0;
assign soft_reset_o = s_soft_reset;
assign abort_o = s_abort;
assign direct_start_o = s_direct_start;
assign ring_kick_o = s_ring_kick;
assign microcode_load_o = s_microcode_load;
assign model_load_o = s_model_load;
assign coefficient_load_o = s_coefficient_load;
assign kws_clear_history_o = s_kws_clear_history;
assign counter_clear_o = s_cnt_clear;
assign perf_enable_o = s_perf_en_q;
@@ -232,6 +244,9 @@ module apu_reg #(
assign kws_model_address_o = s_kws_model_cfg_q[0];
assign kws_model_size_o = s_kws_model_cfg_q[1];
assign kws_model_expected_crc_o = s_kws_model_cfg_q[2];
assign kws_coeff_address_o = s_kws_coeff_cfg_q[0];
assign kws_coeff_size_o = s_kws_coeff_cfg_q[1];
assign kws_coeff_expected_crc_o = s_kws_coeff_cfg_q[2];
assign kws_control_o = s_kws_control_q;
assign kws_config_o = s_kws_cfg_q;
assign kws_input_config_o = s_kws_input_cfg_q;
@@ -243,6 +258,7 @@ module apu_reg #(
(s_offset == `APB4_APU__ERROR_STATUS) && s_strobed_write[0];
assign s_unused_snapshot = ^s_perf_snapshot_q[3] ^ ^s_perf_snapshot_q[7] ^ ^s_perf_snapshot_q[8];
assign s_mc_image_end = {1'b0, s_mc_cfg_q[0]} + {1'b0, s_mc_cfg_q[1]} - 1'b1;
assign s_kws_coeff_image_end = {1'b0, s_kws_coeff_cfg_q[0]} + {1'b0, s_kws_coeff_cfg_q[1]} - 1'b1;
always_comb begin
s_read_data = 32'd0;
@@ -267,8 +283,8 @@ module apu_reg #(
kws_status_i[0],
mc_status_i[`APB4_APU__MC_STATUS_VALID]
};
`APB4_APU__IRQ_STATE: s_read_data = {21'd0, s_irq_state_q};
`APB4_APU__IRQ_ENABLE: s_read_data = {21'd0, s_irq_en_q};
`APB4_APU__IRQ_STATE: s_read_data = {20'd0, s_irq_state_q};
`APB4_APU__IRQ_ENABLE: s_read_data = {20'd0, s_irq_en_q};
`APB4_APU__ERROR_STATUS: s_read_data = s_err_stat_q;
`APB4_APU__ERROR_ADDRESS: s_read_data = s_err_addr_q;
`APB4_APU__ERROR_DETAIL: s_read_data = s_err_detail_q;
@@ -302,6 +318,38 @@ module apu_reg #(
`APB4_APU__KWS_OVERRUN_COUNT: s_read_data = kws_overrun_count_i;
`APB4_APU__KWS_MODEL_STATUS: s_read_data = kws_model_status_i;
`APB4_APU__KWS_MODEL_ACTUAL_CRC: s_read_data = kws_model_actual_crc_i;
`APB4_APU__KWS_COEFF_ADDRESS: begin
s_read_data = s_kws_coeff_cfg_q[0];
s_read_err = !EnableP7;
end
`APB4_APU__KWS_COEFF_SIZE: begin
s_read_data = s_kws_coeff_cfg_q[1];
s_read_err = !EnableP7;
end
`APB4_APU__KWS_COEFF_EXPECTED_CRC: begin
s_read_data = s_kws_coeff_cfg_q[2];
s_read_err = !EnableP7;
end
`APB4_APU__KWS_COEFF_STATUS: begin
s_read_data = kws_coeff_status_i;
s_read_err = !EnableP7;
end
`APB4_APU__KWS_COEFF_ACTUAL_CRC: begin
s_read_data = kws_coeff_actual_crc_i;
s_read_err = !EnableP7;
end
`APB4_APU__KWS_COEFF_ID_LO: begin
s_read_data = kws_coefficient_id_i[31:0];
s_read_err = !EnableP7;
end
`APB4_APU__KWS_COEFF_ID_HI: begin
s_read_data = kws_coefficient_id_i[63:32];
s_read_err = !EnableP7;
end
`APB4_APU__KWS_COEFF_CAPACITY: begin
s_read_data = 32'h0000_f000;
s_read_err = !EnableP7;
end
`APB4_APU__STREAM_WATERMARK: s_read_data = {16'd0, s_stream_watermark_q};
`APB4_APU__MC_IMAGE_ADDRESS: s_read_data = s_mc_cfg_q[0];
`APB4_APU__MC_IMAGE_SIZE: s_read_data = s_mc_cfg_q[1];
@@ -358,7 +406,8 @@ module apu_reg #(
`APB4_APU__PERF_KWS_CYCLES_LO, `APB4_APU__PERF_KWS_CYCLES_HI: s_read_data = 32'd0;
`APB4_APU__PERF_FAULTS_LO: s_read_data = s_perf_snapshot_q[9][31:0];
`APB4_APU__PERF_FAULTS_HI: s_read_data = s_perf_snapshot_q[9][63:32];
`APB4_APU__COMMAND, `APB4_APU__IRQ_TEST, `APB4_APU__RING_DOORBELL: begin
`APB4_APU__COMMAND, `APB4_APU__IRQ_TEST, `APB4_APU__RING_DOORBELL,
`APB4_APU__KWS_COEFF_COMMAND: begin
s_read_data = 32'd0;
s_read_err = 1'b1;
end
@@ -394,7 +443,8 @@ module apu_reg #(
(!EnableP7 || !core_idle_i || !quiesce_i || (owner_i != 2'd0) ||
(s_kws_model_cfg_q[0][5:0] != 6'd0) ||
(s_kws_model_cfg_q[1] != 32'd32768) ||
kws_model_status_i[2] || s_kws_control_q[0] ||
kws_model_status_i[2] || (kws_coeff_status_i[2:1] != 2'b11) ||
s_kws_control_q[0] ||
(s_kws_model_cfg_q[0] < s_acl_q[0]) ||
(({1'b0, s_kws_model_cfg_q[0]} + 33'd32768 - 33'd1) >
{1'b0, s_acl_q[1]}))) ||
@@ -404,7 +454,7 @@ module apu_reg #(
end
`APB4_APU__IRQ_STATE: s_write_err = (s_strobed_write & ~32'(IrqMask)) != 32'd0;
`APB4_APU__IRQ_ENABLE: begin
s_merged_write = apply_wstrb({21'd0, s_irq_en_q}, apb4.pwdata, apb4.pstrb);
s_merged_write = apply_wstrb({20'd0, s_irq_en_q}, apb4.pwdata, apb4.pstrb);
s_write_err = (s_merged_write & ~32'(IrqMask)) != 32'd0;
end
`APB4_APU__IRQ_TEST:
@@ -449,6 +499,25 @@ module apu_reg #(
s_write_err = (apb4.pstrb != 4'hf) || (apb4.pwdata == 32'd0) ||
!core_idle_i || (owner_i != 2'd0) ||
((s_offset == `APB4_APU__KWS_MODEL_SIZE) && kws_model_status_i[2]);
`APB4_APU__KWS_COEFF_ADDRESS:
s_write_err = !EnableP7 || (apb4.pstrb != 4'hf) || (apb4.pwdata[5:0] != 6'd0) ||
!core_idle_i || (owner_i != 2'd0) || kws_coeff_status_i[2];
`APB4_APU__KWS_COEFF_SIZE:
s_write_err = !EnableP7 || (apb4.pstrb != 4'hf) || (apb4.pwdata != 32'd61504) ||
!core_idle_i || (owner_i != 2'd0) || kws_coeff_status_i[2];
`APB4_APU__KWS_COEFF_EXPECTED_CRC:
s_write_err = !EnableP7 || (apb4.pstrb != 4'hf) || !core_idle_i ||
(owner_i != 2'd0) || kws_coeff_status_i[2];
`APB4_APU__KWS_COEFF_COMMAND: begin
s_write_unsupported = !EnableP7;
s_write_err = s_write_unsupported || (apb4.pstrb != 4'hf) ||
(apb4.pwdata != 32'd1) || !core_idle_i || !quiesce_i ||
(owner_i != 2'd0) || s_kws_control_q[0] || kws_coeff_status_i[2] ||
kws_model_status_i[1] || (s_kws_coeff_cfg_q[0][5:0] != 6'd0) ||
(s_kws_coeff_cfg_q[1] != 32'd61504) || s_kws_coeff_image_end[32] ||
(s_kws_coeff_cfg_q[0] < s_acl_q[0]) ||
(s_kws_coeff_image_end[31:0] > s_acl_q[1]);
end
`APB4_APU__JOB_CONTROL: begin
s_merged_write = apply_wstrb(s_job_cfg_q[0], apb4.pwdata, apb4.pstrb);
s_write_err = (s_merged_write[31:12] != 20'd0) ||
@@ -494,7 +563,8 @@ module apu_reg #(
(s_kws_control_q[0] &&
((apb4.pwdata[1] != s_kws_control_q[1]) || apb4.pwdata[2])) ||
((apb4.pwdata[0] && !s_kws_control_q[0]) &&
((kws_model_status_i[2:1] != 2'd3) || !core_idle_i)) ||
((kws_model_status_i[2:1] != 2'd3) ||
(kws_coeff_status_i[2:1] != 2'd3) || !core_idle_i)) ||
(!core_idle_i && !s_kws_disable_write);
end
`APB4_APU__KWS_CONFIG: begin
@@ -530,7 +600,9 @@ module apu_reg #(
`APB4_APU__KWS_TIMESTAMP_HI, `APB4_APU__KWS_FRAME_COUNT,
`APB4_APU__KWS_INFERENCE_COUNT, `APB4_APU__KWS_HIT_COUNT,
`APB4_APU__KWS_OVERRUN_COUNT, `APB4_APU__KWS_MODEL_STATUS,
`APB4_APU__KWS_MODEL_ACTUAL_CRC,
`APB4_APU__KWS_MODEL_ACTUAL_CRC, `APB4_APU__KWS_COEFF_STATUS,
`APB4_APU__KWS_COEFF_ACTUAL_CRC, `APB4_APU__KWS_COEFF_ID_LO,
`APB4_APU__KWS_COEFF_ID_HI, `APB4_APU__KWS_COEFF_CAPACITY,
`APB4_APU__PERF_STATUS,
`APB4_APU__PERF_ACTIVE_CYCLES_LO, `APB4_APU__PERF_ACTIVE_CYCLES_HI,
`APB4_APU__PERF_INPUT_BYTES_LO, `APB4_APU__PERF_INPUT_BYTES_HI,
@@ -561,6 +633,7 @@ module apu_reg #(
s_job_cfg_d = s_job_cfg_q;
s_ring_cfg_d = s_ring_cfg_q;
s_kws_model_cfg_d = s_kws_model_cfg_q;
s_kws_coeff_cfg_d = s_kws_coeff_cfg_q;
s_kws_cfg_d = s_kws_cfg_q;
s_kws_input_cfg_d = s_kws_input_cfg_q;
s_kws_control_d = s_kws_control_q;
@@ -574,9 +647,10 @@ module apu_reg #(
s_ring_kick = 1'b0;
s_microcode_load = 1'b0;
s_model_load = 1'b0;
s_coefficient_load = 1'b0;
s_kws_clear_history = 1'b0;
s_cnt_clear = 1'b0;
s_irq_clear = 11'd0;
s_irq_clear = 12'd0;
s_irq_set = irq_set_i;
if (s_write && !s_write_err) begin
@@ -591,9 +665,9 @@ module apu_reg #(
s_ring_kick = apb4.pwdata[`APB4_APU__COMMAND_RING_KICK];
end
`APB4_APU__RING_DOORBELL: s_ring_kick = 1'b1;
`APB4_APU__IRQ_STATE: s_irq_clear = s_strobed_write[10:0];
`APB4_APU__IRQ_ENABLE: s_irq_en_d = s_merged_write[10:0];
`APB4_APU__IRQ_TEST: s_irq_set = s_irq_set | apb4.pwdata[10:0];
`APB4_APU__IRQ_STATE: s_irq_clear = s_strobed_write[11:0];
`APB4_APU__IRQ_ENABLE: s_irq_en_d = s_merged_write[11:0];
`APB4_APU__IRQ_TEST: s_irq_set = s_irq_set | apb4.pwdata[11:0];
`APB4_APU__ERROR_STATUS: begin
if (s_strobed_write[0]) begin
s_err_stat_d = 32'd0;
@@ -628,6 +702,10 @@ module apu_reg #(
`APB4_APU__KWS_MODEL_ADDRESS: s_kws_model_cfg_d[0] = apb4.pwdata;
`APB4_APU__KWS_MODEL_SIZE: s_kws_model_cfg_d[1] = apb4.pwdata;
`APB4_APU__KWS_MODEL_EXPECTED_CRC: s_kws_model_cfg_d[2] = apb4.pwdata;
`APB4_APU__KWS_COEFF_ADDRESS: s_kws_coeff_cfg_d[0] = apb4.pwdata;
`APB4_APU__KWS_COEFF_SIZE: s_kws_coeff_cfg_d[1] = apb4.pwdata;
`APB4_APU__KWS_COEFF_EXPECTED_CRC: s_kws_coeff_cfg_d[2] = apb4.pwdata;
`APB4_APU__KWS_COEFF_COMMAND: s_coefficient_load = 1'b1;
`APB4_APU__KWS_CONTROL: begin
if (s_kws_disable_write) begin
if (kws_disable_done_i) begin
@@ -694,8 +772,8 @@ module apu_reg #(
s_irq_state_d = (s_irq_state_d & ~s_irq_clear) | s_irq_set;
if (s_soft_reset || resource_reset_apply_i) begin
s_irq_state_d = 11'd0;
s_irq_en_d = 11'd0;
s_irq_state_d = 12'd0;
s_irq_en_d = 12'd0;
s_err_stat_d = 32'd0;
s_err_addr_d = 32'd0;
s_err_detail_d = 32'd0;
@@ -706,6 +784,7 @@ module apu_reg #(
s_job_cfg_d = '0;
s_ring_cfg_d = RingReset;
s_kws_model_cfg_d = '0;
s_kws_coeff_cfg_d = '0;
s_kws_cfg_d = KwsConfigReset[15:0];
s_kws_control_d = 2'd0;
s_kws_input_cfg_d = KwsInputConfigReset;
@@ -758,7 +837,7 @@ module apu_reg #(
.dat_o (s_apb4_rdata_q)
);
dffr #(
.DATA_WIDTH(11)
.DATA_WIDTH(12)
) u_irq_state_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
@@ -766,7 +845,7 @@ module apu_reg #(
.dat_o (s_irq_state_q)
);
dffr #(
.DATA_WIDTH(11)
.DATA_WIDTH(12)
) u_irq_enable_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
@@ -856,6 +935,14 @@ module apu_reg #(
.dat_i (s_kws_model_cfg_d),
.dat_o (s_kws_model_cfg_q)
);
dffr #(
.DATA_WIDTH(3 * 32)
) u_kws_coeff_cfg_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_kws_coeff_cfg_d),
.dat_o (s_kws_coeff_cfg_q)
);
dffrc #(
.DATA_WIDTH(16),
.RESET_VAL (KwsConfigReset[15:0])
+3
View File
@@ -130,8 +130,11 @@
/ip/multimedia/apu_ring_scheduler.sv
/ip/multimedia/apu_stream_router.sv
/ip/multimedia/apu_control_store.sv
/ip/multimedia/apu_proof_memo.sv
/ip/multimedia/apu_microcode_loader.sv
/ip/multimedia/apu_local_sram.sv
/ip/multimedia/apu_kws_coeff_store.sv
/ip/multimedia/apu_kws_coeff_loader.sv
/ip/multimedia/apu_kws_engine.sv
/ip/multimedia/apu_kws_sram_client.sv
/ip/multimedia/apu_kws_model_loader.sv
+7
View File
@@ -97,6 +97,13 @@ module apu_kws_formal_design (
.local_data_o (),
.local_strb_o (),
.local_ready_i (1'b1),
.profile_request_valid_o (),
.profile_request_ready_i (1'b1),
.profile_index_o (),
.profile_response_valid_i(1'b0),
.profile_response_ready_o(),
.profile_data_i (32'd0),
.profile_fault_i (1'b0),
.busy_o (loader_busy),
.valid_o (valid),
.lock_o (lock),
+106
View File
@@ -0,0 +1,106 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// SPDX-License-Identifier: MulanPSL-2.0
module apu_p9_formal_design (
input logic clk_i,
output logic rst_n_i,
output logic f_past_valid,
output logic busy,
output logic valid,
output logic lock,
output logic [31:0] actual_crc,
output logic [63:0] coefficient_id,
output logic [31:0] status,
output logic [ 5:0] error_code,
output logic done,
output logic abort_done,
output logic dma_request_valid,
output logic [31:0] dma_request_address,
output logic [31:0] dma_request_bytes,
output logic store_active,
output logic store_req,
output logic store_write,
output logic [13:0] store_addr
);
logic s_start_q;
(* anyseq *) logic s_abort;
(* anyseq *) logic s_dma_request_ready;
(* anyseq *) logic s_dma_valid;
(* anyseq *) logic s_dma_done;
(* anyseq *) logic s_dma_error;
(* anyseq *) logic [31:0] s_dma_data;
(* anyseq *) logic s_store_ready;
(* anyseq *) logic s_store_valid;
(* anyseq *) logic [31:0] s_store_data;
(* anyseq *) logic s_store_fault;
apu_kws_coeff_loader u_dut (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.start_i (s_start_q),
.abort_i (s_abort),
.soft_reset_i (1'b0),
.resource_reset_request_i(1'b0),
.resource_reset_i (1'b0),
.quiesce_i (1'b1),
.address_i (32'h1000_0000),
.size_i (32'd61504),
.expected_crc_i (32'h25e7_c27d),
.acl_base_i (32'h1000_0000),
.acl_limit_i (32'h1000_f03f),
.dma_request_valid_o (dma_request_valid),
.dma_request_ready_i (s_dma_request_ready),
.dma_request_address_o (dma_request_address),
.dma_request_bytes_o (dma_request_bytes),
.dma_data_i (s_dma_data),
.dma_keep_i (4'hf),
.dma_last_i (1'b0),
.dma_valid_i (s_dma_valid),
.dma_ready_o (),
.dma_done_i (s_dma_done),
.dma_error_i (s_dma_error),
.dma_error_code_i (6'd15),
.dma_error_stage_i (4'd3),
.dma_error_resp_i (2'd2),
.dma_error_address_i (32'h1000_0040),
.store_active_o (store_active),
.store_req_o (store_req),
.store_write_o (store_write),
.store_addr_o (store_addr),
.store_data_o (),
.store_ready_i (s_store_ready),
.store_valid_i (s_store_valid),
.store_data_i (s_store_data),
.store_fault_i (s_store_fault),
.busy_o (busy),
.valid_o (valid),
.lock_o (lock),
.actual_crc_o (actual_crc),
.coefficient_id_o (coefficient_id),
.status_o (status),
.error_code_o (error_code),
.error_stage_o (),
.error_resp_o (),
.error_address_o (),
.error_detail_o (),
.done_o (done),
.abort_done_o (abort_done)
);
initial begin
rst_n_i = 1'b0;
f_past_valid = 1'b0;
s_start_q = 1'b0;
end
always_ff @(posedge clk_i) begin
rst_n_i <= 1'b1;
f_past_valid <= 1'b1;
s_start_q <= !f_past_valid;
if (rst_n_i) begin
assume (!s_dma_valid || busy);
assume (!s_dma_done || busy);
assume (!s_store_valid || store_active);
end
end
endmodule
+53
View File
@@ -0,0 +1,53 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// SPDX-License-Identifier: MulanPSL-2.0
module apu_p9_formal;
(* anyseq *) (* gclk *) reg clk_i;
wire rst_n_i;
wire f_past_valid;
wire busy;
wire valid;
wire lock;
wire [31:0] actual_crc;
wire [63:0] coefficient_id;
wire [31:0] status;
wire [5:0] error_code;
wire done;
wire abort_done;
wire dma_request_valid;
wire [31:0] dma_request_address;
wire [31:0] dma_request_bytes;
wire store_active;
wire store_req;
wire store_write;
wire [13:0] store_addr;
apu_p9_formal_design u_design (.*);
always @(posedge clk_i) begin
if (rst_n_i) begin
assert (valid == lock);
assert (!(done && abort_done));
assert (!store_req || store_active);
assert (!store_req || (store_addr < 14'd15360));
if (store_write) assert (store_active && busy);
if (valid) begin
assert (actual_crc == 32'h25e7_c27d);
assert (coefficient_id == 64'h806c_780d_8a0b_038d);
assert (status[2:1] == 2'b11);
end
if (done && valid) assert (!busy);
if (done && (error_code != 6'd0)) begin
assert (!valid);
assert (!lock);
end
if (dma_request_valid) begin
assert (busy);
assert ((dma_request_address == 32'h1000_0000) || (dma_request_address == 32'h1000_0040));
assert ((dma_request_bytes == 32'd64) || (dma_request_bytes == 32'd61440));
end
end
cover (rst_n_i && dma_request_valid);
cover (rst_n_i && abort_done && !done);
end
endmodule
@@ -324,6 +324,11 @@ def source_files(target: str) -> list[Path]:
MULTIMEDIA / "apu_codec_controller.sv",
SCRIPT_DIR / "apu_kws_formal.sv",
]
if target == "apu_p9":
return [
MULTIMEDIA / "apu_kws_coeff_loader.sv",
SCRIPT_DIR / "apu_p9_formal.sv",
]
if target == "apu_primitives" or target in APU_PRIMITIVE_SCENARIOS:
return [
COMMON_RTL / "utils/fifo.sv",
@@ -342,6 +347,7 @@ def source_files(target: str) -> list[Path]:
return [
ROOT / "rtl/tech/tc_sram.sv",
MULTIMEDIA / "apu_microcode_pkg.sv",
MULTIMEDIA / "apu_proof_memo.sv",
MULTIMEDIA / "apu_microcode_loader.sv",
SCRIPT_DIR / "apu_loader_formal.sv",
]
@@ -453,6 +459,7 @@ def parse_args() -> argparse.Namespace:
"apu",
"apu_codec",
"apu_kws",
"apu_p9",
"apu_primitives",
*APU_PRIMITIVE_SCENARIOS,
*APU_LOADER_SCENARIOS,
+14 -8
View File
@@ -25,10 +25,11 @@ FORMAL_APU_CODEC_DEPTH ?= 32
FORMAL_APU_LOADER_DEPTH ?= 128
FORMAL_APU_LOADER_COVER_DEPTH ?= 128
FORMAL_APU_P5_LOADER_DEPTH ?= 48
FORMAL_APU_P5_LOADER_COVER_DEPTH ?= 145
FORMAL_APU_P5_LOADER_COVER_SKIP = $(if $(filter apu_loader_p5_success,$*),120,$(if $(filter apu_loader_p5_bound,$*),107,$(if $(filter apu_loader_p5_memo_fallback,$*),105,30)))
FORMAL_APU_P5_LOADER_COVER_DEPTH ?= 160
FORMAL_APU_P5_LOADER_COVER_SKIP = $(if $(filter apu_loader_p5_success,$*),130,$(if $(filter apu_loader_p5_bound,$*),110,$(if $(filter apu_loader_p5_memo_fallback,$*),115,30)))
FORMAL_APU_SEQUENCER_DEPTH ?= 38
FORMAL_APU_KWS_DEPTH ?= 32
FORMAL_APU_P9_DEPTH ?= 40
FORMAL_GATEWAY_A_DEPTH ?= 20
FORMAL_SDIO_DEPTH ?= 20
FORMAL_SDIO_COVER_DEPTH ?= 24
@@ -42,20 +43,21 @@ FORMAL_GA2D_TIMEOUT ?= 7200
FORMAL_NPU_DMA_TIMEOUT ?= 7200
FORMAL_NPU_P6_TIMEOUT ?= 7200
FORMAL_APU_TIMEOUT ?= 300
FORMAL_APU_P5_LOADER_TIMEOUT ?= 600
FORMAL_GATEWAY_A_TIMEOUT ?= 120
FORMAL_SDIO_TIMEOUT ?= 120
FORMAL_APU_P5_LOADER_TARGETS := apu_loader_p5_success apu_loader_p5_bound apu_loader_p5_abort apu_loader_p5_resource_reset apu_loader_p5_memo_fallback
FORMAL_APU_LOADER_TARGETS := apu_loader_success apu_loader_header_range apu_loader_descriptor_range apu_loader_crc apu_loader_control_flow apu_loader_abort apu_loader_resource_reset $(FORMAL_APU_P5_LOADER_TARGETS)
FORMAL_APU_PRIMITIVE_TARGETS := apu_primitives_invalid_read apu_primitives_local apu_primitives_local_overflow apu_primitives_kernel apu_primitives_input_fifo apu_primitives_alignment apu_primitives_output_fifo
FORMAL_TARGET_TIMEOUT = $(if $(filter ga2d,$*),$(FORMAL_GA2D_TIMEOUT),$(if $(filter npu_dma,$*),$(FORMAL_NPU_DMA_TIMEOUT),$(if $(filter npu_context npu_control,$*),$(FORMAL_NPU_P6_TIMEOUT),$(if $(filter dma,$*),$(FORMAL_DMA_TIMEOUT),$(if $(filter apu apu_codec apu_kws apu_primitives apu_sequencer $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS),$*),$(FORMAL_APU_TIMEOUT),$(if $(filter gateway_a,$*),$(FORMAL_GATEWAY_A_TIMEOUT),$(if $(filter sdio,$*),$(FORMAL_SDIO_TIMEOUT),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_TIMEOUT),$(if $(filter ws2812,$*),$(FORMAL_WS2812_TIMEOUT),$(if $(filter i2c,$*),$(FORMAL_I2C_TIMEOUT),$(FORMAL_TIMEOUT)))))))))))
FORMAL_APU_TARGET_DEPTH = $(if $(filter $(FORMAL_APU_P5_LOADER_TARGETS),$*),$(FORMAL_APU_P5_LOADER_DEPTH),$(if $(filter $(FORMAL_APU_LOADER_TARGETS),$*),$(FORMAL_APU_LOADER_DEPTH),$(if $(filter apu_kws,$*),$(FORMAL_APU_KWS_DEPTH),$(if $(filter apu_sequencer,$*),$(FORMAL_APU_SEQUENCER_DEPTH),$(if $(filter apu_codec,$*),$(FORMAL_APU_CODEC_DEPTH),$(FORMAL_APU_DEPTH))))))
FORMAL_TARGET_TIMEOUT = $(if $(filter ga2d,$*),$(FORMAL_GA2D_TIMEOUT),$(if $(filter npu_dma,$*),$(FORMAL_NPU_DMA_TIMEOUT),$(if $(filter npu_context npu_control,$*),$(FORMAL_NPU_P6_TIMEOUT),$(if $(filter $(FORMAL_APU_P5_LOADER_TARGETS),$*),$(FORMAL_APU_P5_LOADER_TIMEOUT),$(if $(filter dma,$*),$(FORMAL_DMA_TIMEOUT),$(if $(filter apu apu_codec apu_kws apu_p9 apu_primitives apu_sequencer $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS),$*),$(FORMAL_APU_TIMEOUT),$(if $(filter gateway_a,$*),$(FORMAL_GATEWAY_A_TIMEOUT),$(if $(filter sdio,$*),$(FORMAL_SDIO_TIMEOUT),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_TIMEOUT),$(if $(filter ws2812,$*),$(FORMAL_WS2812_TIMEOUT),$(if $(filter i2c,$*),$(FORMAL_I2C_TIMEOUT),$(FORMAL_TIMEOUT))))))))))))
FORMAL_APU_TARGET_DEPTH = $(if $(filter $(FORMAL_APU_P5_LOADER_TARGETS),$*),$(FORMAL_APU_P5_LOADER_DEPTH),$(if $(filter $(FORMAL_APU_LOADER_TARGETS),$*),$(FORMAL_APU_LOADER_DEPTH),$(if $(filter apu_p9,$*),$(FORMAL_APU_P9_DEPTH),$(if $(filter apu_kws,$*),$(FORMAL_APU_KWS_DEPTH),$(if $(filter apu_sequencer,$*),$(FORMAL_APU_SEQUENCER_DEPTH),$(if $(filter apu_codec,$*),$(FORMAL_APU_CODEC_DEPTH),$(FORMAL_APU_DEPTH)))))))
FORMAL_APU_TARGET_COVER_DEPTH = $(if $(filter $(FORMAL_APU_P5_LOADER_TARGETS),$*),$(FORMAL_APU_P5_LOADER_COVER_DEPTH),$(if $(filter $(FORMAL_APU_LOADER_TARGETS),$*),$(FORMAL_APU_LOADER_COVER_DEPTH),$(FORMAL_APU_TARGET_DEPTH)))
FORMAL_TARGET_TOP = $(if $(filter $(FORMAL_APU_LOADER_TARGETS),$*),apu_loader,$(if $(filter $(FORMAL_APU_PRIMITIVE_TARGETS),$*),apu_primitives,$*))
FORMAL_PROPERTIES_SOURCE = $(if $(filter ga2d,$*),$(RTL_PATH)/formal/ga2d_formal.sv,$(RTL_PATH)/formal/$(FORMAL_TARGET_TOP)_formal_props.sv)
FORMAL_PROPERTIES_DEFINE = $(if $(filter ga2d,$*),--properties-define GA2D_FORMAL_PROPERTIES,)
FORMAL_COMPACT_COVER_TARGETS := i2c opipsram dma ga2d npu_context npu_control $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS)
FORMAL_P6_TARGETS := npu_context npu_control
FORMAL_TARGETS := bus rib_adapter rib2apb sysctrl pll_rcu gpio ws2812 uart i2c timer clint dvp i2s psram onchip_ram opipsram dma ga2d apu apu_codec apu_kws $(FORMAL_APU_PRIMITIVE_TARGETS) $(FORMAL_APU_LOADER_TARGETS) apu_sequencer gateway_a sdio npu_dma
FORMAL_TARGETS := bus rib_adapter rib2apb sysctrl pll_rcu gpio ws2812 uart i2c timer clint dvp i2s psram onchip_ram opipsram dma ga2d apu apu_codec apu_kws apu_p9 $(FORMAL_APU_PRIMITIVE_TARGETS) $(FORMAL_APU_LOADER_TARGETS) apu_sequencer gateway_a sdio npu_dma
FORMAL_ALL_TARGETS := $(FORMAL_TARGETS) $(FORMAL_P6_TARGETS)
FORMAL_FILELIST_GENERATOR := $(RTL_PATH)/formal/generate_formal_filelist.py
FORMAL_SBY_GENERATOR := $(RTL_PATH)/formal/generate_sby_config.py
@@ -102,6 +104,8 @@ FORMAL_SOURCE_FILES := $(RTL_PATH)/formal/bus_formal.sv \
$(RTL_PATH)/formal/apu_codec_formal_props.sv \
$(RTL_PATH)/formal/apu_kws_formal.sv \
$(RTL_PATH)/formal/apu_kws_formal_props.sv \
$(RTL_PATH)/formal/apu_p9_formal.sv \
$(RTL_PATH)/formal/apu_p9_formal_props.sv \
$(RTL_PATH)/formal/apu_primitives_formal.sv \
$(RTL_PATH)/formal/apu_primitives_formal_props.sv \
$(RTL_PATH)/formal/apu_loader_formal.sv \
@@ -248,14 +252,14 @@ $(FORMAL_DIR)/%/prove.sby: $(FORMAL_DIR)/%/design.v \
$(FORMAL_SBY_GENERATOR)
python3 $(FORMAL_SBY_GENERATOR) --top $(FORMAL_TARGET_TOP)_formal --input $< \
--properties $(FORMAL_PROPERTIES_SOURCE) $(FORMAL_PROPERTIES_DEFINE) --solver $(FORMAL_SOLVER) \
--mode $(if $(filter ga2d,$*),bmc,$(if $(filter dma npu_dma npu_context npu_control apu apu_codec apu_kws apu_primitives apu_sequencer $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS) gateway_a sdio onchip_ram,$*),bmc,prove)) --depth $(if $(filter ga2d,$*),$(FORMAL_GA2D_DEPTH),$(if $(filter pll_rcu,$*),$(FORMAL_PLL_RCU_DEPTH),$(if $(filter npu_dma,$*),$(FORMAL_NPU_DMA_DEPTH),$(if $(filter npu_context,$*),$(FORMAL_NPU_CONTEXT_DEPTH),$(if $(filter npu_control,$*),$(FORMAL_NPU_CONTROL_DEPTH),$(if $(filter dma,$*),$(FORMAL_DMA_DEPTH),$(if $(filter apu apu_codec apu_kws apu_primitives apu_sequencer $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS),$*),$(FORMAL_APU_TARGET_DEPTH),$(if $(filter gateway_a,$*),$(FORMAL_GATEWAY_A_DEPTH),$(if $(filter sdio,$*),$(FORMAL_SDIO_DEPTH),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_DEPTH),$(if $(filter ws2812,$*),$(FORMAL_WS2812_DEPTH),$(if $(filter i2c,$*),$(FORMAL_I2C_DEPTH),$(if $(filter clint,$*),$(FORMAL_CLINT_DEPTH),$(if $(filter psram,$*),$(FORMAL_PSRAM_DEPTH),$(FORMAL_DEPTH))))))))))))))) --output $@
--mode $(if $(filter ga2d,$*),bmc,$(if $(filter dma npu_dma npu_context npu_control apu apu_codec apu_kws apu_p9 apu_primitives apu_sequencer $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS) gateway_a sdio onchip_ram,$*),bmc,prove)) --depth $(if $(filter ga2d,$*),$(FORMAL_GA2D_DEPTH),$(if $(filter pll_rcu,$*),$(FORMAL_PLL_RCU_DEPTH),$(if $(filter npu_dma,$*),$(FORMAL_NPU_DMA_DEPTH),$(if $(filter npu_context,$*),$(FORMAL_NPU_CONTEXT_DEPTH),$(if $(filter npu_control,$*),$(FORMAL_NPU_CONTROL_DEPTH),$(if $(filter dma,$*),$(FORMAL_DMA_DEPTH),$(if $(filter apu apu_codec apu_kws apu_p9 apu_primitives apu_sequencer $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS),$*),$(FORMAL_APU_TARGET_DEPTH),$(if $(filter gateway_a,$*),$(FORMAL_GATEWAY_A_DEPTH),$(if $(filter sdio,$*),$(FORMAL_SDIO_DEPTH),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_DEPTH),$(if $(filter ws2812,$*),$(FORMAL_WS2812_DEPTH),$(if $(filter i2c,$*),$(FORMAL_I2C_DEPTH),$(if $(filter clint,$*),$(FORMAL_CLINT_DEPTH),$(if $(filter psram,$*),$(FORMAL_PSRAM_DEPTH),$(FORMAL_DEPTH))))))))))))))) --output $@
@if [ "$*" = opipsram ]; then sed -i '/^async2sync/i clk2fflogic' $@; fi
$(FORMAL_DIR)/%/cover.sby: $(FORMAL_DIR)/%/design.v \
$(FORMAL_SBY_GENERATOR)
python3 $(FORMAL_SBY_GENERATOR) --top $(FORMAL_TARGET_TOP)_formal --input $< \
--properties $(FORMAL_PROPERTIES_SOURCE) $(FORMAL_PROPERTIES_DEFINE) --solver $(FORMAL_SOLVER) \
--mode cover --depth $(if $(filter ga2d,$*),$(FORMAL_GA2D_COVER_DEPTH),$(if $(filter pll_rcu,$*),$(FORMAL_PLL_RCU_DEPTH),$(if $(filter npu_dma,$*),$(FORMAL_NPU_DMA_COVER_DEPTH),$(if $(filter npu_context,$*),$(FORMAL_NPU_CONTEXT_DEPTH),$(if $(filter npu_control,$*),$(FORMAL_NPU_CONTROL_DEPTH),$(if $(filter dma,$*),$(FORMAL_DMA_COVER_DEPTH),$(if $(filter apu apu_codec apu_kws apu_primitives apu_sequencer $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS),$*),$(FORMAL_APU_TARGET_COVER_DEPTH),$(if $(filter gateway_a,$*),$(FORMAL_GATEWAY_A_DEPTH),$(if $(filter sdio,$*),$(FORMAL_SDIO_COVER_DEPTH),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_DEPTH),$(if $(filter ws2812,$*),$(FORMAL_WS2812_DEPTH),$(if $(filter i2c,$*),$(FORMAL_I2C_DEPTH),$(if $(filter clint,$*),$(FORMAL_CLINT_DEPTH),$(if $(filter psram,$*),$(FORMAL_PSRAM_DEPTH),$(FORMAL_DEPTH))))))))))))))) \
--mode cover --depth $(if $(filter ga2d,$*),$(FORMAL_GA2D_COVER_DEPTH),$(if $(filter pll_rcu,$*),$(FORMAL_PLL_RCU_DEPTH),$(if $(filter npu_dma,$*),$(FORMAL_NPU_DMA_COVER_DEPTH),$(if $(filter npu_context,$*),$(FORMAL_NPU_CONTEXT_DEPTH),$(if $(filter npu_control,$*),$(FORMAL_NPU_CONTROL_DEPTH),$(if $(filter dma,$*),$(FORMAL_DMA_COVER_DEPTH),$(if $(filter apu apu_codec apu_kws apu_p9 apu_primitives apu_sequencer $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS),$*),$(FORMAL_APU_TARGET_COVER_DEPTH),$(if $(filter gateway_a,$*),$(FORMAL_GATEWAY_A_DEPTH),$(if $(filter sdio,$*),$(FORMAL_SDIO_COVER_DEPTH),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_DEPTH),$(if $(filter ws2812,$*),$(FORMAL_WS2812_DEPTH),$(if $(filter i2c,$*),$(FORMAL_I2C_DEPTH),$(if $(filter clint,$*),$(FORMAL_CLINT_DEPTH),$(if $(filter psram,$*),$(FORMAL_PSRAM_DEPTH),$(FORMAL_DEPTH))))))))))))))) \
$(if $(filter $(FORMAL_APU_P5_LOADER_TARGETS),$*),--skip $(FORMAL_APU_P5_LOADER_COVER_SKIP)) \
$(if $(filter $(FORMAL_COMPACT_COVER_TARGETS),$*),--no-vcd) --output $@
@if [ "$*" = opipsram ]; then sed -i '/^async2sync/i clk2fflogic' $@; fi
@@ -321,6 +325,8 @@ formal: $(FORMAL_RESULT) | manifest
formal-apu-kws: $(FORMAL_DIR)/apu_kws/.stamp | manifest
formal-apu-p9: $(FORMAL_DIR)/apu_p9/.stamp | manifest
formal-bus: $(FORMAL_DIR)/bus/.stamp | manifest
formal-rib-adapter: $(FORMAL_DIR)/rib_adapter/.stamp | manifest
@@ -384,4 +390,4 @@ formal-doctor:
formal-clean:
python3 $(ROOT_PATH)/scripts/clean.py --root $(ROOT_PATH) --path $(FORMAL_DIR)
.PHONY: formal formal-bus formal-rib-adapter formal-rib2apb formal-sysctrl formal-pll-rcu formal-gpio formal-ws2812 formal-uart formal-i2c formal-timer formal-clint formal-dvp formal-i2s formal-psram formal-onchip-ram formal-opipsram formal-dma formal-ga2d formal-apu formal-apu-kws formal-apu-loader formal-apu-loader-p5 formal-apu-sequencer formal-gateway-a formal-sdio formal-npu_dma formal-npu-context formal-npu-control formal-npu-p6 formal-doctor formal-clean
.PHONY: formal formal-bus formal-rib-adapter formal-rib2apb formal-sysctrl formal-pll-rcu formal-gpio formal-ws2812 formal-uart formal-i2c formal-timer formal-clint formal-dvp formal-i2s formal-psram formal-onchip-ram formal-opipsram formal-dma formal-ga2d formal-apu formal-apu-kws formal-apu-p9 formal-apu-loader formal-apu-loader-p5 formal-apu-sequencer formal-gateway-a formal-sdio formal-npu_dma formal-npu-context formal-npu-control formal-npu-p6 formal-doctor formal-clean
+3
View File
@@ -147,6 +147,8 @@
"apu_entropy_engine.sv",
"apu_kernel_engine.sv",
"apu_kws_apum_profile.svh",
"apu_kws_coeff_loader.sv",
"apu_kws_coeff_store.sv",
"apu_kws_engine.sv",
"apu_kws_model_loader.sv",
"apu_kws_rom.svh",
@@ -155,6 +157,7 @@
"apu_microcode_loader.sv",
"apu_microcode_pkg.sv",
"apu_primitive_dispatcher.sv",
"apu_proof_memo.sv",
"apu_reconstruction_engine.sv",
"apu_resampler.sv",
"apu_ring_scheduler.sv",
+29
View File
@@ -26,6 +26,35 @@ Scripts are part of the build contract. Prefer existing helpers over ad-hoc
shell behavior, preserve structured JSON results, and keep setup/download
behavior controlled by `dependencies/dependencies.lock.json`.
`generate_apu_kws_rtl_constants.py` also emits the frozen APU-P9 APUC image
and 15-bank layout manifest. `apu_kws_coeff.py` owns the independent APUC 1.0
schema and release validator; generated assets remain below the selected
variant's `apu/coefficients/` directory.
`apu_p9_evidence.py` initializes or assembles the six frozen P9 evidence files;
missing qualifying runs stay explicitly `unrun` rather than becoming passes.
`apu_p9_memory_ab.py` performs the like-for-like inferred-memory, macro-count,
synthesis-time and process-tree peak-RSS comparison from two explicit variant
roots; it never creates or switches Git worktrees itself.
The frozen P9 qualification entry points are explicit and do not turn smoke
runs into release evidence:
```sh
make CONFIG=configs/ci/ihp130.mk setup-apu-kws-reference apu-p9-coefficients apu-p9-evidence
python3 scripts/run_apu_p7_kws_rtl.py \
--build-dir build/<candidate>/apu/p9/accuracy --profile ihp130-p9
python3 scripts/run_apu_p7_concurrent_rtl.py \
--build-dir build/<candidate>/apu/p9/concurrent --seconds 60
make CONFIG=configs/ci/ihp130.mk apu-p9-memory-ab \
APU_P9_BASELINE_ROOT=build/<baseline> APU_P9_CANDIDATE_ROOT=build/<candidate>
```
Both A/B roots must come from clean committed revisions; the baseline is the
frozen pre-P9 revision and both configurations must enable P7 with the same
IHP130 tool, PDK, recipe, and dependency inputs. Assemble the six final reports
with `scripts/apu_p9_evidence.py assemble`; a `passed` input is accepted only
when its report-specific checks and named source artifacts are complete.
`setup_npu_reference.py` prepares the locked KWS/VWW inputs and the existing
TensorFlow/gemmlowp reference sources. `npu_framework_reference.py` builds the
host-only adapter in `tests/cpp/npu_framework_reference.cc`; its integer kernels
+63 -8
View File
@@ -51,6 +51,7 @@ ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "scripts"))
import apu_kws # noqa: E402
import apu_kws_coeff # noqa: E402
from apu_isa import ( # noqa: E402
APUMC_ABI_V2,
ControlOpcode,
@@ -73,6 +74,7 @@ SECTION_ORDER = (
"formats",
"apumc",
"apum",
"apuc",
"isa",
"mp3_stub",
)
@@ -147,16 +149,26 @@ def _rtl_discovery_values() -> dict[str, int]:
"""Extract the release-selected discovery localparams from apu_reg.sv."""
text = RTL_REGISTER.read_text(encoding="utf-8")
branch = r"localparam logic \[31:0\] %s = (?:\([^)]*\)|\w+) \? 32'h([0-9a-fA-F_]+) :"
return {
"IP_ID_VALUE": _hex_literal(
r"localparam logic \[31:0\] IpId = 32'h([0-9a-fA-F_]+);", text
),
"IP_VERSION_VALUE": _hex_literal(branch % "IpVersion", text),
"CAPABILITY0_IMPLEMENTED": _hex_literal(branch % "Capability0", text),
"CAPABILITY1_IMPLEMENTED": _hex_literal(branch % "Capability1", text),
"IP_VERSION_VALUE": _hex_literal(
r"localparam logic \[31:0\] IpVersion = EnableP7 \? 32'h([0-9a-fA-F_]+) :",
text,
),
"CAPABILITY0_IMPLEMENTED": _hex_literal(
r"localparam logic \[31:0\] Capability0 = EnableP7 \? 32'h([0-9a-fA-F_]+) :",
text,
),
"CAPABILITY1_IMPLEMENTED": _hex_literal(
r"localparam logic \[31:0\] Capability1 = \(EnableP5 \|\| EnableP7\) \? "
r"32'h([0-9a-fA-F_]+) :",
text,
),
"IRQ_ALL": _hex_literal(
r"localparam logic \[10:0\] IrqMask = 11'h([0-9a-fA-F_]+);", text
r"localparam logic \[11:0\] IrqMask = EnableP7 \? 12'h([0-9a-fA-F_]+) :",
text,
),
}
@@ -350,6 +362,32 @@ def canonical_inventory() -> dict[str, dict[str, int]]:
{name: apb[name] for name in ("APUM_MAGIC", "APUM_ABI", "APUM_IMAGE_BYTES",
"APUM_PAYLOAD_CRC")},
)
_require_equal(
"APUC container/SVH",
{
"APUC_MAGIC": apu_kws_coeff.APUC_MAGIC,
"APUC_ABI": apu_kws_coeff.APUC_ABI,
"APUC_IMAGE_BYTES": apu_kws_coeff.APUC_BYTES,
"APUC_HEADER_BYTES": apu_kws_coeff.APUC_HEADER_BYTES,
"APUC_PAYLOAD_BYTES": apu_kws_coeff.APUC_PAYLOAD_BYTES,
"APUC_PAYLOAD_CRC": apu_kws_coeff.APUC_PAYLOAD_CRC,
"APUC_COEFFICIENT_ID_LO": apu_kws_coeff.APUC_COEFFICIENT_ID[0],
"APUC_COEFFICIENT_ID_HI": apu_kws_coeff.APUC_COEFFICIENT_ID[1],
},
{
name: apb[name]
for name in (
"APUC_MAGIC",
"APUC_ABI",
"APUC_IMAGE_BYTES",
"APUC_HEADER_BYTES",
"APUC_PAYLOAD_BYTES",
"APUC_PAYLOAD_CRC",
"APUC_COEFFICIENT_ID_LO",
"APUC_COEFFICIENT_ID_HI",
)
},
)
discovery_c = _c_discovery_defines()
discovery_rtl = _rtl_discovery_values()
_require_equal("discovery C/RTL", discovery_rtl,
@@ -400,6 +438,22 @@ def canonical_inventory() -> dict[str, dict[str, int]]:
"parameter_end": apu_kws.APUM_PARAMETER_END,
"payload_crc": apu_kws.APUM_PAYLOAD_CRC,
}
apuc = {
"magic": apu_kws_coeff.APUC_MAGIC,
"abi": apu_kws_coeff.APUC_ABI,
"image_bytes": apu_kws_coeff.APUC_BYTES,
"header_bytes": apu_kws_coeff.APUC_HEADER_BYTES,
"payload_bytes": apu_kws_coeff.APUC_PAYLOAD_BYTES,
"profile": apu_kws_coeff.APUC_PROFILE,
"layout_id": apu_kws_coeff.APUC_LAYOUT_ID,
"bank_count": apu_kws_coeff.APUC_BANK_COUNT,
"table_count": apu_kws_coeff.APUC_TABLE_COUNT,
"payload_crc": apu_kws_coeff.APUC_PAYLOAD_CRC,
"coefficient_id_low": apu_kws_coeff.APUC_COEFFICIENT_ID[0],
"coefficient_id_high": apu_kws_coeff.APUC_COEFFICIENT_ID[1],
"associated_apum_crc": apu_kws_coeff.APUC_APUM_PAYLOAD_CRC,
"ln2_q24": apu_kws_coeff.APUC_LN2_Q24,
}
discovery = {
"ip_id": discovery_c["IP_ID_VALUE"],
@@ -425,6 +479,7 @@ def canonical_inventory() -> dict[str, dict[str, int]]:
"formats": format_section,
"apumc": apumc,
"apum": apum,
"apuc": apuc,
"isa": isa,
"mp3_stub": _mp3_stub_section(apb),
}
@@ -465,16 +520,16 @@ def rtl_implemented_digest() -> int:
def c_implemented_digest() -> int:
"""Extract the handwritten P7 release digest from apu_regs.h."""
"""Extract the handwritten P9 release digest from apu_regs.h."""
text = C_HEADER.read_text(encoding="utf-8")
match = re.search(
r"^#define\s+RS_APU_DIGEST_P7_IMPLEMENTED\s+UINT32_C\((0[xX][0-9a-fA-F]+)\)\s*$",
r"^#define\s+RS_APU_DIGEST_P9_IMPLEMENTED\s+UINT32_C\((0[xX][0-9a-fA-F]+)\)\s*$",
text,
re.MULTILINE,
)
if match is None:
raise ValueError(f"RS_APU_DIGEST_P7_IMPLEMENTED not found in {C_HEADER.name}")
raise ValueError(f"RS_APU_DIGEST_P9_IMPLEMENTED not found in {C_HEADER.name}")
return int(match.group(1), 16)
+228
View File
@@ -0,0 +1,228 @@
"""Frozen APUC 1.0 coefficient-container definitions and validator."""
from __future__ import annotations
import hashlib
import struct
import zlib
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Final
APUC_MAGIC: Final = 0x43555041
APUC_ABI: Final = 0x00010000
APUC_HEADER_BYTES: Final = 64
APUC_PAYLOAD_BYTES: Final = 15 * 1024 * 4
APUC_BYTES: Final = APUC_HEADER_BYTES + APUC_PAYLOAD_BYTES
APUC_PROFILE: Final = 1
APUC_LAYOUT_ID: Final = 1
APUC_BANK_COUNT: Final = 15
APUC_TABLE_COUNT: Final = 10
APUC_PAYLOAD_CRC: Final = 0x25E7C27D
APUC_COEFFICIENT_ID: Final = (0x8A0B038D, 0x806C780D)
APUC_APUM_PAYLOAD_CRC: Final = 0xB9034B22
APUC_LN2_Q24: Final = 11629080
APUC_LOGICAL_SHA256: Final = "f416585119e488c82e5cea1dabb17424e33a16dd8041393135903a613edced46"
APUC_PAYLOAD_SHA256: Final = "8d030b8a0d786c80a7fa072439700a4485711971d79a6a62e78fcafb1657d83a"
APUC_SHA256: Final = "d670688442ea6efe1e638108157f55fc55c75218c8edbee931aaca3497567818"
APUC_TABLES: Final = {
"hann": (480, "5fb8748b60ff53c721d3f97c2516b9f77aebd8bb15103a072219505769d4af61"),
"twiddle_real": (256, "49faa57826aa487032eef985d7d25f29b5346692f22ab9780209cb118c9c5b8a"),
"twiddle_imag": (256, "3552f5ed86694388ee674dcb988509e1f8fec5c61c7435e289a27591a2c817ec"),
"mel": (10280, "a13dc9eaf00e6e2be9710c4a1bd432239c0181b5d2ee9903250c667d906f16be"),
"dct": (400, "93a71d890110064feb515cb7527740ce3ff0116451931e27925ac1c36339153e"),
"log": (1025, "20b0b265de2c9e319dc70b2fa0ee136add8693937108c5f0c65ccc0ca146050a"),
"fir3": (63, "77ccbd59d8346f709d299ce762c1976dd8abbba3263d47ed65ef394db570870a"),
"fir6": (63, "a00ceab9105dddbed5e6d2426753dd5dbc99c9b0c166b1f19f43c530ceb9faac"),
"softmax": (125, "410f11dd7f10d5516b41efdec861afe6a7f2a34dd116163f7758ac5110b65ae7"),
"apum_profile": (
1496,
"4c30d9a5e054436bab5acfb4651ea361d0211956c9211e9d36d182b9fdc88991",
),
}
def _expected_locations() -> dict[str, list[tuple[int, int]]]:
locations: dict[str, list[tuple[int, int]]] = {
name: [] for name in APUC_TABLES
}
locations["hann"] = [(0, index) for index in range(480)]
locations["dct"] = [(0, 480 + index) for index in range(400)]
locations["fir3"] = [(0, 880 + index) for index in range(63)]
locations["fir6"] = [(0, 943 + index) for index in range(63)]
locations["twiddle_real"] = [(1, index) for index in range(256)]
locations["twiddle_imag"] = [(2, index) for index in range(256)]
locations["log"] = [(1 + (index & 1), 256 + (index >> 1)) for index in range(1025)]
locations["mel"] = [
(3 + (index >> 10), index & 1023) if index < 10240 else (13, index - 10240)
for index in range(10280)
]
locations["apum_profile"] = [
(13, 40 + index) if index < 984 else (14, index - 984)
for index in range(1496)
]
locations["softmax"] = [(14, 512 + index) for index in range(125)]
return locations
def _expected_profile_offsets() -> list[int]:
offsets = list(range(0, 0x500, 4))
for start in (0x1000, 0x1540, 0x2840, 0x2D80, 0x4080, 0x45C0, 0x58C0, 0x5E00, 0x7100):
offsets.extend(range(start, start + 0x200, 4))
offsets.extend(range(0x7630, 0x7690, 4))
return offsets
def validate_layout_manifest(image: bytes, manifest: dict[str, Any]) -> None:
"""Validate the generated layout with a frozen oracle independent of its packer."""
validate_apuc(image)
if manifest.get("schema_version") != 1:
raise ValueError("APUC layout manifest schema version is not 1")
generator = manifest.get("generator")
if not isinstance(generator, dict):
raise ValueError("APUC layout manifest lacks generator identity")
generator_path = generator.get("path")
generator_sha = generator.get("sha256")
generator_revision = generator.get("revision")
repository_commit = generator.get("repository_commit")
if generator_path != "scripts/generate_apu_kws_rtl_constants.py" or not isinstance(
generator_sha, str
):
raise ValueError("APUC generator path/SHA identity is incomplete")
if generator_revision != f"sha256:{generator_sha}":
raise ValueError("APUC generator revision does not match its source SHA")
if (
not isinstance(repository_commit, str)
or len(repository_commit) != 40
or any(character not in "0123456789abcdef" for character in repository_commit.lower())
):
raise ValueError("APUC generator repository revision is not a full Git commit")
source = Path(__file__).resolve().parents[1] / generator_path
if not source.is_file() or hashlib.sha256(source.read_bytes()).hexdigest() != generator_sha:
raise ValueError("APUC generator source does not match the recorded revision")
expected_container = {
"magic": f"0x{APUC_MAGIC:08x}",
"abi": f"0x{APUC_ABI:08x}",
"bytes": APUC_BYTES,
"header_bytes": APUC_HEADER_BYTES,
"payload_bytes": APUC_PAYLOAD_BYTES,
"payload_crc32": f"0x{APUC_PAYLOAD_CRC:08x}",
"coefficient_id": [f"0x{word:08x}" for word in APUC_COEFFICIENT_ID],
"associated_apum_crc32": f"0x{APUC_APUM_PAYLOAD_CRC:08x}",
"logical_sha256": APUC_LOGICAL_SHA256,
"payload_sha256": APUC_PAYLOAD_SHA256,
"sha256": APUC_SHA256,
}
if manifest.get("container") != expected_container:
raise ValueError("APUC container manifest does not match the frozen identity")
if manifest.get("layout") != {
"geometry": "tc_sram_1024x32",
"banks": APUC_BANK_COUNT,
"words_per_bank": 1024,
"populated_words": 14444,
"zero_padding_words": 916,
}:
raise ValueError("APUC physical layout summary does not match the frozen geometry")
if manifest.get("apum_profile_offsets") != _expected_profile_offsets():
raise ValueError("APUC APUM-profile offsets do not match the independent oracle")
table_records = manifest.get("tables")
if not isinstance(table_records, dict) or set(table_records) != set(APUC_TABLES):
raise ValueError("APUC layout manifest does not contain the exact ten tables")
payload_words = struct.unpack(f"<{APUC_BANK_COUNT * 1024}I", image[APUC_HEADER_BYTES:])
expected_locations = _expected_locations()
occupied: set[tuple[int, int]] = set()
for name, (word_count, frozen_sha) in APUC_TABLES.items():
record = table_records.get(name)
locations = expected_locations[name]
expected_record = {
"words": word_count,
"sha256_le32": frozen_sha,
"locations": [[bank, row] for bank, row in locations],
}
if record != expected_record:
raise ValueError(f"APUC table {name} does not match the independent map/hash oracle")
if any(location in occupied for location in locations):
raise ValueError(f"APUC table {name} overlaps another table")
occupied.update(locations)
table_bytes = b"".join(
struct.pack("<I", payload_words[(bank * 1024) + row]) for bank, row in locations
)
if hashlib.sha256(table_bytes).hexdigest() != frozen_sha:
raise ValueError(f"APUC payload words for {name} do not match the frozen hash")
if len(occupied) != 14444:
raise ValueError("APUC independent populated-position count is not 14444")
for bank in range(APUC_BANK_COUNT):
for row in range(1024):
if (bank, row) not in occupied and payload_words[(bank * 1024) + row] != 0:
raise ValueError(f"APUC padding is nonzero at bank {bank} row {row}")
@dataclass(frozen=True)
class ApucHeader:
magic: int
abi: int
total_bytes: int
payload_offset: int
payload_bytes: int
profile: int
layout_id: int
bank_count: int
table_count: int
payload_crc: int
coefficient_id: tuple[int, int]
associated_apum_crc: int
ln2_q24: int
def validate_apuc(image: bytes) -> ApucHeader:
"""Validate exact APUC 1.0 bytes and return the frozen header."""
if len(image) != APUC_BYTES:
raise ValueError(f"APUC image must be exactly {APUC_BYTES} bytes")
words = struct.unpack_from("<16I", image)
expected = (
APUC_MAGIC,
APUC_ABI,
APUC_BYTES,
APUC_HEADER_BYTES,
APUC_PAYLOAD_BYTES,
APUC_PROFILE,
APUC_LAYOUT_ID,
APUC_BANK_COUNT,
APUC_TABLE_COUNT,
APUC_PAYLOAD_CRC,
*APUC_COEFFICIENT_ID,
APUC_APUM_PAYLOAD_CRC,
APUC_LN2_Q24,
0,
0,
)
if words != expected:
raise ValueError("APUC header does not match the frozen profile")
payload = image[APUC_HEADER_BYTES:]
if zlib.crc32(payload) & 0xFFFFFFFF != APUC_PAYLOAD_CRC:
raise ValueError("APUC payload CRC mismatch")
if hashlib.sha256(payload).hexdigest() != APUC_PAYLOAD_SHA256:
raise ValueError("APUC payload SHA-256 mismatch")
if hashlib.sha256(image).hexdigest() != APUC_SHA256:
raise ValueError("APUC image SHA-256 mismatch")
return ApucHeader(
magic=words[0],
abi=words[1],
total_bytes=words[2],
payload_offset=words[3],
payload_bytes=words[4],
profile=words[5],
layout_id=words[6],
bank_count=words[7],
table_count=words[8],
payload_crc=words[9],
coefficient_id=(words[10], words[11]),
associated_apum_crc=words[12],
ln2_q24=words[13],
)
+342
View File
@@ -0,0 +1,342 @@
#!/usr/bin/env python3
"""Initialize or assemble fail-closed APU-P9 evidence reports."""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
from typing import Any
from apu_kws_coeff import validate_layout_manifest
REPORTS = (
"coefficient-layout.json",
"coefficient-lifecycle.json",
"memo-equivalence.json",
"numerical-latency.json",
"memory-synthesis-ab.json",
"physical.json",
)
ROOT = Path(__file__).resolve().parents[1]
REQUIRED_CHECKS = {
REPORTS[1]: (
"lp_admission",
"unsupported_discovery",
"cache_acl",
"dma_backpressure",
"payload_corruption",
"readback_corruption",
"partial_retry",
"lock",
"ownership",
"hard_reset",
"soft_reset",
"resource_reset",
"quiesce_load",
"abort_every_state",
),
REPORTS[2]: (
"valid_v1_v2",
"mutated_v1_v2",
"duplicate_keys",
"collisions",
"full_wrap",
"full_table",
"deeper_call_bypass",
"key_boundaries",
"bitmap_boundaries",
"interrupted_clear_insert",
"verdict_equivalence",
"first_error_equivalence",
"visit_count_equivalence",
),
REPORTS[3]: (
"all_coefficients",
"scalar_table_boundaries",
"variable_backpressure",
"no_double_commit",
"no_stale_response",
"mfcc_bit_exact",
"layers_bit_exact",
"full_1000_windows",
"complete_window_before_successor",
"concurrent_60s_zero_xrun",
),
REPORTS[4]: (
"configuration_matches",
"revision_qualified",
"candidate_synthesis_completed",
"target_inferred_bits_zero",
"apu_reduction_at_least_90_percent",
"candidate_apu_bits_at_most_65536",
"candidate_macro_inventory_76",
"pass_timing_reported",
"post_memory_inventory_reported",
"peak_rss_measured_both",
"candidate_peak_rss_lower",
"synthesis_duration_measured_both",
"candidate_duration_lower",
),
REPORTS[5]: (
"revision_qualified",
"macro_inventory_76",
"logic_macro_area_separate",
"wns_nonnegative",
"tns_zero",
"hold_reported",
"no_unconstrained_paths",
"macro_views_complete",
"netlist_models_complete",
),
}
BASELINE_REVISION = "0b73994772068e5fac00fb1a98f87f88ed121cdf"
def _read_json(path: Path) -> dict[str, Any]:
try:
value = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as error:
raise ValueError(f"cannot read JSON evidence {path}: {error}") from error
if not isinstance(value, dict):
raise ValueError(f"JSON evidence is not an object: {path}")
return value
def _write_json(path: Path, value: dict[str, Any]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n", encoding="ascii")
def _full_revision(value: object) -> bool:
return isinstance(value, str) and len(value) == 40 and all(
character in "0123456789abcdef" for character in value.lower()
)
def _require_artifacts(report: dict[str, Any], name: str) -> None:
artifacts = report.get("artifacts")
if not isinstance(artifacts, dict) or not artifacts:
raise ValueError(f"{name} passed report lacks named artifacts")
missing = [
key
for key, value in artifacts.items()
if not isinstance(value, str) or not Path(value).is_file()
]
if missing:
raise ValueError(f"{name} passed report has missing artifacts: {missing}")
def _require_passed_tests(report: dict[str, Any], name: str) -> None:
tests = report.get("tests")
if not isinstance(tests, list) or not tests or any(
not isinstance(test, dict) or test.get("status") != "passed" for test in tests
):
raise ValueError(f"{name} passed report lacks passed test records")
def _validate_memory_ab(report: dict[str, Any]) -> None:
baseline = report.get("baseline")
candidate = report.get("candidate")
tool = report.get("tool_identity")
if not isinstance(baseline, dict) or not isinstance(candidate, dict) or not isinstance(tool, dict):
raise ValueError("memory-synthesis-ab.json lacks baseline/candidate/tool identity")
if (
baseline.get("revision") != BASELINE_REVISION
or baseline.get("dirty") is not False
or candidate.get("revision") != report.get("revision")
or candidate.get("dirty") is not False
):
raise ValueError("memory-synthesis-ab.json revisions are not frozen and clean")
memory = candidate.get("memory")
macros = candidate.get("macros")
pass_timing = candidate.get("pass_timing")
post_memory = candidate.get("post_memory")
if (
not isinstance(memory, dict)
or memory.get("target_bits") != 0
or not isinstance(memory.get("apu_bits"), int)
or memory["apu_bits"] > 65536
or not isinstance(macros, dict)
or macros.get("apu_wrappers") != 76
or not isinstance(pass_timing, dict)
or any(pass_timing.get(name) is None for name in ("memory", "opt_dff", "abc", "total_ns"))
or not isinstance(post_memory, dict)
or not isinstance(post_memory.get("cell_types"), dict)
):
raise ValueError("memory-synthesis-ab.json lacks the frozen memory/pass inventory")
yosys_path = Path(str(tool.get("yosys_path", "")))
yosys_sha = tool.get("yosys_sha256")
if not yosys_path.is_file() or not isinstance(yosys_sha, str):
raise ValueError("memory-synthesis-ab.json lacks the Yosys executable identity")
if hashlib.sha256(yosys_path.read_bytes()).hexdigest() != yosys_sha:
raise ValueError("memory-synthesis-ab.json Yosys executable SHA-256 does not match")
if tool.get("read_slang_provider") != "yosys-executable":
raise ValueError("memory-synthesis-ab.json does not identify the Slang provider")
def _validate_numerical(report: dict[str, Any]) -> None:
accuracy = report.get("accuracy")
concurrency = report.get("concurrency")
latency = report.get("latency")
if (
not isinstance(accuracy, dict)
or accuracy.get("windows") != 1000
or not isinstance(accuracy.get("top1_correct"), int)
or accuracy["top1_correct"] < 900
or not isinstance(concurrency, dict)
or concurrency.get("seconds_per_scenario", 0) < 60
or sorted(concurrency.get("rates", [])) != [48000, 96000]
or sorted(concurrency.get("precisions", [])) != [16, 24]
or concurrency.get("xrun_count") != 0
or not isinstance(latency, dict)
or not isinstance(latency.get("maximum_window_cycles"), int)
or latency["maximum_window_cycles"] > 4_800_000
):
raise ValueError("numerical-latency.json lacks the frozen accuracy/latency/concurrency result")
def _validate_physical(report: dict[str, Any]) -> None:
inventory = report.get("macro_inventory")
area = report.get("area")
timing = report.get("timing")
if (
not isinstance(inventory, dict)
or inventory.get("wrappers") != 76
or not isinstance(area, dict)
or any(not isinstance(area.get(name), (int, float)) for name in ("logic_um2", "macro_um2", "total_um2"))
or not isinstance(timing, dict)
or not isinstance(timing.get("wns_ns"), (int, float))
or timing["wns_ns"] < 0
or timing.get("tns_ns") != 0
or timing.get("unconstrained_paths") != 0
or not isinstance(timing.get("hold"), dict)
):
raise ValueError("physical.json lacks the frozen macro/area/timing result")
def _validate_passed(name: str, report: dict[str, Any]) -> None:
if name == REPORTS[0]:
if report.get("profile") != "configs/ci/ihp130.mk":
raise ValueError("coefficient-layout.json does not use configs/ci/ihp130.mk")
layout_source = Path(str(report.get("source", "")))
apuc_source = Path(str(report.get("apuc_source", "")))
if not layout_source.is_file() or not apuc_source.is_file():
raise ValueError("coefficient-layout.json lacks its layout/APUC source artifacts")
validate_layout_manifest(apuc_source.read_bytes(), _read_json(layout_source))
return
if report.get("schema_version") != 1 or report.get("phase") != "APU-P9":
raise ValueError(f"{name} passed report lacks the APU-P9 schema identity")
if report.get("profile") != "configs/ci/ihp130.mk":
raise ValueError(f"{name} passed report does not use configs/ci/ihp130.mk")
if not _full_revision(report.get("revision")) or report.get("dirty") is not False:
raise ValueError(f"{name} passed report is not revision-qualified")
checks = report.get("checks")
required = REQUIRED_CHECKS[name]
if not isinstance(checks, dict) or any(checks.get(check) is not True for check in required):
raise ValueError(f"{name} passed report lacks one or more frozen checks")
if name in (REPORTS[1], REPORTS[2]):
_require_passed_tests(report, name)
if name == REPORTS[2] and (
report.get("baseline_revision") != BASELINE_REVISION
or report.get("candidate_revision") != report.get("revision")
):
raise ValueError("memo-equivalence.json revisions do not match the frozen comparison")
if name == REPORTS[3]:
_validate_numerical(report)
elif name == REPORTS[4]:
_validate_memory_ab(report)
elif name == REPORTS[5]:
_validate_physical(report)
_require_artifacts(report, name)
def _initialize(args: argparse.Namespace) -> None:
layout = _read_json(args.layout)
try:
apuc = args.apuc.read_bytes()
except OSError as error:
raise ValueError(f"cannot read APUC evidence {args.apuc}: {error}") from error
validate_layout_manifest(apuc, layout)
profile_path = args.profile.resolve()
try:
profile = profile_path.relative_to(ROOT).as_posix()
except ValueError:
profile = str(profile_path)
layout["phase"] = "APU-P9"
layout["profile"] = profile
layout["status"] = "passed"
layout["source"] = str(args.layout.resolve())
layout["apuc_source"] = str(args.apuc.resolve())
_write_json(args.output_dir / REPORTS[0], layout)
requirements = {
REPORTS[1]: "APUC admission, corruption, retry, lock, reset, quiesce and abort matrix",
REPORTS[2]: "baseline versus macro memo verdict, first-error and visit-count equivalence",
REPORTS[3]: "bit-exact coefficient, MFCC/layer/window and bounded-cycle qualification",
REPORTS[4]: "same-input baseline/candidate inferred memory, runtime and peak-RSS comparison",
REPORTS[5]: "76-wrapper area, WNS/TNS, unconstrained-path and macro-view accounting",
}
for name, requirement in requirements.items():
destination = args.output_dir / name
if not destination.exists():
_write_json(
destination,
{
"schema_version": 1,
"phase": "APU-P9",
"status": "unrun",
"requirement": requirement,
"reason": "required qualifying run has not been supplied",
},
)
def _assemble(args: argparse.Namespace) -> None:
sources: dict[str, Path] = {}
for item in args.report:
name, separator, raw_path = item.partition("=")
if not separator or name not in REPORTS:
raise ValueError(f"report must be NAME=PATH for one of {REPORTS}: {item}")
sources[name] = Path(raw_path)
missing = [name for name in REPORTS if name not in sources]
if missing:
raise ValueError(f"missing required P9 reports: {missing}")
for name, source in sources.items():
report = _read_json(source)
status = report.get("status")
if status not in ("passed", "unrun", "failed", "timed_out"):
raise ValueError(f"{name} lacks a recognized fail-closed status")
if status == "passed":
_validate_passed(name, report)
elif not isinstance(report.get("reason", report.get("failure")), str):
raise ValueError(f"{name} {status} report lacks a reason/failure")
report["source"] = str(source.resolve())
_write_json(args.output_dir / name, report)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
subparsers = parser.add_subparsers(dest="command", required=True)
initialize = subparsers.add_parser("initialize")
initialize.add_argument("--layout", type=Path, required=True)
initialize.add_argument("--apuc", type=Path, required=True)
initialize.add_argument("--profile", type=Path, required=True)
initialize.add_argument("--output-dir", type=Path, required=True)
initialize.set_defaults(handler=_initialize)
assemble = subparsers.add_parser("assemble")
assemble.add_argument("--report", action="append", default=[])
assemble.add_argument("--output-dir", type=Path, required=True)
assemble.set_defaults(handler=_assemble)
args = parser.parse_args()
try:
args.handler(args)
except ValueError as error:
parser.error(str(error))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+306
View File
@@ -0,0 +1,306 @@
#!/usr/bin/env python3
"""Compare revision-qualified APU-P9 baseline/candidate synthesis artifacts."""
from __future__ import annotations
import argparse
import hashlib
import json
import shutil
from pathlib import Path
from typing import Any
BASELINE_REVISION = "0b73994772068e5fac00fb1a98f87f88ed121cdf"
TARGET_MODULES = (
"apu_kws_engine",
"apu_kws_model_loader",
"apu_microcode_loader",
"apu_kws_coeff_store",
"apu_proof_memo",
)
APU_PATH_MARKER = ".u_apb4_apu."
REQUIRED_CONFIGURATION = {
"PDK": "IHP130",
"APU_ENABLE_P7": "YES",
"HAVE_SRAM_MACRO": "YES",
"SYNTH": "YOSYS",
}
def _one(root: Path, pattern: str) -> Path:
matches = sorted(root.glob(pattern))
if len(matches) != 1:
raise ValueError(f"expected one {pattern} below {root}, found {len(matches)}")
return matches[0]
def _json(path: Path) -> dict[str, Any]:
try:
value = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as error:
raise ValueError(f"cannot read {path}: {error}") from error
if not isinstance(value, dict):
raise ValueError(f"expected JSON object: {path}")
return value
def _memory_inventory(path: Path) -> dict[str, Any]:
report = _json(path)
modules = report.get("modules")
if not isinstance(modules, dict):
raise ValueError(f"Yosys stat report lacks modules: {path}")
rows: list[dict[str, Any]] = []
for name, values in modules.items():
if not isinstance(values, dict):
continue
bits = int(values.get("num_memory_bits", 0))
memories = int(values.get("num_memories", 0))
if bits == 0 and memories == 0:
continue
rows.append({"module": name, "memories": memories, "bits": bits})
apu_rows = [row for row in rows if APU_PATH_MARKER in row["module"]]
target_rows = [
row for row in apu_rows if any(name in row["module"] for name in TARGET_MODULES)
]
return {
"artifact": str(path.resolve()),
"whole_soc_bits": sum(row["bits"] for row in rows),
"apu_bits": sum(row["bits"] for row in apu_rows),
"target_bits": sum(row["bits"] for row in target_rows),
"apu_rows": sorted(apu_rows, key=lambda row: (-row["bits"], row["module"])),
"target_rows": sorted(target_rows, key=lambda row: (-row["bits"], row["module"])),
}
def _logic_inventory(path: Path) -> dict[str, Any]:
report = _json(path)
design = report.get("design")
if not isinstance(design, dict):
raise ValueError(f"post-memory report lacks design statistics: {path}")
cell_types = design.get("num_cells_by_type")
if not isinstance(cell_types, dict):
raise ValueError(f"post-memory report lacks cell types: {path}")
normalized = {str(name): int(count) for name, count in cell_types.items()}
return {
"artifact": str(path.resolve()),
"cells": int(design.get("num_cells", sum(normalized.values()))),
"flops": sum(count for name, count in normalized.items() if "DFF" in name.upper()),
"muxes": sum(count for name, count in normalized.items() if "MUX" in name.upper()),
"cell_types": normalized,
}
def _pass_timing(path: Path) -> dict[str, Any]:
report = _json(path)
passes = report.get("passes")
if not isinstance(passes, dict) or not isinstance(report.get("total_ns"), int):
raise ValueError(f"Yosys performance report is incomplete: {path}")
timings: dict[str, Any] = {
"artifact": str(path.resolve()),
"total_ns": report["total_ns"],
"generator": report.get("generator"),
}
for name in ("memory", "opt_dff", "abc"):
entry = passes.get(name)
timings[name] = int(entry["runtime_ns"]) if isinstance(entry, dict) and "runtime_ns" in entry else None
return timings
def _macro_inventory(path: Path) -> dict[str, Any]:
instances = [line.strip().replace("/", ".") for line in path.read_text().splitlines() if line.strip()]
apu = [instance for instance in instances if APU_PATH_MARKER in instance]
classes = {
"control_store": sum(".u_control_store." in value for value in apu),
"codec_common_data": sum(".u_local_sram." in value for value in apu),
"kws_model_scratch": sum(".u_kws_sram_client." in value for value in apu),
"loader_path_stack": sum(
(".u_microcode_loader." in value) and (".u_proof_memo." not in value) for value in apu
),
"verifier_memo": sum(".u_proof_memo." in value for value in apu),
"coefficient_store": sum(".u_kws_coeff_store." in value for value in apu),
}
return {
"artifact": str(path.resolve()),
"apu_wrappers": len(apu),
"classes": classes,
"instances": apu,
}
def _variant(root: Path) -> dict[str, Any]:
pre_memory_path = _one(root, "syn/yosys/rpt/*_pre_memory.json")
pre_tech_path = _one(root, "syn/yosys/rpt/*_pre_tech.json")
macro_path = _one(root, "syn/yosys/rpt/*_macros.rpt")
perf_path = _one(root, "syn/yosys/yosys-perf.json")
result_path = _one(root, "syn/yosys/result-synth.json")
manifest_path = _one(root, "meta/manifest.json")
memory = _memory_inventory(pre_memory_path)
result = _json(result_path)
manifest = _json(manifest_path)
repository = manifest.get("repository")
if not isinstance(repository, dict):
raise ValueError(f"manifest lacks repository identity: {manifest_path}")
return {
"root": str(root.resolve()),
"memory": memory,
"post_memory": _logic_inventory(pre_tech_path),
"pass_timing": _pass_timing(perf_path),
"synthesis": {
"status": result.get("status"),
"exit_code": result.get("exit_code"),
"duration_seconds": result.get("duration_seconds"),
"peak_rss_kib": result.get("peak_rss_kib"),
"result": str(result_path.resolve()),
},
"macros": _macro_inventory(macro_path),
"manifest": manifest,
"revision": repository.get("commit"),
"dirty": repository.get("dirty"),
"artifacts": {
"pre_memory": str(pre_memory_path.resolve()),
"pre_tech": str(pre_tech_path.resolve()),
"macros": str(macro_path.resolve()),
"yosys_perf": str(perf_path.resolve()),
"synthesis_result": str(result_path.resolve()),
"manifest": str(manifest_path.resolve()),
},
}
def _configuration_digest(variant: dict[str, Any]) -> Any:
manifest = variant.get("manifest")
if not isinstance(manifest, dict):
return None
comparable = {
"configuration": manifest.get("configuration"),
"dependency_lock": manifest.get("dependency_lock"),
"tools": manifest.get("tools"),
}
if any(value is None for value in comparable.values()):
return None
return json.dumps(comparable, sort_keys=True, separators=(",", ":"))
def _configuration_is_frozen(variant: dict[str, Any]) -> bool:
configuration = variant["manifest"].get("configuration")
return (
variant["manifest"].get("profile") == "ihp130"
and isinstance(configuration, dict)
and all(configuration.get(name) == value for name, value in REQUIRED_CONFIGURATION.items())
)
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as source:
for block in iter(lambda: source.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--baseline-root", type=Path, required=True)
parser.add_argument("--candidate-root", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--yosys", type=Path, default=None)
args = parser.parse_args()
try:
baseline = _variant(args.baseline_root)
candidate = _variant(args.candidate_root)
except ValueError as error:
parser.error(str(error))
baseline_bits = baseline["memory"]["apu_bits"]
candidate_bits = candidate["memory"]["apu_bits"]
reduction = 0.0 if baseline_bits == 0 else 1.0 - (candidate_bits / baseline_bits)
expected_macros = {
"control_store": 8,
"codec_common_data": 12,
"kws_model_scratch": 16,
"loader_path_stack": 8,
"verifier_memo": 17,
"coefficient_store": 15,
}
baseline_digest = _configuration_digest(baseline)
candidate_digest = _configuration_digest(candidate)
baseline_duration = baseline["synthesis"]["duration_seconds"]
candidate_duration = candidate["synthesis"]["duration_seconds"]
yosys_path = args.yosys
if yosys_path is None:
resolved_yosys = shutil.which("yosys")
if resolved_yosys is None:
parser.error("yosys executable is required to record its SHA-256")
yosys_path = Path(resolved_yosys)
if not yosys_path.is_file():
parser.error(f"yosys executable does not exist: {yosys_path}")
baseline_passes = baseline["pass_timing"]
candidate_passes = candidate["pass_timing"]
revision_qualified = (
baseline["revision"] == BASELINE_REVISION
and candidate["revision"] != BASELINE_REVISION
and isinstance(candidate["revision"], str)
and len(candidate["revision"]) == 40
and baseline["dirty"] is False
and candidate["dirty"] is False
)
checks = {
"configuration_matches": baseline_digest is not None
and baseline_digest == candidate_digest
and _configuration_is_frozen(baseline)
and _configuration_is_frozen(candidate),
"revision_qualified": revision_qualified,
"candidate_synthesis_completed": candidate["synthesis"]["status"] == "passed",
"target_inferred_bits_zero": candidate["memory"]["target_bits"] == 0,
"apu_reduction_at_least_90_percent": reduction >= 0.90,
"candidate_apu_bits_at_most_65536": candidate_bits <= 65536,
"candidate_macro_inventory_76": candidate["macros"]["apu_wrappers"] == 76
and candidate["macros"]["classes"] == expected_macros,
"pass_timing_reported": all(candidate_passes[name] is not None for name in ("memory", "opt_dff", "abc"))
and baseline_passes["memory"] is not None
and baseline_passes["opt_dff"] is not None,
"post_memory_inventory_reported": baseline["post_memory"]["cells"] > 0
and candidate["post_memory"]["cells"] > 0,
"peak_rss_measured_both": baseline["synthesis"]["peak_rss_kib"] is not None
and candidate["synthesis"]["peak_rss_kib"] is not None,
"candidate_peak_rss_lower": baseline["synthesis"]["peak_rss_kib"] is not None
and candidate["synthesis"]["peak_rss_kib"] is not None
and candidate["synthesis"]["peak_rss_kib"] < baseline["synthesis"]["peak_rss_kib"],
"synthesis_duration_measured_both": baseline_duration is not None
and candidate_duration is not None,
"candidate_duration_lower": baseline_duration is not None
and candidate_duration is not None
and candidate_duration < baseline_duration,
}
report = {
"schema_version": 1,
"phase": "APU-P9",
"profile": "configs/ci/ihp130.mk",
"revision": candidate["revision"],
"dirty": candidate["dirty"],
"status": "passed" if all(checks.values()) else "failed",
"reason": None if all(checks.values()) else "one or more frozen APU-P9 A/B checks failed",
"baseline": baseline,
"candidate": candidate,
"apu_inferred_reduction": reduction,
"expected_candidate_macro_classes": expected_macros,
"tool_identity": {
"yosys_path": str(yosys_path.resolve()),
"yosys_sha256": _sha256(yosys_path),
"read_slang_provider": "yosys-executable",
},
"artifacts": {
**{f"baseline_{name}": path for name, path in baseline["artifacts"].items()},
**{f"candidate_{name}": path for name, path in candidate["artifacts"].items()},
},
"checks": checks,
}
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="ascii")
print(f"APU-P9 memory A/B: {report['status']} ({args.output})")
return 0 if report["status"] == "passed" else 1
if __name__ == "__main__":
raise SystemExit(main())
+17 -2
View File
@@ -1,8 +1,9 @@
#!/usr/bin/env python3
"""Generate the embedded APU release-evidence assets for the apu_release app.
Reads the deterministic P5 APUMC microcode bundle, the frozen P7 APUM KWS
model, and one locked KWS corpus utterance; synthesizes a small deterministic
Reads the deterministic P5 APUMC microcode bundle, the frozen P9 APUC
coefficient image, the P7 APUM KWS model, and one locked KWS corpus utterance;
synthesizes a small deterministic
mono 48 kHz S16 WAV fixture; computes the frozen expected PCM through the
bit-accurate codec model; and emits a C header with every byte array and CRC
the LP/HP evidence flow needs. The generator is deterministic: no network, no
@@ -24,6 +25,7 @@ sys.path.insert(0, str(ROOT / "scripts"))
from apu_codecs import decode_wav, process_pcm # noqa: E402
from apu_kws import APUM_ABI, APUM_BYTES, APUM_MAGIC, APUM_PAYLOAD_CRC, APUM_SHA256 # noqa: E402
from apu_kws_coeff import APUC_PAYLOAD_CRC, validate_apuc # noqa: E402
APUMC_MAGIC = 0x41504D43
APUMC_ABI_VERSIONS = (0x00010000, 0x00020000)
@@ -82,6 +84,12 @@ def _load_apum(path: Path) -> tuple[bytes, int]:
return image, _crc32(image)
def _load_apuc(path: Path) -> tuple[bytes, int]:
image = path.read_bytes()
validate_apuc(image)
return image, _crc32(image)
def _synthesize_wav() -> bytes:
samples = [((index * 48271 + 12345) % 65536) - 32768 for index in range(WAV_FRAMES)]
payload = struct.pack(f"<{WAV_FRAMES}h", *samples)
@@ -150,6 +158,7 @@ def _format_array(name: str, data: bytes) -> str:
def _emit(args: argparse.Namespace) -> str:
apumc, apumc_crc, apumc_payload_crc = _load_apumc(args.apumc)
apuc, apuc_crc = _load_apuc(args.apuc)
apum, apum_crc = _load_apum(args.apum)
wav = _synthesize_wav()
pcm_crc, pcm_bytes, pcm_frames = _expected_pcm(wav)
@@ -165,6 +174,9 @@ def _emit(args: argparse.Namespace) -> str:
f"#define RS_APU_RELEASE_APUMC_SIZE UINT32_C({len(apumc)})",
f"#define RS_APU_RELEASE_APUMC_CRC UINT32_C(0x{apumc_crc:08X})",
f"#define RS_APU_RELEASE_APUMC_PAYLOAD_CRC UINT32_C(0x{apumc_payload_crc:08X})",
f"#define RS_APU_RELEASE_APUC_SIZE UINT32_C({len(apuc)})",
f"#define RS_APU_RELEASE_APUC_CRC UINT32_C(0x{apuc_crc:08X})",
f"#define RS_APU_RELEASE_APUC_PAYLOAD_CRC UINT32_C(0x{APUC_PAYLOAD_CRC:08X})",
f"#define RS_APU_RELEASE_APUM_SIZE UINT32_C({len(apum)})",
f"#define RS_APU_RELEASE_APUM_CRC UINT32_C(0x{apum_crc:08X})",
f"#define RS_APU_RELEASE_APUM_PAYLOAD_CRC UINT32_C(0x{APUM_PAYLOAD_CRC:08X})",
@@ -184,6 +196,8 @@ def _emit(args: argparse.Namespace) -> str:
"",
_format_array("rs_apu_release_apumc", apumc),
"",
_format_array("rs_apu_release_apuc", apuc),
"",
_format_array("rs_apu_release_apum", apum),
"",
_format_array("rs_apu_release_wav", wav),
@@ -199,6 +213,7 @@ def _emit(args: argparse.Namespace) -> str:
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--apumc", type=Path, required=True, help="P5 APUMC bundle path")
parser.add_argument("--apuc", type=Path, required=True, help="P9 APUC coefficient image path")
parser.add_argument("--apum", type=Path, required=True, help="P7 APUM KWS model path")
parser.add_argument(
"--kws-corpus",
+197 -4
View File
@@ -1,20 +1,41 @@
#!/usr/bin/env python3
"""Generate the frozen APU-P7 frontend ROM and APUM validation constants."""
"""Generate frozen APU-P7 references and the APU-P9 coefficient image."""
from __future__ import annotations
import argparse
import hashlib
import json
import struct
import subprocess
import zlib
from decimal import Decimal, ROUND_HALF_EVEN, getcontext
from pathlib import Path
from apu_kws_coeff import (
APUC_ABI,
APUC_APUM_PAYLOAD_CRC,
APUC_BANK_COUNT,
APUC_BYTES,
APUC_COEFFICIENT_ID,
APUC_HEADER_BYTES,
APUC_LAYOUT_ID,
APUC_LOGICAL_SHA256,
APUC_MAGIC,
APUC_PAYLOAD_BYTES,
APUC_PAYLOAD_CRC,
APUC_PAYLOAD_SHA256,
APUC_PROFILE,
APUC_SHA256,
APUC_TABLE_COUNT,
)
getcontext().prec = 100
ZERO = Decimal(0)
ONE = Decimal(1)
TWO = Decimal(2)
def _atan(value: Decimal) -> Decimal:
square = value * value
term = value
@@ -224,6 +245,160 @@ def _fixed_apum_words(image: bytes) -> tuple[list[int], list[int]]:
return offsets, [struct.unpack_from("<I", image, offset)[0] for offset in offsets]
def _word_bytes(words: list[int]) -> bytes:
return b"".join(struct.pack("<I", value & 0xFFFFFFFF) for value in words)
def _coefficient_tables(image: bytes) -> tuple[list[tuple[str, list[int]]], list[int], list[int]]:
fixed_offsets, fixed_words = _fixed_apum_words(image)
twiddle_real, twiddle_imag = _twiddle()
tables = [
("hann", _hann()),
("twiddle_real", twiddle_real),
("twiddle_imag", twiddle_imag),
("mel", _mel_weights()),
("dct", _dct()),
("log", _log_rom()),
("fir3", _fir(3)),
("fir6", _fir(6)),
("softmax", _softmax_exp()),
("apum_profile", fixed_words),
]
return tables, fixed_offsets, fixed_words
def _pack_coefficient_banks(image: bytes) -> tuple[list[list[int]], dict[str, list[tuple[int, int]]]]:
tables, _fixed_offsets, fixed_words = _coefficient_tables(image)
by_name = dict(tables)
banks = [[0] * 1024 for _ in range(APUC_BANK_COUNT)]
inverse: dict[str, list[tuple[int, int]]] = {name: [] for name, _values in tables}
def place(name: str, index: int, bank: int, row: int) -> None:
banks[bank][row] = by_name[name][index] & 0xFFFFFFFF
inverse[name].append((bank, row))
for index in range(480):
place("hann", index, 0, index)
for index in range(400):
place("dct", index, 0, 480 + index)
for index in range(63):
place("fir3", index, 0, 880 + index)
place("fir6", index, 0, 943 + index)
for index in range(256):
place("twiddle_real", index, 1, index)
place("twiddle_imag", index, 2, index)
for index in range(1025):
bank = 1 + (index & 1)
place("log", index, bank, 256 + (index >> 1))
for index in range(10280):
if index < 10240:
place("mel", index, 3 + (index >> 10), index & 1023)
else:
place("mel", index, 13, index - 10240)
for index in range(len(fixed_words)):
if index < 984:
place("apum_profile", index, 13, 40 + index)
else:
place("apum_profile", index, 14, index - 984)
for index in range(125):
place("softmax", index, 14, 512 + index)
return banks, inverse
def build_apuc(image: bytes) -> tuple[bytes, dict[str, object]]:
"""Build the exact APUC 1.0 image and its reviewable layout manifest."""
if len(image) != 32768:
raise ValueError("APUM image must be exactly 32768 bytes")
tables, fixed_offsets, _fixed_words = _coefficient_tables(image)
banks, inverse = _pack_coefficient_banks(image)
logical = b"".join(_word_bytes(values) for _name, values in tables)
payload = b"".join(_word_bytes(bank) for bank in banks)
payload_crc = zlib.crc32(payload) & 0xFFFFFFFF
ln2_q24 = _rne(TWO.ln() * Decimal(1 << 24))
header = struct.pack(
"<16I",
APUC_MAGIC,
APUC_ABI,
APUC_BYTES,
APUC_HEADER_BYTES,
APUC_PAYLOAD_BYTES,
APUC_PROFILE,
APUC_LAYOUT_ID,
APUC_BANK_COUNT,
APUC_TABLE_COUNT,
payload_crc,
APUC_COEFFICIENT_ID[0],
APUC_COEFFICIENT_ID[1],
APUC_APUM_PAYLOAD_CRC,
ln2_q24,
0,
0,
)
apuc = header + payload
logical_sha = hashlib.sha256(logical).hexdigest()
payload_sha = hashlib.sha256(payload).hexdigest()
apuc_sha = hashlib.sha256(apuc).hexdigest()
if payload_crc != APUC_PAYLOAD_CRC:
raise ValueError(f"APUC payload CRC drift: 0x{payload_crc:08x}")
if logical_sha != APUC_LOGICAL_SHA256:
raise ValueError(f"APUC logical SHA-256 drift: {logical_sha}")
if payload_sha != APUC_PAYLOAD_SHA256:
raise ValueError(f"APUC payload SHA-256 drift: {payload_sha}")
if apuc_sha != APUC_SHA256:
raise ValueError(f"APUC image SHA-256 drift: {apuc_sha}")
populated = sum(len(locations) for locations in inverse.values())
generator_path = Path(__file__).resolve()
generator_sha = hashlib.sha256(generator_path.read_bytes()).hexdigest()
revision = subprocess.run(
["git", "-C", str(generator_path.parents[1]), "rev-parse", "HEAD"],
check=False,
capture_output=True,
text=True,
)
repository_commit = revision.stdout.strip() if revision.returncode == 0 else ""
manifest: dict[str, object] = {
"schema_version": 1,
"generator": {
"path": "scripts/generate_apu_kws_rtl_constants.py",
"revision": f"sha256:{generator_sha}",
"sha256": generator_sha,
"repository_commit": repository_commit,
},
"container": {
"magic": f"0x{APUC_MAGIC:08x}",
"abi": f"0x{APUC_ABI:08x}",
"bytes": len(apuc),
"header_bytes": APUC_HEADER_BYTES,
"payload_bytes": len(payload),
"payload_crc32": f"0x{payload_crc:08x}",
"coefficient_id": [f"0x{word:08x}" for word in APUC_COEFFICIENT_ID],
"associated_apum_crc32": f"0x{APUC_APUM_PAYLOAD_CRC:08x}",
"logical_sha256": logical_sha,
"payload_sha256": payload_sha,
"sha256": apuc_sha,
},
"layout": {
"geometry": "tc_sram_1024x32",
"banks": APUC_BANK_COUNT,
"words_per_bank": 1024,
"populated_words": populated,
"zero_padding_words": APUC_BANK_COUNT * 1024 - populated,
},
"tables": {
name: {
"words": len(values),
"sha256_le32": hashlib.sha256(_word_bytes(values)).hexdigest(),
"locations": [[bank, row] for bank, row in inverse[name]],
}
for name, values in tables
},
"apum_profile_offsets": fixed_offsets,
}
return apuc, manifest
def _emit_profile_words(offsets: list[int], values: list[int]) -> str:
ranges: list[tuple[int, int, int]] = []
range_start = offsets[0]
@@ -284,10 +459,18 @@ def _emit_profile_words(offsets: list[int], values: list[int]) -> str:
return "\n".join(lines)
def generate(apum: Path, output: Path, profile_output: Path) -> None:
def generate(
apum: Path,
output: Path,
profile_output: Path,
apuc_output: Path | None = None,
manifest_output: Path | None = None,
) -> None:
image = apum.read_bytes()
if len(image) != 32768:
raise ValueError("APUM image must be exactly 32768 bytes")
output.parent.mkdir(parents=True, exist_ok=True)
profile_output.parent.mkdir(parents=True, exist_ok=True)
fixed_offsets, fixed_words = _fixed_apum_words(image)
text = [
"// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>",
@@ -328,6 +511,14 @@ def generate(apum: Path, output: Path, profile_output: Path) -> None:
),
encoding="ascii",
)
if (apuc_output is None) != (manifest_output is None):
raise ValueError("APUC output and manifest output must be requested together")
if apuc_output is not None and manifest_output is not None:
apuc, manifest = build_apuc(image)
apuc_output.parent.mkdir(parents=True, exist_ok=True)
manifest_output.parent.mkdir(parents=True, exist_ok=True)
apuc_output.write_bytes(apuc)
manifest_output.write_text(json.dumps(manifest, indent=2) + "\n", encoding="ascii")
def main() -> int:
@@ -335,11 +526,13 @@ def main() -> int:
parser.add_argument("--apum", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--profile-output", type=Path)
parser.add_argument("--apuc-output", type=Path)
parser.add_argument("--manifest-output", type=Path)
args = parser.parse_args()
profile_output = args.profile_output
if profile_output is None:
profile_output = args.output.with_name("apu_kws_apum_profile.svh")
generate(args.apum, args.output, profile_output)
generate(args.apum, args.output, profile_output, args.apuc_output, args.manifest_output)
return 0
+3
View File
@@ -112,8 +112,11 @@ def _production_sources() -> list[Path]:
"apu_ring_scheduler.sv",
"apu_stream_router.sv",
"apu_control_store.sv",
"apu_proof_memo.sv",
"apu_microcode_loader.sv",
"apu_local_sram.sv",
"apu_kws_coeff_store.sv",
"apu_kws_coeff_loader.sv",
"apu_kws_engine.sv",
"apu_kws_sram_client.sv",
"apu_kws_model_loader.sv",
+14 -1
View File
@@ -45,6 +45,7 @@ sys.path.insert(0, str(ROOT / "scripts"))
from apu_codecs import crc8, crc16, decode_flac, decode_wav, process_pcm # noqa: E402
from apu_kws import APUM_PAYLOAD_CRC, APUM_SHA256 # noqa: E402
from generate_apu_kws_rtl_constants import build_apuc # noqa: E402
PCLK_HZ = 48_000_000
FULL_GATE_SECONDS = 60.0
@@ -175,8 +176,11 @@ def _production_sources() -> list[Path]:
"apu_ring_scheduler.sv",
"apu_stream_router.sv",
"apu_control_store.sv",
"apu_proof_memo.sv",
"apu_microcode_loader.sv",
"apu_local_sram.sv",
"apu_kws_coeff_store.sv",
"apu_kws_coeff_loader.sv",
"apu_kws_engine.sv",
"apu_kws_sram_client.sv",
"apu_kws_model_loader.sv",
@@ -452,6 +456,7 @@ def _run_scenario(
compile_result["binary"],
f"+APUMC_HEX={artifacts['apumc_hex']}",
f"+APUM_HEX={artifacts['apum_hex']}",
f"+APUC_HEX={artifacts['apuc_hex']}",
f"+WAV_HEX={scenario_dir / 'wav.hex'}",
f"+FLAC_HEX={scenario_dir / 'flac.hex'}",
f"+WAV_PCM_HEX={scenario_dir / 'wav_pcm.hex'}",
@@ -658,7 +663,15 @@ def main() -> int:
_hex_words(apumc_hex, apumc_payload, 16384)
apum_hex = build_dir / "kws-apum.hex"
_hex_words(apum_hex, apum_payload, 8192)
artifacts = {"apumc_hex": str(apumc_hex), "apum_hex": str(apum_hex), "scenario_dir": ""}
apuc_hex = build_dir / "kws-apuc.hex"
apuc_payload, _layout = build_apuc(apum_payload)
_hex_words(apuc_hex, apuc_payload, 15376)
artifacts = {
"apumc_hex": str(apumc_hex),
"apum_hex": str(apum_hex),
"apuc_hex": str(apuc_hex),
"scenario_dir": "",
}
started = time.monotonic()
compile_result = _compile(build_dir)
+7
View File
@@ -30,6 +30,7 @@ from apu_kws import ( # noqa: E402
parse_apum,
)
from apu_kws_convert import TfliteError, import_tflite # noqa: E402
from generate_apu_kws_rtl_constants import build_apuc # noqa: E402
from setup_apu_kws_reference import REQUIRED_ARCHIVE, REQUIRED_SOURCES # noqa: E402
MANIFEST = ROOT / "docs/ip/apu-kws-corpus.tsv"
@@ -220,7 +221,9 @@ def _production_sources() -> list[Path]:
return [
common / "interface/axi4_stream_if.sv",
multimedia / "apu_kws_sram_client.sv",
multimedia / "apu_kws_coeff_store.sv",
multimedia / "apu_kws_engine.sv",
ROOT / "tests/rtl/apu_kws_coeff_fixture.sv",
FIXTURE,
]
@@ -448,6 +451,9 @@ def main() -> int:
)
apum_hex = build_dir / "kws_apum.hex"
_write_hex(apum_hex, image)
apuc_hex = build_dir / "kws_apuc.hex"
apuc, _layout = build_apuc(image)
_write_hex(apuc_hex, apuc)
built = _compile(build_dir, args.simulator)
except (OSError, TfliteError, ValueError, QualificationError) as error:
raise SystemExit(str(error)) from error
@@ -459,6 +465,7 @@ def main() -> int:
)
plusargs = [
f"+APUM_HEX={apum_hex}",
f"+APUC_HEX={apuc_hex}",
f"+PCM_DIR={pcm_dir}",
f"+WINDOW_COUNT={window_count}",
"+FIRST_INDEX=0",
+7
View File
@@ -50,6 +50,13 @@ tooling.
every pinned official FLAC file against locked libFLAC and records matching
released-APUMC production results from identical verification-only Icarus
and Verilator fixtures.
- `test_apu_p9_coefficients.py` checks the frozen APUC hashes and complete
logical-to-bank inverse map with an independent frozen oracle.
`test_apu_p9_storage.py` runs the synchronous
coefficient clients and macro-memo clear/insert/lookup boundary fixture;
the P8 quiesced-loader test covers the successful integrated APUC DMA and
readback path. The full corruption/retry/reset/abort lifecycle remains a
separate required P9 evidence matrix and is not inferred from that smoke.
- `test_apu_p8_quiesce_load.py` drives the full `apb4_apu` shell and checks
that MICROCODE_LOAD and MODEL_LOAD DMA fetches are admitted and complete
while the LP quiesce is held (the APU-P8 loader-admission regression).
+26
View File
@@ -1037,13 +1037,20 @@ static int test_apu_kws_validation(void) {
.bytes = UINT32_C(32768),
.expected_crc = UINT32_C(0xB9034B22),
};
const rs_apu_image_t coefficient_image = {
.address = UINT32_C(0x10008000),
.bytes = RS_APU_ABI_APUC_IMAGE_BYTES,
.expected_crc = RS_APU_ABI_APUC_PAYLOAD_CRC,
};
rs_apu_image_t bad_image = image;
rs_apu_kws_completion_t completion;
rs_apu_kws_coeff_status_t coefficient_status;
test_apu_mmio_reset();
APU_TEST_REG(RS_APU_ABI_KWS_MODEL_ADDRESS) = UINT32_C(0xA5A5A5A5);
if ((rs_apu_kws_validate_job(&job) != RS_OK) ||
(rs_apu_kws_model_load(&image, 0U) != RS_ENOTSUP) ||
(rs_apu_kws_coeff_load(&coefficient_image, 0U) != RS_ENOTSUP) ||
(APU_TEST_REG(RS_APU_ABI_KWS_MODEL_ADDRESS) != UINT32_C(0xA5A5A5A5))) {
return 1;
}
@@ -1072,6 +1079,25 @@ static int test_apu_kws_validation(void) {
return 5;
}
APU_TEST_REG(RS_APU_ABI_OWNER_STATUS) = UINT32_C(1) << RS_APU_ABI_OWNER_STATUS_QUIESCE;
APU_TEST_REG(RS_APU_ABI_KWS_COEFF_STATUS) = 0U;
if ((rs_apu_kws_coeff_load(&coefficient_image, 1U) != RS_EIO) ||
(APU_TEST_REG(RS_APU_ABI_KWS_COEFF_COMMAND) != UINT32_C(1))) {
return 11;
}
APU_TEST_REG(RS_APU_ABI_KWS_COEFF_STATUS) = UINT32_C(6);
APU_TEST_REG(RS_APU_ABI_KWS_COEFF_ACTUAL_CRC) = RS_APU_ABI_APUC_PAYLOAD_CRC;
APU_TEST_REG(RS_APU_ABI_KWS_COEFF_ID_LO) = RS_APU_ABI_APUC_COEFFICIENT_ID_LO;
APU_TEST_REG(RS_APU_ABI_KWS_COEFF_ID_HI) = RS_APU_ABI_APUC_COEFFICIENT_ID_HI;
APU_TEST_REG(RS_APU_ABI_KWS_COEFF_CAPACITY) = RS_APU_ABI_APUC_PAYLOAD_BYTES;
if ((rs_apu_kws_coeff_status_read(NULL) != RS_EINVAL) ||
(rs_apu_kws_coeff_status_read(&coefficient_status) != RS_OK) ||
(coefficient_status.status != UINT32_C(6)) ||
(coefficient_status.actual_crc != RS_APU_ABI_APUC_PAYLOAD_CRC) ||
(coefficient_status.coefficient_id[0] != RS_APU_ABI_APUC_COEFFICIENT_ID_LO) ||
(coefficient_status.coefficient_id[1] != RS_APU_ABI_APUC_COEFFICIENT_ID_HI) ||
(coefficient_status.capacity_bytes != RS_APU_ABI_APUC_PAYLOAD_BYTES)) {
return 12;
}
APU_TEST_REG(RS_APU_ABI_KWS_MODEL_STATUS) = 0U;
if ((rs_apu_kws_model_load(&image, 1U) != RS_EIO) ||
(APU_TEST_REG(RS_APU_ABI_COMMAND) != (UINT32_C(1) << RS_APU_ABI_COMMAND_MODEL_LOAD))) {
+99
View File
@@ -0,0 +1,99 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// SPDX-License-Identifier: MulanPSL-2.0
// Verification-only APUC loader for focused engine tests. Product integration
// always uses apu_kws_coeff_loader through the private AXI DMA.
module apu_kws_coeff_fixture (
input logic clk_i,
input logic rst_n_i,
input logic frontend_req_valid_i,
output logic frontend_req_ready_o,
input logic [ 3:0] frontend_kind_i,
input logic [13:0] frontend_index_i,
output logic frontend_resp_valid_o,
input logic frontend_resp_ready_i,
output logic [63:0] frontend_resp_data_o,
output logic frontend_resp_fault_o,
input logic inference_req_valid_i,
output logic inference_req_ready_o,
input logic [ 6:0] inference_index_i,
output logic inference_resp_valid_o,
input logic inference_resp_ready_i,
output logic [31:0] inference_resp_data_o,
output logic inference_resp_fault_o,
output logic initialized_o
);
logic [31:0] s_apuc_words[0:15375];
logic s_loader_active, s_loader_req, s_loader_ready, s_loader_valid, s_loader_fault;
logic [13:0] s_loader_addr;
logic [31:0] s_loader_data, s_loader_rdata;
string s_apuc_path;
apu_kws_coeff_store u_coeff_store (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.abort_i (1'b0),
.valid_i (initialized_o),
.loader_active_i (s_loader_active),
.loader_req_i (s_loader_req),
.loader_write_i (1'b1),
.loader_addr_i (s_loader_addr),
.loader_data_i (s_loader_data),
.loader_ready_o (s_loader_ready),
.loader_valid_o (s_loader_valid),
.loader_data_o (s_loader_rdata),
.loader_fault_o (s_loader_fault),
.frontend_req_valid_i (frontend_req_valid_i),
.frontend_req_ready_o (frontend_req_ready_o),
.frontend_kind_i (frontend_kind_i),
.frontend_index_i (frontend_index_i),
.frontend_resp_valid_o (frontend_resp_valid_o),
.frontend_resp_ready_i (frontend_resp_ready_i),
.frontend_resp_data_o (frontend_resp_data_o),
.frontend_resp_fault_o (frontend_resp_fault_o),
.inference_req_valid_i (inference_req_valid_i),
.inference_req_ready_o (inference_req_ready_o),
.inference_index_i (inference_index_i),
.inference_resp_valid_o(inference_resp_valid_o),
.inference_resp_ready_i(inference_resp_ready_i),
.inference_resp_data_o (inference_resp_data_o),
.inference_resp_fault_o(inference_resp_fault_o),
.profile_req_valid_i (1'b0),
.profile_req_ready_o (),
.profile_index_i (11'd0),
.profile_resp_valid_o (),
.profile_resp_ready_i (1'b0),
.profile_resp_data_o (),
.profile_resp_fault_o ()
);
initial begin
initialized_o = 1'b0;
s_loader_active = 1'b0;
s_loader_req = 1'b0;
s_loader_addr = 14'd0;
s_loader_data = 32'd0;
if (!$value$plusargs("APUC_HEX=%s", s_apuc_path)) begin
$fatal(1, "missing +APUC_HEX=<path>");
end
$readmemh(s_apuc_path, s_apuc_words);
wait (rst_n_i);
@(negedge clk_i);
s_loader_active = 1'b1;
for (int unsigned word_index = 0; word_index < 15360; word_index++) begin
while (!s_loader_ready) @(negedge clk_i);
s_loader_addr = 14'(word_index);
s_loader_data = s_apuc_words[word_index+16];
s_loader_req = 1'b1;
@(negedge clk_i);
s_loader_req = 1'b0;
while (!s_loader_valid) @(negedge clk_i);
if (s_loader_fault) $fatal(1, "APUC fixture store fault at word %0d", word_index);
end
s_loader_active = 1'b0;
initialized_o = 1'b1;
end
logic s_unused_loader_data;
assign s_unused_loader_data = ^s_loader_rdata;
endmodule
+47 -1
View File
@@ -51,6 +51,15 @@ module apu_kws_engine_tb;
logic [ 5:0][31:0] scratch_write_data;
logic [ 5:0][ 3:0] scratch_write_strb;
logic scratch_access_err;
logic coeff_front_req, coeff_front_ready, coeff_front_resp_valid, coeff_front_resp_ready;
logic [3:0] coeff_front_kind;
logic [13:0] coeff_front_index;
logic [63:0] coeff_front_data;
logic coeff_front_fault;
logic coeff_infer_req, coeff_infer_ready, coeff_infer_resp_valid, coeff_infer_resp_ready;
logic [ 6:0] coeff_infer_index;
logic [31:0] coeff_infer_data;
logic coeff_infer_fault, coeff_initialized;
logic [ 7:0] mfcc_bytes [ 0:489];
logic [ 7:0] expected_layers [0:72087];
logic use_apum = 1'b0;
@@ -160,6 +169,27 @@ module apu_kws_engine_tb;
.scratch_access_err_o (scratch_access_err)
);
apu_kws_coeff_fixture u_coeff_fixture (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.frontend_req_valid_i (coeff_front_req),
.frontend_req_ready_o (coeff_front_ready),
.frontend_kind_i (coeff_front_kind),
.frontend_index_i (coeff_front_index),
.frontend_resp_valid_o (coeff_front_resp_valid),
.frontend_resp_ready_i (coeff_front_resp_ready),
.frontend_resp_data_o (coeff_front_data),
.frontend_resp_fault_o (coeff_front_fault),
.inference_req_valid_i (coeff_infer_req),
.inference_req_ready_o (coeff_infer_ready),
.inference_index_i (coeff_infer_index),
.inference_resp_valid_o(coeff_infer_resp_valid),
.inference_resp_ready_i(coeff_infer_resp_ready),
.inference_resp_data_o (coeff_infer_data),
.inference_resp_fault_o(coeff_infer_fault),
.initialized_o (coeff_initialized)
);
apu_kws_engine dut (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
@@ -222,6 +252,21 @@ module apu_kws_engine_tb;
.scratch_write_data_o (scratch_write_data),
.scratch_write_strb_o (scratch_write_strb),
.scratch_access_err_i (scratch_access_err),
.coeff_frontend_req_valid_o (coeff_front_req),
.coeff_frontend_req_ready_i (coeff_front_ready),
.coeff_frontend_kind_o (coeff_front_kind),
.coeff_frontend_index_o (coeff_front_index),
.coeff_frontend_resp_valid_i (coeff_front_resp_valid),
.coeff_frontend_resp_ready_o (coeff_front_resp_ready),
.coeff_frontend_data_i (coeff_front_data),
.coeff_frontend_fault_i (coeff_front_fault),
.coeff_inference_req_valid_o (coeff_infer_req),
.coeff_inference_req_ready_i (coeff_infer_ready),
.coeff_inference_index_o (coeff_infer_index),
.coeff_inference_resp_valid_i(coeff_infer_resp_valid),
.coeff_inference_resp_ready_o(coeff_infer_resp_ready),
.coeff_inference_data_i (coeff_infer_data),
.coeff_inference_fault_i (coeff_infer_fault),
.stream_i (stream_i),
.rx_ready_o (rx_ready_o),
.status_o (status_o),
@@ -258,6 +303,7 @@ module apu_kws_engine_tb;
repeat (2) @(negedge clk_i);
rst_n_i = 1'b1;
wait (coeff_initialized);
@(negedge clk_i);
if (!model_valid_o || !model_lock_o) $fatal(1, "KWS model admission was not observed");
dut.s_frame_count_q = 32'd11;
@@ -381,7 +427,7 @@ module apu_kws_engine_tb;
dut.s_infer_state_q = 4'd1;
elapsed_cycles = 0;
observed_operator = 0;
while ((inference_count_o == 0) && (elapsed_cycles < 600000)) begin
while ((inference_count_o == 0) && (elapsed_cycles < 4000000)) begin
@(negedge clk_i);
if (check_layers && (dut.s_operator_q != observed_operator)) begin
check_layer(observed_operator);
+25
View File
@@ -1,6 +1,7 @@
`timescale 1ns / 1ps
module apu_kws_loader_tb;
`include "apu_kws_apum_profile.svh"
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
logic start_i = 1'b0;
@@ -50,6 +51,10 @@ module apu_kws_loader_tb;
integer abort_done_count;
integer cycle_count;
integer writes_before_success;
logic profile_request_valid_o;
logic [10:0] profile_index_o;
logic profile_response_valid_i;
logic [31:0] profile_data_i;
task automatic drive_dma(input integer first_word, input integer word_count,
input logic [31:0] expected_address, input logic [31:0] expected_bytes);
@@ -137,6 +142,19 @@ module apu_kws_loader_tb;
abort_done_count <= abort_done_count + 1;
end
end
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
profile_response_valid_i <= 1'b0;
profile_data_i <= 32'd0;
end else begin
if (profile_request_valid_o) begin
profile_response_valid_i <= 1'b1;
profile_data_i <= apu_kws_apum_fixed_word(profile_index_o);
end else if (profile_response_valid_i) begin
profile_response_valid_i <= 1'b0;
end
end
end
apu_kws_model_loader dut (
.clk_i (clk_i),
@@ -172,6 +190,13 @@ module apu_kws_loader_tb;
.local_data_o (),
.local_strb_o (),
.local_ready_i (1'b1),
.profile_request_valid_o (profile_request_valid_o),
.profile_request_ready_i (1'b1),
.profile_index_o (profile_index_o),
.profile_response_valid_i(profile_response_valid_i),
.profile_response_ready_o(),
.profile_data_i (profile_data_i),
.profile_fault_i (1'b0),
.busy_o (busy_o),
.valid_o (valid_o),
.lock_o (lock_o),
+46
View File
@@ -57,6 +57,15 @@ module apu_p7_accuracy_tb;
logic [ 5:0][31:0] scratch_write_data;
logic [ 5:0][ 3:0] scratch_write_strb;
logic scratch_access_err;
logic coeff_front_req, coeff_front_ready, coeff_front_resp_valid, coeff_front_resp_ready;
logic [3:0] coeff_front_kind;
logic [13:0] coeff_front_index;
logic [63:0] coeff_front_data;
logic coeff_front_fault;
logic coeff_infer_req, coeff_infer_ready, coeff_infer_resp_valid, coeff_infer_resp_ready;
logic [ 6:0] coeff_infer_index;
logic [31:0] coeff_infer_data;
logic coeff_infer_fault, coeff_initialized;
logic [31:0] pcm_mem[0:PcmWords-1];
always #5 clk_i = ~clk_i;
@@ -108,6 +117,27 @@ module apu_p7_accuracy_tb;
.scratch_access_err_o (scratch_access_err)
);
apu_kws_coeff_fixture u_coeff_fixture (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.frontend_req_valid_i (coeff_front_req),
.frontend_req_ready_o (coeff_front_ready),
.frontend_kind_i (coeff_front_kind),
.frontend_index_i (coeff_front_index),
.frontend_resp_valid_o (coeff_front_resp_valid),
.frontend_resp_ready_i (coeff_front_resp_ready),
.frontend_resp_data_o (coeff_front_data),
.frontend_resp_fault_o (coeff_front_fault),
.inference_req_valid_i (coeff_infer_req),
.inference_req_ready_o (coeff_infer_ready),
.inference_index_i (coeff_infer_index),
.inference_resp_valid_o(coeff_infer_resp_valid),
.inference_resp_ready_i(coeff_infer_resp_ready),
.inference_resp_data_o (coeff_infer_data),
.inference_resp_fault_o(coeff_infer_fault),
.initialized_o (coeff_initialized)
);
apu_kws_engine dut (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
@@ -170,6 +200,21 @@ module apu_p7_accuracy_tb;
.scratch_write_data_o (scratch_write_data),
.scratch_write_strb_o (scratch_write_strb),
.scratch_access_err_i (scratch_access_err),
.coeff_frontend_req_valid_o (coeff_front_req),
.coeff_frontend_req_ready_i (coeff_front_ready),
.coeff_frontend_kind_o (coeff_front_kind),
.coeff_frontend_index_o (coeff_front_index),
.coeff_frontend_resp_valid_i (coeff_front_resp_valid),
.coeff_frontend_resp_ready_o (coeff_front_resp_ready),
.coeff_frontend_data_i (coeff_front_data),
.coeff_frontend_fault_i (coeff_front_fault),
.coeff_inference_req_valid_o (coeff_infer_req),
.coeff_inference_req_ready_i (coeff_infer_ready),
.coeff_inference_index_o (coeff_infer_index),
.coeff_inference_resp_valid_i(coeff_infer_resp_valid),
.coeff_inference_resp_ready_o(coeff_infer_resp_ready),
.coeff_inference_data_i (coeff_infer_data),
.coeff_inference_fault_i (coeff_infer_fault),
.stream_i (stream_i),
.rx_ready_o (rx_ready_o),
.status_o (status_o),
@@ -293,6 +338,7 @@ module apu_p7_accuracy_tb;
repeat (4) @(negedge clk_i);
rst_n_i = 1'b1;
wait (coeff_initialized);
@(negedge clk_i);
if (!model_valid_o || !model_lock_o) $fatal(1, "KWS model admission was not observed");
$readmemh(apum_hex, u_kws_sram_client.mem, 0, ModelWords - 1);
+25 -8
View File
@@ -16,6 +16,7 @@ module apu_p7_concurrent_tb;
localparam logic [31:0] WavBase = 32'h3002_0000;
localparam logic [31:0] FlacBase = 32'h3003_0000;
localparam logic [31:0] WriteBase = 32'h3004_0000;
localparam logic [31:0] CoeffBase = 32'h3005_0000;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
@@ -24,6 +25,7 @@ module apu_p7_concurrent_tb;
logic [31:0] model [ 0:8191];
logic [31:0] wav_image [ 0:8191];
logic [31:0] flac_image [ 0:1023];
logic [31:0] coefficient_image[0:15375];
logic [31:0] wav_pcm [0:16383];
logic [31:0] flac_pcm [0:16383];
logic read_active_q;
@@ -33,7 +35,7 @@ module apu_p7_concurrent_tb;
logic [31:0] s_value;
// Scenario plusarg state.
string apumc_path, apum_path, wav_path, flac_path, wav_pcm_path, flac_pcm_path;
string apumc_path, apum_path, apuc_path, wav_path, flac_path, wav_pcm_path, flac_pcm_path;
logic [31:0] wav_bytes, wav_frames, wav_out_words;
logic [31:0] flac_bytes, flac_frames, flac_out_words;
logic [31:0] rate_hz, precision_bits, seconds_ms, seed_value, workload_mask, job_log;
@@ -115,10 +117,12 @@ module apu_p7_concurrent_tb;
// Ready-memory AXI read model: no injected stalls, back-to-back beats.
assign axi4.arready = !read_active_q;
assign axi4.rid = 1'b0;
assign axi4.rdata = (read_addr_q >= FlacBase) ? flac_image[(read_addr_q-FlacBase)>>2] :
assign axi4.rdata = (read_addr_q >= CoeffBase) ?
coefficient_image[(read_addr_q-CoeffBase)>>2] :
((read_addr_q >= FlacBase) ? flac_image[(read_addr_q-FlacBase)>>2] :
((read_addr_q >= WavBase) ? wav_image[(read_addr_q-WavBase)>>2] :
((read_addr_q >= ModelBase) ? model[(read_addr_q-ModelBase)>>2] :
image[(read_addr_q-ImageBase)>>2]));
image[(read_addr_q-ImageBase)>>2])));
assign axi4.rresp = 2'd0;
assign axi4.rlast = read_beat_q == read_len_q;
assign axi4.ruser = 1'b0;
@@ -479,6 +483,7 @@ module apu_p7_concurrent_tb;
wav_source_rate = 32'd0;
if (!$value$plusargs("APUMC_HEX=%s", apumc_path)) $fatal(1, "APUMC_HEX plusarg missing");
if (!$value$plusargs("APUM_HEX=%s", apum_path)) $fatal(1, "APUM_HEX plusarg missing");
if (!$value$plusargs("APUC_HEX=%s", apuc_path)) $fatal(1, "APUC_HEX plusarg missing");
if (!$value$plusargs("WAV_HEX=%s", wav_path)) $fatal(1, "WAV_HEX plusarg missing");
if (!$value$plusargs("FLAC_HEX=%s", flac_path)) $fatal(1, "FLAC_HEX plusarg missing");
if (!$value$plusargs("WAV_PCM_HEX=%s", wav_pcm_path)) $fatal(1, "WAV_PCM_HEX missing");
@@ -508,6 +513,7 @@ module apu_p7_concurrent_tb;
$readmemh(apumc_path, image);
$readmemh(apum_path, model);
$readmemh(apuc_path, coefficient_image);
$readmemh(wav_path, wav_image);
$readmemh(flac_path, flac_image);
$readmemh(wav_pcm_path, wav_pcm);
@@ -542,7 +548,7 @@ module apu_p7_concurrent_tb;
repeat (2) @(posedge clk_i);
apb_write(`APB4_APU__READ_BASE, ImageBase);
apb_write(`APB4_APU__READ_LIMIT, FlacBase + 32'd4095);
apb_write(`APB4_APU__READ_LIMIT, CoeffBase + 32'd61503);
apb_write(`APB4_APU__WRITE_BASE, WriteBase);
apb_write(`APB4_APU__WRITE_LIMIT, WriteBase + 32'd255);
apb_write(`APB4_APU__MC_IMAGE_ADDRESS, ImageBase);
@@ -558,14 +564,24 @@ module apu_p7_concurrent_tb;
if (!s_value[`APB4_APU__MC_STATUS_VALID]) $fatal(1, "P7 concurrent microcode load timeout");
$display("CONCURRENT_SETUP microcode loaded crc=%h", image[11]);
apb_write(`APB4_APU__KWS_COEFF_ADDRESS, CoeffBase);
apb_write(`APB4_APU__KWS_COEFF_SIZE, 32'd61504);
apb_write(`APB4_APU__KWS_COEFF_EXPECTED_CRC, `APB4_APU__APUC_PAYLOAD_CRC);
apb_write(`APB4_APU__KWS_COEFF_COMMAND, 32'd1);
for (int poll = 0; poll < 200000; poll++) begin
apb_read(`APB4_APU__KWS_COEFF_STATUS, s_value);
if (!s_value[`APB4_APU__KWS_COEFF_STATUS_BUSY]) break;
end
if (s_value[2:1] != 2'b11) begin
apb_read(`APB4_APU__ERROR_DETAIL, s_value);
$fatal(1, "P7 concurrent coefficient load failed detail=%h", s_value);
end
$display("CONCURRENT_SETUP coefficients loaded status=%h", s_value);
apb_write(`APB4_APU__KWS_MODEL_ADDRESS, ModelBase);
apb_write(`APB4_APU__KWS_MODEL_SIZE, 32'd32768);
apb_write(`APB4_APU__KWS_MODEL_EXPECTED_CRC, `APB4_APU__APUM_PAYLOAD_CRC);
apb_write(`APB4_APU__COMMAND, 32'd1 << `APB4_APU__COMMAND_MODEL_LOAD);
// The model load is admitted under quiesce, but its DMA only runs once
// quiesce deasserts (apu_dma blocks new requests while quiesced and the
// microcode loader is idle).
apb4.pprot = 3'd0;
for (int poll = 0; poll < 500000; poll++) begin
apb_read(`APB4_APU__KWS_MODEL_STATUS, s_value);
if (!s_value[0]) break;
@@ -577,6 +593,7 @@ module apu_p7_concurrent_tb;
$fatal(1, "P7 concurrent model load failed detail=%h", s_value);
end
$display("CONCURRENT_SETUP model loaded status=%h", s_value);
apb4.pprot = 3'd0;
apb_write(`APB4_APU__KWS_INPUT_CONFIG, kws_input_config);
apb_write(`APB4_APU__KWS_CONFIG, 32'h0000_0180);
+65
View File
@@ -60,10 +60,37 @@ module apu_p7_frontend_dump_tb;
logic [ 5:0][31:0] scratch_write_data;
logic [ 5:0][ 3:0] scratch_write_strb;
logic scratch_access_err;
logic coeff_front_req, coeff_front_ready, coeff_front_resp_valid, coeff_front_resp_ready;
logic [3:0] coeff_front_kind;
logic [13:0] coeff_front_index;
logic [63:0] coeff_front_data;
logic coeff_front_fault;
logic coeff_infer_req, coeff_infer_ready, coeff_infer_resp_valid, coeff_infer_resp_ready;
logic [ 6:0] coeff_infer_index;
logic [31:0] coeff_infer_data;
logic coeff_infer_fault, coeff_initialized;
int unsigned debug_commit_count;
logic [31:0] pcm_mem[0:PcmWords-1];
always #5 clk_i = ~clk_i;
always_ff @(posedge clk_i) begin
if (rst_n_i && $test$plusargs(
"DEBUG_P9"
) && dut.s_commit_valid && (debug_commit_count < 5)) begin
$display("P9_COMMIT index=%0d sample=%0d next_peak=%0d wait=%0b ready=%0b",
debug_commit_count, $signed(dut.s_commit_sample), dut.s_next_peak,
dut.s_storage_wait_q, storage_ready);
debug_commit_count <= debug_commit_count + 1'b1;
end
if (rst_n_i && $test$plusargs(
"DEBUG_P9"
) && dut.s_commit_valid && (dut.s_block_samples_q == 8'd159) &&
(dut.s_peak_write_q == 7'd0)) begin
$display("P9_PEAK_WRITE value=%0d", dut.s_next_peak);
end
end
axi4_stream_if #(
.DATA_WIDTH(32)
) stream_i (
@@ -111,6 +138,27 @@ module apu_p7_frontend_dump_tb;
.scratch_access_err_o (scratch_access_err)
);
apu_kws_coeff_fixture u_coeff_fixture (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.frontend_req_valid_i (coeff_front_req),
.frontend_req_ready_o (coeff_front_ready),
.frontend_kind_i (coeff_front_kind),
.frontend_index_i (coeff_front_index),
.frontend_resp_valid_o (coeff_front_resp_valid),
.frontend_resp_ready_i (coeff_front_resp_ready),
.frontend_resp_data_o (coeff_front_data),
.frontend_resp_fault_o (coeff_front_fault),
.inference_req_valid_i (coeff_infer_req),
.inference_req_ready_o (coeff_infer_ready),
.inference_index_i (coeff_infer_index),
.inference_resp_valid_o(coeff_infer_resp_valid),
.inference_resp_ready_i(coeff_infer_resp_ready),
.inference_resp_data_o (coeff_infer_data),
.inference_resp_fault_o(coeff_infer_fault),
.initialized_o (coeff_initialized)
);
apu_kws_engine dut (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
@@ -173,6 +221,21 @@ module apu_p7_frontend_dump_tb;
.scratch_write_data_o (scratch_write_data),
.scratch_write_strb_o (scratch_write_strb),
.scratch_access_err_i (scratch_access_err),
.coeff_frontend_req_valid_o (coeff_front_req),
.coeff_frontend_req_ready_i (coeff_front_ready),
.coeff_frontend_kind_o (coeff_front_kind),
.coeff_frontend_index_o (coeff_front_index),
.coeff_frontend_resp_valid_i (coeff_front_resp_valid),
.coeff_frontend_resp_ready_o (coeff_front_resp_ready),
.coeff_frontend_data_i (coeff_front_data),
.coeff_frontend_fault_i (coeff_front_fault),
.coeff_inference_req_valid_o (coeff_infer_req),
.coeff_inference_req_ready_i (coeff_infer_ready),
.coeff_inference_index_o (coeff_infer_index),
.coeff_inference_resp_valid_i(coeff_infer_resp_valid),
.coeff_inference_resp_ready_o(coeff_infer_resp_ready),
.coeff_inference_data_i (coeff_infer_data),
.coeff_inference_fault_i (coeff_infer_fault),
.stream_i (stream_i),
.rx_ready_o (rx_ready_o),
.status_o (status_o),
@@ -303,9 +366,11 @@ module apu_p7_frontend_dump_tb;
max_window_cycles = 4000000;
end
if (window_count == 0) $fatal(1, "WINDOW_COUNT must be positive");
debug_commit_count = 0;
repeat (4) @(negedge clk_i);
rst_n_i = 1'b1;
wait (coeff_initialized);
@(negedge clk_i);
if (!model_valid_o || !model_lock_o) $fatal(1, "KWS model admission was not observed");
$readmemh(apum_hex, u_kws_sram_client.mem, 0, ModelWords - 1);
+29 -4
View File
@@ -19,6 +19,7 @@ module apu_p8_quiesce_load_tb;
localparam logic [31:0] ImageBase = 32'h3000_0000;
localparam logic [31:0] ModelBase = 32'h3001_0000;
localparam logic [31:0] ModelBytes = 32'd32768;
localparam logic [31:0] CoeffBase = 32'h3002_0000;
localparam logic [63:0] MaxCycles = 64'd2000000;
logic clk_i = 1'b0;
@@ -26,6 +27,7 @@ module apu_p8_quiesce_load_tb;
logic quiesce_q;
logic [31:0] image [0:16383];
logic [31:0] model [ 0:8191];
logic [31:0] coefficient [0:15375];
logic read_active_q;
logic [31:0] read_addr_q;
logic [7:0] read_len_q, read_beat_q;
@@ -34,7 +36,7 @@ module apu_p8_quiesce_load_tb;
logic [63:0] beats_before;
logic [31:0] s_value;
string apumc_path, apum_path;
string apumc_path, apum_path, apuc_path;
apb4_if apb4 (
.pclk (clk_i),
@@ -79,8 +81,9 @@ module apu_p8_quiesce_load_tb;
// Ready-memory AXI read model: no injected stalls, back-to-back beats.
assign axi4.arready = !read_active_q;
assign axi4.rid = 1'b0;
assign axi4.rdata = (read_addr_q >= ModelBase) ? model[(read_addr_q-ModelBase)>>2] :
image[(read_addr_q-ImageBase)>>2];
assign axi4.rdata = (read_addr_q >= CoeffBase) ? coefficient[(read_addr_q-CoeffBase)>>2] :
((read_addr_q >= ModelBase) ? model[(read_addr_q-ModelBase)>>2] :
image[(read_addr_q-ImageBase)>>2]);
assign axi4.rresp = 2'd0;
assign axi4.rlast = read_beat_q == read_len_q;
assign axi4.ruser = 1'b0;
@@ -206,8 +209,10 @@ module apu_p8_quiesce_load_tb;
initial begin
if (!$value$plusargs("APUMC_HEX=%s", apumc_path)) $fatal(1, "APUMC_HEX plusarg missing");
if (!$value$plusargs("APUM_HEX=%s", apum_path)) $fatal(1, "APUM_HEX plusarg missing");
if (!$value$plusargs("APUC_HEX=%s", apuc_path)) $fatal(1, "APUC_HEX plusarg missing");
$readmemh(apumc_path, image);
$readmemh(apum_path, model);
$readmemh(apuc_path, coefficient);
apb4.paddr = 32'd0;
apb4.pprot = 3'd0;
@@ -232,7 +237,7 @@ module apu_p8_quiesce_load_tb;
if (!s_value[`APB4_APU__STATUS_IDLE]) $fatal(1, "P8 quiesce load core not idle %h", s_value);
apb_write(`APB4_APU__READ_BASE, ImageBase);
apb_write(`APB4_APU__READ_LIMIT, ModelBase + ModelBytes - 32'd1);
apb_write(`APB4_APU__READ_LIMIT, CoeffBase + 32'd61503);
// MICROCODE_LOAD under quiesce: the microcode loader was always exempt
// from the DMA quiesce while it is itself busy; this leg must keep working.
@@ -262,6 +267,26 @@ module apu_p8_quiesce_load_tb;
);
$display("P8_QUIESCE_MC crc=%h beats=%0d", s_value, quiesced_beats_q - beats_before);
apb_write(`APB4_APU__KWS_COEFF_ADDRESS, CoeffBase);
apb_write(`APB4_APU__KWS_COEFF_SIZE, 32'd61504);
apb_write(`APB4_APU__KWS_COEFF_EXPECTED_CRC, `APB4_APU__APUC_PAYLOAD_CRC);
beats_before = quiesced_beats_q;
apb_write(`APB4_APU__KWS_COEFF_COMMAND, 32'd1);
for (int poll = 0; poll < 200000; poll++) begin
apb_read(`APB4_APU__KWS_COEFF_STATUS, s_value);
if (!s_value[`APB4_APU__KWS_COEFF_STATUS_BUSY]) break;
end
if (s_value[2:1] != 2'b11) begin
apb_read(`APB4_APU__ERROR_DETAIL, s_value);
$fatal(1, "P8 quiesce load coefficient load failed detail=%h", s_value);
end
apb_read(`APB4_APU__KWS_COEFF_ACTUAL_CRC, s_value);
if (s_value != `APB4_APU__APUC_PAYLOAD_CRC)
$fatal(1, "P8 quiesce load coefficient CRC mismatch actual=%h", s_value);
if (quiesced_beats_q - beats_before < 64'd15376)
$fatal(1, "P8 quiesce load coefficient bytes did not cross AXI under quiesce");
$display("P8_QUIESCE_COEFF crc=%h beats=%0d", s_value, quiesced_beats_q - beats_before);
apb_read(`APB4_APU__STATUS, s_value);
if (!s_value[`APB4_APU__STATUS_IDLE])
$fatal(1, "P8 quiesce load core not idle before model load %h", s_value);
+199
View File
@@ -0,0 +1,199 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// SPDX-License-Identifier: MulanPSL-2.0
`timescale 1ns / 1ps
module apu_p9_storage_tb;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
logic loader_active, loader_req, loader_write, loader_ready, loader_valid, loader_fault;
logic [13:0] loader_addr;
logic [31:0] loader_data, loader_rdata;
logic front_req, front_ready, front_valid, front_consume, front_fault;
logic [ 3:0] front_kind;
logic [13:0] front_index;
logic [63:0] front_data;
logic infer_req, infer_ready, infer_valid, infer_consume, infer_fault;
logic [ 6:0] infer_index;
logic [31:0] infer_data;
logic memo_clear, memo_lookup, memo_insert, memo_ready, memo_done, memo_valid;
logic [12:0] memo_addr;
logic [63:0] memo_key, memo_read_key;
int unsigned clear_cycles;
always #5 clk_i = ~clk_i;
apu_kws_coeff_store u_coeff_store (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.abort_i (1'b0),
.valid_i (!loader_active),
.loader_active_i (loader_active),
.loader_req_i (loader_req),
.loader_write_i (loader_write),
.loader_addr_i (loader_addr),
.loader_data_i (loader_data),
.loader_ready_o (loader_ready),
.loader_valid_o (loader_valid),
.loader_data_o (loader_rdata),
.loader_fault_o (loader_fault),
.frontend_req_valid_i (front_req),
.frontend_req_ready_o (front_ready),
.frontend_kind_i (front_kind),
.frontend_index_i (front_index),
.frontend_resp_valid_o (front_valid),
.frontend_resp_ready_i (front_consume),
.frontend_resp_data_o (front_data),
.frontend_resp_fault_o (front_fault),
.inference_req_valid_i (infer_req),
.inference_req_ready_o (infer_ready),
.inference_index_i (infer_index),
.inference_resp_valid_o(infer_valid),
.inference_resp_ready_i(infer_consume),
.inference_resp_data_o (infer_data),
.inference_resp_fault_o(infer_fault),
.profile_req_valid_i (1'b0),
.profile_req_ready_o (),
.profile_index_i (11'd0),
.profile_resp_valid_o (),
.profile_resp_ready_i (1'b0),
.profile_resp_data_o (),
.profile_resp_fault_o ()
);
apu_proof_memo u_proof_memo (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.abort_i (1'b0),
.clear_i (memo_clear),
.lookup_i (memo_lookup),
.insert_i (memo_insert),
.addr_i (memo_addr),
.key_i (memo_key),
.ready_o (memo_ready),
.done_o (memo_done),
.read_valid_o(memo_valid),
.read_key_o (memo_read_key)
);
task automatic write_coefficient(input logic [13:0] address_i, input logic [31:0] data_i);
begin
@(negedge clk_i);
loader_addr = address_i;
loader_data = data_i;
loader_req = 1'b1;
@(negedge clk_i);
loader_req = 1'b0;
wait (loader_valid);
if (loader_fault) $fatal(1, "coefficient write fault at %0d", address_i);
end
endtask
task automatic memo_command(input logic clear_i, input logic lookup_i, input logic insert_i,
input logic [12:0] address_i, input logic [63:0] key_i);
begin
wait (memo_ready);
@(negedge clk_i);
memo_addr = address_i;
memo_key = key_i;
memo_clear = clear_i;
memo_lookup = lookup_i;
memo_insert = insert_i;
@(negedge clk_i);
memo_clear = 1'b0;
memo_lookup = 1'b0;
memo_insert = 1'b0;
wait (memo_done);
end
endtask
initial begin
loader_active = 1'b0;
loader_req = 1'b0;
loader_write = 1'b1;
loader_addr = 14'd0;
loader_data = 32'd0;
front_req = 1'b0;
front_kind = 4'd0;
front_index = 14'd0;
front_consume = 1'b0;
infer_req = 1'b0;
infer_index = 7'd0;
infer_consume = 1'b0;
memo_clear = 1'b0;
memo_lookup = 1'b0;
memo_insert = 1'b0;
memo_addr = 13'd0;
memo_key = 64'd0;
repeat (3) @(negedge clk_i);
rst_n_i = 1'b1;
loader_active = 1'b1;
write_coefficient(14'(1024 + 5), 32'h1111_1111);
write_coefficient(14'(2048 + 5), 32'h2222_2222);
write_coefficient(14'(14 * 1024 + 512 + 3), 32'h3333_3333);
loader_active = 1'b0;
@(negedge clk_i);
front_kind = 4'd1;
front_index = 14'd5;
front_req = 1'b1;
infer_index = 7'd3;
infer_req = 1'b1;
if (!front_ready || !infer_ready) $fatal(1, "parallel coefficient clients were not ready");
@(negedge clk_i);
front_req = 1'b0;
infer_req = 1'b0;
wait (front_valid && infer_valid);
if (front_fault || infer_fault || (front_data != 64'h2222_2222_1111_1111) ||
(infer_data != 32'h3333_3333)) begin
$fatal(1, "parallel coefficient response mismatch");
end
repeat (2) begin
@(negedge clk_i);
if (!front_valid || !infer_valid) $fatal(1, "coefficient response was not retained");
end
front_consume = 1'b1;
infer_consume = 1'b1;
@(negedge clk_i);
front_consume = 1'b0;
infer_consume = 1'b0;
front_kind = 4'd9;
front_index = 14'd0;
front_req = 1'b1;
@(negedge clk_i);
front_req = 1'b0;
wait (front_valid);
if (!front_fault) $fatal(1, "illegal coefficient kind did not fault");
front_consume = 1'b1;
@(negedge clk_i);
front_consume = 1'b0;
clear_cycles = 0;
@(negedge clk_i);
memo_clear = 1'b1;
@(negedge clk_i);
memo_clear = 1'b0;
while (!memo_done) begin
clear_cycles++;
@(negedge clk_i);
end
if (clear_cycles > 260) $fatal(1, "memo clear exceeded 260 cycles");
memo_command(1'b0, 1'b0, 1'b1, 13'd31, 64'h0123_4567_89ab_cdef);
memo_command(1'b0, 1'b1, 1'b0, 13'd31, 64'h0123_4567_89ab_cdef);
if (!memo_valid || (memo_read_key != 64'h0123_4567_89ab_cdef)) begin
$fatal(1, "memo key/bitmap lookup mismatch");
end
memo_command(1'b0, 1'b1, 1'b0, 13'd32, 64'd0);
if (memo_valid) $fatal(1, "adjacent bitmap row was unexpectedly valid");
$display("APU-P9 coefficient store and proof memo test passed");
$finish;
end
initial begin
repeat (2000) @(posedge clk_i);
$fatal(1, "APU-P9 storage test timeout");
end
endmodule
+55 -14
View File
@@ -2,9 +2,11 @@
`include "apu_define.svh"
module apu_reg_tb;
module apu_reg_tb #(
parameter bit EnableP7 = 1'b1
);
localparam logic [31:0] ApuBase = 32'h1001_3000;
localparam int unsigned RegisterCount = 97;
localparam int unsigned RegisterCount = 106;
localparam logic [1:0] AccessRo = 2'd0;
localparam logic [1:0] AccessWo = 2'd1;
localparam logic [1:0] AccessRw = 2'd2;
@@ -98,7 +100,7 @@ module apu_reg_tb;
always #5 clk_i = ~clk_i;
apb4_apu #(
.EnableP7(1'b1)
.EnableP7(EnableP7)
) u_dut (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
@@ -122,7 +124,7 @@ module apu_reg_tb;
function automatic logic [31:0] legal_write_value(input logic [11:0] offset_i);
case (offset_i)
`APB4_APU__IRQ_STATE: legal_write_value = 32'h0000_07ff;
`APB4_APU__IRQ_STATE: legal_write_value = 32'h0000_0fff;
`APB4_APU__IRQ_ENABLE: legal_write_value = 32'h0000_0005;
`APB4_APU__ERROR_STATUS: legal_write_value = 32'h0000_0001;
`APB4_APU__SEQUENCER_TIMEOUT: legal_write_value = 32'h1234_5678;
@@ -155,6 +157,9 @@ module apu_reg_tb;
`APB4_APU__KWS_CONTROL: legal_write_value = 32'h0000_0004;
`APB4_APU__KWS_CONFIG: legal_write_value = 32'h0000_0590;
`APB4_APU__KWS_INPUT_CONFIG: legal_write_value = 32'h0104_bb80;
`APB4_APU__KWS_COEFF_ADDRESS: legal_write_value = 32'd0;
`APB4_APU__KWS_COEFF_SIZE: legal_write_value = 32'd61504;
`APB4_APU__KWS_COEFF_EXPECTED_CRC: legal_write_value = 32'h25e7_c27d;
`APB4_APU__PERF_CONTROL: legal_write_value = 32'h0000_0001;
default: legal_write_value = 32'd0;
endcase
@@ -194,8 +199,8 @@ module apu_reg_tb;
begin
s_reg_count = 0;
add_register(`APB4_APU__IP_ID, AccessRo, 32'h4150_5530, 1'b0);
add_register(`APB4_APU__IP_VERSION, AccessRo, 32'h0001_0001, 1'b0);
add_register(`APB4_APU__CAPABILITY0, AccessRo, 32'h0000_01fd, 1'b0);
add_register(`APB4_APU__IP_VERSION, AccessRo, 32'h0001_0002, 1'b0);
add_register(`APB4_APU__CAPABILITY0, AccessRo, 32'h0000_03fd, 1'b0);
add_register(`APB4_APU__CAPABILITY1, AccessRo, 32'h0182_7020, 1'b0);
add_register(`APB4_APU__COMMAND, AccessWo, 32'd0, 1'b1);
add_register(`APB4_APU__STATUS, AccessRo, 32'h0000_0100, 1'b0);
@@ -214,7 +219,7 @@ module apu_reg_tb;
add_register(`APB4_APU__WRITE_BASE, AccessRw, 32'hffff_ffff, 1'b1);
add_register(`APB4_APU__WRITE_LIMIT, AccessRw, 32'd0, 1'b1);
add_register(`APB4_APU__DMA_TIMEOUT, AccessRw, 32'h0000_ffff, 1'b0);
add_register(`APB4_APU__ABI_DIGEST, AccessRo, 32'hf500_5d7c, 1'b0);
add_register(`APB4_APU__ABI_DIGEST, AccessRo, 32'h63e9_6066, 1'b0);
add_register(`APB4_APU__SEQUENCER_STATUS, AccessRo, 32'd0, 1'b0);
add_register(`APB4_APU__SEQUENCER_RETIRED, AccessRo, 32'd0, 1'b0);
add_register(`APB4_APU__STREAM_WATERMARK, AccessRw, 32'd0, 1'b0);
@@ -268,6 +273,15 @@ module apu_reg_tb;
add_register(`APB4_APU__KWS_MODEL_STATUS, AccessRo, 32'd0, 1'b0);
add_register(`APB4_APU__KWS_MODEL_ACTUAL_CRC, AccessRo, 32'd0, 1'b0);
add_register(`APB4_APU__KWS_INPUT_CONFIG, AccessRw, 32'h0104_bb80, 1'b0);
add_register(`APB4_APU__KWS_COEFF_ADDRESS, AccessRw, 32'd0, 1'b1);
add_register(`APB4_APU__KWS_COEFF_SIZE, AccessRw, 32'd0, 1'b1);
add_register(`APB4_APU__KWS_COEFF_EXPECTED_CRC, AccessRw, 32'd0, 1'b1);
add_register(`APB4_APU__KWS_COEFF_COMMAND, AccessWo, 32'd0, 1'b1);
add_register(`APB4_APU__KWS_COEFF_STATUS, AccessRo, 32'd0, 1'b0);
add_register(`APB4_APU__KWS_COEFF_ACTUAL_CRC, AccessRo, 32'd0, 1'b0);
add_register(`APB4_APU__KWS_COEFF_ID_LO, AccessRo, 32'd0, 1'b0);
add_register(`APB4_APU__KWS_COEFF_ID_HI, AccessRo, 32'd0, 1'b0);
add_register(`APB4_APU__KWS_COEFF_CAPACITY, AccessRo, 32'h0000_f000, 1'b0);
add_register(`APB4_APU__PERF_CONTROL, AccessRw, 32'd0, 1'b0);
add_register(`APB4_APU__PERF_STATUS, AccessRo, 32'd0, 1'b0);
add_register(`APB4_APU__PERF_ACTIVE_CYCLES_LO, AccessRo, 32'd0, 1'b0);
@@ -448,7 +462,8 @@ module apu_reg_tb;
hard_reset();
apb_write(`APB4_APU__SEQUENCER_TIMEOUT, 32'h1234_5678, 4'hf, 1'b0);
if ((offset_i != `APB4_APU__COMMAND) && (offset_i != `APB4_APU__IRQ_TEST) &&
(offset_i != `APB4_APU__RING_DOORBELL)) begin
(offset_i != `APB4_APU__RING_DOORBELL) &&
(offset_i != `APB4_APU__KWS_COEFF_COMMAND)) begin
apb_read(offset_i, s_before, 1'b0);
end else begin
s_before = 32'd0;
@@ -458,7 +473,9 @@ module apu_reg_tb;
expect_read(`APB4_APU__ERROR_STATUS, 32'd1 | (32'(error_code_i) << 1));
expect_read(`APB4_APU__ERROR_ADDRESS, ApuBase + offset_i);
if ((offset_i != `APB4_APU__COMMAND) && (offset_i != `APB4_APU__IRQ_TEST) &&
(offset_i != `APB4_APU__RING_DOORBELL) && (offset_i != `APB4_APU__IRQ_STATE) &&
(offset_i != `APB4_APU__RING_DOORBELL) &&
(offset_i != `APB4_APU__KWS_COEFF_COMMAND) &&
(offset_i != `APB4_APU__IRQ_STATE) &&
(offset_i != `APB4_APU__ERROR_STATUS) && (offset_i != `APB4_APU__ERROR_ADDRESS) &&
(offset_i != `APB4_APU__ERROR_DETAIL)) begin
expect_read(offset_i, s_before);
@@ -488,6 +505,22 @@ module apu_reg_tb;
hard_reset();
if (!idle_o || irq_o) $fatal(1, "APU reset lifecycle outputs mismatch");
if ($test$plusargs("LEGACY_IRQ_ONLY")) begin
s_phase = "legacy IRQ mask";
expect_read(`APB4_APU__IP_VERSION, 32'h0001_0001);
check_rejected_write(`APB4_APU__IRQ_STATE, 32'd1 << `APB4_APU__IRQ_COEFFICIENT_LOAD_DONE,
`APB4_APU__ERROR_CODE_INVALID_CONFIG);
check_rejected_write(`APB4_APU__IRQ_ENABLE, 32'd1 << `APB4_APU__IRQ_COEFFICIENT_LOAD_DONE,
`APB4_APU__ERROR_CODE_INVALID_CONFIG);
check_rejected_write(`APB4_APU__IRQ_TEST, 32'd1 << `APB4_APU__IRQ_COEFFICIENT_LOAD_DONE,
`APB4_APU__ERROR_CODE_INVALID_CONFIG);
hard_reset();
apb_write(`APB4_APU__IRQ_TEST, 32'd1 << `APB4_APU__IRQ_SEQUENCER_TRAP, 4'hf, 1'b0);
expect_read(`APB4_APU__IRQ_STATE, 32'd1 << `APB4_APU__IRQ_SEQUENCER_TRAP);
$display("APU legacy APB IRQ mask passed");
$finish;
end
s_phase = "access matrix";
for (int unsigned register_index = 0; register_index < RegisterCount; register_index++) begin
hard_reset();
@@ -566,10 +599,10 @@ module apu_reg_tb;
check_rejected_write(`APB4_APU__COMMAND, 32'd1 << `APB4_APU__COMMAND_MODEL_LOAD,
`APB4_APU__ERROR_CODE_INVALID_CONFIG);
check_rejected_write(`APB4_APU__COMMAND, 32'h0000_0010, `APB4_APU__ERROR_CODE_INVALID_CONFIG);
check_rejected_write(`APB4_APU__IRQ_STATE, 32'h0000_0800, `APB4_APU__ERROR_CODE_INVALID_CONFIG);
check_rejected_write(`APB4_APU__IRQ_ENABLE, 32'h0000_0800,
check_rejected_write(`APB4_APU__IRQ_STATE, 32'h0000_1000, `APB4_APU__ERROR_CODE_INVALID_CONFIG);
check_rejected_write(`APB4_APU__IRQ_ENABLE, 32'h0000_1000,
`APB4_APU__ERROR_CODE_INVALID_CONFIG);
check_rejected_write(`APB4_APU__IRQ_TEST, 32'h0000_0800, `APB4_APU__ERROR_CODE_INVALID_CONFIG);
check_rejected_write(`APB4_APU__IRQ_TEST, 32'h0000_1000, `APB4_APU__ERROR_CODE_INVALID_CONFIG);
check_rejected_write(`APB4_APU__ERROR_STATUS, 32'h0000_0002,
`APB4_APU__ERROR_CODE_INVALID_CONFIG);
check_rejected_write(`APB4_APU__SEQUENCER_TIMEOUT, 32'd0, `APB4_APU__ERROR_CODE_INVALID_CONFIG);
@@ -605,6 +638,10 @@ module apu_reg_tb;
check_rejected_write(`APB4_APU__KWS_MODEL_ADDRESS, 32'h3005_0001,
`APB4_APU__ERROR_CODE_INVALID_CONFIG);
check_rejected_write(`APB4_APU__KWS_MODEL_SIZE, 32'd0, `APB4_APU__ERROR_CODE_INVALID_CONFIG);
check_rejected_write(`APB4_APU__KWS_COEFF_ADDRESS, 32'h3005_0001,
`APB4_APU__ERROR_CODE_INVALID_CONFIG);
check_rejected_write(`APB4_APU__KWS_COEFF_SIZE, 32'd61440,
`APB4_APU__ERROR_CODE_INVALID_CONFIG);
check_rejected_write(`APB4_APU__KWS_CONTROL, 32'h0000_0001,
`APB4_APU__ERROR_CODE_INVALID_CONFIG);
hard_reset();
@@ -620,7 +657,7 @@ module apu_reg_tb;
s_phase = "set over clear";
hard_reset();
apb_write(`APB4_APU__IRQ_TEST, 32'h0000_0001, 4'hf, 1'b0);
force u_dut.u_apu_reg.s_irq_set = 11'h001;
force u_dut.u_apu_reg.s_irq_set = 12'h001;
apb_write(`APB4_APU__IRQ_STATE, 32'h0000_0001, 4'h1, 1'b0);
release u_dut.u_apu_reg.s_irq_set;
expect_read(`APB4_APU__IRQ_STATE, 32'h0000_0001);
@@ -790,7 +827,7 @@ module apu_reg_tb;
(32'd1 << `APB4_APU__IRQ_MODEL_LOAD_DONE) | (32'd1 << `APB4_APU__IRQ_FIRST_ERROR));
expect_read(`APB4_APU__ERROR_ADDRESS, 32'h3005_1234);
expect_read(`APB4_APU__ERROR_DETAIL, 32'd0);
apb_write(`APB4_APU__IRQ_STATE, 32'h0000_07ff, 4'hf, 1'b0);
apb_write(`APB4_APU__IRQ_STATE, 32'h0000_0fff, 4'hf, 1'b0);
release u_dut.s_kws_loader_done;
force u_dut.s_kws_loader_abort_done = 1'b1;
@(posedge clk_i);
@@ -844,6 +881,8 @@ module apu_reg_tb;
force u_dut.u_apu_reg.s_kws_control_q = 2'd3;
force u_dut.s_kws_model_valid = 1'b1;
force u_dut.s_kws_model_lock = 1'b1;
force u_dut.s_kws_coeff_valid = 1'b1;
force u_dut.s_kws_coeff_lock = 1'b1;
force u_dut.s_kws_disable_request = 1'b1;
@(negedge clk_i);
u_dut.u_codec_controller.s_descriptor_q[(0*32)+:32] = 32'd1;
@@ -862,6 +901,8 @@ module apu_reg_tb;
release u_dut.s_kws_disable_request;
release u_dut.s_kws_model_lock;
release u_dut.s_kws_model_valid;
release u_dut.s_kws_coeff_lock;
release u_dut.s_kws_coeff_valid;
release u_dut.u_apu_reg.s_kws_control_q;
s_phase = "KWS model default publication";
+83 -4
View File
@@ -2,6 +2,7 @@
from __future__ import annotations
import os
import shutil
import struct
import subprocess
@@ -90,8 +91,11 @@ def test_apu_p1_apb_register_shell(tmp_path: Path) -> None:
str(multimedia / "apu_ring_scheduler.sv"),
str(multimedia / "apu_stream_router.sv"),
str(multimedia / "apu_control_store.sv"),
str(multimedia / "apu_proof_memo.sv"),
str(multimedia / "apu_microcode_loader.sv"),
str(multimedia / "apu_local_sram.sv"),
str(multimedia / "apu_kws_coeff_store.sv"),
str(multimedia / "apu_kws_coeff_loader.sv"),
str(multimedia / "apu_kws_engine.sv"),
str(multimedia / "apu_kws_sram_client.sv"),
str(multimedia / "apu_kws_model_loader.sv"),
@@ -133,6 +137,28 @@ def test_apu_p1_apb_register_shell(tmp_path: Path) -> None:
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
assert "APU-P1 complete APB register matrix passed" in result.stdout
legacy_simulation = tmp_path / "apu_reg_legacy_tb"
subprocess.run(
[
iverilog,
"-g2012",
"-s",
"apu_reg_tb",
"-Papu_reg_tb.EnableP7=0",
"-o",
str(legacy_simulation),
str(converted),
],
check=True,
)
legacy = subprocess.run(
[vvp, str(legacy_simulation), "+LEGACY_IRQ_ONLY"],
text=True,
capture_output=True,
check=True,
)
assert "APU legacy APB IRQ mask passed" in legacy.stdout
def test_apu_p1_integrated_irq_ownership_topology(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
@@ -180,8 +206,11 @@ def test_apu_p1_integrated_irq_ownership_topology(tmp_path: Path) -> None:
str(multimedia / "apu_ring_scheduler.sv"),
str(multimedia / "apu_stream_router.sv"),
str(multimedia / "apu_control_store.sv"),
str(multimedia / "apu_proof_memo.sv"),
str(multimedia / "apu_microcode_loader.sv"),
str(multimedia / "apu_local_sram.sv"),
str(multimedia / "apu_kws_coeff_store.sv"),
str(multimedia / "apu_kws_coeff_loader.sv"),
str(multimedia / "apu_kws_engine.sv"),
str(multimedia / "apu_kws_sram_client.sv"),
str(multimedia / "apu_kws_model_loader.sv"),
@@ -488,6 +517,7 @@ last:
str(ROOT / "rtl/tech/tc_sram.sv"),
str(multimedia / "apu_microcode_pkg.sv"),
str(multimedia / "apu_control_store.sv"),
str(multimedia / "apu_proof_memo.sv"),
str(multimedia / "apu_microcode_loader.sv"),
str(multimedia / "apu_codec_sequencer.sv"),
str(ROOT / "tests/rtl/apu_p3_microcode_tb.sv"),
@@ -598,8 +628,11 @@ done:
str(multimedia / "apu_ring_scheduler.sv"),
str(multimedia / "apu_stream_router.sv"),
str(multimedia / "apu_control_store.sv"),
str(multimedia / "apu_proof_memo.sv"),
str(multimedia / "apu_microcode_loader.sv"),
str(multimedia / "apu_local_sram.sv"),
str(multimedia / "apu_kws_coeff_store.sv"),
str(multimedia / "apu_kws_coeff_loader.sv"),
str(multimedia / "apu_kws_engine.sv"),
str(multimedia / "apu_kws_sram_client.sv"),
str(multimedia / "apu_kws_model_loader.sv"),
@@ -973,6 +1006,13 @@ def test_apu_p7_kws_engine_directed(tmp_path: Path) -> None:
sv2v = shutil.which("sv2v")
if iverilog is None or vvp is None or sv2v is None:
return
feature = (
ROOT
/ ".cache/retrosoc/sources/apu-kws-mfcc/datasets/kws01"
/ "tst_000000_Stop_7.bin"
)
if not KWS_MODEL.is_file() or not feature.is_file():
return
common = ROOT / "rtl/managed/clusterip/common/rtl"
multimedia = ROOT / "rtl/ip/multimedia"
source_list = tmp_path / "apu_kws_engine.fl"
@@ -984,8 +1024,11 @@ def test_apu_p7_kws_engine_directed(tmp_path: Path) -> None:
f"+incdir+{common / 'interface'}",
f"+incdir+{multimedia}",
str(common / "interface/axi4_stream_if.sv"),
str(ROOT / "rtl/tech/tc_sram.sv"),
str(multimedia / "apu_kws_sram_client.sv"),
str(multimedia / "apu_kws_coeff_store.sv"),
str(multimedia / "apu_kws_engine.sv"),
str(ROOT / "tests/rtl/apu_kws_coeff_fixture.sv"),
str(ROOT / "tests/rtl/apu_kws_engine_tb.sv"),
"",
]
@@ -1005,19 +1048,46 @@ def test_apu_p7_kws_engine_directed(tmp_path: Path) -> None:
check=True,
)
simulation = tmp_path / "apu_kws_engine"
verilator = shutil.which("verilator")
if verilator is None:
subprocess.run(
[iverilog, "-g2012", "-s", "apu_kws_engine_tb", "-o", str(simulation), str(converted)],
check=True,
)
command = [vvp, str(simulation)]
feature = (
ROOT
/ ".cache/retrosoc/sources/apu-kws-mfcc/datasets/kws01"
/ "tst_000000_Stop_7.bin"
else:
verilator_dir = tmp_path / "verilator"
ccache_dir = tmp_path / "ccache"
ccache_temp = tmp_path / "ccache-tmp"
ccache_dir.mkdir()
ccache_temp.mkdir()
environment = os.environ.copy()
environment["CCACHE_DIR"] = str(ccache_dir)
environment["CCACHE_TEMPDIR"] = str(ccache_temp)
subprocess.run(
[
verilator,
"--binary",
"--timing",
"-Wno-fatal",
"--top-module",
"apu_kws_engine_tb",
"--Mdir",
str(verilator_dir),
str(converted),
],
check=True,
capture_output=True,
text=True,
env=environment,
)
command = [str(verilator_dir / "Vapu_kws_engine_tb")]
if KWS_MODEL.is_file() and feature.is_file():
from generate_apu_kws_rtl_constants import build_apuc
image = import_tflite(KWS_MODEL)
apum_hex = tmp_path / "kws_apum.hex"
apuc_hex = tmp_path / "kws_apuc.hex"
mfcc_hex = tmp_path / "kws_mfcc.hex"
layers_hex = tmp_path / "kws_layers.hex"
apum_hex.write_text(
@@ -1031,6 +1101,14 @@ def test_apu_p7_kws_engine_directed(tmp_path: Path) -> None:
"".join(f"{value:02x}\n" for value in feature.read_bytes()),
encoding="ascii",
)
apuc, _manifest = build_apuc(image)
apuc_hex.write_text(
"".join(
f"{int.from_bytes(apuc[offset : offset + 4], 'little'):08x}\n"
for offset in range(0, len(apuc), 4)
),
encoding="ascii",
)
layers = infer_apum(image, feature.read_bytes())
layers_hex.write_text(
"".join(f"{value & 0xff:02x}\n" for layer in layers for value in layer),
@@ -1039,6 +1117,7 @@ def test_apu_p7_kws_engine_directed(tmp_path: Path) -> None:
command.extend(
(
f"+APUM_HEX={apum_hex}",
f"+APUC_HEX={apuc_hex}",
f"+MFCC_HEX={mfcc_hex}",
f"+LAYERS_HEX={layers_hex}",
)
+4
View File
@@ -486,6 +486,7 @@ def test_p5_v2_4096_word_loader_store_and_sequencer(tmp_path: Path) -> None:
multimedia = ROOT / "rtl/ip/multimedia"
sources = [
multimedia / "apu_microcode_pkg.sv",
multimedia / "apu_proof_memo.sv",
multimedia / "apu_microcode_loader.sv",
multimedia / "apu_control_store.sv",
multimedia / "apu_codec_sequencer.sv",
@@ -662,8 +663,11 @@ def test_p5_direct_wav_uses_product_loader_dma_sequencer_and_tx(tmp_path: Path)
"apu_ring_scheduler.sv",
"apu_stream_router.sv",
"apu_control_store.sv",
"apu_proof_memo.sv",
"apu_microcode_loader.sv",
"apu_local_sram.sv",
"apu_kws_coeff_store.sv",
"apu_kws_coeff_loader.sv",
"apu_kws_engine.sv",
"apu_kws_sram_client.sv",
"apu_kws_model_loader.sv",
+8 -1
View File
@@ -135,7 +135,9 @@ def _compile_dump_tb(build_dir: Path) -> dict[str, Path | None]:
f"+incdir+{multimedia}",
str(common / "interface/axi4_stream_if.sv"),
str(multimedia / "apu_kws_sram_client.sv"),
str(multimedia / "apu_kws_coeff_store.sv"),
str(multimedia / "apu_kws_engine.sv"),
str(ROOT / "tests/rtl/apu_kws_coeff_fixture.sv"),
str(DUMP_TB),
"",
]
@@ -209,12 +211,17 @@ def test_apu_p7_frontend_rtl_differential(tmp_path: Path) -> None:
windows[index] = padded
_write_hex(pcm_dir / f"pcm_{index:06d}.hex", padded)
apum_hex = tmp_path / "kws_apum.hex"
_write_hex(apum_hex, import_tflite(TFLITE))
image = import_tflite(TFLITE)
_write_hex(apum_hex, image)
apuc_hex = tmp_path / "kws_apuc.hex"
apuc, _manifest = gen.build_apuc(image)
_write_hex(apuc_hex, apuc)
binaries = _compile_dump_tb(tmp_path)
dumps: dict[int, tuple[bytes, int]] = {}
for first, count in spans:
plusargs = [
f"+APUM_HEX={apum_hex}",
f"+APUC_HEX={apuc_hex}",
f"+PCM_DIR={pcm_dir}",
f"+WINDOW_COUNT={count}",
f"+FIRST_INDEX={first}",
+11 -1
View File
@@ -24,8 +24,11 @@ PRODUCTION_SOURCES = (
"apu_ring_scheduler.sv",
"apu_stream_router.sv",
"apu_control_store.sv",
"apu_proof_memo.sv",
"apu_microcode_loader.sv",
"apu_local_sram.sv",
"apu_kws_coeff_store.sv",
"apu_kws_coeff_loader.sv",
"apu_kws_engine.sv",
"apu_kws_sram_client.sv",
"apu_kws_model_loader.sv",
@@ -72,11 +75,16 @@ def test_apu_p8_quiesced_loader_admission(tmp_path: Path) -> None:
pytest.skip("locked P7 model was not installed")
from apu_kws_convert import import_tflite
from generate_apu_kws_rtl_constants import build_apuc
apumc_hex = tmp_path / "apu-p5.hex"
_hex_words(apumc_hex, bundle.read_bytes(), 16384)
apum_hex = tmp_path / "kws-apum.hex"
_hex_words(apum_hex, import_tflite(KWS_MODEL), 8192)
apum = import_tflite(KWS_MODEL)
_hex_words(apum_hex, apum, 8192)
apuc_hex = tmp_path / "kws-apuc.hex"
apuc, _layout = build_apuc(apum)
_hex_words(apuc_hex, apuc, 15376)
common = ROOT / "rtl/managed/clusterip/common/rtl"
multimedia = ROOT / "rtl/ip/multimedia"
@@ -125,11 +133,13 @@ def test_apu_p8_quiesced_loader_admission(tmp_path: Path) -> None:
str(simulation),
f"+APUMC_HEX={apumc_hex}",
f"+APUM_HEX={apum_hex}",
f"+APUC_HEX={apuc_hex}",
],
check=True,
capture_output=True,
text=True,
)
assert "P8_QUIESCE_MC" in result.stdout
assert "P8_QUIESCE_COEFF" in result.stdout
assert "P8_QUIESCE_MODEL" in result.stdout
assert "APU-P8 quiesced loader admission test passed" in result.stdout
+100
View File
@@ -0,0 +1,100 @@
"""Frozen APU-P9 APUC image and physical-bank layout coverage."""
from __future__ import annotations
import hashlib
import copy
import struct
import sys
import zlib
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "scripts"))
import generate_apu_kws_rtl_constants as gen # noqa: E402
from apu_kws_coeff import validate_layout_manifest # noqa: E402
from apu_kws_convert import import_tflite # noqa: E402
TFLITE = (
ROOT
/ ".cache/retrosoc/sources/apu-mlperf-tiny/benchmark/training/keyword_spotting"
/ "trained_models/kws_ref_model.tflite"
)
def _require_tflite() -> Path:
if not TFLITE.is_file():
pytest.fail(
"locked P7 model is required for APU-P9 coefficient validation; "
"run `make setup-apu-kws-reference`"
)
return TFLITE
def test_apu_p9_frozen_apuc_and_layout() -> None:
image = import_tflite(_require_tflite())
apuc, manifest = gen.build_apuc(image)
validate_layout_manifest(apuc, manifest)
header = struct.unpack_from("<16I", apuc)
assert len(apuc) == gen.APUC_BYTES == 61504
assert header[:10] == (
gen.APUC_MAGIC,
gen.APUC_ABI,
gen.APUC_BYTES,
gen.APUC_HEADER_BYTES,
gen.APUC_PAYLOAD_BYTES,
gen.APUC_PROFILE,
gen.APUC_LAYOUT_ID,
gen.APUC_BANK_COUNT,
gen.APUC_TABLE_COUNT,
gen.APUC_PAYLOAD_CRC,
)
assert header[10:13] == (*gen.APUC_COEFFICIENT_ID, gen.APUC_APUM_PAYLOAD_CRC)
assert header[14:] == (0, 0)
assert zlib.crc32(apuc[64:]) & 0xFFFFFFFF == gen.APUC_PAYLOAD_CRC
assert hashlib.sha256(apuc[64:]).hexdigest() == gen.APUC_PAYLOAD_SHA256
assert hashlib.sha256(apuc).hexdigest() == gen.APUC_SHA256
assert manifest["layout"] == {
"geometry": "tc_sram_1024x32",
"banks": 15,
"words_per_bank": 1024,
"populated_words": 14444,
"zero_padding_words": 916,
}
def test_apu_p9_layout_locations_are_unique_and_complete() -> None:
apuc, manifest = gen.build_apuc(import_tflite(_require_tflite()))
validate_layout_manifest(apuc, manifest)
locations = [
tuple(location)
for table in manifest["tables"].values()
for location in table["locations"]
]
assert len(locations) == 14444
assert len(set(locations)) == len(locations)
assert all(0 <= bank < 15 and 0 <= row < 1024 for bank, row in locations)
def test_apu_p9_layout_oracle_rejects_generator_self_consistency() -> None:
apuc, manifest = gen.build_apuc(import_tflite(_require_tflite()))
stale = copy.deepcopy(manifest)
stale["tables"]["hann"]["locations"][0] = [14, 1023]
with pytest.raises(ValueError, match="independent map/hash oracle"):
validate_layout_manifest(apuc, stale)
missing_revision = copy.deepcopy(manifest)
del missing_revision["generator"]["revision"]
with pytest.raises(ValueError, match="generator revision"):
validate_layout_manifest(apuc, missing_revision)
def test_apu_p9_irq_mask_preserves_legacy_rejection() -> None:
register_source = (ROOT / "rtl/ip/multimedia/apu_reg.sv").read_text(encoding="utf-8")
assert "IrqMask = EnableP7 ? 12'hfff : 12'h7ff" in register_source
+315
View File
@@ -0,0 +1,315 @@
"""Fail-closed APU-P9 evidence assembly and synthesis A/B coverage."""
from __future__ import annotations
import json
import subprocess
import sys
from copy import deepcopy
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
import generate_apu_kws_rtl_constants as coefficient_generator # noqa: E402
from apu_kws_convert import import_tflite # noqa: E402
ROOT = Path(__file__).resolve().parents[1]
TFLITE = (
ROOT
/ ".cache/retrosoc/sources/apu-mlperf-tiny/benchmark/training/keyword_spotting"
/ "trained_models/kws_ref_model.tflite"
)
def _write_json(path: Path, value: object) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(value) + "\n", encoding="ascii")
def _variant(root: Path, *, baseline: bool) -> None:
apu_path = "retrosoc_asic.u_retrosoc.u_apb4_periph.u_apb4_apu."
modules = {
f"\\fifo${apu_path}u_stream_fifo": {"num_memories": 1, "num_memory_bits": 60000},
}
if baseline:
modules.update(
{
f"\\apu_kws_engine${apu_path}u_kws_engine": {
"num_memories": 18,
"num_memory_bits": 799680,
},
f"\\apu_microcode_loader${apu_path}u_microcode_loader": {
"num_memories": 2,
"num_memory_bits": 532480,
},
}
)
_write_json(root / "syn/yosys/rpt/retrosoc_asic_pre_memory.json", {"modules": modules})
_write_json(
root / "syn/yosys/rpt/retrosoc_asic_pre_tech.json",
{
"design": {
"num_cells": 1200 if baseline else 600,
"num_cells_by_type": {"$_DFF_P_": 400 if baseline else 180, "$_MUX_": 300 if baseline else 90},
}
},
)
_write_json(
root / "syn/yosys/yosys-perf.json",
{
"generator": "Yosys 0.67",
"total_ns": 100_000_000_000 if baseline else 60_000_000_000,
"passes": {
"memory": {"runtime_ns": 10_000_000_000 if baseline else 5_000_000_000},
"opt_dff": {"runtime_ns": 70_000_000_000 if baseline else 10_000_000_000},
**({} if baseline else {"abc": {"runtime_ns": 20_000_000_000}}),
},
},
)
_write_json(
root / "syn/yosys/result-synth.json",
{
"status": "timed_out" if baseline else "passed",
"exit_code": 124 if baseline else 0,
"duration_seconds": 100 if baseline else 60,
"peak_rss_kib": 1000 if baseline else 500,
},
)
configuration = {"PDK": "IHP130", "APU_ENABLE_P7": "YES", "SYNTH": "YOSYS"}
configuration["HAVE_SRAM_MACRO"] = "YES"
revision = (
"0b73994772068e5fac00fb1a98f87f88ed121cdf"
if baseline
else "1111111111111111111111111111111111111111"
)
_write_json(
root / "meta/manifest.json",
{
"configuration": configuration,
"dependency_lock": {"sha256": "abc"},
"tools": {"yosys": "0.67"},
"profile": "ihp130",
"repository": {"commit": revision, "dirty": False},
},
)
groups = {
"u_control_store": 8,
"u_local_sram": 12,
"u_kws_sram_client": 16,
"u_microcode_loader": 8,
**({} if baseline else {"u_proof_memo": 17, "u_kws_coeff_store": 15}),
}
lines: list[str] = []
for group, count in groups.items():
for index in range(count):
if group == "u_proof_memo":
path = f"{apu_path}u_microcode_loader.{group}.u_mem_{index}"
else:
path = f"{apu_path}{group}.u_mem_{index}"
lines.append(path)
macro_path = root / "syn/yosys/rpt/retrosoc_asic_macros.rpt"
macro_path.parent.mkdir(parents=True, exist_ok=True)
macro_path.write_text("\n".join(lines) + "\n", encoding="ascii")
def test_apu_p9_memory_ab_requires_all_frozen_checks(tmp_path: Path) -> None:
baseline = tmp_path / "baseline"
candidate = tmp_path / "candidate"
output = tmp_path / "memory-synthesis-ab.json"
_variant(baseline, baseline=True)
_variant(candidate, baseline=False)
subprocess.run(
[
sys.executable,
str(ROOT / "scripts/apu_p9_memory_ab.py"),
"--baseline-root",
str(baseline),
"--candidate-root",
str(candidate),
"--output",
str(output),
],
check=True,
)
report = json.loads(output.read_text(encoding="ascii"))
assert report["status"] == "passed"
assert report["candidate"]["memory"]["target_bits"] == 0
assert report["candidate"]["macros"]["apu_wrappers"] == 76
assert all(report["checks"].values())
assembled = tmp_path / "assembled"
report_arguments: list[str] = []
for name in (
"coefficient-layout.json",
"coefficient-lifecycle.json",
"memo-equivalence.json",
"numerical-latency.json",
"memory-synthesis-ab.json",
"physical.json",
):
source = output if name == "memory-synthesis-ab.json" else tmp_path / name
if source != output:
_write_json(source, {"status": "unrun", "reason": "fixture intentionally unrun"})
report_arguments.extend(("--report", f"{name}={source}"))
subprocess.run(
[
sys.executable,
str(ROOT / "scripts/apu_p9_evidence.py"),
"assemble",
*report_arguments,
"--output-dir",
str(assembled),
],
check=True,
)
assert json.loads((assembled / "memory-synthesis-ab.json").read_text())["status"] == "passed"
def test_apu_p9_memory_ab_rejects_unqualified_revisions(tmp_path: Path) -> None:
baseline = tmp_path / "baseline"
candidate = tmp_path / "candidate"
output = tmp_path / "memory-synthesis-ab.json"
_variant(baseline, baseline=True)
_variant(candidate, baseline=False)
manifest = json.loads((candidate / "meta/manifest.json").read_text())
manifest["repository"] = {"commit": "0b73994772068e5fac00fb1a98f87f88ed121cdf", "dirty": False}
_write_json(candidate / "meta/manifest.json", manifest)
result = subprocess.run(
[
sys.executable,
str(ROOT / "scripts/apu_p9_memory_ab.py"),
"--baseline-root",
str(baseline),
"--candidate-root",
str(candidate),
"--output",
str(output),
],
check=False,
)
assert result.returncode != 0
assert json.loads(output.read_text())["checks"]["revision_qualified"] is False
def test_apu_p9_memory_ab_counts_new_storage_modules_as_targets(tmp_path: Path) -> None:
baseline = tmp_path / "baseline"
candidate = tmp_path / "candidate"
output = tmp_path / "memory-synthesis-ab.json"
_variant(baseline, baseline=True)
_variant(candidate, baseline=False)
inventory_path = candidate / "syn/yosys/rpt/retrosoc_asic_pre_memory.json"
inventory = json.loads(inventory_path.read_text())
apu_path = "retrosoc_asic.u_retrosoc.u_apb4_periph.u_apb4_apu."
inventory["modules"][f"\\apu_proof_memo${apu_path}u_microcode_loader.u_proof_memo"] = {
"num_memories": 1,
"num_memory_bits": 32,
}
_write_json(inventory_path, inventory)
result = subprocess.run(
[
sys.executable,
str(ROOT / "scripts/apu_p9_memory_ab.py"),
"--baseline-root",
str(baseline),
"--candidate-root",
str(candidate),
"--output",
str(output),
],
check=False,
)
assert result.returncode != 0
report = json.loads(output.read_text())
assert report["candidate"]["memory"]["target_bits"] == 32
assert report["checks"]["target_inferred_bits_zero"] is False
def test_apu_p9_evidence_initialization_keeps_missing_runs_unrun(tmp_path: Path) -> None:
layout = tmp_path / "layout.json"
apuc_path = tmp_path / "apu-p9.apuc"
output = tmp_path / "evidence"
apuc, manifest = coefficient_generator.build_apuc(import_tflite(TFLITE))
apuc_path.write_bytes(apuc)
_write_json(layout, manifest)
subprocess.run(
[
sys.executable,
str(ROOT / "scripts/apu_p9_evidence.py"),
"initialize",
"--layout",
str(layout),
"--apuc",
str(apuc_path),
"--profile",
str(ROOT / "configs/ci/ihp130.mk"),
"--output-dir",
str(output),
],
check=True,
)
assert json.loads((output / "coefficient-layout.json").read_text())["status"] == "passed"
assert json.loads((output / "memory-synthesis-ab.json").read_text())["status"] == "unrun"
def test_apu_p9_evidence_rejects_incomplete_passed_reports(tmp_path: Path) -> None:
reports = tmp_path / "reports"
output = tmp_path / "evidence"
sources: list[str] = []
for name in (
"coefficient-layout.json",
"coefficient-lifecycle.json",
"memo-equivalence.json",
"numerical-latency.json",
"memory-synthesis-ab.json",
"physical.json",
):
source = reports / name
_write_json(source, {"status": "passed"})
sources.extend(("--report", f"{name}={source}"))
result = subprocess.run(
[
sys.executable,
str(ROOT / "scripts/apu_p9_evidence.py"),
"assemble",
*sources,
"--output-dir",
str(output),
],
text=True,
capture_output=True,
check=False,
)
assert result.returncode != 0
assert "coefficient-layout.json" in result.stderr
def test_apu_p9_evidence_rejects_stale_layout_mapping(tmp_path: Path) -> None:
layout = tmp_path / "layout.json"
apuc_path = tmp_path / "apu-p9.apuc"
output = tmp_path / "evidence"
apuc, manifest = coefficient_generator.build_apuc(import_tflite(TFLITE))
stale = deepcopy(manifest)
stale["tables"]["log"]["locations"][1024] = [0, 0]
apuc_path.write_bytes(apuc)
_write_json(layout, stale)
result = subprocess.run(
[
sys.executable,
str(ROOT / "scripts/apu_p9_evidence.py"),
"initialize",
"--layout",
str(layout),
"--apuc",
str(apuc_path),
"--profile",
str(ROOT / "configs/ci/ihp130.mk"),
"--output-dir",
str(output),
],
text=True,
capture_output=True,
check=False,
)
assert result.returncode != 0
assert "independent map/hash oracle" in result.stderr
+84
View File
@@ -0,0 +1,84 @@
"""Focused RTL coverage for the APU-P9 physical storage transactions."""
from __future__ import annotations
import shutil
import subprocess
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
def test_apu_p9_production_storage_inventory() -> None:
multimedia = ROOT / "rtl/ip/multimedia"
engine = (multimedia / "apu_kws_engine.sv").read_text(encoding="utf-8")
model_loader = (multimedia / "apu_kws_model_loader.sv").read_text(encoding="utf-8")
microcode_loader = (multimedia / "apu_microcode_loader.sv").read_text(encoding="utf-8")
coefficient_store = (multimedia / "apu_kws_coeff_store.sv").read_text(encoding="utf-8")
proof_memo = (multimedia / "apu_proof_memo.sv").read_text(encoding="utf-8")
assert 'include "apu_kws_rom.svh"' not in engine
assert 'include "apu_kws_apum_profile.svh"' not in model_loader
assert "s_key_mem" not in microcode_loader
assert "localparam int unsigned BankCount = 15" in coefficient_store
assert "for (genvar bank = 0; bank < BankCount; bank++)" in coefficient_store
assert proof_memo.count("tc_sram_1024x32") == 3
assert "for (genvar bank = 0; bank < 8; bank++)" in proof_memo
assert "u_valid_bitmap_sram" in proof_memo
inventory = {
"control_store": 8,
"codec_common_data": 12,
"kws_model_scratch": 16,
"loader_path_stack": 8,
"verifier_memo": 17,
"coefficient_store": 15,
}
assert sum(inventory.values()) == 76
assert sum(inventory.values()) * 4 == 304
def test_apu_p9_coefficient_store_and_proof_memo(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
sv2v = shutil.which("sv2v")
if iverilog is None or vvp is None or sv2v is None:
pytest.fail("APU-P9 storage validation requires locked iverilog, vvp, and sv2v tools")
multimedia = ROOT / "rtl/ip/multimedia"
source_list = tmp_path / "apu_p9_storage.fl"
source_list.write_text(
"\n".join(
(
f"+incdir+{multimedia}",
str(ROOT / "rtl/tech/tc_sram.sv"),
str(multimedia / "apu_kws_coeff_store.sv"),
str(multimedia / "apu_proof_memo.sv"),
str(ROOT / "tests/rtl/apu_p9_storage_tb.sv"),
"",
)
),
encoding="utf-8",
)
converted = tmp_path / "apu_p9_storage.v"
subprocess.run(
[
sys.executable,
str(ROOT / "rtl/mini/script/convt_sv2v.py"),
"-f",
str(source_list),
"--output",
str(converted),
],
check=True,
)
simulation = tmp_path / "apu_p9_storage"
subprocess.run(
[iverilog, "-g2012", "-s", "apu_p9_storage_tb", "-o", str(simulation), str(converted)],
check=True,
)
result = subprocess.run([vvp, str(simulation)], check=True, capture_output=True, text=True)
assert "APU-P9 coefficient store and proof memo test passed" in result.stdout
+23 -2
View File
@@ -167,8 +167,28 @@ def test_p4_local_sram_has_macro_and_inferred_profiles() -> None:
encoding="utf-8"
)
compact_engine = "".join(engine_source.split())
assert "KwsWordCount=ModelWordCount+ScratchWordCount" in "".join(kws_source.split())
assert "`RETROSOC_APU_KWS__SCRATCH_BYTES/4" in "".join(kws_source.split())
compact_kws = "".join(kws_source.split())
assert "localparamintunsignedBankCount=16" in compact_kws
assert "tc_sram_1024x32u_kws_sram" in compact_kws
assert "logic[31:0]mem[0:16383]" in compact_kws
assert "`ifdefHAVE_SRAM_MACRO" in compact_kws
coefficient_source = (
ROOT / "rtl/ip/multimedia/apu_kws_coeff_store.sv"
).read_text(encoding="utf-8")
compact_coefficient = "".join(coefficient_source.split())
assert "localparamintunsignedBankCount=15" in compact_coefficient
assert "tc_sram_1024x32u_coefficient_sram" in compact_coefficient
assert "logics_storage_q[0:1023]" not in compact_coefficient
assert "logic[31:0]s_storage_q[0:1023]" in compact_coefficient
memo_source = (ROOT / "rtl/ip/multimedia/apu_proof_memo.sv").read_text(
encoding="utf-8"
)
compact_memo = "".join(memo_source.split())
assert "tc_sram_1024x32u_key_low_sram" in compact_memo
assert "tc_sram_1024x32u_key_high_sram" in compact_memo
assert "tc_sram_1024x32u_valid_bitmap_sram" in compact_memo
assert "logicsigned[7:0]s_tensor_a_q[" not in compact_engine
assert "logicsigned[7:0]s_tensor_b_q[" not in compact_engine
assert "logic[31:0]s_mel_q[" not in compact_engine
@@ -1372,6 +1392,7 @@ def test_p4_loader_publishes_control_and_table_atomically(tmp_path: Path) -> Non
str(multimedia / "apu_microcode_pkg.sv"),
str(multimedia / "apu_control_store.sv"),
str(multimedia / "apu_local_sram.sv"),
str(multimedia / "apu_proof_memo.sv"),
str(multimedia / "apu_microcode_loader.sv"),
str(ROOT / "tests/rtl/apu_p4_loader_tb.sv"),
"",
+2 -1
View File
@@ -38,7 +38,7 @@ def test_apu_profiles_follow_deployed_p5_and_committed_p7_selection():
assert ordinary["formats"] == {"WAV": True, "FLAC": True, "MP3": False, "KWS": False}
assert acceptance["formats"] == {"WAV": True, "FLAC": True, "MP3": False, "KWS": True}
assert (ordinary["capability"], ordinary["digest"]) == (0x1BD, 0)
assert (acceptance["capability"], acceptance["digest"]) == (0x1FD, 0xF5005D7C)
assert (acceptance["capability"], acceptance["digest"]) == (0x3FD, 0x63E96066)
@pytest.mark.parametrize("relative,before,after,message", [
@@ -69,6 +69,7 @@ def test_apu_rendered_acceptance_is_source_ordered_and_failure_checked():
("if (!rs_apu_release_quiesce())", "if (!rs_apu_release_stage_assets())", "call order"),
("rs_apu_release_fail(UINT8_C(6));", "rs_apu_release_fail(UINT8_C(7));", "failure checkpoint"),
("load_status = rs_apu_microcode_load(&mc_image", "load_status = rs_apu_kws_model_load(&mc_image", "image-load order"),
("load_status = rs_apu_kws_coeff_load(&coefficient_image", "load_status = rs_apu_kws_model_load(&coefficient_image", "image-load order"),
("rs_sysctrl_set_hp_release(true)", "rs_sysctrl_set_hp_release(false)", "publication/release order"),
])
def test_changed_acceptance_cannot_retain_published_steps(tmp_path, before, after, message):
+4 -4
View File
@@ -27,16 +27,16 @@ def reference():
return collect_registers()
def test_current_apu_interface_declares_p5_default_and_p7_release(reference):
def test_current_apu_interface_declares_p5_default_and_p9_release(reference):
registers = {r["name"]: r for r in reference["apu"]["registers"]}
constants = (ROOT / "crt/include/retrosoc/hal/apu_regs.h").read_text(
encoding="utf-8"
)
assert re.search(r"RS_APU_IP_VERSION_VALUE\s+UINT32_C\(0x00010001\)", constants)
assert re.search(r"RS_APU_IP_VERSION_VALUE\s+UINT32_C\(0x00010002\)", constants)
assert re.search(r"RS_APU_CAPABILITY0_P5_IMPLEMENTED\s+UINT32_C\(0x000001BD\)", constants)
assert re.search(r"RS_APU_CAPABILITY0_P7_IMPLEMENTED\s+UINT32_C\(0x000001FD\)", constants)
assert re.search(r"RS_APU_CAPABILITY0_P7_IMPLEMENTED\s+UINT32_C\(0x000003FD\)", constants)
assert re.search(r"RS_APU_CAPABILITY1_IMPLEMENTED\s+UINT32_C\(0x01827020\)", constants)
assert "V1.1" in registers["IP_VERSION"]["description"]
assert "V1.2" in registers["IP_VERSION"]["description"]
assert "32 KiB" in registers["CAPABILITY1"]["description"]
+1 -1
View File
@@ -34,7 +34,7 @@ def test_npu_deployed_geometry_dma_and_wire_layout(data):
def test_apu_capability_and_digest_remain_profile_specific(data):
rows = data["system_reference"]["apu_implementation"]["profiles"]
assert [(r["enabled"], r["capability"], r["digest"]) for r in rows] == [(False, 0x1BD, 0), (True, 0x1FD, 0xF5005D7C)]
assert [(r["enabled"], r["capability"], r["digest"]) for r in rows] == [(False, 0x1BD, 0), (True, 0x3FD, 0x63E96066)]
assert all(r["capability"] & 2 == 0 for r in rows) # MP3 remains clear.
+1 -1
View File
@@ -1726,7 +1726,7 @@ def test_quality_runs_accelerator_tests_with_locked_references_and_simulators()
assert "python3 rtl/ip/setup.py" in quality
assert "make CONFIG=configs/ci/ihp130.mk setup-pdk" in quality
assert "--requirement requirements/build.txt" in quality
assert "make setup-apu-reference setup-npu-reference" in quality
assert "make setup-apu-reference setup-apu-kws-reference setup-npu-reference" in quality
for path in (
".cache/retrosoc/sources/apu-mlperf-tiny",
".cache/retrosoc/sources/apu-kws-mfcc",