From 92830d963da2f1e39bb219c0d377bc4889c07755 Mon Sep 17 00:00:00 2001 From: maksyuki Date: Fri, 25 Sep 2026 08:35:07 +0800 Subject: [PATCH] feat: replace the rom of apu into sram --- .github/workflows/quality.yml | 2 +- Makefile | 42 +- app/apps/apu_release/app.mk | 7 +- app/apps/apu_release/apu_release_page.h | 2 + app/apps/apu_release/main.c | 52 +- app/ports/hp-apu/main.c | 21 + crt/include/retrosoc/hal/apu.h | 9 + crt/include/retrosoc/hal/apu_regs.h | 37 +- crt/src/hal/apu.c | 98 ++- docs/ip/apu.md | 31 +- physical/smoke/syn/yosys/report_apu_block.py | 70 +- publications/README.md | 9 +- publications/datasheets/chapter-review.md | 2 +- publications/datasheets/content-review.md | 13 +- publications/datasheets/diagram-storage.json | 2 +- publications/datasheets/features.json | 2 +- publications/datasheets/ip-content.json | 6 +- publications/datasheets/ips/apu.typ | 6 +- .../datasheets/register-annotations.json | 10 +- .../datasheets/sections/apu-release.typ | 4 +- .../datasheets/sections/product-use.typ | 2 +- publications/datasheets/sections/software.typ | 2 +- publications/datasheets/system-reference.json | 8 +- publications/dev_reference.py | 25 +- rtl/README.md | 6 +- rtl/ip/multimedia/apb4_apu.sv | 450 +++++++--- rtl/ip/multimedia/apu_define.svh | 27 + rtl/ip/multimedia/apu_kws_coeff_loader.sv | 501 +++++++++++ rtl/ip/multimedia/apu_kws_coeff_store.sv | 314 +++++++ rtl/ip/multimedia/apu_kws_engine.sv | 780 +++++++++++------- rtl/ip/multimedia/apu_kws_model_loader.sv | 104 ++- rtl/ip/multimedia/apu_microcode_loader.sv | 86 +- rtl/ip/multimedia/apu_proof_memo.sv | 207 +++++ rtl/ip/multimedia/apu_reg.sv | 137 ++- rtl/mini/filelist/ip.fl | 3 + rtl/mini/formal/apu_kws_formal.sv | 7 + rtl/mini/formal/apu_p9_formal.sv | 106 +++ rtl/mini/formal/apu_p9_formal_props.sv | 53 ++ rtl/mini/formal/generate_formal_filelist.py | 7 + rtl/mini/mk/formal.mk | 22 +- rtl/rtl_style_audit.json | 3 + scripts/README.md | 29 + scripts/apu_abi_digest.py | 71 +- scripts/apu_kws_coeff.py | 228 +++++ scripts/apu_p9_evidence.py | 342 ++++++++ scripts/apu_p9_memory_ab.py | 306 +++++++ scripts/build_apu_release_assets.py | 19 +- scripts/generate_apu_kws_rtl_constants.py | 201 ++++- scripts/run_apu_p5_corpus_rtl.py | 3 + scripts/run_apu_p7_concurrent_rtl.py | 15 +- scripts/run_apu_p7_kws_rtl.py | 7 + tests/README.md | 7 + tests/c/test_runtime.c | 26 + tests/rtl/apu_kws_coeff_fixture.sv | 99 +++ tests/rtl/apu_kws_engine_tb.sv | 58 +- tests/rtl/apu_kws_loader_tb.sv | 25 + tests/rtl/apu_p7_accuracy_tb.sv | 48 +- tests/rtl/apu_p7_concurrent_tb.sv | 33 +- tests/rtl/apu_p7_frontend_dump_tb.sv | 67 +- tests/rtl/apu_p8_quiesce_load_tb.sv | 33 +- tests/rtl/apu_p9_storage_tb.sv | 199 +++++ tests/rtl/apu_reg_tb.sv | 69 +- tests/test_apu.py | 99 ++- tests/test_apu_codec_transport.py | 4 + tests/test_apu_kws_frontend.py | 9 +- tests/test_apu_p8_quiesce_load.py | 12 +- tests/test_apu_p9_coefficients.py | 100 +++ tests/test_apu_p9_evidence.py | 315 +++++++ tests/test_apu_p9_storage.py | 84 ++ tests/test_apu_primitives.py | 25 +- tests/test_publication_refresh_claims.py | 3 +- tests/test_publication_registers.py | 8 +- tests/test_publication_v05.py | 2 +- tests/test_script_tools.py | 2 +- 74 files changed, 5126 insertions(+), 697 deletions(-) create mode 100644 rtl/ip/multimedia/apu_kws_coeff_loader.sv create mode 100644 rtl/ip/multimedia/apu_kws_coeff_store.sv create mode 100644 rtl/ip/multimedia/apu_proof_memo.sv create mode 100644 rtl/mini/formal/apu_p9_formal.sv create mode 100644 rtl/mini/formal/apu_p9_formal_props.sv create mode 100644 scripts/apu_kws_coeff.py create mode 100644 scripts/apu_p9_evidence.py create mode 100644 scripts/apu_p9_memory_ab.py create mode 100644 tests/rtl/apu_kws_coeff_fixture.sv create mode 100644 tests/rtl/apu_p9_storage_tb.sv create mode 100644 tests/test_apu_p9_coefficients.py create mode 100644 tests/test_apu_p9_evidence.py create mode 100644 tests/test_apu_p9_storage.py diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 9acd7d09..18dcd8a3 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -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 diff --git a/Makefile b/Makefile index 321a366f..53a51482 100644 --- a/Makefile +++ b/Makefile @@ -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 \ @@ -1058,4 +1092,4 @@ regress-nightly: python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite nightly sim-asm: asm - $(MAKE) SIM_FIRMWARE_NAME=$(ASM_FIRMWARE_NAME) sim \ No newline at end of file + $(MAKE) SIM_FIRMWARE_NAME=$(ASM_FIRMWARE_NAME) sim diff --git a/app/apps/apu_release/app.mk b/app/apps/apu_release/app.mk index 1e8f77b6..0b4ee5e4 100644 --- a/app/apps/apu_release/app.mk +++ b/app/apps/apu_release/app.mk @@ -4,13 +4,14 @@ 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 $@ -$(SW_BUILD_DIR)/firmware: $(APU_RELEASE_ASSETS_H) \ No newline at end of file +$(SW_BUILD_DIR)/firmware: $(APU_RELEASE_ASSETS_H) diff --git a/app/apps/apu_release/apu_release_page.h b/app/apps/apu_release/apu_release_page.h index a470fd39..47e55e89 100644 --- a/app/apps/apu_release/apu_release_page.h +++ b/app/apps/apu_release/apu_release_page.h @@ -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) diff --git a/app/apps/apu_release/main.c b/app/apps/apu_release/main.c index cdb956cc..fcc6e2b5 100644 --- a/app/apps/apu_release/main.c +++ b/app/apps/apu_release/main.c @@ -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)); } diff --git a/app/ports/hp-apu/main.c b/app/ports/hp-apu/main.c index 757b9d84..f8ebb8c4 100644 --- a/app/ports/hp-apu/main.c +++ b/app/ports/hp-apu/main.c @@ -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 (;;) { } } diff --git a/crt/include/retrosoc/hal/apu.h b/crt/include/retrosoc/hal/apu.h index 587ad85d..6a3be73d 100644 --- a/crt/include/retrosoc/hal/apu.h +++ b/crt/include/retrosoc/hal/apu.h @@ -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); diff --git a/crt/include/retrosoc/hal/apu_regs.h b/crt/include/retrosoc/hal/apu_regs.h index 05fda93a..e54c2d2f 100644 --- a/crt/include/retrosoc/hal/apu_regs.h +++ b/crt/include/retrosoc/hal/apu_regs.h @@ -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 diff --git a/crt/src/hal/apu.c b/crt/src/hal/apu.c index 57b915a4..2215bd41 100644 --- a/crt/src/hal/apu.c +++ b/crt/src/hal/apu.c @@ -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; } diff --git a/docs/ip/apu.md b/docs/ip/apu.md index ccab523d..1f432187 100644 --- a/docs/ip/apu.md +++ b/docs/ip/apu.md @@ -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, diff --git a/physical/smoke/syn/yosys/report_apu_block.py b/physical/smoke/syn/yosys/report_apu_block.py index b7569ccc..8bf30d2d 100644 --- a/physical/smoke/syn/yosys/report_apu_block.py +++ b/physical/smoke/syn/yosys/report_apu_block.py @@ -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) diff --git a/publications/README.md b/publications/README.md index b1bb8255..62858355 100644 --- a/publications/README.md +++ b/publications/README.md @@ -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 diff --git a/publications/datasheets/chapter-review.md b/publications/datasheets/chapter-review.md index 550b5db7..ef4c90fc 100644 --- a/publications/datasheets/chapter-review.md +++ b/publications/datasheets/chapter-review.md @@ -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. | diff --git a/publications/datasheets/content-review.md b/publications/datasheets/content-review.md index 53258383..a4c11945 100644 --- a/publications/datasheets/content-review.md +++ b/publications/datasheets/content-review.md @@ -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 diff --git a/publications/datasheets/diagram-storage.json b/publications/datasheets/diagram-storage.json index a03be87e..7c8a6dd1 100644 --- a/publications/datasheets/diagram-storage.json +++ b/publications/datasheets/diagram-storage.json @@ -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" }, { diff --git a/publications/datasheets/features.json b/publications/datasheets/features.json index 12554d52..ae286c94 100644 --- a/publications/datasheets/features.json +++ b/publications/datasheets/features.json @@ -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": [ diff --git a/publications/datasheets/ip-content.json b/publications/datasheets/ip-content.json index 33446d9d..5561c0a9 100644 --- a/publications/datasheets/ip-content.json +++ b/publications/datasheets/ip-content.json @@ -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.", diff --git a/publications/datasheets/ips/apu.typ b/publications/datasheets/ips/apu.typ index 89153dec..93040e7c 100644 --- a/publications/datasheets/ips/apu.typ +++ b/publications/datasheets/ips/apu.typ @@ -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. diff --git a/publications/datasheets/register-annotations.json b/publications/datasheets/register-annotations.json index 66e17049..2a594168 100644 --- a/publications/datasheets/register-annotations.json +++ b/publications/datasheets/register-annotations.json @@ -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." diff --git a/publications/datasheets/sections/apu-release.typ b/publications/datasheets/sections/apu-release.typ index e2508030..a9559624 100644 --- a/publications/datasheets/sections/apu-release.typ +++ b/publications/datasheets/sections/apu-release.typ @@ -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") diff --git a/publications/datasheets/sections/product-use.typ b/publications/datasheets/sections/product-use.typ index 412819ec..3db171c0 100644 --- a/publications/datasheets/sections/product-use.typ +++ b/publications/datasheets/sections/product-use.typ @@ -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. diff --git a/publications/datasheets/sections/software.typ b/publications/datasheets/sections/software.typ index 777c1a66..a9861338 100644 --- a/publications/datasheets/sections/software.typ +++ b/publications/datasheets/sections/software.typ @@ -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. diff --git a/publications/datasheets/system-reference.json b/publications/datasheets/system-reference.json index fcd821ab..016ddd69 100644 --- a/publications/datasheets/system-reference.json +++ b/publications/datasheets/system-reference.json @@ -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" diff --git a/publications/dev_reference.py b/publications/dev_reference.py index 08371ceb..6511c091 100644 --- a/publications/dev_reference.py +++ b/publications/dev_reference.py @@ -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|" diff --git a/rtl/README.md b/rtl/README.md index 40a1c463..5968fc2c 100644 --- a/rtl/README.md +++ b/rtl/README.md @@ -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 diff --git a/rtl/ip/multimedia/apb4_apu.sv b/rtl/ip/multimedia/apb4_apu.sv index 3555eec3..c5d71d40 100644 --- a/rtl/ip/multimedia/apb4_apu.sv +++ b/rtl/ip/multimedia/apb4_apu.sv @@ -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_loader_dma_req ? 3'd3 : - (s_kws_mem_dma_req ? 3'd4 : - (s_ring_dma_req_valid ? 3'd1 : 3'd2))); + (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)))); 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), @@ -678,131 +840,146 @@ module apb4_apu #( ); apu_kws_engine u_kws_engine ( - .clk_i (clk_i), - .rst_n_i (rst_n_i), - .soft_reset_i (s_soft_reset), - .resource_reset_i (s_resource_reset_apply_q), - .counter_clear_i (s_cnt_clear), - .xrun_clear_i (s_xrun_clear), - .abort_i (s_abort), - .quiesce_i (quiesce_i), - .disable_i (s_kws_disable_request), - .flush_busy_i (i2s_rx_flush_busy_i), - .enable_i (s_kws_control[0]), - .memory_window_i (s_kws_control[1]), - .clear_history_i (s_kws_clear_history), - .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_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), - .memory_dma_request_ready_i (s_kws_mem_dma_rdy), + .clk_i(clk_i), + .rst_n_i(rst_n_i), + .soft_reset_i(s_soft_reset), + .resource_reset_i(s_resource_reset_apply_q), + .counter_clear_i(s_cnt_clear), + .xrun_clear_i(s_xrun_clear), + .abort_i(s_abort), + .quiesce_i(quiesce_i), + .disable_i(s_kws_disable_request), + .flush_busy_i(i2s_rx_flush_busy_i), + .enable_i(s_kws_control[0]), + .memory_window_i(s_kws_control[1]), + .clear_history_i(s_kws_clear_history), + .model_valid_i(s_kws_model_valid), + .model_lock_i(s_kws_model_lock), + .sequencer_timeout_i(s_sequencer_timeout), + .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), + .memory_dma_request_ready_i(s_kws_mem_dma_rdy), .memory_dma_request_address_o(s_kws_mem_dma_addr), - .memory_dma_request_bytes_o (s_kws_mem_dma_bytes), - .memory_dma_data_i (u_dma_read_axis.tdata), - .memory_dma_keep_i (u_dma_read_axis.tkeep), - .memory_dma_last_i (u_dma_read_axis.tlast), - .memory_dma_valid_i (s_kws_mem_data_v), - .memory_dma_ready_o (s_kws_mem_dma_data_rdy), - .memory_dma_done_i (s_kws_mem_dma_done), - .memory_dma_error_i (s_kws_mem_dma_error), - .memory_dma_error_code_i (s_dma_err_code), - .memory_dma_error_stage_i (s_dma_err_stage), - .memory_dma_error_resp_i (s_dma_err_resp), - .memory_dma_error_address_i (s_dma_err_addr), - .memory_done_o (s_kws_mem_done), - .memory_error_o (s_kws_mem_err), - .memory_error_code_o (s_kws_mem_err_code), - .memory_error_stage_o (s_kws_mem_err_stage), - .memory_error_resp_o (s_kws_mem_err_resp), - .memory_error_address_o (s_kws_mem_err_addr), - .memory_error_detail_o (s_kws_mem_err_detail), - .memory_input_used_o (s_kws_mem_input_used), - .kws_config_i (s_codec_kws_config_valid ? s_codec_kws_config : s_kws_config), - .input_config_i (s_kws_input_config), - .storage_req_o (s_kws_storage_req), - .storage_ready_i (s_kws_storage_ready), - .storage_done_i (s_kws_storage_done), - .storage_progress_i (s_kws_storage_progress), - .storage_write_o (s_kws_storage_write), - .model_read_valid_o (s_kws_model_read_valid), - .model_addr_o (s_kws_model_read_addr), - .model_data_i (s_kws_model_read_data), - .scratch_clear_o (s_kws_scratch_clear), - .scratch_read_valid_o (s_kws_scratch_read_valid), - .scratch_read_addr_o (s_kws_scratch_read_addr), - .scratch_read_data_i (s_kws_scratch_read_data), - .scratch_write_valid_o (s_kws_scratch_write_valid), - .scratch_write_addr_o (s_kws_scratch_write_addr), - .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), - .stream_i (u_apu_rx_axis), - .rx_ready_o (s_kws_rx_ready), - .status_o (s_kws_status), - .result_o (s_kws_result), - .timestamp_lo_o (s_kws_timestamp_lo), - .timestamp_hi_o (s_kws_timestamp_hi), - .frame_count_o (s_kws_frame_count), - .inference_count_o (s_kws_inference_count), - .hit_count_o (s_kws_hit_count), - .overrun_count_o (s_kws_overrun_count), - .model_status_o (s_kws_engine_model_stat_unused), - .model_actual_crc_o (s_kws_engine_model_crc_unused), - .irq_set_o (s_kws_irq_set), - .fault_valid_o (s_kws_fault_valid), - .fault_code_o (s_kws_fault_code), - .fault_stage_o (s_kws_fault_stage), - .fault_resp_o (s_kws_fault_resp), - .fault_index_o (s_kws_fault_index), - .fault_addr_o (s_kws_fault_addr), - .fault_detail_o (s_kws_fault_detail), - .model_valid_o (s_kws_engine_model_v_unused), - .model_lock_o (s_kws_engine_model_lock_unused), - .busy_o (s_kws_busy), - .idle_o (s_kws_idle) + .memory_dma_request_bytes_o(s_kws_mem_dma_bytes), + .memory_dma_data_i(u_dma_read_axis.tdata), + .memory_dma_keep_i(u_dma_read_axis.tkeep), + .memory_dma_last_i(u_dma_read_axis.tlast), + .memory_dma_valid_i(s_kws_mem_data_v), + .memory_dma_ready_o(s_kws_mem_dma_data_rdy), + .memory_dma_done_i(s_kws_mem_dma_done), + .memory_dma_error_i(s_kws_mem_dma_error), + .memory_dma_error_code_i(s_dma_err_code), + .memory_dma_error_stage_i(s_dma_err_stage), + .memory_dma_error_resp_i(s_dma_err_resp), + .memory_dma_error_address_i(s_dma_err_addr), + .memory_done_o(s_kws_mem_done), + .memory_error_o(s_kws_mem_err), + .memory_error_code_o(s_kws_mem_err_code), + .memory_error_stage_o(s_kws_mem_err_stage), + .memory_error_resp_o(s_kws_mem_err_resp), + .memory_error_address_o(s_kws_mem_err_addr), + .memory_error_detail_o(s_kws_mem_err_detail), + .memory_input_used_o(s_kws_mem_input_used), + .kws_config_i(s_codec_kws_config_valid ? s_codec_kws_config : s_kws_config), + .input_config_i(s_kws_input_config), + .storage_req_o(s_kws_storage_req), + .storage_ready_i(s_kws_storage_ready), + .storage_done_i(s_kws_storage_done), + .storage_progress_i(s_kws_storage_progress), + .storage_write_o(s_kws_storage_write), + .model_read_valid_o(s_kws_model_read_valid), + .model_addr_o(s_kws_model_read_addr), + .model_data_i(s_kws_model_read_data), + .scratch_clear_o(s_kws_scratch_clear), + .scratch_read_valid_o(s_kws_scratch_read_valid), + .scratch_read_addr_o(s_kws_scratch_read_addr), + .scratch_read_data_i(s_kws_scratch_read_data), + .scratch_write_valid_o(s_kws_scratch_write_valid), + .scratch_write_addr_o(s_kws_scratch_write_addr), + .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), + .result_o(s_kws_result), + .timestamp_lo_o(s_kws_timestamp_lo), + .timestamp_hi_o(s_kws_timestamp_hi), + .frame_count_o(s_kws_frame_count), + .inference_count_o(s_kws_inference_count), + .hit_count_o(s_kws_hit_count), + .overrun_count_o(s_kws_overrun_count), + .model_status_o(s_kws_engine_model_stat_unused), + .model_actual_crc_o(s_kws_engine_model_crc_unused), + .irq_set_o(s_kws_irq_set), + .fault_valid_o(s_kws_fault_valid), + .fault_code_o(s_kws_fault_code), + .fault_stage_o(s_kws_fault_stage), + .fault_resp_o(s_kws_fault_resp), + .fault_index_o(s_kws_fault_index), + .fault_addr_o(s_kws_fault_addr), + .fault_detail_o(s_kws_fault_detail), + .model_valid_o(s_kws_engine_model_v_unused), + .model_lock_o(s_kws_engine_model_lock_unused), + .busy_o(s_kws_busy), + .idle_o(s_kws_idle) ); apu_dma u_apu_dma ( - .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), - .bridge_epoch_i (bridge_epoch_i), - .perf_enable_i (s_perf_enable), - .counter_clear_i (s_cnt_clear), - .request_valid_i (s_dma_req_valid), - .request_ready_o (s_dma_req_ready), - .request_write_i (s_dma_req_write), - .request_addr_i (s_dma_req_addr), - .request_bytes_i (s_dma_req_bytes), - .read_base_i (s_read_base), - .read_limit_i (s_read_limit), - .write_base_i (s_write_base), - .write_limit_i (s_write_limit), - .timeout_i (s_dma_timeout), - .read_axis (u_dma_read_axis), - .write_axis (u_dma_write_axis), - .busy_o (s_dma_busy), - .done_o (s_dma_done), - .error_o (s_dma_err), - .aborted_o (s_dma_aborted), - .aborting_o (s_dma_aborting), - .error_code_o (s_dma_err_code), - .error_stage_o (s_dma_err_stage), - .error_resp_o (s_dma_err_resp), - .error_addr_o (s_dma_err_addr), - .input_pending_o (s_dma_input_pending), - .output_pending_o (s_dma_output_pending), - .read_bytes_o (s_dma_read_bytes), - .write_bytes_o (s_dma_write_bytes), - .write_burst_done_o (s_dma_write_burst_done), + .clk_i(clk_i), + .rst_n_i(rst_n_i), + .abort_i(s_dma_abort), + .quiesce_i(s_dma_quiesce), + .bridge_epoch_i(bridge_epoch_i), + .perf_enable_i(s_perf_enable), + .counter_clear_i(s_cnt_clear), + .request_valid_i(s_dma_req_valid), + .request_ready_o(s_dma_req_ready), + .request_write_i(s_dma_req_write), + .request_addr_i(s_dma_req_addr), + .request_bytes_i(s_dma_req_bytes), + .read_base_i(s_read_base), + .read_limit_i(s_read_limit), + .write_base_i(s_write_base), + .write_limit_i(s_write_limit), + .timeout_i(s_dma_timeout), + .read_axis(u_dma_read_axis), + .write_axis(u_dma_write_axis), + .busy_o(s_dma_busy), + .done_o(s_dma_done), + .error_o(s_dma_err), + .aborted_o(s_dma_aborted), + .aborting_o(s_dma_aborting), + .error_code_o(s_dma_err_code), + .error_stage_o(s_dma_err_stage), + .error_resp_o(s_dma_err_resp), + .error_addr_o(s_dma_err_addr), + .input_pending_o(s_dma_input_pending), + .output_pending_o(s_dma_output_pending), + .read_bytes_o(s_dma_read_bytes), + .write_bytes_o(s_dma_write_bytes), + .write_burst_done_o(s_dma_write_burst_done), .write_burst_bytes_o(s_dma_write_burst_bytes), - .read_stalls_o (s_dma_read_stalls), - .write_stalls_o (s_dma_write_stalls), - .axi4 (axi4) + .read_stalls_o(s_dma_read_stalls), + .write_stalls_o(s_dma_write_stalls), + .axi4(axi4) ); apu_microcode_loader #( @@ -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), diff --git a/rtl/ip/multimedia/apu_define.svh b/rtl/ip/multimedia/apu_define.svh index d1a1e087..138e1404 100644 --- a/rtl/ip/multimedia/apu_define.svh +++ b/rtl/ip/multimedia/apu_define.svh @@ -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 diff --git a/rtl/ip/multimedia/apu_kws_coeff_loader.sv b/rtl/ip/multimedia/apu_kws_coeff_loader.sv new file mode 100644 index 00000000..9c801246 --- /dev/null +++ b/rtl/ip/multimedia/apu_kws_coeff_loader.sv @@ -0,0 +1,501 @@ +// Copyright (c) 2026 Yuchi Miao +// 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 diff --git a/rtl/ip/multimedia/apu_kws_coeff_store.sv b/rtl/ip/multimedia/apu_kws_coeff_store.sv new file mode 100644 index 00000000..eed4c39d --- /dev/null +++ b/rtl/ip/multimedia/apu_kws_coeff_store.sv @@ -0,0 +1,314 @@ +// Copyright (c) 2026 Yuchi Miao +// 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 diff --git a/rtl/ip/multimedia/apu_kws_engine.sv b/rtl/ip/multimedia/apu_kws_engine.sv index da39223c..10b55715 100644 --- a/rtl/ip/multimedia/apu_kws_engine.sv +++ b/rtl/ip/multimedia/apu_kws_engine.sv @@ -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, - input logic [15:0] peak_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; + 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,35 +1258,95 @@ 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; - s_storage_state_owned_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 @@ -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,295 +1750,313 @@ module apu_kws_engine ( end if (s_storage_state_step) begin - unique case (s_front_state_q) - FrontIdle: begin - if (s_frame_pending_q) begin - s_frame_pending_q <= 1'b0; - s_front_index_q <= 10'd0; - s_front_state_q <= FrontFftLoad; - end else if (s_window_pending_q && (s_mel_count_q >= 6'd49) && + 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 + s_frame_pending_q <= 1'b0; + s_front_index_q <= 10'd0; + s_front_state_q <= FrontFftLoad; + end else if (s_window_pending_q && (s_mel_count_q >= 6'd49) && (s_infer_state_q == InferIdle)) begin - s_window_pending_q <= 1'b0; - s_peak_scan_q <= 7'd0; - s_window_peak_q <= 16'd1; - s_front_state_q <= FrontPeak; - end - end - FrontFir: begin - if (s_fir_tap_q == 6'd62) begin - s_fir_tap_q <= 6'd0; - s_fir_acc_q <= 64'sd0; - s_fir_phase_q <= (s_fir_phase_q == (s_decimation - 1'b1)) ? - 3'd0 : s_fir_phase_q + 1'b1; - s_front_state_q <= FrontIdle; - end else begin - s_fir_acc_q <= s_fir_total; - s_fir_tap_q <= s_fir_tap_q + 1'b1; - end - end - FrontFftLoad: begin - if (s_front_index_q == 10'd511) begin - s_fft_len_q <= 10'd2; - s_fft_half_q <= 9'd1; - s_fft_group_q <= 9'd0; - s_fft_j_q <= 8'd0; - s_front_state_q <= FrontFftRun; - end else begin - s_front_index_q <= s_front_index_q + 1'b1; - end - end - FrontFftRun: begin - if (s_fft_overflow) 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_detail_q <= 32'h0700_0014; - s_stop_latched_q <= 1'b1; - s_front_state_q <= FrontIdle; - end else begin - if (s_fft_j_q + 1'b1 == s_fft_half_q) begin - s_fft_j_q <= 8'd0; - if (s_fft_group_q + s_fft_len_q == 10'd512) begin - s_fft_group_q <= 9'd0; - if (s_fft_len_q == 10'd512) begin - s_front_index_q <= 10'd0; - s_front_state_q <= FrontMagnitude; - end else begin - s_fft_len_q <= s_fft_len_q << 1; - s_fft_half_q <= s_fft_half_q << 1; - end - end else begin - s_fft_group_q <= 9'(s_fft_group_q + s_fft_len_q); - end - end else begin - s_fft_j_q <= s_fft_j_q + 1'b1; + s_window_pending_q <= 1'b0; + s_peak_scan_q <= 7'd0; + s_window_peak_q <= 16'd1; + s_front_state_q <= FrontPeak; end end - end - FrontMagnitude: begin - if (s_front_index_q == 10'd256) begin - s_mel_band_q <= 8'd0; - s_mel_bin_q <= 9'd0; - s_mel_acc_q <= 64'd0; - s_front_state_q <= FrontMel; - end else begin - s_front_index_q <= s_front_index_q + 1'b1; - end - end - FrontMel: begin - if (s_mel_total < s_mel_acc_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_detail_q <= 32'h0700_0015; - s_stop_latched_q <= 1'b1; - s_front_state_q <= FrontIdle; - end else if (s_mel_overwrite) begin - s_front_state_q <= FrontIdle; - end else if (s_mel_bin_q == 9'd256) begin - s_mel_bin_q <= 9'd0; - s_mel_acc_q <= 64'd0; - if (s_mel_band_q == 8'd39) begin - s_mel_band_q <= 8'd0; - s_mel_write_q <= (s_mel_write_q == 6'd49) ? 6'd0 : s_mel_write_q + 1'b1; - if (s_mel_count_q < 6'd50) s_mel_count_q <= s_mel_count_q + 1'b1; - if (!counter_clear_i) begin - s_frame_count_q <= (s_frame_count_q == 32'hffff_ffff) ? - s_frame_count_q : s_frame_count_q + 1'b1; - end + FrontFir: begin + if (s_fir_tap_q == 6'd62) begin + s_fir_tap_q <= 6'd0; + s_fir_acc_q <= 64'sd0; + s_fir_phase_q <= (s_fir_phase_q == (s_decimation - 1'b1)) ? + 3'd0 : s_fir_phase_q + 1'b1; s_front_state_q <= FrontIdle; end else begin - s_mel_band_q <= s_mel_band_q + 1'b1; + s_fir_acc_q <= s_fir_total; + s_fir_tap_q <= s_fir_tap_q + 1'b1; end - end else begin - s_mel_acc_q <= s_mel_total; - s_mel_bin_q <= s_mel_bin_q + 1'b1; end - end - FrontPeak: begin - if (s_peak_data > s_window_peak_q) begin - s_window_peak_q <= s_peak_data; + FrontFftLoad: begin + if (s_front_index_q == 10'd511) begin + s_fft_len_q <= 10'd2; + s_fft_half_q <= 9'd1; + s_fft_group_q <= 9'd0; + s_fft_j_q <= 8'd0; + s_front_state_q <= FrontFftRun; + end else begin + s_front_index_q <= s_front_index_q + 1'b1; + end end - if (s_peak_scan_q == 7'd99) begin - s_mfcc_row_q <= 6'd0; - s_mfcc_band_q <= 6'd0; - s_front_state_q <= FrontLog; - end else begin - s_peak_scan_q <= s_peak_scan_q + 1'b1; + FrontFftRun: begin + if (s_fft_overflow) 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_detail_q <= 32'h0700_0014; + s_stop_latched_q <= 1'b1; + s_front_state_q <= FrontIdle; + end else begin + if (s_fft_j_q + 1'b1 == s_fft_half_q) begin + s_fft_j_q <= 8'd0; + if (s_fft_group_q + s_fft_len_q == 10'd512) begin + s_fft_group_q <= 9'd0; + if (s_fft_len_q == 10'd512) begin + s_front_index_q <= 10'd0; + s_front_state_q <= FrontMagnitude; + end else begin + s_fft_len_q <= s_fft_len_q << 1; + s_fft_half_q <= s_fft_half_q << 1; + end + end else begin + s_fft_group_q <= 9'(s_fft_group_q + s_fft_len_q); + end + end else begin + s_fft_j_q <= s_fft_j_q + 1'b1; + end + end end - end - FrontLog: begin - if (s_mfcc_band_q == 6'd39) begin - s_mfcc_band_q <= 6'd0; - s_mfcc_coefficient_q <= 4'd0; - s_dct_acc_q <= 64'sd0; - s_front_state_q <= FrontDct; - end else begin - s_mfcc_band_q <= s_mfcc_band_q + 1'b1; + FrontMagnitude: begin + if (s_front_index_q == 10'd256) begin + s_mel_band_q <= 8'd0; + s_mel_bin_q <= 9'd0; + s_mel_acc_q <= 64'd0; + s_front_state_q <= FrontMel; + end else begin + s_front_index_q <= s_front_index_q + 1'b1; + end end - end - FrontDct: begin - if (s_mfcc_band_q == 6'd39) begin - s_mfcc_band_q <= 6'd0; - s_dct_acc_q <= 64'sd0; - if (s_mfcc_coefficient_q == 4'd9) begin - s_mfcc_coefficient_q <= 4'd0; - if (s_mfcc_row_q == 6'd48) begin - s_operator_q <= 4'd0; - s_output_q <= 14'd0; - s_infer_state_q <= InferBias; + FrontMel: begin + if (s_mel_total < s_mel_acc_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_detail_q <= 32'h0700_0015; + s_stop_latched_q <= 1'b1; + s_front_state_q <= FrontIdle; + end else if (s_mel_overwrite) begin + s_front_state_q <= FrontIdle; + end else if (s_mel_bin_q == 9'd256) begin + s_mel_bin_q <= 9'd0; + s_mel_acc_q <= 64'd0; + if (s_mel_band_q == 8'd39) begin + s_mel_band_q <= 8'd0; + s_mel_write_q <= (s_mel_write_q == 6'd49) ? 6'd0 : s_mel_write_q + 1'b1; + if (s_mel_count_q < 6'd50) s_mel_count_q <= s_mel_count_q + 1'b1; + if (!counter_clear_i) begin + s_frame_count_q <= (s_frame_count_q == 32'hffff_ffff) ? + s_frame_count_q : s_frame_count_q + 1'b1; + end s_front_state_q <= FrontIdle; end else begin - s_mfcc_row_q <= s_mfcc_row_q + 1'b1; - s_front_state_q <= FrontLog; + s_mel_band_q <= s_mel_band_q + 1'b1; end end else begin - s_mfcc_coefficient_q <= s_mfcc_coefficient_q + 1'b1; + s_mel_acc_q <= s_mel_total; + s_mel_bin_q <= s_mel_bin_q + 1'b1; end - end else begin - s_dct_acc_q <= s_dct_total; - s_mfcc_band_q <= s_mfcc_band_q + 1'b1; end - end - default: s_front_state_q <= FrontIdle; - endcase - - unique case (s_infer_state_q) - InferIdle: begin - end - InferBias: begin - s_mac_q <= 64'($signed(s_bias_word)); - s_term_q <= 8'd0; - s_infer_state_q <= InferMac; - end - InferMac: begin - if ((s_mac_total > 64'sh0000_0000_7fff_ffff) || - (s_mac_total < -64'sh0000_0000_8000_0000)) 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_detail_q <= 32'h0700_0016; - s_stop_latched_q <= 1'b1; - s_infer_state_q <= InferIdle; - end else if (s_term_q + 8'd16 >= s_term_count) begin - s_mac_q <= s_mac_total; - s_infer_state_q <= InferQuant; - end else begin - s_mac_q <= s_mac_total; - s_term_q <= s_term_q + 8'd16; + FrontPeak: begin + if (s_peak_data > s_window_peak_q) begin + s_window_peak_q <= s_peak_data; + end + if (s_peak_scan_q == 7'd99) begin + s_mfcc_row_q <= 6'd0; + s_mfcc_band_q <= 6'd0; + s_front_state_q <= FrontLog; + end else begin + s_peak_scan_q <= s_peak_scan_q + 1'b1; + end end - end - InferQuant: begin - if (s_requant_overflow) 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_detail_q <= 32'h0700_0017; - s_stop_latched_q <= 1'b1; - s_infer_state_q <= InferIdle; - end else begin - if (s_output_q + 1'b1 == s_output_count) begin - s_output_q <= 14'd0; - if (s_operator_q == 4'd8) begin - s_operator_q <= 4'd9; - s_term_q <= 8'd0; - s_mac_q <= 64'sd0; - s_infer_state_q <= InferPool; - end else if (s_operator_q == 4'd10) begin - s_operator_q <= 4'd11; - s_softmax_index_q <= 4'd0; - s_softmax_max_q <= -8'sd128; - s_infer_state_q <= InferSoftmaxMax; + FrontLog: begin + if (s_mfcc_band_q == 6'd39) begin + s_mfcc_band_q <= 6'd0; + s_mfcc_coefficient_q <= 4'd0; + s_dct_acc_q <= 64'sd0; + s_front_state_q <= FrontDct; + end else begin + s_mfcc_band_q <= s_mfcc_band_q + 1'b1; + end + end + FrontDct: begin + if (s_mfcc_band_q == 6'd39) begin + s_mfcc_band_q <= 6'd0; + s_dct_acc_q <= 64'sd0; + if (s_mfcc_coefficient_q == 4'd9) begin + s_mfcc_coefficient_q <= 4'd0; + if (s_mfcc_row_q == 6'd48) begin + s_operator_q <= 4'd0; + s_output_q <= 14'd0; + s_infer_state_q <= InferBias; + s_front_state_q <= FrontIdle; + end else begin + s_mfcc_row_q <= s_mfcc_row_q + 1'b1; + s_front_state_q <= FrontLog; + end end else begin - s_operator_q <= s_operator_q + 1'b1; + s_mfcc_coefficient_q <= s_mfcc_coefficient_q + 1'b1; + end + end else begin + s_dct_acc_q <= s_dct_total; + s_mfcc_band_q <= s_mfcc_band_q + 1'b1; + end + end + 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 + InferBias: begin + s_mac_q <= 64'($signed(s_bias_word)); + s_term_q <= 8'd0; + s_infer_state_q <= InferMac; + end + InferMac: begin + if ((s_mac_total > 64'sh0000_0000_7fff_ffff) || + (s_mac_total < -64'sh0000_0000_8000_0000)) 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_detail_q <= 32'h0700_0016; + s_stop_latched_q <= 1'b1; + s_infer_state_q <= InferIdle; + end else if (s_term_q + 8'd16 >= s_term_count) begin + s_mac_q <= s_mac_total; + s_infer_state_q <= InferQuant; + end else begin + s_mac_q <= s_mac_total; + s_term_q <= s_term_q + 8'd16; + end + end + InferQuant: begin + if (s_requant_overflow) 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_detail_q <= 32'h0700_0017; + s_stop_latched_q <= 1'b1; + s_infer_state_q <= InferIdle; + end else begin + if (s_output_q + 1'b1 == s_output_count) begin + s_output_q <= 14'd0; + if (s_operator_q == 4'd8) begin + s_operator_q <= 4'd9; + s_term_q <= 8'd0; + s_mac_q <= 64'sd0; + s_infer_state_q <= InferPool; + end else if (s_operator_q == 4'd10) begin + s_operator_q <= 4'd11; + s_softmax_index_q <= 4'd0; + s_softmax_max_q <= -8'sd128; + s_infer_state_q <= InferSoftmaxMax; + end else begin + s_operator_q <= s_operator_q + 1'b1; + s_infer_state_q <= InferBias; + end + end else begin + s_output_q <= s_output_q + 1'b1; s_infer_state_q <= InferBias; end - end else begin - s_output_q <= s_output_q + 1'b1; - s_infer_state_q <= InferBias; end end - end - InferPool: begin - if (s_term_q + 8'd16 >= 8'd125) begin - s_term_q <= 8'd0; - s_mac_q <= 64'sd0; - if (s_output_q == 14'd63) begin - s_output_q <= 14'd0; - s_operator_q <= 4'd10; - s_infer_state_q <= InferBias; + InferPool: begin + if (s_term_q + 8'd16 >= 8'd125) begin + s_term_q <= 8'd0; + s_mac_q <= 64'sd0; + if (s_output_q == 14'd63) begin + s_output_q <= 14'd0; + s_operator_q <= 4'd10; + s_infer_state_q <= InferBias; + end else begin + s_output_q <= s_output_q + 1'b1; + end end else begin - s_output_q <= s_output_q + 1'b1; + s_mac_q <= s_pool_sum; + s_term_q <= s_term_q + 8'd16; end - end else begin - s_mac_q <= s_pool_sum; - s_term_q <= s_term_q + 8'd16; end - end - InferSoftmaxMax: begin - if (s_logit_data > s_softmax_max_q) begin - s_softmax_max_q <= s_logit_data; + InferSoftmaxMax: begin + if (s_logit_data > s_softmax_max_q) begin + s_softmax_max_q <= s_logit_data; + end + if (s_softmax_index_q == 4'd11) begin + s_softmax_index_q <= 4'd0; + s_softmax_sum_q <= 32'd0; + s_infer_state_q <= InferSoftmaxSum; + end else begin + s_softmax_index_q <= s_softmax_index_q + 1'b1; + end end - if (s_softmax_index_q == 4'd11) begin - s_softmax_index_q <= 4'd0; - s_softmax_sum_q <= 32'd0; - s_infer_state_q <= InferSoftmaxSum; - end else begin - s_softmax_index_q <= s_softmax_index_q + 1'b1; + InferSoftmaxSum: begin + if (s_softmax_sum_diff >= -9'sd124) begin + s_softmax_sum_q <= s_softmax_sum_q + + 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; + s_infer_state_q <= InferSoftmaxScale; + end else begin + s_softmax_index_q <= s_softmax_index_q + 1'b1; + end end - end - 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); + InferSoftmaxScale: begin + if (s_softmax_index_q == 0) begin + s_softmax_scale_q <= reciprocal_q31(s_softmax_shifted_sum); + s_softmax_bits_q <= 6'(7'sd12 - $signed({1'b0, s_softmax_headroom})); + end + if (s_softmax_index_q == 4'd12) begin + s_softmax_index_q <= 4'd0; + s_infer_state_q <= InferComplete; + end else begin + s_softmax_index_q <= s_softmax_index_q + 1'b1; + end end - if (s_softmax_index_q == 4'd11) begin - s_softmax_index_q <= 4'd0; - s_infer_state_q <= InferSoftmaxScale; - end else begin - s_softmax_index_q <= s_softmax_index_q + 1'b1; - end - end - InferSoftmaxScale: begin - if (s_softmax_index_q == 0) begin - s_softmax_scale_q <= reciprocal_q31(s_softmax_shifted_sum); - s_softmax_bits_q <= 6'(7'sd12 - $signed({1'b0, s_softmax_headroom})); - end - if (s_softmax_index_q == 4'd12) begin - s_softmax_index_q <= 4'd0; - s_infer_state_q <= InferComplete; - end else begin - s_softmax_index_q <= s_softmax_index_q + 1'b1; - end - end - InferComplete: begin - s_result_q <= {15'd0, s_stable_hit, s_best_score, s_best_class}; - s_result_valid_q <= 1'b1; - s_timestamp_lo_q <= s_timestamp_q; - s_timestamp_hi_q <= 32'd0; - if (!counter_clear_i) begin - s_inference_count_q <= (s_inference_count_q == 32'hffff_ffff) ? - s_inference_count_q : s_inference_count_q + 1'b1; - end - s_debounce_run_q <= s_next_debounce_run[7:0]; - s_last_class_q <= s_best_class; - s_last_match_q <= s_match; - s_stable_hit_q <= s_stable_hit; - if (s_hit_event) begin + InferComplete: begin + s_result_q <= {15'd0, s_stable_hit, s_best_score, s_best_class}; + s_result_valid_q <= 1'b1; + s_timestamp_lo_q <= s_timestamp_q; + s_timestamp_hi_q <= 32'd0; if (!counter_clear_i) begin - s_hit_count_q <= (s_hit_count_q == 32'hffff_ffff) ? + s_inference_count_q <= (s_inference_count_q == 32'hffff_ffff) ? + s_inference_count_q : s_inference_count_q + 1'b1; + end + s_debounce_run_q <= s_next_debounce_run[7:0]; + s_last_class_q <= s_best_class; + s_last_match_q <= s_match; + s_stable_hit_q <= s_stable_hit; + if (s_hit_event) begin + if (!counter_clear_i) begin + s_hit_count_q <= (s_hit_count_q == 32'hffff_ffff) ? s_hit_count_q : s_hit_count_q + 1'b1; + end + s_hit_irq_q <= 1'b1; + end + s_infer_state_q <= InferIdle; + if (s_mem_job_q) begin + s_mem_job_q <= 1'b0; + s_mem_done_q <= 1'b1; end - s_hit_irq_q <= 1'b1; end - s_infer_state_q <= InferIdle; - if (s_mem_job_q) begin - s_mem_job_q <= 1'b0; - s_mem_done_q <= 1'b1; - end - end - default: s_infer_state_q <= InferIdle; - endcase + default: s_infer_state_q <= InferIdle; + endcase end if ((s_mem_job_q && s_mem_dma_done_q && !s_mem_word_valid_q) && diff --git a/rtl/ip/multimedia/apu_kws_model_loader.sv b/rtl/ip/multimedia/apu_kws_model_loader.sv index 0e7d80e8..4946938a 100644 --- a/rtl/ip/multimedia/apu_kws_model_loader.sv +++ b/rtl/ip/multimedia/apu_kws_model_loader.sv @@ -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_crc_q <= s_crc_after_word; + s_received_q <= s_received_q + 32'd4; + 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; @@ -584,8 +641,9 @@ module apu_kws_model_loader ( end s_state_q <= Complete; end else begin - s_actual_crc_q <= ~s_crc_q; - s_state_q <= Validate; + s_actual_crc_q <= ~s_crc_q; + s_dma_complete_q <= 1'b0; + s_state_q <= Validate; end end end diff --git a/rtl/ip/multimedia/apu_microcode_loader.sv b/rtl/ip/multimedia/apu_microcode_loader.sv index c974069b..8b5f2f36 100644 --- a/rtl/ip/multimedia/apu_microcode_loader.sv +++ b/rtl/ip/multimedia/apu_microcode_loader.sv @@ -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,12 +1123,11 @@ 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 end - ScanRequest: s_state_q <= ScanWait; + ScanRequest: s_state_q <= ScanWait; ScanWait: begin if (store_valid_i) s_state_q <= ScanCheck; 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 @@ -1394,7 +1400,7 @@ module apu_microcode_loader #( end end end - default: s_state_q <= Idle; + default: s_state_q <= Idle; endcase end end diff --git a/rtl/ip/multimedia/apu_proof_memo.sv b/rtl/ip/multimedia/apu_proof_memo.sv new file mode 100644 index 00000000..16bc35a1 --- /dev/null +++ b/rtl/ip/multimedia/apu_proof_memo.sv @@ -0,0 +1,207 @@ +// Copyright (c) 2026 Yuchi Miao +// 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 diff --git a/rtl/ip/multimedia/apu_reg.sv b/rtl/ip/multimedia/apu_reg.sv index 7a990262..cf09bffa 100644 --- a/rtl/ip/multimedia/apu_reg.sv +++ b/rtl/ip/multimedia/apu_reg.sv @@ -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]) diff --git a/rtl/mini/filelist/ip.fl b/rtl/mini/filelist/ip.fl index 281de29b..1b810ca9 100644 --- a/rtl/mini/filelist/ip.fl +++ b/rtl/mini/filelist/ip.fl @@ -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 diff --git a/rtl/mini/formal/apu_kws_formal.sv b/rtl/mini/formal/apu_kws_formal.sv index 1faac96c..16c667b7 100644 --- a/rtl/mini/formal/apu_kws_formal.sv +++ b/rtl/mini/formal/apu_kws_formal.sv @@ -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), diff --git a/rtl/mini/formal/apu_p9_formal.sv b/rtl/mini/formal/apu_p9_formal.sv new file mode 100644 index 00000000..036e9534 --- /dev/null +++ b/rtl/mini/formal/apu_p9_formal.sv @@ -0,0 +1,106 @@ +// Copyright (c) 2026 Yuchi Miao +// 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 diff --git a/rtl/mini/formal/apu_p9_formal_props.sv b/rtl/mini/formal/apu_p9_formal_props.sv new file mode 100644 index 00000000..769a47f2 --- /dev/null +++ b/rtl/mini/formal/apu_p9_formal_props.sv @@ -0,0 +1,53 @@ +// Copyright (c) 2026 Yuchi Miao +// 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 diff --git a/rtl/mini/formal/generate_formal_filelist.py b/rtl/mini/formal/generate_formal_filelist.py index e3a4bdcb..d40c176e 100644 --- a/rtl/mini/formal/generate_formal_filelist.py +++ b/rtl/mini/formal/generate_formal_filelist.py @@ -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, diff --git a/rtl/mini/mk/formal.mk b/rtl/mini/mk/formal.mk index 5e30dd61..a44689b3 100644 --- a/rtl/mini/mk/formal.mk +++ b/rtl/mini/mk/formal.mk @@ -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 \ No newline at end of file +.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 diff --git a/rtl/rtl_style_audit.json b/rtl/rtl_style_audit.json index dcf8c34f..7619667a 100644 --- a/rtl/rtl_style_audit.json +++ b/rtl/rtl_style_audit.json @@ -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", diff --git a/scripts/README.md b/scripts/README.md index d0588622..d1cc22f2 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -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//apu/p9/accuracy --profile ihp130-p9 +python3 scripts/run_apu_p7_concurrent_rtl.py \ + --build-dir build//apu/p9/concurrent --seconds 60 +make CONFIG=configs/ci/ihp130.mk apu-p9-memory-ab \ + APU_P9_BASELINE_ROOT=build/ APU_P9_CANDIDATE_ROOT=build/ +``` + +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 diff --git a/scripts/apu_abi_digest.py b/scripts/apu_abi_digest.py index d48b8fae..9d2c8e4a 100644 --- a/scripts/apu_abi_digest.py +++ b/scripts/apu_abi_digest.py @@ -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) diff --git a/scripts/apu_kws_coeff.py b/scripts/apu_kws_coeff.py new file mode 100644 index 00000000..f90d9604 --- /dev/null +++ b/scripts/apu_kws_coeff.py @@ -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(" 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], + ) diff --git a/scripts/apu_p9_evidence.py b/scripts/apu_p9_evidence.py new file mode 100644 index 00000000..0ff73f1f --- /dev/null +++ b/scripts/apu_p9_evidence.py @@ -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()) diff --git a/scripts/apu_p9_memory_ab.py b/scripts/apu_p9_memory_ab.py new file mode 100644 index 00000000..046ec0ba --- /dev/null +++ b/scripts/apu_p9_memory_ab.py @@ -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()) diff --git a/scripts/build_apu_release_assets.py b/scripts/build_apu_release_assets.py index 4d85ceec..5e2e384d 100644 --- a/scripts/build_apu_release_assets.py +++ b/scripts/build_apu_release_assets.py @@ -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", diff --git a/scripts/generate_apu_kws_rtl_constants.py b/scripts/generate_apu_kws_rtl_constants.py index 3a0c0479..2cf24906 100644 --- a/scripts/generate_apu_kws_rtl_constants.py +++ b/scripts/generate_apu_kws_rtl_constants.py @@ -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(" bytes: + return b"".join(struct.pack(" 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 ", @@ -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 diff --git a/scripts/run_apu_p5_corpus_rtl.py b/scripts/run_apu_p5_corpus_rtl.py index 7d5d200d..03889e83 100644 --- a/scripts/run_apu_p5_corpus_rtl.py +++ b/scripts/run_apu_p5_corpus_rtl.py @@ -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", diff --git a/scripts/run_apu_p7_concurrent_rtl.py b/scripts/run_apu_p7_concurrent_rtl.py index fabdcd4c..f93bc999 100644 --- a/scripts/run_apu_p7_concurrent_rtl.py +++ b/scripts/run_apu_p7_concurrent_rtl.py @@ -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) diff --git a/scripts/run_apu_p7_kws_rtl.py b/scripts/run_apu_p7_kws_rtl.py index c9066b63..e5e5d601 100644 --- a/scripts/run_apu_p7_kws_rtl.py +++ b/scripts/run_apu_p7_kws_rtl.py @@ -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", diff --git a/tests/README.md b/tests/README.md index 6b75d042..f9afef71 100644 --- a/tests/README.md +++ b/tests/README.md @@ -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). diff --git a/tests/c/test_runtime.c b/tests/c/test_runtime.c index b9fb01ee..07f0b01d 100644 --- a/tests/c/test_runtime.c +++ b/tests/c/test_runtime.c @@ -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))) { diff --git a/tests/rtl/apu_kws_coeff_fixture.sv b/tests/rtl/apu_kws_coeff_fixture.sv new file mode 100644 index 00000000..c44ab6f3 --- /dev/null +++ b/tests/rtl/apu_kws_coeff_fixture.sv @@ -0,0 +1,99 @@ +// Copyright (c) 2026 Yuchi Miao +// 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="); + 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 diff --git a/tests/rtl/apu_kws_engine_tb.sv b/tests/rtl/apu_kws_engine_tb.sv index a85ee8ef..baa18e70 100644 --- a/tests/rtl/apu_kws_engine_tb.sv +++ b/tests/rtl/apu_kws_engine_tb.sv @@ -51,11 +51,20 @@ 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 [ 7:0] mfcc_bytes [ 0:489]; - logic [ 7:0] expected_layers [0:72087]; - logic use_apum = 1'b0; - logic check_layers = 1'b0; - logic [31:0] sequencer_timeout = 32'd0; + 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; + logic check_layers = 1'b0; + logic [31:0] sequencer_timeout = 32'd0; function automatic logic [7:0] synthetic_model_byte(input logic [14:0] address_i); begin @@ -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); diff --git a/tests/rtl/apu_kws_loader_tb.sv b/tests/rtl/apu_kws_loader_tb.sv index 453c16b7..4478ef57 100644 --- a/tests/rtl/apu_kws_loader_tb.sv +++ b/tests/rtl/apu_kws_loader_tb.sv @@ -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), diff --git a/tests/rtl/apu_p7_accuracy_tb.sv b/tests/rtl/apu_p7_accuracy_tb.sv index 9754e5be..d2b0eb58 100644 --- a/tests/rtl/apu_p7_accuracy_tb.sv +++ b/tests/rtl/apu_p7_accuracy_tb.sv @@ -57,7 +57,16 @@ 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 [31:0] pcm_mem [0:PcmWords-1]; + 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); diff --git a/tests/rtl/apu_p7_concurrent_tb.sv b/tests/rtl/apu_p7_concurrent_tb.sv index be626835..fb5e80d5 100644 --- a/tests/rtl/apu_p7_concurrent_tb.sv +++ b/tests/rtl/apu_p7_concurrent_tb.sv @@ -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); diff --git a/tests/rtl/apu_p7_frontend_dump_tb.sv b/tests/rtl/apu_p7_frontend_dump_tb.sv index 8e185d5b..03ff8486 100644 --- a/tests/rtl/apu_p7_frontend_dump_tb.sv +++ b/tests/rtl/apu_p7_frontend_dump_tb.sv @@ -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 [31:0] pcm_mem [0:PcmWords-1]; + 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); diff --git a/tests/rtl/apu_p8_quiesce_load_tb.sv b/tests/rtl/apu_p8_quiesce_load_tb.sv index b60299e3..a547dd87 100644 --- a/tests/rtl/apu_p8_quiesce_load_tb.sv +++ b/tests/rtl/apu_p8_quiesce_load_tb.sv @@ -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); diff --git a/tests/rtl/apu_p9_storage_tb.sv b/tests/rtl/apu_p9_storage_tb.sv new file mode 100644 index 00000000..4a8e44cb --- /dev/null +++ b/tests/rtl/apu_p9_storage_tb.sv @@ -0,0 +1,199 @@ +// Copyright (c) 2026 Yuchi Miao +// 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 diff --git a/tests/rtl/apu_reg_tb.sv b/tests/rtl/apu_reg_tb.sv index ab0d647b..ca730d1a 100644 --- a/tests/rtl/apu_reg_tb.sv +++ b/tests/rtl/apu_reg_tb.sv @@ -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"; diff --git a/tests/test_apu.py b/tests/test_apu.py index 1e17a243..59e66f09 100644 --- a/tests/test_apu.py +++ b/tests/test_apu.py @@ -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" - 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" - ) + 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)] + 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}", ) diff --git a/tests/test_apu_codec_transport.py b/tests/test_apu_codec_transport.py index f8595571..df30cf02 100644 --- a/tests/test_apu_codec_transport.py +++ b/tests/test_apu_codec_transport.py @@ -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", diff --git a/tests/test_apu_kws_frontend.py b/tests/test_apu_kws_frontend.py index 6ffbc47e..0e09476e 100644 --- a/tests/test_apu_kws_frontend.py +++ b/tests/test_apu_kws_frontend.py @@ -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}", diff --git a/tests/test_apu_p8_quiesce_load.py b/tests/test_apu_p8_quiesce_load.py index d803986d..523572d7 100644 --- a/tests/test_apu_p8_quiesce_load.py +++ b/tests/test_apu_p8_quiesce_load.py @@ -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 diff --git a/tests/test_apu_p9_coefficients.py b/tests/test_apu_p9_coefficients.py new file mode 100644 index 00000000..4320a9a7 --- /dev/null +++ b/tests/test_apu_p9_coefficients.py @@ -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 diff --git a/tests/test_apu_p9_evidence.py b/tests/test_apu_p9_evidence.py new file mode 100644 index 00000000..bbeb908a --- /dev/null +++ b/tests/test_apu_p9_evidence.py @@ -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 diff --git a/tests/test_apu_p9_storage.py b/tests/test_apu_p9_storage.py new file mode 100644 index 00000000..87e642a1 --- /dev/null +++ b/tests/test_apu_p9_storage.py @@ -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 diff --git a/tests/test_apu_primitives.py b/tests/test_apu_primitives.py index 95dc29e2..793f2f5a 100644 --- a/tests/test_apu_primitives.py +++ b/tests/test_apu_primitives.py @@ -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"), "", diff --git a/tests/test_publication_refresh_claims.py b/tests/test_publication_refresh_claims.py index aabec1c6..7a83e579 100644 --- a/tests/test_publication_refresh_claims.py +++ b/tests/test_publication_refresh_claims.py @@ -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): diff --git a/tests/test_publication_registers.py b/tests/test_publication_registers.py index c84be443..7f857e34 100644 --- a/tests/test_publication_registers.py +++ b/tests/test_publication_registers.py @@ -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"] diff --git a/tests/test_publication_v05.py b/tests/test_publication_v05.py index ce9594d9..6159b6ef 100644 --- a/tests/test_publication_v05.py +++ b/tests/test_publication_v05.py @@ -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. diff --git a/tests/test_script_tools.py b/tests/test_script_tools.py index 48f5b24a..7026a0f1 100644 --- a/tests/test_script_tools.py +++ b/tests/test_script_tools.py @@ -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",