diff --git a/.github/workflows/_regression.yml b/.github/workflows/_regression.yml index 2c2a24d9..5c76cffe 100644 --- a/.github/workflows/_regression.yml +++ b/.github/workflows/_regression.yml @@ -132,7 +132,7 @@ jobs: - name: Upload reports and structured results if: always() - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: ${{ inputs.suite }}-regression-${{ github.run_id }}-${{ github.run_attempt }} path: | diff --git a/.github/workflows/development-environment.yml b/.github/workflows/development-environment.yml index c95bc215..2fbbe8c6 100644 --- a/.github/workflows/development-environment.yml +++ b/.github/workflows/development-environment.yml @@ -110,7 +110,7 @@ jobs: - name: Upload reports and structured results if: always() - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: environment-${{ matrix.environment }}-ihp130-${{ github.run_id }}-${{ github.run_attempt }} path: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 78306082..fe4ffdc5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,7 +46,7 @@ jobs: run: make CONFIG=configs/ci/ihp130.mk package - name: Upload workflow artifact - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: retrosoc-${{ github.ref_name }} path: dist/** diff --git a/Makefile b/Makefile index 6504b975..7510e603 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,7 @@ JTAG_IDCODE ?= DEADBEEF EXT_CLK_HZ ?= 72000000 AUD_CLK_HZ ?= 18432000 CLINT_TIMEBASE_HZ ?= 1000000 -MGMT_CPU_CLK_HZ := $(if $(filter PRODUCT,$(MINI_MODE)),24000000,$(EXT_CLK_HZ)) +MGMT_CPU_CLK_HZ := $(if $(filter TINY,$(SOC)),24000000,$(if $(filter PRODUCT,$(MINI_MODE)),24000000,$(EXT_CLK_HZ))) # Reset-state peripheral clock: the PCLK divider in the clock/reset subsystem # resets to passthrough on the LP root clock, so PCLK starts at the management # CPU frequency until software programs a different divider. @@ -84,6 +84,7 @@ SW_ISA_PROFILE ?= COMPAT SW_OPT ?= APP SW_LTO ?= NO WS2812_P3_ACCEPTANCE ?= NO +TINY_P7_ACCEPTANCE ?= NO COREMARK_MODE ?= quick NPU_P5_ACCEPTANCE ?= NO # Select the HP (OpenC906) JTAG target before the LP releases it; debug aid @@ -132,6 +133,9 @@ endif ifeq ($(WS2812_P3_ACCEPTANCE),YES) CONFIG_KEY_VARS += WS2812_P3_ACCEPTANCE endif +ifeq ($(TINY_P7_ACCEPTANCE),YES) +CONFIG_KEY_VARS += TINY_P7_ACCEPTANCE +endif VARIANT_ID := $(strip $(shell $(VCS_SHELL_PYTHON) $(ROOT_PATH)/scripts/config_key.py \ --lock $(LOCK_FILE) --profile $(PROFILE_NAME) --timestamp $(BUILD_TIMESTAMP) \ $(foreach var,$(CONFIG_KEY_VARS),--value $(var)=$($(var))) | tail -n 1)) @@ -291,6 +295,12 @@ $(call validate_value,SW_ISA_PROFILE,COMPAT TINY_PERF) $(call validate_value,SW_OPT,APP O2 O3 Os) $(call validate_value,SW_LTO,$(VALID_BOOL)) $(call validate_value,WS2812_P3_ACCEPTANCE,$(VALID_BOOL)) +$(call validate_value,TINY_P7_ACCEPTANCE,$(VALID_BOOL)) +ifeq ($(TINY_P7_ACCEPTANCE),YES) +ifneq ($(SOC)/$(APP)/$(HAVE_CSR),TINY/ci_smoke/YES) +$(error TINY_P7_ACCEPTANCE requires SOC=TINY APP=ci_smoke HAVE_CSR=YES) +endif +endif ifeq ($(WS2812_P3_ACCEPTANCE),YES) ifneq ($(SOC)/$(APP)/$(HAVE_CSR),MINI/ci_smoke/YES) $(error WS2812_P3_ACCEPTANCE requires SOC=MINI APP=ci_smoke HAVE_CSR=YES) @@ -387,7 +397,7 @@ endif ifeq ($(STA),OPENSTA) ifeq ($(HAVE_PLL),YES) -ifneq ($(SOC)/$(PDK)/$(TINY_SAFE24_PLL_OFF),TINY/ICS55/YES) +ifneq ($(SOC)/$(PDK)/$(TINY_R2_P7),TINY/ICS55/YES) $(error STA=OPENSTA requires a qualified PDK PLL timing profile; HAVE_PLL=YES is unsupported) endif endif @@ -424,6 +434,8 @@ endif ifeq ($(HAVE_SVA), NO) DEF_LIST += +define+SV_ASSRT_DISABLE +else ifeq ($(SOC),TINY) + DEF_LIST += +define+HAVE_SVA endif ifeq ($(HAVE_HP), YES) diff --git a/README.md b/README.md index 41a07c2c..f53eeeda 100644 --- a/README.md +++ b/README.md @@ -138,8 +138,10 @@ points, not the complete profile or application inventory. Tiny's [2026-10-07 target refreeze](docs/ip/tiny-soc.md#ics55-default-platform-and-final-timing-gate-2026-10-07) selects ICS55 with `HAVE_PLL=YES` and SAFE24 startup as the default. -The profile instantiates a parked PLL; dynamic SYS switching remains R2-P7 and -physical qualification is pending. IHP130 remains explicit compatibility. +The PLL starts parked off. The [R2-P7 implementation](docs/ip/tiny-soc-r2-clock-reset.md) +adds software-controlled SYS transitions; acceptance evidence is tracked in +the Tiny ledger and physical qualification remains pending. IHP130 remains +explicit compatibility. Locked PLL views can be acquired with `python3 physical/pdk/setup.py --pdk ICS55 --component pll` independently of platform implementation or timing qualification. @@ -266,4 +268,4 @@ Thanks to everyone contributing to retroSoC. Star History Chart - \ No newline at end of file + diff --git a/app/README.md b/app/README.md index 8ebe3f1b..57781b97 100644 --- a/app/README.md +++ b/app/README.md @@ -176,7 +176,11 @@ configurations, and build artifact locations. For `SOC=TINY`, `bringup` and `ci_smoke` use the MCU acceptance composition. It boots through XPI into SRAM, checks CPU C/A behavior and bus exceptions, DMA -(including UART requests), interrupts, RTC, GPIO, UART1 and I2C error completion, -then verifies watchdog reset by completing after the next NOR boot. It uses +(including UART requests), interrupts, RTC, GPIO and I2C0 error completion. +The P6 composition additionally checks RNG qualification rejection, CRC DMA, +Crypto initialization/vectors/erasure, audio plus finite WS refill and background +DMA, and SDIO transfers/recovery with verification-board models. Missing external +devices fail boundedly; they do not hold CPU boot reset. The image then verifies +watchdog reset by completing after the next NOR boot. It uses SYSCTRL terminal status. The small `asm/tiny_boot.S` image separately qualifies the synthesized SRAM/pad path; it is not a replacement for the full RTL test. diff --git a/app/apps/bringup/tiny.mk b/app/apps/bringup/tiny.mk index fd133f66..240ff06f 100644 --- a/app/apps/bringup/tiny.mk +++ b/app/apps/bringup/tiny.mk @@ -1,3 +1,4 @@ APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/tiny.c APP_CFLAGS += -Os -APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/mcu_acceptance.S \ No newline at end of file +APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/mcu_acceptance.S +APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/tiny_r2_p6.c \ No newline at end of file diff --git a/app/apps/ci_smoke/mcu_acceptance.S b/app/apps/ci_smoke/mcu_acceptance.S index e48bfe8c..cd4ee824 100644 --- a/app/apps/ci_smoke/mcu_acceptance.S +++ b/app/apps/ci_smoke/mcu_acceptance.S @@ -34,3 +34,15 @@ rs_mcu_unmapped_probe: .option pop ret .size rs_mcu_unmapped_probe, .-rs_mcu_unmapped_probe + + .balign 4 + .global rs_mcu_execute_probe + .type rs_mcu_execute_probe,@function +rs_mcu_execute_probe: + mv t0, a0 + .option push + .option arch, +zifencei + fence.i + .option pop + jr t0 + .size rs_mcu_execute_probe, .-rs_mcu_execute_probe diff --git a/app/apps/ci_smoke/mcu_acceptance.h b/app/apps/ci_smoke/mcu_acceptance.h index 3f05d234..2c791b1d 100644 --- a/app/apps/ci_smoke/mcu_acceptance.h +++ b/app/apps/ci_smoke/mcu_acceptance.h @@ -6,5 +6,6 @@ uint32_t rs_mcu_compressed_probe(void); void rs_mcu_atomic_probe(uint32_t *address); void rs_mcu_unmapped_probe(void); +uint32_t rs_mcu_execute_probe(uint32_t address); #endif diff --git a/app/apps/ci_smoke/mcu_shared_acceptance.h b/app/apps/ci_smoke/mcu_shared_acceptance.h new file mode 100644 index 00000000..873f1abc --- /dev/null +++ b/app/apps/ci_smoke/mcu_shared_acceptance.h @@ -0,0 +1,9 @@ +#ifndef RETROSOC_APP_MCU_SHARED_ACCEPTANCE_H +#define RETROSOC_APP_MCU_SHARED_ACCEPTANCE_H + +#include + +/* Zero means every P6 acceptance case completed; other values identify failure. */ +uint8_t rs_tiny_p6_acceptance(void); + +#endif diff --git a/app/apps/ci_smoke/tiny.c b/app/apps/ci_smoke/tiny.c index df318bc1..902a6b81 100644 --- a/app/apps/ci_smoke/tiny.c +++ b/app/apps/ci_smoke/tiny.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -20,12 +21,16 @@ #include #include "mcu_acceptance.h" +#include "mcu_shared_acceptance.h" static _Alignas(64) uint32_t rs_mcu_source[80]; static _Alignas(64) uint32_t rs_mcu_destination[80]; static volatile uint32_t rs_mcu_irq_count; static volatile uint32_t rs_mcu_exception_count; static volatile bool rs_mcu_handler_failed; +static volatile bool rs_mcu_probes_active; +static uint8_t rs_mcu_io_code; +static volatile uint32_t rs_mcu_watchdog_cookie __attribute__((section(".noinit.rs_mcu_wdg"))); static void rs_mcu_finish(bool pass, uint8_t code) { printf("Tiny acceptance %s code=%u\n", pass ? "passed" : "failed", code); @@ -39,8 +44,8 @@ static void rs_mcu_finish(bool pass, uint8_t code) { static void rs_mcu_exception(uintptr_t mcause, uintptr_t stack_pointer) { volatile uint32_t *const frame = (volatile uint32_t *)stack_pointer; - if ((mcause != UINT32_C(2)) && (mcause != UINT32_C(5))) { - rs_mcu_handler_failed = true; + if (!rs_mcu_probes_active || ((mcause != UINT32_C(2)) && (mcause != UINT32_C(5)))) { + rs_mcu_finish(false, 11U); } ++rs_mcu_exception_count; /* system_irq.S saves MEPC in slot 11 of the RV32 trap frame. Both test @@ -75,7 +80,7 @@ static bool rs_mcu_dma(void) { rs_mcu_source[index] = UINT32_C(0xa519c300) ^ index; rs_mcu_destination[index] = UINT32_C(0xcccccccc); } - if ((rs_dma_configure(4U, &config) != RS_EINVAL) || + if ((rs_dma_configure(8U, &config) != RS_EINVAL) || (rs_dma_configure(RS_DMA_CHANNEL_BULK, &config) != RS_OK) || (rs_dma_start(RS_DMA_CHANNEL_BULK) != RS_OK) || (rs_dma_wait(RS_DMA_CHANNEL_BULK, RS_TIMEOUT_DEFAULT) != RS_OK)) { @@ -91,8 +96,26 @@ static bool rs_mcu_dma(void) { return false; } config.kind = RS_DMA_KIND_MM_TO_STREAM; - config.request = RS_DMA_REQUEST_I2S_TX; - return rs_dma_config_validate(0U, &config) != RS_OK; + config.request = RS_DMA_REQUEST_DVP_RX; + if (rs_dma_config_validate(0U, &config) == RS_OK) { + return false; + } + /* DMA owns the destination until completion. FENCE.I is executed by the + * assembly helper before entering either the DMA-written or patched code. */ + rs_mcu_source[0] = UINT32_C(0x02a00513); /* addi a0,zero,42 */ + rs_mcu_source[1] = UINT32_C(0x00008067); /* jalr zero,ra,0 */ + config.kind = RS_DMA_KIND_MM_TO_MM; + config.request = RS_DMA_REQUEST_SOFTWARE; + config.byte_count = UINT32_C(8); + config.burst_beats = 1U; + if ((rs_dma_configure(RS_DMA_CHANNEL_BULK, &config) != RS_OK) || + (rs_dma_start(RS_DMA_CHANNEL_BULK) != RS_OK) || + (rs_dma_wait(RS_DMA_CHANNEL_BULK, RS_TIMEOUT_DEFAULT) != RS_OK) || + (rs_mcu_execute_probe((uint32_t)(uintptr_t)rs_mcu_destination) != UINT32_C(42))) { + return false; + } + rs_mcu_destination[0] = UINT32_C(0x02b00513); /* addi a0,zero,43 */ + return rs_mcu_execute_probe((uint32_t)(uintptr_t)rs_mcu_destination) == UINT32_C(43); } static bool rs_mcu_interrupts(void) { @@ -146,9 +169,7 @@ static bool rs_mcu_io(void) { const uint32_t transmit[4] = {UINT32_C(0x54), UINT32_C(0x49), UINT32_C(0x4e), UINT32_C(0x59)}; uint32_t receive[4] = {0U}; rs_gpio_capabilities_t capabilities; - rs_uart_timing_t timing; bool high; - uint32_t timeout = UINT32_C(10000); uint8_t payload = UINT8_C(0x55); const rs_i2c_transfer_t transfer = { .address = UINT16_C(0x50), @@ -157,54 +178,51 @@ static bool rs_mcu_io(void) { }; rs_i2c_status_t status; + rs_mcu_io_code = 40U; if ((rs_gpio_get_capabilities(&capabilities) != RS_OK) || ((capabilities.pad_features & (RS_GPIO_PAD_CAP_PULL_UP | RS_GPIO_PAD_CAP_PULL_DOWN)) != ((RS_SOC_GPIO_HAS_PULLS != 0U) ? (RS_GPIO_PAD_CAP_PULL_UP | RS_GPIO_PAD_CAP_PULL_DOWN) : 0U)) || - (rs_gpio_configure(15U, &pull_up) != + (rs_gpio_configure(27U, &pull_up) != ((RS_SOC_GPIO_HAS_PULLS != 0U) ? RS_OK : RS_ENOTSUP)) || - (rs_gpio_configure(15U, &pull_down) != + (rs_gpio_configure(27U, &pull_down) != ((RS_SOC_GPIO_HAS_PULLS != 0U) ? RS_OK : RS_ENOTSUP)) || - (rs_gpio_configure(15U, &output) != RS_OK) || (rs_gpio_write(15U, true) != RS_OK) || - (rs_gpio_read(15U, &high) != RS_OK) || !high || (rs_gpio_write(15U, false) != RS_OK) || - (rs_gpio_read(15U, &high) != RS_OK) || high) { + (rs_gpio_configure(27U, &output) != RS_OK) || (rs_gpio_write(27U, true) != RS_OK) || + (rs_gpio_read(27U, &high) != RS_OK) || !high || (rs_gpio_write(27U, false) != RS_OK) || + (rs_gpio_read(27U, &high) != RS_OK) || high) { return false; } + rs_mcu_io_code = 41U; if ((rs_uart_configure(&loopback, RS_TIMEOUT_DEFAULT) != RS_OK) || (rs_uart_write_dma(transmit, 4U, RS_TIMEOUT_DEFAULT) != RS_OK) || (rs_uart_read_dma(receive, 4U, RS_TIMEOUT_DEFAULT) != RS_OK)) { return false; } + rs_mcu_io_code = 42U; for (uint32_t index = 0U; index < 4U; ++index) { if (transmit[index] != receive[index]) { return false; } } + rs_mcu_io_code = 43U; if ((rs_uart_init(RS_CPU_CLOCK_HZ, UART_BPS) != RS_OK) || - (rs_uart_timing_calculate(RS_CPU_CLOCK_HZ, UART_BPS, &timing) != RS_OK)) { - return false; - } - /* UART1 uses the same register ABI; the current shared high-level UART - * API selects UART0. Exercise UART1 PIO and its internal loopback here. */ - RS_SOC_REG32(RS_SOC_APB4_UART1_BASE, UINT32_C(0x00)) = timing.baud_integer; - RS_SOC_REG32(RS_SOC_APB4_UART1_BASE, UINT32_C(0x04)) = timing.baud_fraction; - RS_SOC_REG32(RS_SOC_APB4_UART1_BASE, UINT32_C(0x08)) = UINT32_C(3); - RS_SOC_REG32(RS_SOC_APB4_UART1_BASE, UINT32_C(0x0c)) = UINT32_C(7); - RS_SOC_REG32(RS_SOC_APB4_UART1_BASE, UINT32_C(0x10)) = UINT32_C(0x5a); - while (((RS_SOC_REG32(RS_SOC_APB4_UART1_BASE, UINT32_C(0x18)) & UINT32_C(0x40)) != 0U) && - (timeout != 0U)) { - --timeout; - } - if ((timeout == 0U) || ((RS_SOC_REG32(RS_SOC_APB4_UART1_BASE, UINT32_C(0x14)) & - UINT32_C(0xff)) != UINT32_C(0x5a))) { + (rs_i2c_init(RS_I2C_BUS_1, RS_CPU_CLOCK_HZ, UINT32_C(100000)) != RS_ENOTSUP)) { return false; } /* There is no slave on the pulled-up acceptance board: a real address * transaction must terminate as NACK rather than stick waiting on the bus. */ - for (uint32_t bus = 0U; bus < 2U; ++bus) { + for (uint32_t bus = 0U; bus < 1U; ++bus) { + rs_mcu_io_code = 44U; const rs_i2c_bus_t instance = (bus == 0U) ? RS_I2C_BUS_0 : RS_I2C_BUS_1; - if ((rs_i2c_init(instance, RS_CPU_CLOCK_HZ, UINT32_C(100000)) != RS_OK) || - (rs_i2c_transfer(instance, &transfer, RS_TIMEOUT_DEFAULT) != RS_EIO) || + rs_status_t init_result = rs_i2c_init(instance, RS_CPU_CLOCK_HZ, UINT32_C(100000)); + rs_status_t transfer_result = init_result == RS_OK + ? rs_i2c_transfer(instance, &transfer, RS_TIMEOUT_DEFAULT) + : init_result; + printf("R2_P6_IO i2c_init=%d i2c_transfer=%d\n", (int)init_result, (int)transfer_result); + if (rs_i2c_get_status(instance, &status) == RS_OK) { + printf("R2_P6_IO i2c_errors=%lx\n", (unsigned long)status.errors); + } + if ((init_result != RS_OK) || (transfer_result != RS_EIO) || (rs_i2c_get_status(instance, &status) != RS_OK) || ((status.errors & RS_I2C_ERROR_ADDR_NACK) == 0U)) { return false; @@ -235,8 +253,10 @@ int main(void) { rs_onchip_sram_info_t memory; rs_sysctrl_fault_status_t fault; rs_watchdog_status_t watchdog = {0}; + rs_tiny_rcu_snapshot_t reset_snapshot; uint64_t before; uint64_t after; + uint8_t p6_result; const rs_watchdog_config_t watchdog_config = { .prescale_divider = 1U, .timeout_ticks = UINT32_C(20000), @@ -251,12 +271,21 @@ int main(void) { rs_mcu_finish(false, 1U); } printf("Hello retroSoC! Tiny MCU\n"); - if (rs_watchdog_get_status(&watchdog, RS_TIMEOUT_DEFAULT) != RS_OK) { + if (rs_watchdog_get_status(&watchdog, RS_TIMEOUT_DEFAULT) != RS_OK || + rs_tiny_rcu_get_snapshot(&reset_snapshot) != RS_OK) { rs_mcu_finish(false, 2U); } - if (watchdog.reset_count != 0U) { - rs_mcu_finish(true, 0U); + if ((reset_snapshot.reset_cause & UINT32_C(4)) != 0U) { + /* P7 resets WDG functional state. The AON RCU owns the retained cause; + * the SRAM cookie proves all pre-reset acceptance work completed. */ + const bool reset_valid = + rs_mcu_watchdog_cookie == UINT32_C(0x57375037) && watchdog.reset_count == 0U && + reset_snapshot.current_profile == 0U && reset_snapshot.sys_hz == UINT32_C(24000000) && + reset_snapshot.gate_status == 0U; + rs_mcu_watchdog_cookie = 0U; + rs_mcu_finish(reset_valid, reset_valid ? 0U : 2U); } + rs_mcu_watchdog_cookie = 0U; if ((rs_archinfo_read(&info) != RS_OK) || (rs_archinfo_validate_build(&info) != RS_OK) || (info.soc_id != RS_SOC_ID) || (info.topology != UINT32_C(0x20200001)) || (info.technology != RS_SOC_TECHNOLOGY_ID) || @@ -267,11 +296,14 @@ int main(void) { } if ((rs_mcu_compressed_probe() != 42U) || ((__RV_CSR_READ(CSR_MISA) & UINT32_C(1)) != 0U) || (rs_irq_register_exception(2U, rs_mcu_exception) != RS_OK) || - (rs_irq_register_exception(5U, rs_mcu_exception) != RS_OK)) { + (rs_irq_register_exception(5U, rs_mcu_exception) != RS_OK) || + (rs_irq_register_exception(7U, rs_mcu_exception) != RS_OK)) { rs_mcu_finish(false, 4U); } + rs_mcu_probes_active = true; rs_mcu_atomic_probe(rs_mcu_source); rs_mcu_unmapped_probe(); + rs_mcu_probes_active = false; if ((rs_mcu_exception_count != 2U) || rs_mcu_handler_failed || (rs_sysctrl_get_fault_status(&fault) != RS_OK) || !fault.pending || (fault.address != UINT32_C(0x38000000)) || (rs_sysctrl_clear_fault() != RS_OK)) { @@ -289,9 +321,14 @@ int main(void) { rs_mcu_finish(false, 8U); } if (!rs_mcu_io()) { - rs_mcu_finish(false, 10U); + rs_mcu_finish(false, rs_mcu_io_code); + } + p6_result = rs_tiny_p6_acceptance(); + if (p6_result != 0U) { + rs_mcu_finish(false, p6_result); } printf("Tiny CPU/DMA/IRQ/RTC/I/O passed; exercising watchdog reset\n"); + rs_mcu_watchdog_cookie = UINT32_C(0x57375037); if ((rs_watchdog_configure(&watchdog_config) != RS_OK) || (rs_watchdog_start(RS_TIMEOUT_DEFAULT) != RS_OK)) { rs_mcu_finish(false, 9U); diff --git a/app/apps/ci_smoke/tiny.mk b/app/apps/ci_smoke/tiny.mk index fd133f66..4478e0e2 100644 --- a/app/apps/ci_smoke/tiny.mk +++ b/app/apps/ci_smoke/tiny.mk @@ -1,3 +1,7 @@ APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/tiny.c APP_CFLAGS += -Os -APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/mcu_acceptance.S \ No newline at end of file +APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/mcu_acceptance.S +APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/tiny_r2_p6.c +ifeq ($(TINY_P7_ACCEPTANCE),YES) +APP_SRCS := $(ROOT_PATH)/app/apps/ci_smoke/tiny_r2_p7.c +endif \ No newline at end of file diff --git a/app/apps/ci_smoke/tiny_r2_p6.c b/app/apps/ci_smoke/tiny_r2_p6.c new file mode 100644 index 00000000..ad74f328 --- /dev/null +++ b/app/apps/ci_smoke/tiny_r2_p6.c @@ -0,0 +1,801 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mcu_shared_acceptance.h" + +static volatile uint32_t rs_p6_irq_count; +static uint32_t rs_p6_irq_selected; +static volatile bool rs_p6_irq_error; +static _Alignas(64) uint32_t rs_p6_source[256]; +static _Alignas(64) uint32_t rs_p6_destination[258]; +static _Alignas(64) uint32_t rs_p6_audio_tx[768]; +static _Alignas(64) uint32_t rs_p6_audio_rx[562]; +static _Alignas(64) rs_dma_tcd_t rs_p6_descriptors[4]; +static rs_dma_session_t rs_p6_audio_tx_owner; +static rs_dma_session_t rs_p6_audio_rx_owner; +static rs_dma_session_t rs_p6_background_owner; +static uint32_t rs_p6_pixels[65]; + +static void rs_p6_irq_handler(uintptr_t cause, uintptr_t stack) { + (void)cause; + (void)stack; + ++rs_p6_irq_count; + if (rs_irq_disable_external(rs_p6_irq_selected) != RS_OK) { + rs_p6_irq_error = true; + } +} + +static bool rs_p6_irq_arm(uint32_t irq) { + rs_p6_irq_selected = irq; + rs_p6_irq_count = 0U; + rs_p6_irq_error = false; + if ((rs_irq_register_external(irq, rs_p6_irq_handler) != RS_OK) || + (rs_irq_enable_external(irq, rs_p6_irq_handler) != RS_OK)) { + return false; + } + __RV_CSR_SET(CSR_MSTATUS, UINT32_C(8)); + return true; +} + +static bool rs_p6_irq_received(void) { + uint32_t timeout = UINT32_C(100000); + while ((rs_p6_irq_count == 0U) && (timeout != 0U)) { + --timeout; + } + return (rs_p6_irq_count == 1U) && !rs_p6_irq_error; +} + +static bool rs_p6_route(uint32_t pin, bool open_drain) { + const rs_gpio_config_t config = { + .mode = RS_GPIO_MODE_ALT0, + .pull = RS_GPIO_PULL_NONE, + .trigger = RS_GPIO_TRIGGER_NONE, + .open_drain = open_drain, + .filter_enable = false, + }; + return rs_gpio_configure(pin, &config) == RS_OK; +} + +static bool rs_p6_uart(void) { + const uint8_t transmit[4] = {0xA5U, 0x5AU, 0x96U, 0x69U}; + rs_uart_rx_data_t receive[4]; + const rs_uart_config_t unsupported = { + .source_clock_hz = RS_PCLK_CLOCK_HZ, + .baud_rate = UART_BPS, + .data_bits = 8U, + .stop_bits = 1U, + .parity = RS_UART_PARITY_NONE, + .rx_watermark = 1U, + .rx_timeout_bits = 16U, + .tx_enable = true, + .rx_enable = true, + .auto_cts_enable = true, + }; + if ((rs_uart_configure(&unsupported, RS_TIMEOUT_DEFAULT) != RS_ENOTSUP) || + (rs_uart_write(transmit, sizeof(transmit), RS_TIMEOUT_DEFAULT) != RS_OK) || + (rs_uart_read(receive, 4U, RS_TIMEOUT_DEFAULT) != RS_OK)) { + return false; + } + for (uint32_t index = 0U; index < 4U; ++index) { + if (receive[index].data != transmit[index] || receive[index].errors != 0U) { + return false; + } + } + printf("\nR2_P6_UART pads=1 echo_bytes=4 flow_rejected=1\n"); + return true; +} + +static bool rs_p6_rng(void) { + const rs_rng_config_t config = {.fifo_watermark = 1U}; + rs_rng_snapshot_t snapshot; + uint32_t word = 0U; + if ((rs_rng_init(&config) != RS_OK) || + (rs_rng_read_entropy(&word, RS_TIMEOUT_DEFAULT) != RS_ENOTSUP) || + (rs_rng_read_diagnostic(&word, RS_TIMEOUT_DEFAULT) != RS_OK) || + (rs_rng_get_status(&snapshot) != RS_OK) || snapshot.source_qualified || + !rs_p6_irq_arm(RS_SOC_EXT_IRQ_RNG) || + (rs_rng_interrupt_enable(RS_RNG_INTERRUPT_SOURCE_FAULT) != RS_OK) || + (rs_rng_interrupt_test(RS_RNG_INTERRUPT_SOURCE_FAULT) != RS_OK) || !rs_p6_irq_received() || + (rs_rng_interrupt_enable(0U) != RS_OK) || + (rs_rng_interrupt_clear(RS_RNG_INTERRUPT_ALL) != RS_OK)) { + return false; + } + printf("R2_P6_RNG unqualified=1 diagnostic=1 irq=1\n"); + return true; +} + +static bool rs_p6_dma_capabilities(void) { + const uint32_t capability = RS_DMA_REG(RS_DMA_REG_CAPABILITY); + rs_dma_config_t config = { + .kind = RS_DMA_KIND_MM_TO_MM, + .request = RS_DMA_REQUEST_SOFTWARE, + .source = (uintptr_t)rs_p6_source, + .destination = (uintptr_t)rs_p6_destination, + .byte_count = 4U, + .width = RS_DMA_WIDTH_32, + .source_increment = true, + .destination_increment = true, + .burst_beats = 1U, + }; + rs_dma_status_t status; + const uint32_t unsupported[] = {9U, 10U, 11U, 14U, 15U}; + if (((capability >> 8U) & UINT32_C(0xFF)) != 8U) { + return false; + } + for (size_t index = 0U; index < sizeof(unsupported) / sizeof(unsupported[0]); ++index) { + rs_p6_destination[0] = UINT32_C(0x12345678); + if (rs_dma_configure(RS_DMA_CHANNEL_DVP, &config) != RS_OK) { + return false; + } + /* Exercise hardware rejection independently of the SDK's earlier gate. */ + RS_DMA_CH_REG(RS_DMA_CHANNEL_DVP, RS_DMA_CH_REG_REQUEST_SEL) = unsupported[index]; + if ((rs_dma_start(RS_DMA_CHANNEL_DVP) != RS_OK) || + (rs_dma_wait(RS_DMA_CHANNEL_DVP, RS_TIMEOUT_DEFAULT) != RS_EIO) || + (rs_dma_get_status(RS_DMA_CHANNEL_DVP, &status) != RS_OK) || !status.error || + status.busy || (status.bytes_done != 0U) || + (rs_p6_destination[0] != UINT32_C(0x12345678))) { + return false; + } + } + printf("R2_P6_DMA channels=8 rejected_requests=5\n"); + return true; +} + +static uint32_t rs_p6_crc_reference(const uint8_t *data, size_t bytes) { + uint32_t value = UINT32_MAX; + for (size_t index = 0U; index < bytes; ++index) { + value ^= (uint32_t)data[index]; + for (uint32_t bit = 0U; bit < 8U; ++bit) { + value = (value >> 1U) ^ (((value & 1U) != 0U) ? UINT32_C(0xEDB88320) : 0U); + } + } + return ~value; +} + +static bool rs_p6_crc(void) { + rs_crc_config_t crc; + static rs_dma_session_t lease; + uint32_t result; + rs_dma_config_t transfer = { + .kind = RS_DMA_KIND_MM_TO_MM, + .request = RS_DMA_REQUEST_SOFTWARE, + .source = (uintptr_t)rs_p6_source, + .destination = RS_SOC_APB4_CRC_BASE + CRC_DATA_OFFSET, + .byte_count = UINT32_C(64), + .width = RS_DMA_WIDTH_32, + .source_increment = true, + .burst_beats = 1U, + }; + for (uint32_t index = 0U; index < 256U; ++index) { + rs_p6_source[index] = UINT32_C(0x12345678) ^ (index * UINT32_C(0x01020408)); + } + if ((rs_crc_get_profile(RS_CRC_PROFILE_CRC32_ISO_HDLC, &crc) != RS_OK) || + (rs_dma_session_acquire(&lease, RS_DMA_CHANNEL_BULK) != RS_OK)) { + return false; + } + for (size_t tail = 0U; tail < 4U; ++tail) { + if ((rs_crc_init(&crc) != RS_OK) || (rs_crc_start() != RS_OK) || + (rs_dma_session_configure(&lease, &transfer) != RS_OK) || + (rs_dma_session_start(&lease) != RS_OK) || + (rs_dma_wait(RS_DMA_CHANNEL_BULK, RS_TIMEOUT_DEFAULT) != RS_OK) || + (rs_crc_update((const uint8_t *)rs_p6_source + 64U, tail) != RS_OK) || + (rs_crc_finish(&result) != RS_OK) || + (result != rs_p6_crc_reference((const uint8_t *)rs_p6_source, 64U + tail))) { + return false; + } + } + if ((rs_dma_session_release(&lease) != RS_OK) || + (rs_crc_compute(&crc, rs_p6_source, 0U, &result) != RS_OK) || (result != 0U)) { + return false; + } + printf("R2_P6_CRC dma=1 tails=4 empty=1\n"); + return true; +} + +static bool rs_p6_crypto(void) { + static uint32_t modulus[RS_CRYPTO_RSA_WORDS]; + static uint32_t base[RS_CRYPTO_RSA_WORDS]; + static uint32_t exponent[RS_CRYPTO_RSA_WORDS]; + static uint32_t rsa_result[RS_CRYPTO_RSA_WORDS]; + static const uint8_t key[16] = { + 0x00U, 0x01U, 0x02U, 0x03U, 0x04U, 0x05U, 0x06U, 0x07U, + 0x08U, 0x09U, 0x0AU, 0x0BU, 0x0CU, 0x0DU, 0x0EU, 0x0FU, + }; + static const uint32_t input[4] = { + UINT32_C(0x33221100), + UINT32_C(0x77665544), + UINT32_C(0xBBAA9988), + UINT32_C(0xFFEEDDCC), + }; + static const uint32_t expected[4] = { + UINT32_C(0xD8E0C469), + UINT32_C(0x30047B6A), + UINT32_C(0x80B7CDD8), + UINT32_C(0x5AC5B470), + }; + if ((rs_crypto_zeroize_wait(1U) != RS_ETIMEOUT) || + (rs_crypto_zeroize_wait(RS_TIMEOUT_DEFAULT) != RS_OK) || + (rs_crypto_init(2050U) != RS_ETIMEOUT) || + (rs_crypto_selftest(RS_TIMEOUT_DEFAULT) != RS_OK) || + (rs_crypto_aes_set_key(key, sizeof(key), RS_TIMEOUT_DEFAULT) != RS_OK)) { + return false; + } + for (uint32_t index = 0U; index < 16U; ++index) { + rs_p6_source[index] = input[index % 4U]; + } + rs_p6_destination[0] = UINT32_C(0x5A5AA5A5); + rs_p6_destination[17] = UINT32_C(0xA5A55A5A); + if (rs_crypto_aes_crypt_dma(RS_CRYPTO_AES_ECB, false, NULL, rs_p6_source, &rs_p6_destination[1], + 64U, RS_TIMEOUT_DEFAULT) != RS_OK) { + return false; + } + for (uint32_t index = 0U; index < 16U; ++index) { + if (rs_p6_destination[index + 1U] != expected[index % 4U]) { + return false; + } + } + /* A small public-operation integration vector exercises the full 2048-bit + * storage/control path. Full private vectors remain in the shared suite. */ + for (uint32_t index = 0U; index < RS_CRYPTO_RSA_WORDS; ++index) { + modulus[index] = UINT32_MAX; + } + base[0] = 2U; + exponent[0] = 3U; + if ((rs_crypto_rsa_prepare(modulus, RS_TIMEOUT_DEFAULT) != RS_OK) || + (rs_crypto_rsa_modexp(base, exponent, 2U, false, rsa_result, RS_TIMEOUT_DEFAULT) != + RS_OK)) { + return false; + } + for (uint32_t index = 0U; index < RS_CRYPTO_RSA_WORDS; ++index) { + if (rsa_result[index] != ((index == 0U) ? 8U : 0U)) { + return false; + } + } + if ((rs_p6_destination[0] != UINT32_C(0x5A5AA5A5)) || + (rs_p6_destination[17] != UINT32_C(0xA5A55A5A)) || !rs_p6_irq_arm(RS_SOC_EXT_IRQ_CRYPTO)) { + return false; + } + RS_CRYPTO_REG(RS_CRYPTO_REG_IRQ_STATE) = RS_CRYPTO_IRQ_ALL; + RS_CRYPTO_REG(RS_CRYPTO_REG_IRQ_ENABLE) = RS_CRYPTO_IRQ_ZEROIZED; + if ((rs_crypto_zeroize_wait(0U) != RS_ETIMEOUT) || + (rs_crypto_zeroize_wait(RS_TIMEOUT_DEFAULT) != RS_OK) || !rs_p6_irq_received()) { + return false; + } + RS_CRYPTO_REG(RS_CRYPTO_REG_IRQ_ENABLE) = 0U; + RS_CRYPTO_REG(RS_CRYPTO_REG_IRQ_STATE) = RS_CRYPTO_IRQ_ALL; + printf("R2_P6_CRYPTO init=1 aes_sha=1 dma45=1 rsa_public=1 zeroize=1\n"); + return true; +} + +static bool rs_p6_stop_session(rs_dma_session_t *owner) { + rs_dma_status_t status; + uint32_t timeout = UINT32_C(100000); + if ((rs_dma_get_status(owner->channel, &status) != RS_OK) || + (status.busy && (rs_dma_session_abort(owner) != RS_OK))) { + return false; + } + do { + if ((rs_dma_get_status(owner->channel, &status) != RS_OK) || (timeout == 0U)) { + return false; + } + --timeout; + } while (status.busy); + return rs_dma_session_release(owner) == RS_OK; +} + +static uint32_t rs_p6_audio_sample(bool wide, uint32_t index) { + const uint32_t word = rs_p6_audio_rx[1U + (wide ? index : index / 2U)]; + return wide ? word : ((word >> ((index % 2U) * 16U)) & UINT32_C(0xFFFF)); +} + +static bool rs_p6_audio_words(bool wide, uint32_t received_words) { + const uint32_t samples = wide ? received_words : received_words * 2U; + const uint32_t first = wide ? UINT32_C(0x120020) : UINT32_C(0x2020); + uint32_t offset = 0U; + /* Ignore only the bounded PHY startup preamble, never a missing payload. + * Exactly 256 stereo frames must then match consecutive independent samples. */ + while ((offset < 40U) && (rs_p6_audio_sample(wide, offset) != first)) { + ++offset; + } + if ((offset >= 40U) || ((offset + 512U) > samples)) { + return false; + } + for (uint32_t sample = 0U; sample < 512U; ++sample) { + if (rs_p6_audio_sample(wide, offset + sample) != (first + sample)) { + printf("R2_P6_AUDIO mismatch wide=%u sample=%lu got=%lx expected=%lx\n", wide ? 1U : 0U, + (unsigned long)sample, (unsigned long)rs_p6_audio_sample(wide, offset + sample), + (unsigned long)(first + sample)); + return false; + } + } + return (rs_p6_audio_rx[0] == UINT32_C(0x5A5AA5A5)) && + (rs_p6_audio_rx[received_words + 1U] == UINT32_C(0xA5A55A5A)); +} + +static bool rs_p6_audio_ws(bool wide) { + printf("R2_P6_STAGE audio_bits=%u cycles=%lu\n", wide ? 24U : 16U, + (unsigned long)__RV_CSR_READ(CSR_MCYCLE)); + const uint32_t rx_words = wide ? 548U : 280U; + const rs_i2s_config_t audio = { + .stream_tx = true, + .stream_rx = true, + .preset = wide ? RS_I2S_PRESET_24B_48K : RS_I2S_PRESET_16B_48K, + .upbound = 96U, + .lowbound = 32U, + }; + const rs_ws2812_config_t ws = { + .source_clock_hz = RS_PCLK_CLOCK_HZ, + .bit_period_ns = 1250U, + .t0h_ns = 350U, + .t1h_ns = 700U, + .reset_ns = 300000U, + .fifo_watermark = 8U, + }; + rs_dma_config_t tx = { + .kind = RS_DMA_KIND_MM_TO_STREAM, + .request = RS_DMA_REQUEST_I2S_TX, + .source = (uintptr_t)rs_p6_audio_tx, + .byte_count = sizeof(rs_p6_audio_tx), + .width = RS_DMA_WIDTH_32, + .source_increment = true, + .priority = 3U, + .burst_beats = 16U, + }; + rs_dma_config_t rx = { + .kind = RS_DMA_KIND_STREAM_TO_MM, + .request = RS_DMA_REQUEST_I2S_RX, + .destination = (uintptr_t)&rs_p6_audio_rx[1], + .byte_count = rx_words * 4U, + .width = RS_DMA_WIDTH_32, + .destination_increment = true, + .priority = 3U, + .burst_beats = 16U, + }; + rs_dma_config_t background = { + .kind = RS_DMA_KIND_MM_TO_MM, + .request = RS_DMA_REQUEST_SOFTWARE, + .source = (uintptr_t)rs_p6_source, + .destination = (uintptr_t)&rs_p6_destination[1], + .byte_count = 256U, + .width = RS_DMA_WIDTH_32, + .source_increment = true, + .destination_increment = true, + .burst_beats = 16U, + }; + rs_i2s_status_t audio_status; + rs_dma_status_t rx_status, bg_status; + rs_ws2812_dma_status_t ws_status; + uint64_t now, deadline, start; + uint32_t cycles, instructions; + uint32_t frame = 0U, background_jobs = 0U; + for (uint32_t pin = 12U; pin <= 17U; ++pin) { + if (!rs_p6_route(pin, false)) { + return false; + } + } + if (!rs_p6_route(26U, false) || (rs_i2s_disable() != RS_OK) || + (rs_i2s_flush(true, true, RS_TIMEOUT_DEFAULT) != RS_OK) || + (rs_i2s_configure(&audio) != RS_OK) || (rs_ws2812_init(&ws) != RS_OK)) { + return false; + } + printf("R2_P6_STAGE audio_host_ready=1 cycles=%lu\n", (unsigned long)__RV_CSR_READ(CSR_MCYCLE)); + for (uint32_t index = 0U; index < 768U; ++index) { + rs_p6_audio_tx[index] = + wide ? UINT32_C(0x120000) + index + : ((UINT32_C(0x2001) + 2U * index) << 16U) | (UINT32_C(0x2000) + 2U * index); + } + for (uint32_t index = 0U; index < 65U; ++index) { + rs_p6_pixels[index] = index * UINT32_C(0x010203); + } + for (uint32_t index = 0U; index < 256U; ++index) { + rs_p6_source[index] = UINT32_C(0x5A123456) ^ index; + rs_p6_destination[index + 1U] = 0U; + } + rs_p6_destination[0] = UINT32_C(0x5A5AA5A5); + rs_p6_destination[257] = UINT32_C(0xA5A55A5A); + rs_p6_audio_rx[0] = UINT32_C(0x5A5AA5A5); + rs_p6_audio_rx[rx_words + 1U] = UINT32_C(0xA5A55A5A); + for (uint32_t index = 0U; index < 4U; ++index) { + rs_p6_descriptors[index] = (rs_dma_tcd_t){ + .source = (uint32_t)(uintptr_t)&rs_p6_source[index * 64U], + .destination = (uint32_t)(uintptr_t)&rs_p6_destination[1U + index * 64U], + .byte_count = 256U, + .next_ptr = index == 3U ? 0U : (uint32_t)(uintptr_t)&rs_p6_descriptors[index + 1U], + .control = RS_DMA_TCD_VALID | RS_DMA_TCD_SRC_INC | RS_DMA_TCD_DST_INC | + (UINT32_C(16) << RS_DMA_TCD_BURST_SHIFT), + }; + } + if (!rs_p6_irq_arm(RS_SOC_EXT_IRQ_I2S) || + (rs_i2s_interrupt_enable(RS_I2S_INTERRUPT_TX_LOW) != RS_OK) || + (rs_i2s_interrupt_test(RS_I2S_INTERRUPT_TX_LOW) != RS_OK) || !rs_p6_irq_received() || + (rs_i2s_interrupt_enable(0U) != RS_OK) || + (rs_i2s_interrupt_clear(RS_I2S_INTERRUPT_ALL) != RS_OK) || + (rs_dma_session_acquire(&rs_p6_audio_tx_owner, RS_DMA_CHANNEL_I2S_TX) != RS_OK) || + (rs_dma_session_acquire(&rs_p6_audio_rx_owner, RS_DMA_CHANNEL_I2S_RX) != RS_OK) || + (rs_dma_session_acquire(&rs_p6_background_owner, RS_DMA_CHANNEL_DVP) != RS_OK) || + (rs_dma_session_configure(&rs_p6_audio_tx_owner, &tx) != RS_OK) || + (rs_dma_session_configure(&rs_p6_audio_rx_owner, &rx) != RS_OK) || + (rs_dma_session_start(&rs_p6_audio_tx_owner) != RS_OK) || + (rs_dma_session_start(&rs_p6_audio_rx_owner) != RS_OK) || + (rs_clint_get_time(&start) != RS_OK)) { + return false; + } + deadline = start + UINT64_C(50000); + printf("R2_P6_STAGE audio_dma_started=1 cycles=%lu\n", + (unsigned long)__RV_CSR_READ(CSR_MCYCLE)); + do { + if ((rs_i2s_get_status(&audio_status) != RS_OK) || (rs_clint_get_time(&now) != RS_OK) || + (now >= deadline)) { + return false; + } + } while (audio_status.tx_level < 96U); + printf("R2_P6_STAGE audio_prefilled=1 cycles=%lu\n", (unsigned long)__RV_CSR_READ(CSR_MCYCLE)); + cycles = (uint32_t)__RV_CSR_READ(CSR_MCYCLE); + instructions = (uint32_t)__RV_CSR_READ(CSR_MINSTRET); + if (!rs_p6_irq_arm(RS_SOC_EXT_IRQ_WS2812) || + (rs_ws2812_irq_enable(RS_WS2812_INTR_FIFO_LOW | RS_WS2812_INTR_DONE) != RS_OK) || + (rs_i2s_enable(true, true) != RS_OK) || + (rs_ws2812_dma_begin(rs_p6_pixels, 33U, now, deadline) != RS_OK)) { + return false; + } + do { + if ((rs_clint_get_time(&now) != RS_OK) || (now >= deadline) || + (rs_ws2812_dma_service(now) != RS_OK) || (rs_ws2812_dma_status(&ws_status) != RS_OK) || + (rs_dma_get_status(RS_DMA_CHANNEL_I2S_RX, &rx_status) != RS_OK) || + (rs_dma_get_status(RS_DMA_CHANNEL_DVP, &bg_status) != RS_OK) || rx_status.error || + bg_status.error) { + return false; + } + if (!bg_status.busy && (background_jobs < 64U)) { + if (rs_dma_session_configure(&rs_p6_background_owner, &background) != RS_OK) { + return false; + } + /* This application exclusively leases channel 2; the ordinary TCD + * helper is blocking, so publish the same finite hardware chain. */ + RS_DMA_CH_REG(RS_DMA_CHANNEL_DVP, RS_DMA_CH_REG_TCD_HEAD) = + wide ? (uint32_t)(uintptr_t)rs_p6_descriptors : 0U; + RS_DMA_CH_REG(RS_DMA_CHANNEL_DVP, RS_DMA_CH_REG_TCD_COUNT) = wide ? 4U : 0U; + if (rs_dma_session_start(&rs_p6_background_owner) != RS_OK) { + return false; + } + ++background_jobs; + } + if (!ws_status.active && (frame == 0U)) { + if ((ws_status.result != RS_OK) || + (rs_ws2812_dma_begin(rs_p6_pixels, 65U, now, deadline) != RS_OK)) { + return false; + } + frame = 1U; + ws_status.active = true; + } + } while (rx_status.busy || ws_status.active); + cycles = (uint32_t)__RV_CSR_READ(CSR_MCYCLE) - cycles; + instructions = (uint32_t)__RV_CSR_READ(CSR_MINSTRET) - instructions; + if ((rx_status.bytes_done != rx_words * 4U) || !rx_status.done || (ws_status.result != RS_OK) || + (rs_p6_irq_count != 1U) || (rs_i2s_get_status(&audio_status) != RS_OK) || + ((audio_status.interrupt_state & + (RS_I2S_INTERRUPT_TX_UNDERRUN | RS_I2S_INTERRUPT_RX_OVERRUN)) != 0U) || + !rs_p6_stop_session(&rs_p6_audio_tx_owner) || !rs_p6_stop_session(&rs_p6_audio_rx_owner) || + (rs_i2s_disable() != RS_OK) || + (rs_dma_wait(RS_DMA_CHANNEL_DVP, RS_TIMEOUT_DEFAULT) != RS_OK) || + (rs_dma_session_release(&rs_p6_background_owner) != RS_OK) || + (rs_i2s_flush(true, true, RS_TIMEOUT_DEFAULT) != RS_OK) || + (rs_ws2812_irq_enable(0U) != RS_OK) || !rs_p6_audio_words(wide, rx_words)) { + return false; + } + for (uint32_t index = 0U; index < (wide ? 256U : 64U); ++index) { + if (rs_p6_destination[index + 1U] != rs_p6_source[index]) { + return false; + } + } + if ((rs_p6_destination[0] != UINT32_C(0x5A5AA5A5)) || + (rs_p6_destination[257] != UINT32_C(0xA5A55A5A))) { + return false; + } + if (!wide) { + for (uint32_t index = 65U; index <= 256U; ++index) { + if (rs_p6_destination[index] != 0U) { + return false; + } + } + } + printf("R2_P6_AUDIO bits=%u frames=256 ws_frames=2 background_jobs=%lu tcd=%u elapsed_us=%lu " + "cycles=%lu instructions=%lu\n", + wide ? 24U : 16U, (unsigned long)background_jobs, wide ? 1U : 0U, + (unsigned long)(now - start), (unsigned long)cycles, (unsigned long)instructions); + return background_jobs != 0U; +} + +static bool rs_p6_audio_ws_recovery(void) { + const rs_ws2812_config_t config = { + .source_clock_hz = RS_PCLK_CLOCK_HZ, + .bit_period_ns = 1250U, + .t0h_ns = 350U, + .t1h_ns = 700U, + .reset_ns = 300000U, + .fifo_watermark = 8U, + }; + uint64_t now, began; + rs_i2s_status_t audio; + rs_ws2812_status_t ws; + for (uint32_t phase = 0U; phase < 2U; ++phase) { + if ((rs_gpio_write(27U, true) != RS_OK) || (rs_clint_get_time(&began) != RS_OK)) { + return false; + } + do { + if (rs_clint_get_time(&now) != RS_OK) { + return false; + } + } while ((now - began) < 10U); + if ((rs_i2s_flush(true, true, 16U) != RS_ETIMEOUT) || + (rs_i2s_get_status(&audio) != RS_OK)) { + return false; + } + /* A timed-out command remains owned by the controller. Do not issue + * another COMMAND while BUSY: that write is correctly rejected. */ + do { + if ((rs_i2s_get_status(&audio) != RS_OK) || (rs_clint_get_time(&now) != RS_OK) || + ((now - began) > 1000U)) { + return false; + } + } while ((audio.status & (RS_I2S_STATUS_TX_FLUSH_BUSY | RS_I2S_STATUS_RX_FLUSH_BUSY)) != + 0U); + if (rs_gpio_write(27U, false) != RS_OK) { + return false; + } + } + if ((rs_ws2812_init(&config) != RS_OK) || (rs_ws2812_push(0U, RS_TIMEOUT_DEFAULT) != RS_OK) || + (rs_ws2812_start(2U) != RS_OK) || (rs_ws2812_wait(RS_TIMEOUT_DEFAULT) != RS_EIO) || + (rs_ws2812_get_status(&ws) != RS_OK) || + ((ws.error_status & RS_WS2812_ERROR_UNDERFLOW) == 0U) || + (rs_clint_get_time(&began) != RS_OK)) { + return false; + } + do { + if ((rs_ws2812_get_status(&ws) != RS_OK) || (rs_clint_get_time(&now) != RS_OK) || + ((now - began) > 1000U)) { + return false; + } + } while (ws.busy); + if ((rs_ws2812_init(&config) != RS_OK) || (rs_ws2812_push(0U, RS_TIMEOUT_DEFAULT) != RS_OK) || + (rs_ws2812_start(65U) != RS_OK) || (rs_ws2812_abort(RS_TIMEOUT_DEFAULT) != RS_OK)) { + return false; + } + printf("R2_P6_RECOVERY audio_stops=2 ws_underflow=1 ws_abort=1\n"); + return true; +} + +static bool rs_p6_sdio_step(rs_status_t result, uint32_t step) { + rs_sdio_status_t status; + if (result == RS_OK) { + return true; + } + if (rs_sdio_status_get(RS_SDIO_INSTANCE_0, &status) == RS_OK) { + printf("R2_P6_SDIO_ERROR step=%lu result=%ld command=%lx data=%lx dma=%lx errors=%lx\n", + (unsigned long)step, (long)result, (unsigned long)status.command_status, + (unsigned long)status.data_status, (unsigned long)status.dma_status, + (unsigned long)status.error_status); + } + return false; +} + +static bool rs_p6_sdio(void) { + static _Alignas(16) rs_sdio_descriptor_t descriptor; + rs_sd_memory_info_t card; + rs_sdio_clock_t clock; + rs_sdio_response_t response; + rs_dma_config_t background = { + .kind = RS_DMA_KIND_MM_TO_MM, + .request = RS_DMA_REQUEST_SOFTWARE, + .source = (uintptr_t)rs_p6_audio_tx, + .destination = (uintptr_t)&rs_p6_audio_rx[1], + .byte_count = 1024U, + .width = RS_DMA_WIDTH_32, + .source_increment = true, + .destination_increment = true, + .burst_beats = 16U, + }; + rs_sdio_command_t command = { + .index = 17U, + .argument = 1U, + .response = RS_SDIO_RESPONSE_R1, + .crc_check = true, + .index_check = true, + }; + const rs_sdio_data_config_t pio = { + .block_size = 512U, + .block_count = 1U, + .direction = RS_SDIO_DATA_FROM_CARD, + .dma = false, + .block_mode = true, + }; + const rs_sdio_data_config_t dma_write = { + .block_size = 512U, + .block_count = 2U, + .direction = RS_SDIO_DATA_TO_CARD, + .dma = true, + .block_mode = true, + }; + uint32_t id, version, capability; + rs_status_t initialized; + printf("R2_P6_STAGE sdio_begin=1\n"); + for (uint32_t pin = 0U; pin < 6U; ++pin) { + if (!rs_p6_route(pin, false)) { + return false; + } + } + if ((rs_sdio_probe(RS_SDIO_INSTANCE_1, &id, &version, &capability) != RS_ENOTSUP) || + (rs_sdio_controller_selftest(RS_SDIO_INSTANCE_0) != RS_OK)) { + return false; + } + initialized = + rs_sd_memory_initialize(RS_SDIO_INSTANCE_0, RS_CPU_CLOCK_HZ, &card, RS_TIMEOUT_DEFAULT); + printf("R2_P6_STAGE sdio_init=%ld clock=%lu width=%u rca=%u\n", (long)initialized, + (unsigned long)card.actual_clock_hz, (unsigned)card.bus_width, (unsigned)card.rca); + if ((initialized != RS_OK) || (card.actual_clock_hz != UINT32_C(12000000)) || + !card.high_capacity || (card.rca != 1U) || (card.bus_width != RS_SDIO_BUS_WIDTH_4)) { + return false; + } + for (uint32_t index = 0U; index < 256U; ++index) { + rs_p6_source[index] = UINT32_C(0x13579BDF) ^ index; + rs_p6_destination[index + 1U] = 0U; + rs_p6_audio_tx[index] = UINT32_C(0x55AA0000) + index; + rs_p6_audio_rx[index + 1U] = ~rs_p6_audio_tx[index]; + } + rs_p6_destination[0] = UINT32_C(0x5A5AA5A5); + rs_p6_destination[257] = UINT32_C(0xA5A55A5A); + command.index = 25U; + if (!rs_p6_sdio_step(rs_dma_session_acquire(&rs_p6_background_owner, RS_DMA_CHANNEL_DVP), 1U) || + !rs_p6_sdio_step(rs_dma_session_configure(&rs_p6_background_owner, &background), 2U) || + !rs_p6_sdio_step( + rs_sdio_command_execute(RS_SDIO_INSTANCE_0, &command, &response, RS_TIMEOUT_DEFAULT), + 3U) || + !rs_p6_sdio_step(rs_sdio_descriptor_prepare(&descriptor, (uintptr_t)rs_p6_source, 1024U, + (uintptr_t)0U, true, false), + 4U) || + !rs_p6_sdio_step(rs_sdio_dma_setup(RS_SDIO_INSTANCE_0, &descriptor, 1U, 1024U), 5U) || + !rs_p6_sdio_step(rs_sdio_data_configure(RS_SDIO_INSTANCE_0, &dma_write), 6U) || + !rs_p6_sdio_step(rs_sdio_data_start(RS_SDIO_INSTANCE_0), 7U) || + !rs_p6_sdio_step(rs_dma_session_start(&rs_p6_background_owner), 8U) || + !rs_p6_sdio_step(rs_sdio_data_wait(RS_SDIO_INSTANCE_0, RS_TIMEOUT_DEFAULT), 9U) || + !rs_p6_sdio_step(rs_sdio_dma_wait(RS_SDIO_INSTANCE_0, RS_TIMEOUT_DEFAULT), 10U)) { + return false; + } + printf("R2_P6_STAGE sdio_write=1\n"); + command.index = 12U; + command.argument = 0U; + command.response = RS_SDIO_RESPONSE_R1B; + if (!rs_p6_sdio_step( + rs_sdio_command_execute(RS_SDIO_INSTANCE_0, &command, &response, RS_TIMEOUT_DEFAULT), + 11U) || + !rs_p6_sdio_step(rs_sd_memory_read_blocks(RS_SDIO_INSTANCE_0, &card, 1U, 2U, + &rs_p6_destination[1], RS_TIMEOUT_DEFAULT), + 12U) || + !rs_p6_sdio_step(rs_dma_wait(RS_DMA_CHANNEL_DVP, RS_TIMEOUT_DEFAULT), 13U) || + !rs_p6_sdio_step(rs_dma_session_release(&rs_p6_background_owner), 14U)) { + return false; + } + printf("R2_P6_STAGE sdio_read=1\n"); + for (uint32_t index = 0U; index < 256U; ++index) { + if ((rs_p6_destination[index + 1U] != rs_p6_source[index]) || + (rs_p6_audio_rx[index + 1U] != rs_p6_audio_tx[index])) { + return false; + } + } + if ((rs_p6_destination[0] != UINT32_C(0x5A5AA5A5)) || + (rs_p6_destination[257] != UINT32_C(0xA5A55A5A)) || + (rs_sdio_clock_set(RS_SDIO_INSTANCE_0, RS_CPU_CLOCK_HZ, 400000U, &clock) != RS_OK) || + (clock.actual_hz != 400000U)) { + return false; + } + /* Select one-bit card and host consistently before the PIO read. */ + command.index = 55U; + command.argument = (uint32_t)card.rca << 16U; + command.response = RS_SDIO_RESPONSE_R1; + if (rs_sdio_command_execute(RS_SDIO_INSTANCE_0, &command, &response, RS_TIMEOUT_DEFAULT) != + RS_OK) { + return false; + } + command.index = 6U; + command.argument = 0U; + if ((rs_sdio_command_execute(RS_SDIO_INSTANCE_0, &command, &response, RS_TIMEOUT_DEFAULT) != + RS_OK) || + (rs_sdio_bus_width_set(RS_SDIO_INSTANCE_0, RS_SDIO_BUS_WIDTH_1) != RS_OK)) { + return false; + } + command.index = 17U; + command.argument = 1U; + if (!rs_p6_sdio_step( + rs_sdio_command_execute(RS_SDIO_INSTANCE_0, &command, &response, RS_TIMEOUT_DEFAULT), + 15U) || + !rs_p6_sdio_step(rs_sdio_data_configure(RS_SDIO_INSTANCE_0, &pio), 16U) || + !rs_p6_sdio_step(rs_sdio_data_start(RS_SDIO_INSTANCE_0), 17U) || + !rs_p6_sdio_step( + rs_sdio_pio_read(RS_SDIO_INSTANCE_0, &rs_p6_destination[1], 512U, RS_TIMEOUT_DEFAULT), + 18U) || + !rs_p6_sdio_step(rs_sdio_data_wait(RS_SDIO_INSTANCE_0, RS_TIMEOUT_DEFAULT), 19U)) { + return false; + } + printf("R2_P6_STAGE sdio_pio=1\n"); + for (uint32_t index = 0U; index < 128U; ++index) { + if (rs_p6_destination[index + 1U] != rs_p6_source[index]) { + return false; + } + } + if ((rs_sdio_irq_clear(RS_SDIO_INSTANCE_0, RS_SDIO_IRQ_ALL) != RS_OK) || + !rs_p6_irq_arm(RS_SOC_EXT_IRQ_SDIO) || + (rs_sdio_irq_enable(RS_SDIO_INSTANCE_0, RS_SDIO_IRQ_ALL) != RS_OK) || + (rs_sdio_irq_test(RS_SDIO_INSTANCE_0, 1U) != RS_OK) || !rs_p6_irq_received() || + (rs_sdio_irq_enable(RS_SDIO_INSTANCE_0, 0U) != RS_OK) || + (rs_sdio_irq_clear(RS_SDIO_INSTANCE_0, RS_SDIO_IRQ_ALL) != RS_OK) || + (rs_sdio_timeouts_set(RS_SDIO_INSTANCE_0, 24000U, 24000U, 24000U) != RS_OK)) { + return false; + } + /* Verification card injects faults on these documented CMD13 arguments. */ + command.index = 13U; + command.argument = UINT32_C(0xBADC0DE0); + if (rs_sdio_command_execute(RS_SDIO_INSTANCE_0, &command, &response, RS_TIMEOUT_DEFAULT) != + RS_EFORMAT) { + return false; + } + command.argument = UINT32_C(0xBADC0DE1); + if (rs_sdio_command_execute(RS_SDIO_INSTANCE_0, &command, &response, RS_TIMEOUT_DEFAULT) != + RS_ETIMEOUT) { + return false; + } + command.argument = (uint32_t)card.rca << 16U; + if ((rs_sdio_error_clear(RS_SDIO_INSTANCE_0, RS_SDIO_ERROR_ALL) != RS_OK) || + (rs_sdio_command_execute(RS_SDIO_INSTANCE_0, &command, &response, RS_TIMEOUT_DEFAULT) != + RS_OK)) { + return false; + } + printf("R2_P6_SDIO dma_bytes=2048 pio_bytes=512 width1=1 width4=1 irq=1 errors=2\n"); + return true; +} + +uint8_t rs_tiny_p6_acceptance(void) { + if (!rs_p6_uart()) { + return 27U; + } + if (!rs_p6_dma_capabilities()) { + return 19U; + } + if (!rs_p6_rng()) { + return 20U; + } + if (!rs_p6_crc()) { + return 21U; + } + if (!rs_p6_crypto()) { + return 22U; + } + if (!rs_p6_audio_ws(false)) { + return 23U; + } + if (!rs_p6_audio_ws(true)) { + return 24U; + } + if (!rs_p6_audio_ws_recovery()) { + return 26U; + } + if (!rs_p6_sdio()) { + return 25U; + } + return 0U; +} diff --git a/app/apps/ci_smoke/tiny_r2_p7.c b/app/apps/ci_smoke/tiny_r2_p7.c new file mode 100644 index 00000000..edc785c2 --- /dev/null +++ b/app/apps/ci_smoke/tiny_r2_p7.c @@ -0,0 +1,171 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Short P7 firmware: execute in real main SRAM and use real SYSCTRL/APB + * crossings. The full peripheral firmware remains a separate regression. */ +static volatile uint32_t rs_p7_words[128]; +static _Alignas(64) uint32_t rs_p7_source[1024]; +static _Alignas(64) uint32_t rs_p7_destination[1024]; +static volatile uint32_t rs_p7_retained __attribute__((section(".noinit.rs_p7"))); + +static bool rs_p7_peripheral_rate(uint32_t pclk_hz) { + const rs_timer_config_t config = {.mode = RS_TIMER_MODE_FREE_RUNNING, + .direction = RS_TIMER_DIRECTION_UP}; + const volatile uint32_t *pwm_rate = + (const volatile uint32_t *)(RS_SOC_APB4_PWM_BASE + PWM_CLOCK_HZ_OFFSET); + uint32_t first, last; + uint64_t start, end; + uint64_t expected; + rs_timeout_t budget = RS_TIMEOUT_DEFAULT; + if (*pwm_rate != pclk_hz || rs_timer_configure(RS_TIMER_0, &config) != RS_OK || + rs_timer_start(RS_TIMER_0) != RS_OK || rs_clint_get_time(&start) != RS_OK || + rs_timer_get_value(RS_TIMER_0, &first) != RS_OK) { + return false; + } + do { + if (rs_clint_get_time(&end) != RS_OK || budget-- == 0U) { + return false; + } + } while (end - start < 200U); + if (rs_timer_get_value(RS_TIMER_0, &last) != RS_OK || rs_timer_stop(RS_TIMER_0) != RS_OK) { + return false; + } + expected = (end - start) * (pclk_hz / 1000000U); + /* Independent CLINT reference; allow the bounded APB/software read skew. + * A wrong /2 or /4 domain cannot fit this +/-25% interval. */ + return (uint64_t)(last - first) > expected * 3U / 4U && + (uint64_t)(last - first) < expected * 5U / 4U; +} + +static bool rs_p7_sleep(void) { + rs_dma_session_t session; + rs_dma_config_t config = {.kind = RS_DMA_KIND_MM_TO_MM, + .request = RS_DMA_REQUEST_SOFTWARE, + .source = (uintptr_t)rs_p7_source, + .destination = (uintptr_t)rs_p7_destination, + .byte_count = sizeof(rs_p7_source), + .width = RS_DMA_WIDTH_32, + .source_increment = true, + .destination_increment = true, + .burst_beats = 16U}; + uint64_t now; + uint32_t index; + __disable_irq(); + for (index = 0U; index < 1024U; index++) { + rs_p7_source[index] = index ^ UINT32_C(0x13572468); + rs_p7_destination[index] = 0U; + } + if (rs_tiny_rcu_apply_gate(1U, RS_TIMEOUT_DEFAULT) != RS_OK || + rs_dma_session_acquire(&session, RS_DMA_CHANNEL_BULK) != RS_OK || + rs_dma_session_configure(&session, &config) != RS_OK || rs_clint_get_time(&now) != RS_OK || + rs_clint_set_compare(0U, now + 3000U) != RS_OK || rs_dma_session_start(&session) != RS_OK) { + return false; + } + __RV_CSR_SET(CSR_MIE, MIE_MTIE); + __WFI(); + __RV_CSR_CLEAR(CSR_MIE, MIE_MTIE); + if (rs_clint_set_compare(0U, UINT64_MAX) != RS_OK || + rs_dma_wait(RS_DMA_CHANNEL_BULK, RS_TIMEOUT_DEFAULT) != RS_OK || + rs_dma_session_release(&session) != RS_OK || + rs_tiny_rcu_apply_gate(0U, RS_TIMEOUT_DEFAULT) != RS_OK) { + return false; + } + for (index = 0U; index < 1024U; index++) { + if (rs_p7_destination[index] != rs_p7_source[index]) { + return false; + } + } + return true; +} + +static bool rs_p7_profile(rs_tiny_rcu_profile_t profile, uint32_t sys_hz, uint32_t mem_hz, + uint32_t pclk_hz) { + rs_tiny_rcu_snapshot_t snapshot; + uint32_t index; + uint64_t before, after; + + if (rs_clint_get_time(&before) != RS_OK || + rs_tiny_rcu_request_profile(profile, RS_TIMEOUT_DEFAULT) != RS_OK || + rs_clint_get_time(&after) != RS_OK || after <= before) { + return false; + } + if (rs_tiny_rcu_get_snapshot(&snapshot) != RS_OK || snapshot.current_profile != profile || + snapshot.sys_hz != sys_hz || snapshot.mem_hz != mem_hz || snapshot.pclk_hz != pclk_hz) { + return false; + } + for (index = 0U; index < 128U; index++) { + if (rs_p7_words[index] != (UINT32_C(0xABCDEF00) ^ index)) { + return false; + } + } + return rs_p7_peripheral_rate(pclk_hz); +} + +int main(void) { + rs_tiny_rcu_snapshot_t snapshot; + uint32_t index; + uint8_t code = 0U; + + if (rs_tiny_rcu_get_snapshot(&snapshot) == RS_OK && (snapshot.reset_cause & 2U) != 0U) { + code = (rs_p7_retained == UINT32_C(0x7037CAFE) && snapshot.current_profile == 0U && + snapshot.sys_hz == 24000000U && snapshot.gate_status == 0U) + ? 0U + : 11U; + rs_sysctrl_write_test_status(code == 0U, code); + for (;;) { + } + } + + for (index = 0U; index < 128U; index++) { + rs_p7_words[index] = UINT32_C(0xABCDEF00) ^ index; + } + if (rs_tiny_rcu_get_snapshot(&snapshot) != RS_OK || snapshot.sys_hz != 24000000U || + snapshot.target_capability != UINT32_C(0x3FFF)) { + code = 1U; + } else if ((snapshot.capability & UINT32_C(0xC)) == UINT32_C(0xC)) { + if (!rs_p7_profile(RS_TINY_RCU_PROFILE_PLL192, 192000000U, 96000000U, 48000000U)) { + code = 2U; + } else if (!rs_p7_profile(RS_TINY_RCU_PROFILE_PLL240, 240000000U, 120000000U, 60000000U)) { + code = 3U; + } + } else if (!rs_p7_profile(RS_TINY_RCU_PROFILE_XIN, snapshot.xin_hz, snapshot.xin_hz, + snapshot.xin_hz > 60000000U ? snapshot.xin_hz / 2U + : snapshot.xin_hz)) { + code = 4U; + } else { + /* No-PLL compatibility has no PLL profile bit. */ + } + if (code == 0U && !rs_p7_profile(RS_TINY_RCU_PROFILE_SAFE24, 24000000U, 24000000U, 24000000U)) { + code = 5U; + } + if (code == 0U && rs_tiny_rcu_apply_gate(UINT32_C(1) << 4U, RS_TIMEOUT_DEFAULT) != RS_OK) { + code = 6U; + } + if (code == 0U && rs_tiny_rcu_apply_gate(0U, RS_TIMEOUT_DEFAULT) != RS_OK) { + code = 7U; + } + if (code == 0U && rs_tiny_rcu_apply_reset(UINT32_C(1) << 4U, RS_TIMEOUT_DEFAULT) != RS_OK) { + code = 8U; + } + if (code == 0U && !rs_p7_sleep()) { + code = 9U; + } + if (code == 0U) { + rs_p7_retained = UINT32_C(0x7037CAFE); + (void)rs_tiny_rcu_system_reset(RS_TIMEOUT_DEFAULT); + code = 10U; + } + rs_sysctrl_write_test_status(code == 0U, code); + for (;;) { + } +} diff --git a/configs/README.md b/configs/README.md index 403283bc..f6f2c4d0 100644 --- a/configs/README.md +++ b/configs/README.md @@ -78,15 +78,22 @@ for the matrix and its pending/current evidence boundary. The [2026-10-07 Tiny refreeze](../docs/ip/tiny-soc.md#ics55-default-platform-and-final-timing-gate-2026-10-07) sets the default to ICS55 with `HAVE_PLL=YES` and SAFE24 boot. -`ci/ics55-tiny.mk` implements that entrypoint with the PLL held off; standalone -PLL backend tests do not enable RCU/SYS switching. `make SOC=TINY` selects this +`ci/ics55-tiny.mk` boots with the PLL held off; R2-P7 adds explicit software +requests for functional PLL192/240 transitions, subject to separate acceptance. +`ci/ihp130-tiny-xin48.mk` and `ci/ihp130-tiny-xin96.mk` normalize their declared +external input to REF24 at boot and permit no-PLL XIN transitions. See the +[P7 runbook](../docs/ip/tiny-soc-r2-clock-reset.md) for the evidence boundary. +`TINY_P7_ACCEPTANCE=YES APP=ci_smoke` selects the distinct RCU acceptance image +and configuration digest; ordinary Tiny acceptance firmware remains the default. +`make SOC=TINY` selects this committed profile, and `make SOC=TINY PDK=IHP130` selects explicit compatibility. Do not substitute Mini's `ci/ics55.mk` for Tiny. Acceptance evidence belongs in the Tiny ledger, and physical qualification remains pending. `ci/ihp130-tiny.mk` selects `SOC=TINY`, one RV32IMC Hazard3 (A disabled), -128 KiB SRAM, AXI32/APB4, four DMA channels and the wired peripheral subset. +128 KiB SRAM and AXI32/APB4. The P6 candidate adds eight-channel DMA and the +frozen shared-peripheral integration; source presence is not phase acceptance. The profile uses 24 MHz, `HAVE_HP=NO`, `MINI_MODE=NONE`, CSR-enabled RV32IM firmware and `ld2_all_sram`. Both `bringup` and `ci_smoke` select Tiny acceptance -firmware. Icarus and Verilator use the same pin-level Flash model. Other PDKs, -wireless, external RAM and HP applications are rejected by the Tiny config gate. +firmware. Icarus and Verilator use the same pin-level Flash model. PDKs other +than IHP130/ICS55, external-RAM boot layouts and HP applications remain rejected. diff --git a/configs/ci/ihp130-tiny-xin48.mk b/configs/ci/ihp130-tiny-xin48.mk new file mode 100644 index 00000000..ce972c70 --- /dev/null +++ b/configs/ci/ihp130-tiny-xin48.mk @@ -0,0 +1,19 @@ +# Functional external-input profile; physical qualification is pending. +SOC := TINY +MINI_MODE := NONE +PDK := IHP130 +SIMU := VERILATOR +HAVE_PLL := NO +HAVE_SRAM_IF := YES +HAVE_SRAM_MACRO := YES +SRAM_SIZE_KIB := 128 +HAVE_HP := NO +HAVE_SVA := NO +EXT_CLK_HZ := 48000000 +AUD_CLK_HZ := 24000000 +CLINT_TIMEBASE_HZ := 1000000 +ISA := RV32IM +HAVE_CSR := YES +APP := bringup +LINK_TYPE := ld2_all_sram +RTL_TOP := retrosoc_tiny_tb \ No newline at end of file diff --git a/configs/ci/ihp130-tiny-xin96.mk b/configs/ci/ihp130-tiny-xin96.mk new file mode 100644 index 00000000..1e84419b --- /dev/null +++ b/configs/ci/ihp130-tiny-xin96.mk @@ -0,0 +1,19 @@ +# Functional external-input profile; physical qualification is pending. +SOC := TINY +MINI_MODE := NONE +PDK := IHP130 +SIMU := VERILATOR +HAVE_PLL := NO +HAVE_SRAM_IF := YES +HAVE_SRAM_MACRO := YES +SRAM_SIZE_KIB := 128 +HAVE_HP := NO +HAVE_SVA := NO +EXT_CLK_HZ := 96000000 +AUD_CLK_HZ := 24000000 +CLINT_TIMEBASE_HZ := 1000000 +ISA := RV32IM +HAVE_CSR := YES +APP := bringup +LINK_TYPE := ld2_all_sram +RTL_TOP := retrosoc_tiny_tb \ No newline at end of file diff --git a/crt/README.md b/crt/README.md index 8c5809eb..82fa9125 100644 --- a/crt/README.md +++ b/crt/README.md @@ -239,11 +239,16 @@ For the complete firmware, simulation, synthesis, and timing flow, see the Tiny uses the same `rs_` SDK and selects its generated memory, IRQ and capability headers through the committed profile. The generated memory-map header includes -Tiny capability metadata, so public DMA headers report four channels without +Tiny capability metadata, so public DMA headers report eight channels without requiring an application to reproduce private compiler flags. Unavailable IP drivers are omitted; Tiny SYSCTRL rejects unsupported lifecycle/PLL operations before MMIO. Startup skips external-RAM initialization and loads into SRAM. +P6 selects the existing RNG/CRC/WS2812/Crypto/I2S/SDIO drivers. Crypto uses 4/5, +audio uses 6/7 and channel 3 remains exclusively leased for a WS frame. +I2C1, SDIO1, DVP and future DMA endpoints remain unsupported on Tiny. Shared +Mini defaults are unchanged; Tiny does not acquire Mini's HP channel reservation. + `` provides `rs_watchdog_configure`, `rs_watchdog_start`, `rs_watchdog_service`, `rs_watchdog_get_status` and `rs_watchdog_clear_reset_cause`, with SDK status values and bounded command waits. These wrappers preserve the diff --git a/crt/include/retrosoc/core/status.h b/crt/include/retrosoc/core/status.h index 5cee6728..52f43034 100644 --- a/crt/include/retrosoc/core/status.h +++ b/crt/include/retrosoc/core/status.h @@ -12,6 +12,7 @@ typedef enum { RS_EIO = -5, RS_ENOTSUP = -6, RS_EFORMAT = -7, + RS_EBUSY = -8, } rs_status_t; typedef uint32_t rs_timeout_t; diff --git a/crt/include/retrosoc/hal/dma.h b/crt/include/retrosoc/hal/dma.h index bf35d7ae..9e3cc015 100644 --- a/crt/include/retrosoc/hal/dma.h +++ b/crt/include/retrosoc/hal/dma.h @@ -50,6 +50,15 @@ typedef enum { RS_DMA_CHANNEL_BULK = 3, RS_DMA_CHANNEL_CRYPTO_IN = 4, RS_DMA_CHANNEL_CRYPTO_OUT = 5, +#ifdef RS_SOC_TINY + RS_DMA_CHANNEL_DVP = 2, + RS_DMA_CHANNEL_I2S_TX = 6, + RS_DMA_CHANNEL_I2S_RX = 7, +#else + RS_DMA_CHANNEL_DVP = RS_DMA_CHANNEL_BULK, + RS_DMA_CHANNEL_I2S_TX = RS_DMA_CHANNEL_BULK, + RS_DMA_CHANNEL_I2S_RX = RS_DMA_CHANNEL_BULK, +#endif } rs_dma_channel_t; typedef struct { diff --git a/crt/include/retrosoc/hal/dma_regs.h b/crt/include/retrosoc/hal/dma_regs.h index 596c8e1d..fe70e4f1 100644 --- a/crt/include/retrosoc/hal/dma_regs.h +++ b/crt/include/retrosoc/hal/dma_regs.h @@ -77,15 +77,13 @@ #define RS_DMA_ERROR_READ UINT32_C(0x00000040) #define RS_DMA_ERROR_STREAM_LAST UINT32_C(0x00000100) -#ifdef RS_SOC_TINY -#define RS_DMA_CHANNEL_COUNT 4U -#else -#define RS_DMA_CHANNEL_COUNT 8U -#endif +#define RS_DMA_CHANNEL_COUNT 8U +#ifndef RS_SOC_TINY #define RS_DMA_CHANNEL_HP 6U #define RS_DMA_CHANNEL_RESERVED 7U -#define RS_DMA_MAX_BURST_BEATS 16U -#define RS_DMA_DATA_WIDTH_BITS 32U +#endif +#define RS_DMA_MAX_BURST_BEATS 16U +#define RS_DMA_DATA_WIDTH_BITS 32U #define RS_DMA_CHANNEL_OFFSET(channel, offset) \ (RS_DMA_CH_BASE + ((uint32_t)(channel)*RS_DMA_CH_STRIDE) + (offset)) diff --git a/crt/include/retrosoc/hal/rcu.h b/crt/include/retrosoc/hal/rcu.h new file mode 100644 index 00000000..0965c017 --- /dev/null +++ b/crt/include/retrosoc/hal/rcu.h @@ -0,0 +1,42 @@ +#ifndef RETROSOC_HAL_RCU_H +#define RETROSOC_HAL_RCU_H + +#include +#include + +#include + +typedef enum { + RS_TINY_RCU_PROFILE_SAFE24 = 0, + RS_TINY_RCU_PROFILE_XIN = 1, + RS_TINY_RCU_PROFILE_PLL192 = 2, + RS_TINY_RCU_PROFILE_PLL240 = 3, +} rs_tiny_rcu_profile_t; + +typedef struct { + uint32_t capability; + uint32_t target_capability; + uint32_t status; + uint32_t fault; + uint32_t reset_cause; + uint32_t gate_status; + uint32_t xin_hz; + uint32_t ref_hz; + uint32_t sys_hz; + uint32_t mem_hz; + uint32_t pclk_hz; + uint32_t clint_hz; + rs_tiny_rcu_profile_t current_profile; +} rs_tiny_rcu_snapshot_t; + +rs_status_t rs_tiny_rcu_get_snapshot(rs_tiny_rcu_snapshot_t *snapshot); +rs_status_t rs_tiny_rcu_profile_hz(rs_tiny_rcu_profile_t profile, uint32_t *hz); +rs_status_t rs_tiny_rcu_request_profile(rs_tiny_rcu_profile_t profile, rs_timeout_t timeout); +rs_status_t rs_tiny_rcu_apply_gate(uint32_t target_mask, rs_timeout_t timeout); +rs_status_t rs_tiny_rcu_apply_reset(uint32_t target_mask, rs_timeout_t timeout); +rs_status_t rs_tiny_rcu_clear_fault(uint32_t mask); +rs_status_t rs_tiny_rcu_clear_reset_cause(uint32_t mask); +rs_status_t rs_tiny_rcu_system_reset(rs_timeout_t timeout); +rs_status_t rs_tiny_rcu_set_clkout(uint32_t source, uint16_t half_period); + +#endif diff --git a/crt/include/retrosoc/hal/rcu_regs.h b/crt/include/retrosoc/hal/rcu_regs.h new file mode 100644 index 00000000..f2dfc126 --- /dev/null +++ b/crt/include/retrosoc/hal/rcu_regs.h @@ -0,0 +1,46 @@ +#ifndef RETROSOC_HAL_RCU_REGS_H +#define RETROSOC_HAL_RCU_REGS_H + +#include + +#define RS_TINY_RCU_ID_OFFSET UINT32_C(0x100) +#define RS_TINY_RCU_VERSION_OFFSET UINT32_C(0x104) +#define RS_TINY_RCU_CAPABILITY_OFFSET UINT32_C(0x108) +#define RS_TINY_RCU_CLOCK_REQUEST_OFFSET UINT32_C(0x10C) +#define RS_TINY_RCU_CLOCK_CURRENT_OFFSET UINT32_C(0x110) +#define RS_TINY_RCU_COMMAND_OFFSET UINT32_C(0x114) +#define RS_TINY_RCU_STATUS_OFFSET UINT32_C(0x118) +#define RS_TINY_RCU_TIMEOUT_OFFSET UINT32_C(0x11C) +#define RS_TINY_RCU_GATE_REQUEST_OFFSET UINT32_C(0x120) +#define RS_TINY_RCU_GATE_STATUS_OFFSET UINT32_C(0x124) +#define RS_TINY_RCU_RESET_REQUEST_OFFSET UINT32_C(0x128) +#define RS_TINY_RCU_RESET_DONE_OFFSET UINT32_C(0x12C) +#define RS_TINY_RCU_RESET_CAUSE_OFFSET UINT32_C(0x130) +#define RS_TINY_RCU_FAULT_OFFSET UINT32_C(0x134) +#define RS_TINY_RCU_IRQ_STATE_OFFSET UINT32_C(0x138) +#define RS_TINY_RCU_IRQ_ENABLE_OFFSET UINT32_C(0x13C) +#define RS_TINY_RCU_XIN_HZ_OFFSET UINT32_C(0x140) +#define RS_TINY_RCU_REF_HZ_OFFSET UINT32_C(0x144) +#define RS_TINY_RCU_SYS_HZ_OFFSET UINT32_C(0x148) +#define RS_TINY_RCU_MEM_HZ_OFFSET UINT32_C(0x14C) +#define RS_TINY_RCU_PCLK_HZ_OFFSET UINT32_C(0x150) +#define RS_TINY_RCU_CLINT_HZ_OFFSET UINT32_C(0x154) +#define RS_TINY_RCU_TARGET_CAP_OFFSET UINT32_C(0x158) +#define RS_TINY_RCU_CLKOUT_OFFSET UINT32_C(0x15C) + +#define RS_TINY_RCU_COMMAND_CLOCK_APPLY UINT32_C(1U << 0) +#define RS_TINY_RCU_COMMAND_GATE_APPLY UINT32_C(1U << 1) +#define RS_TINY_RCU_COMMAND_RESET_APPLY UINT32_C(1U << 2) +#define RS_TINY_RCU_COMMAND_SYSTEM_RESET UINT32_C(1U << 3) +#define RS_TINY_RCU_STATUS_BUSY UINT32_C(1U << 0) +#define RS_TINY_RCU_STATUS_SAFE_SOURCE UINT32_C(1U << 1) +#define RS_TINY_RCU_STATUS_PLL_LOCKED UINT32_C(1U << 2) +#define RS_TINY_RCU_STATUS_PLL_SELECTED UINT32_C(1U << 3) +#define RS_TINY_RCU_STATUS_FAULT UINT32_C(1U << 4) +#define RS_TINY_RCU_CAP_PLL_PRESENT UINT32_C(1U << 8) +#define RS_TINY_RCU_CAP_TARGET_MASK UINT32_C(0x0003FFFF) +#define RS_TINY_RCU_FAULT_MASK UINT32_C(0x0000001F) +#define RS_TINY_RCU_RESET_CAUSE_MASK UINT32_C(0x0000003F) +#define RS_TINY_RCU_CSR_MSLEEP 0xBF0 + +#endif diff --git a/crt/src/hal/dma.c b/crt/src/hal/dma.c index bd9c2a81..9b3832da 100644 --- a/crt/src/hal/dma.c +++ b/crt/src/hal/dma.c @@ -5,16 +5,25 @@ static const rs_dma_session_t *rs_dma_owners[RS_DMA_CHANNEL_COUNT]; +#ifdef RS_DMA_TEST_MMIO +uint32_t rs_dma_test_irq_save(void); +void rs_dma_test_irq_restore(uint32_t saved); +#endif + static uint32_t rs_dma_lock(void) { uint32_t saved = 0U; -#if defined(__riscv) && defined(CSR_ENABLE) +#ifdef RS_DMA_TEST_MMIO + saved = rs_dma_test_irq_save(); +#elif defined(__riscv) && defined(CSR_ENABLE) __asm__ volatile("csrrci %0, mstatus, 8" : "=r"(saved)::"memory"); #endif return saved; } static void rs_dma_unlock(uint32_t saved) { -#if defined(__riscv) && defined(CSR_ENABLE) +#ifdef RS_DMA_TEST_MMIO + rs_dma_test_irq_restore(saved); +#elif defined(__riscv) && defined(CSR_ENABLE) if ((saved & UINT32_C(8)) != 0U) { __asm__ volatile("csrsi mstatus, 8" ::: "memory"); } @@ -185,6 +194,7 @@ rs_status_t rs_dma_submit_tcd_chain(uint32_t channel, rs_dma_tcd_t *first, uint3 rs_status_t result; uint32_t control; uint32_t burst; + uint32_t saved; result = rs_dma_tcd_validate(channel, current); if (result != RS_OK) { @@ -208,14 +218,20 @@ rs_status_t rs_dma_submit_tcd_chain(uint32_t channel, rs_dma_tcd_t *first, uint3 config.crc_expected = current->crc_expected; config.priority = (uint8_t)((control >> RS_DMA_TCD_PRIORITY_SHIFT) & UINT32_C(0x3)); config.burst_beats = (uint8_t)burst; + /* Publish configuration, TCD and START before an ISR can lease the + * idle channel. Nested helpers preserve this outer interrupt mask. + * Restore interrupts before the bounded completion wait. */ + saved = rs_dma_lock(); result = rs_dma_configure(channel, &config); if (result != RS_OK) { + rs_dma_unlock(saved); return result; } RS_DMA_CH_REG(channel, RS_DMA_CH_REG_TCD_HEAD) = (uint32_t)(uintptr_t)current; RS_DMA_CH_REG(channel, RS_DMA_CH_REG_TCD_COUNT) = UINT32_C(1); rs_dma_fence(); result = rs_dma_start(channel); + rs_dma_unlock(saved); if (result != RS_OK) { return result; } @@ -411,9 +427,9 @@ void ip_dma_test(void) { .lowbound = 32U, }) != RS_OK) || (rs_i2s_enable(true, false) != RS_OK) || - (rs_dma_configure(RS_DMA_CHANNEL_BULK, &tx_config) != RS_OK) || + (rs_dma_configure(RS_DMA_CHANNEL_I2S_TX, &tx_config) != RS_OK) || (rs_dma_start(RS_DMA_CHANNEL_BULK) != RS_OK) || - (rs_dma_wait(RS_DMA_CHANNEL_BULK, RS_TIMEOUT_DEFAULT) != RS_OK)) { + (rs_dma_wait(RS_DMA_CHANNEL_I2S_TX, RS_TIMEOUT_DEFAULT) != RS_OK)) { printf("dma transmit failed\n"); return; } @@ -432,9 +448,9 @@ void ip_dma_test(void) { .lowbound = 32U, }) != RS_OK) || (rs_i2s_enable(false, true) != RS_OK) || - (rs_dma_configure(RS_DMA_CHANNEL_BULK, &rx_config) != RS_OK) || - (rs_dma_start(RS_DMA_CHANNEL_BULK) != RS_OK) || - (rs_dma_wait(RS_DMA_CHANNEL_BULK, RS_TIMEOUT_DEFAULT) != RS_OK)) { + (rs_dma_configure(RS_DMA_CHANNEL_I2S_RX, &rx_config) != RS_OK) || + (rs_dma_start(RS_DMA_CHANNEL_I2S_RX) != RS_OK) || + (rs_dma_wait(RS_DMA_CHANNEL_I2S_RX, RS_TIMEOUT_DEFAULT) != RS_OK)) { printf("dma receive failed\n"); (void)rs_i2s_disable(); return; diff --git a/crt/src/hal/dma_math.c b/crt/src/hal/dma_math.c index 99c35ca6..4ebd003a 100644 --- a/crt/src/hal/dma_math.c +++ b/crt/src/hal/dma_math.c @@ -19,7 +19,8 @@ static bool rs_dma_address_valid(uintptr_t address, uint32_t byte_count, bool in static bool rs_dma_request_valid(const rs_dma_config_t *config) { #ifdef RS_SOC_TINY - if (config->kind != RS_DMA_KIND_MM_TO_MM) { + if (((uint32_t)config->request >= 16U) || + ((UINT32_C(0x31FF) & (UINT32_C(1) << (uint32_t)config->request)) == 0U)) { return false; } #endif @@ -97,7 +98,7 @@ rs_status_t rs_dma_tcd_validate(uint32_t channel, const rs_dma_tcd_t *tcd) { request = (tcd->control >> RS_DMA_TCD_REQUEST_SHIFT) & UINT32_C(0xF); burst = (tcd->control >> RS_DMA_TCD_BURST_SHIFT) & UINT32_C(0x1F); #ifdef RS_SOC_TINY - if (kind != (uint32_t)RS_DMA_KIND_MM_TO_MM) { + if ((UINT32_C(0x31FF) & (UINT32_C(1) << request)) == 0U) { return RS_ENOTSUP; } #endif diff --git a/crt/src/hal/i2c.c b/crt/src/hal/i2c.c index 48c59260..dea6ccbc 100644 --- a/crt/src/hal/i2c.c +++ b/crt/src/hal/i2c.c @@ -49,8 +49,12 @@ #define RS_I2C_TIMEOUT_MAX_CYCLES UINT32_C(0xFFFFFF) static uintptr_t rs_i2c_base(rs_i2c_bus_t bus) { +#ifdef RS_SOC_TINY + return (bus == RS_I2C_BUS_0) ? (uintptr_t)RS_SOC_APB4_I2C0_BASE : (uintptr_t)0U; +#else return (bus == RS_I2C_BUS_0) ? (uintptr_t)RS_SOC_APB4_I2C0_BASE : (uintptr_t)RS_SOC_APB4_I2C1_BASE; +#endif } static bool rs_i2c_bus_valid(rs_i2c_bus_t bus) { @@ -92,8 +96,13 @@ static rs_status_t rs_i2c_gpio_configure(rs_i2c_bus_t bus) { .filter_enable = false, .interrupt_enable = false, }; +#ifdef RS_SOC_TINY + const uint32_t scl_pin = UINT32_C(10); + const uint32_t sda_pin = UINT32_C(11); +#else const uint32_t scl_pin = (bus == RS_I2C_BUS_0) ? UINT32_C(7) : UINT32_C(3); const uint32_t sda_pin = (bus == RS_I2C_BUS_0) ? UINT32_C(8) : UINT32_C(4); +#endif rs_status_t status = rs_gpio_configure(scl_pin, &pin_config); if (status == RS_OK) { @@ -228,6 +237,11 @@ static rs_status_t rs_i2c_wait_done(rs_i2c_bus_t bus, rs_timeout_t timeout) { rs_status_t rs_i2c_configure(rs_i2c_bus_t bus, const rs_i2c_config_t *config, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif rs_i2c_timing_t timing; uint32_t stretch_timeout; uint32_t bus_idle_timeout; @@ -287,6 +301,11 @@ rs_status_t rs_i2c_configure(rs_i2c_bus_t bus, const rs_i2c_config_t *config, } rs_status_t rs_i2c_init(rs_i2c_bus_t bus, uint32_t source_clock_hz, uint32_t bus_hz) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif const rs_i2c_config_t config = { .source_clock_hz = source_clock_hz, .bus_hz = bus_hz, @@ -303,6 +322,11 @@ rs_status_t rs_i2c_init(rs_i2c_bus_t bus, uint32_t source_clock_hz, uint32_t bus rs_status_t rs_i2c_transfer(rs_i2c_bus_t bus, const rs_i2c_transfer_t *transfer, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif if (transfer == NULL) { return RS_EINVAL; } @@ -338,6 +362,11 @@ static rs_status_t rs_i2c_register_prefix(const rs_i2c_register_access_t *access rs_status_t rs_i2c_register_write(rs_i2c_bus_t bus, const rs_i2c_register_access_t *access, const uint8_t *data, size_t length, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif uint8_t prefix[2]; size_t prefix_length; rs_status_t status; @@ -355,6 +384,11 @@ rs_status_t rs_i2c_register_write(rs_i2c_bus_t bus, const rs_i2c_register_access rs_status_t rs_i2c_register_read(rs_i2c_bus_t bus, const rs_i2c_register_access_t *access, uint8_t *data, size_t length, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif uint8_t prefix[2]; size_t prefix_length; rs_status_t status; @@ -371,6 +405,11 @@ rs_status_t rs_i2c_register_read(rs_i2c_bus_t bus, const rs_i2c_register_access_ } rs_status_t rs_i2c_abort(rs_i2c_bus_t bus, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif if (!rs_i2c_bus_valid(bus)) { return RS_EINVAL; } @@ -382,6 +421,11 @@ rs_status_t rs_i2c_abort(rs_i2c_bus_t bus, rs_timeout_t timeout) { } rs_status_t rs_i2c_recover(rs_i2c_bus_t bus, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif rs_status_t status; if (!rs_i2c_bus_valid(bus)) { @@ -408,6 +452,11 @@ rs_status_t rs_i2c_recover(rs_i2c_bus_t bus, rs_timeout_t timeout) { } rs_status_t rs_i2c_get_status(rs_i2c_bus_t bus, rs_i2c_status_t *status) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif uint32_t levels; if (!rs_i2c_bus_valid(bus) || (status == NULL)) { @@ -423,6 +472,11 @@ rs_status_t rs_i2c_get_status(rs_i2c_bus_t bus, rs_i2c_status_t *status) { } rs_status_t rs_i2c_irq_enable(rs_i2c_bus_t bus, uint32_t mask) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif if (!rs_i2c_bus_valid(bus) || ((mask & ~RS_I2C_INTR_ALL) != 0U)) { return RS_EINVAL; } @@ -431,6 +485,11 @@ rs_status_t rs_i2c_irq_enable(rs_i2c_bus_t bus, uint32_t mask) { } rs_status_t rs_i2c_irq_ack(rs_i2c_bus_t bus, uint32_t mask) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif if (!rs_i2c_bus_valid(bus) || (mask == 0U) || ((mask & ~RS_I2C_INTR_ALL) != 0U)) { return RS_EINVAL; } @@ -439,6 +498,11 @@ rs_status_t rs_i2c_irq_ack(rs_i2c_bus_t bus, uint32_t mask) { } rs_status_t rs_i2c_irq_test(rs_i2c_bus_t bus, uint32_t mask) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif if (!rs_i2c_bus_valid(bus) || (mask == 0U) || ((mask & ~RS_I2C_INTR_ALL) != 0U)) { return RS_EINVAL; } @@ -449,6 +513,11 @@ rs_status_t rs_i2c_irq_test(rs_i2c_bus_t bus, uint32_t mask) { rs_status_t rs_i2c_write_dma(rs_i2c_bus_t bus, uint16_t address, bool ten_bit_address, const uint8_t *data, size_t length, rs_i2c_dma_workspace_t *workspace, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif rs_status_t status; size_t index; const uint32_t channel = (bus == RS_I2C_BUS_0) ? RS_DMA_CHANNEL_I2C0 : RS_DMA_CHANNEL_I2C1; @@ -494,6 +563,11 @@ rs_status_t rs_i2c_write_dma(rs_i2c_bus_t bus, uint16_t address, bool ten_bit_ad rs_status_t rs_i2c_read_dma(rs_i2c_bus_t bus, uint16_t address, bool ten_bit_address, uint8_t *data, size_t length, rs_i2c_dma_workspace_t *workspace, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (bus == RS_I2C_BUS_1) { + return RS_ENOTSUP; + } +#endif rs_status_t status; size_t index; const uint32_t channel = (bus == RS_I2C_BUS_0) ? RS_DMA_CHANNEL_I2C0 : RS_DMA_CHANNEL_I2C1; diff --git a/crt/src/hal/rcu.c b/crt/src/hal/rcu.c new file mode 100644 index 00000000..4dddf99f --- /dev/null +++ b/crt/src/hal/rcu.c @@ -0,0 +1,261 @@ +#include + +#include +#include +#include +#if defined(__riscv) && defined(RS_SOC_TINY) +#include +#endif + +static volatile uint32_t *rs_tiny_rcu_register(uint32_t offset) { +#if defined(RS_TINY_RCU_TEST_MMIO) + extern volatile uint32_t rs_tiny_rcu_test_mmio[1024]; + return &rs_tiny_rcu_test_mmio[offset / 4U]; +#else + return (volatile uint32_t *)(RS_SOC_APB4_SYSCTRL_BASE + (uintptr_t)offset); +#endif +} + +static rs_status_t rs_tiny_rcu_fault_status(void) { + const uint32_t fault = *rs_tiny_rcu_register(RS_TINY_RCU_FAULT_OFFSET); + if ((fault & UINT32_C(0x09)) != 0U) { + return RS_EIO; + } + return (fault != 0U) ? RS_ETIMEOUT : RS_OK; +} + +static rs_status_t rs_tiny_rcu_prepare(rs_timeout_t timeout) { + if (timeout == 0U) { + return RS_EINVAL; + } + if ((*rs_tiny_rcu_register(RS_TINY_RCU_STATUS_OFFSET) & RS_TINY_RCU_STATUS_BUSY) != 0U) { + return RS_EBUSY; + } + /* The caller owns clearing sticky faults. Do not start an operation whose + * result could be confused with a previous failure. */ + return rs_tiny_rcu_fault_status(); +} + +static rs_status_t rs_tiny_rcu_wait(rs_timeout_t timeout, uint32_t *status) { + uint32_t value = 0U; + + while (timeout-- != 0U) { + value = *rs_tiny_rcu_register(RS_TINY_RCU_STATUS_OFFSET); + if ((value & RS_TINY_RCU_STATUS_BUSY) == 0U) { + if (status != NULL) { + *status = value; + } + return RS_OK; + } + } + return RS_ETIMEOUT; +} + +rs_status_t rs_tiny_rcu_get_snapshot(rs_tiny_rcu_snapshot_t *snapshot) { + uint32_t initial_profile; + if (snapshot == NULL) { + return RS_EINVAL; + } + if ((*rs_tiny_rcu_register(RS_TINY_RCU_STATUS_OFFSET) & RS_TINY_RCU_STATUS_BUSY) != 0U) { + return RS_EBUSY; + } + initial_profile = *rs_tiny_rcu_register(RS_TINY_RCU_CLOCK_CURRENT_OFFSET); + snapshot->capability = *rs_tiny_rcu_register(RS_TINY_RCU_CAPABILITY_OFFSET); + snapshot->target_capability = *rs_tiny_rcu_register(RS_TINY_RCU_TARGET_CAP_OFFSET); + snapshot->status = *rs_tiny_rcu_register(RS_TINY_RCU_STATUS_OFFSET); + snapshot->fault = *rs_tiny_rcu_register(RS_TINY_RCU_FAULT_OFFSET); + snapshot->reset_cause = *rs_tiny_rcu_register(RS_TINY_RCU_RESET_CAUSE_OFFSET); + snapshot->gate_status = *rs_tiny_rcu_register(RS_TINY_RCU_GATE_STATUS_OFFSET); + snapshot->xin_hz = *rs_tiny_rcu_register(RS_TINY_RCU_XIN_HZ_OFFSET); + snapshot->ref_hz = *rs_tiny_rcu_register(RS_TINY_RCU_REF_HZ_OFFSET); + snapshot->sys_hz = *rs_tiny_rcu_register(RS_TINY_RCU_SYS_HZ_OFFSET); + snapshot->mem_hz = *rs_tiny_rcu_register(RS_TINY_RCU_MEM_HZ_OFFSET); + snapshot->pclk_hz = *rs_tiny_rcu_register(RS_TINY_RCU_PCLK_HZ_OFFSET); + snapshot->clint_hz = *rs_tiny_rcu_register(RS_TINY_RCU_CLINT_HZ_OFFSET); + snapshot->current_profile = + (rs_tiny_rcu_profile_t)(*rs_tiny_rcu_register(RS_TINY_RCU_CLOCK_CURRENT_OFFSET) & + UINT32_C(3)); + if (((*rs_tiny_rcu_register(RS_TINY_RCU_STATUS_OFFSET) & RS_TINY_RCU_STATUS_BUSY) != 0U) || + ((uint32_t)snapshot->current_profile != initial_profile)) { + return RS_EBUSY; + } + return RS_OK; +} + +rs_status_t rs_tiny_rcu_profile_hz(rs_tiny_rcu_profile_t profile, uint32_t *hz) { + if (hz == NULL) { + return RS_EINVAL; + } + switch (profile) { + case RS_TINY_RCU_PROFILE_SAFE24: + *hz = UINT32_C(24000000); + break; + case RS_TINY_RCU_PROFILE_XIN: + *hz = *rs_tiny_rcu_register(RS_TINY_RCU_XIN_HZ_OFFSET); + break; + case RS_TINY_RCU_PROFILE_PLL192: + *hz = UINT32_C(192000000); + break; + case RS_TINY_RCU_PROFILE_PLL240: + *hz = UINT32_C(240000000); + break; + default: + return RS_EINVAL; + } + return RS_OK; +} + +rs_status_t rs_tiny_rcu_request_profile(rs_tiny_rcu_profile_t profile, rs_timeout_t timeout) { + uint32_t capability; + uint32_t status; + uint32_t hz; + rs_status_t result; + + if (timeout == 0U || rs_tiny_rcu_profile_hz(profile, &hz) != RS_OK) { + return RS_EINVAL; + } + (void)hz; + capability = *rs_tiny_rcu_register(RS_TINY_RCU_CAPABILITY_OFFSET); + if ((capability & (UINT32_C(1) << (uint32_t)profile)) == 0U) { + return RS_ENOTSUP; + } + result = rs_tiny_rcu_prepare(timeout); + if (result != RS_OK) { + return result; + } + *rs_tiny_rcu_register(RS_TINY_RCU_CLOCK_REQUEST_OFFSET) = (uint32_t)profile; + *rs_tiny_rcu_register(RS_TINY_RCU_COMMAND_OFFSET) = RS_TINY_RCU_COMMAND_CLOCK_APPLY; + if (rs_tiny_rcu_wait(timeout, &status) != RS_OK) { + return RS_ETIMEOUT; + } + if ((status & RS_TINY_RCU_STATUS_FAULT) != 0U) { + return rs_tiny_rcu_fault_status(); + } + if (*rs_tiny_rcu_register(RS_TINY_RCU_CLOCK_CURRENT_OFFSET) != (uint32_t)profile) { + return RS_EIO; + } + return RS_OK; +} + +rs_status_t rs_tiny_rcu_apply_gate(uint32_t target_mask, rs_timeout_t timeout) { + uint32_t status; + rs_status_t result; + + if (timeout == 0U || (target_mask & ~RS_TINY_RCU_CAP_TARGET_MASK) != 0U) { + return RS_EINVAL; + } + if ((target_mask & ~(*rs_tiny_rcu_register(RS_TINY_RCU_TARGET_CAP_OFFSET))) != 0U) { + return RS_ENOTSUP; + } + result = rs_tiny_rcu_prepare(timeout); + if (result != RS_OK) { + return result; + } + *rs_tiny_rcu_register(RS_TINY_RCU_GATE_REQUEST_OFFSET) = target_mask; + *rs_tiny_rcu_register(RS_TINY_RCU_COMMAND_OFFSET) = RS_TINY_RCU_COMMAND_GATE_APPLY; + if (rs_tiny_rcu_wait(timeout, &status) != RS_OK) { + return RS_ETIMEOUT; + } + result = rs_tiny_rcu_fault_status(); + if (result != RS_OK) { + return result; + } +#if defined(__riscv) && defined(RS_SOC_TINY) + /* Hazard3's existing sleep permission complements the RCU leaf gate. + * Keep power-down clear: the fabric, SRAM and DMA remain available. */ + if ((target_mask & 1U) != 0U) { + __RV_CSR_CLEAR(RS_TINY_RCU_CSR_MSLEEP, 2U); + __RV_CSR_SET(RS_TINY_RCU_CSR_MSLEEP, 1U); + } else { + __RV_CSR_CLEAR(RS_TINY_RCU_CSR_MSLEEP, 1U); + } +#endif + return RS_OK; +} + +rs_status_t rs_tiny_rcu_apply_reset(uint32_t target_mask, rs_timeout_t timeout) { + uint32_t status; + rs_status_t result; + + if (timeout == 0U || target_mask == 0U || (target_mask & ~RS_TINY_RCU_CAP_TARGET_MASK) != 0U || + (target_mask & 1U) != 0U) { + return RS_EINVAL; + } + if ((target_mask & ~(*rs_tiny_rcu_register(RS_TINY_RCU_TARGET_CAP_OFFSET))) != 0U) { + return RS_ENOTSUP; + } + result = rs_tiny_rcu_prepare(timeout); + if (result != RS_OK) { + return result; + } + if ((*rs_tiny_rcu_register(RS_TINY_RCU_GATE_STATUS_OFFSET) & target_mask) != 0U) { + return RS_EBUSY; + } + *rs_tiny_rcu_register(RS_TINY_RCU_RESET_REQUEST_OFFSET) = target_mask; + *rs_tiny_rcu_register(RS_TINY_RCU_COMMAND_OFFSET) = RS_TINY_RCU_COMMAND_RESET_APPLY; + if (rs_tiny_rcu_wait(timeout, &status) != RS_OK) { + return RS_ETIMEOUT; + } + return rs_tiny_rcu_fault_status(); +} + +rs_status_t rs_tiny_rcu_clear_fault(uint32_t mask) { + if ((mask & ~RS_TINY_RCU_FAULT_MASK) != 0U) { + return RS_EINVAL; + } + *rs_tiny_rcu_register(RS_TINY_RCU_FAULT_OFFSET) = mask; + return RS_OK; +} + +rs_status_t rs_tiny_rcu_clear_reset_cause(uint32_t mask) { + if ((mask & ~RS_TINY_RCU_RESET_CAUSE_MASK) != 0U) { + return RS_EINVAL; + } + *rs_tiny_rcu_register(RS_TINY_RCU_RESET_CAUSE_OFFSET) = mask; + return RS_OK; +} + +rs_status_t rs_tiny_rcu_system_reset(rs_timeout_t timeout) { + uint32_t status; + if (timeout == 0U) { + return RS_EINVAL; + } + status = *rs_tiny_rcu_register(RS_TINY_RCU_STATUS_OFFSET); + if ((status & RS_TINY_RCU_STATUS_BUSY) != 0U) { + return RS_EBUSY; + } + *rs_tiny_rcu_register(RS_TINY_RCU_COMMAND_OFFSET) = RS_TINY_RCU_COMMAND_SYSTEM_RESET; + /* Success restarts execution at the reset vector. Returning means failure. */ + while (timeout-- != 0U) { + status = *rs_tiny_rcu_register(RS_TINY_RCU_STATUS_OFFSET); + if ((status & RS_TINY_RCU_STATUS_FAULT) != 0U) { + return RS_EIO; + } + } + return RS_ETIMEOUT; +} + +rs_status_t rs_tiny_rcu_set_clkout(uint32_t source, uint16_t half_period) { + rs_tiny_rcu_snapshot_t snapshot; + rs_status_t result; + uint32_t hz; + if (source > 4U || (source != 0U && half_period == 0U)) { + return RS_EINVAL; + } + result = rs_tiny_rcu_get_snapshot(&snapshot); + if (result != RS_OK) { + return result; + } + hz = source == 2U ? snapshot.sys_hz + : source == 3U ? snapshot.mem_hz + : source == 4U ? snapshot.pclk_hz + : snapshot.ref_hz; + if (source != 0U && (uint64_t)hz > (UINT64_C(48000000) * (uint64_t)half_period)) { + return RS_EINVAL; + } + if (source != 0U && (*rs_tiny_rcu_register(RS_TINY_RCU_CLKOUT_OFFSET) & 7U) != 0U) { + return RS_EBUSY; + } + *rs_tiny_rcu_register(RS_TINY_RCU_CLKOUT_OFFSET) = source | ((uint32_t)half_period << 8U); + return (*rs_tiny_rcu_register(RS_TINY_RCU_FAULT_OFFSET) != 0U) ? RS_EIO : RS_OK; +} diff --git a/crt/src/hal/sdio.c b/crt/src/hal/sdio.c index bf303255..82b7098e 100644 --- a/crt/src/hal/sdio.c +++ b/crt/src/hal/sdio.c @@ -53,6 +53,12 @@ RS_SD_R1_CARD_ECC_FAILED | RS_SD_R1_CC_ERROR | RS_SD_R1_ERROR | RS_SD_R1_CSD_OVERWRITE | \ RS_SD_R1_WP_ERASE_SKIP) +/* Preserve protocol ceilings while supporting slower product input clocks. */ +static uint32_t rs_sdio_transfer_clock(uint32_t source_hz, uint32_t ceiling_hz) { + const uint32_t maximum_hz = source_hz / UINT32_C(2); + return (maximum_hz < ceiling_hz) ? maximum_hz : ceiling_hz; +} + static bool rs_sdio_instance_valid(rs_sdio_instance_t instance) { return (instance == RS_SDIO_INSTANCE_0) || (instance == RS_SDIO_INSTANCE_1); } @@ -61,8 +67,10 @@ static uintptr_t rs_sdio_base(rs_sdio_instance_t instance) { switch (instance) { case RS_SDIO_INSTANCE_0: return (uintptr_t)RS_SOC_APB4_SDIO0_BASE; +#ifndef RS_SOC_TINY case RS_SDIO_INSTANCE_1: return (uintptr_t)RS_SOC_APB4_SDIO1_BASE; +#endif default: return (uintptr_t)0U; } @@ -257,6 +265,11 @@ static rs_status_t rs_sdio_send_app_command(rs_sdio_instance_t instance, uint16_ rs_status_t rs_sdio_probe(rs_sdio_instance_t instance, uint32_t *ip_id, uint32_t *version, uint32_t *capability) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif if (!rs_sdio_instance_valid(instance) || (ip_id == NULL) || (version == NULL) || (capability == NULL) || (rs_sdio_read(instance, RS_SDIO_REG_IP_ID, ip_id) != RS_OK) || (rs_sdio_read(instance, RS_SDIO_REG_IP_VERSION, version) != RS_OK) || @@ -270,6 +283,11 @@ rs_status_t rs_sdio_probe(rs_sdio_instance_t instance, uint32_t *ip_id, uint32_t } rs_status_t rs_sdio_reset(rs_sdio_instance_t instance, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif volatile uint32_t *status_reg; if (!rs_sdio_instance_valid(instance)) { @@ -294,6 +312,11 @@ rs_status_t rs_sdio_reset(rs_sdio_instance_t instance, rs_timeout_t timeout) { rs_status_t rs_sdio_clock_set(rs_sdio_instance_t instance, uint32_t source_clock_hz, uint32_t target_clock_hz, rs_sdio_clock_t *clock) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif rs_sdio_clock_t calculated; uint32_t value; uint32_t enabled; @@ -318,6 +341,11 @@ rs_status_t rs_sdio_clock_set(rs_sdio_instance_t instance, uint32_t source_clock } rs_status_t rs_sdio_bus_width_set(rs_sdio_instance_t instance, rs_sdio_bus_width_t width) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif uint32_t value; if (!rs_sdio_instance_valid(instance) || rs_sdio_busy(instance)) { @@ -338,6 +366,11 @@ rs_status_t rs_sdio_bus_width_set(rs_sdio_instance_t instance, rs_sdio_bus_width rs_status_t rs_sdio_timeouts_set(rs_sdio_instance_t instance, uint32_t command_timeout, uint32_t data_timeout, uint32_t busy_timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif if (!rs_sdio_instance_valid(instance) || rs_sdio_busy(instance) || (command_timeout == 0U) || (data_timeout == 0U) || (busy_timeout == 0U)) { return RS_EINVAL; @@ -351,6 +384,11 @@ rs_status_t rs_sdio_timeouts_set(rs_sdio_instance_t instance, uint32_t command_t } rs_status_t rs_sdio_enable(rs_sdio_instance_t instance, bool enable) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif uint32_t value; if (!rs_sdio_instance_valid(instance) || @@ -366,6 +404,11 @@ rs_status_t rs_sdio_enable(rs_sdio_instance_t instance, bool enable) { } rs_status_t rs_sdio_configure(rs_sdio_instance_t instance, const rs_sdio_config_t *config) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif rs_sdio_clock_t clock; rs_status_t status; @@ -393,6 +436,11 @@ rs_status_t rs_sdio_configure(rs_sdio_instance_t instance, const rs_sdio_config_ } rs_status_t rs_sdio_status_get(rs_sdio_instance_t instance, rs_sdio_status_t *status) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif uint32_t present; if (!rs_sdio_instance_valid(instance) || (status == NULL)) { @@ -421,6 +469,11 @@ rs_status_t rs_sdio_status_get(rs_sdio_instance_t instance, rs_sdio_status_t *st } rs_status_t rs_sdio_command_start(rs_sdio_instance_t instance, const rs_sdio_command_t *command) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif uint32_t cfg; if (!rs_sdio_instance_valid(instance) || (command == NULL) || (command->index > 63U) || @@ -449,6 +502,11 @@ rs_status_t rs_sdio_command_start(rs_sdio_instance_t instance, const rs_sdio_com } rs_status_t rs_sdio_response_get(rs_sdio_instance_t instance, rs_sdio_response_t *response) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif if (!rs_sdio_instance_valid(instance) || (response == NULL) || (rs_sdio_read(instance, RS_SDIO_REG_RESP0, &response->words[0]) != RS_OK) || (rs_sdio_read(instance, RS_SDIO_REG_RESP1, &response->words[1]) != RS_OK) || @@ -462,6 +520,11 @@ rs_status_t rs_sdio_response_get(rs_sdio_instance_t instance, rs_sdio_response_t rs_status_t rs_sdio_command_wait(rs_sdio_instance_t instance, rs_sdio_response_t *response, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif rs_status_t status; if (!rs_sdio_instance_valid(instance) || (response == NULL)) { @@ -485,6 +548,11 @@ rs_status_t rs_sdio_command_wait(rs_sdio_instance_t instance, rs_sdio_response_t rs_status_t rs_sdio_command_execute(rs_sdio_instance_t instance, const rs_sdio_command_t *command, rs_sdio_response_t *response, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif rs_status_t status; status = rs_sdio_command_start(instance, command); @@ -500,6 +568,11 @@ rs_status_t rs_sdio_command_execute(rs_sdio_instance_t instance, const rs_sdio_c rs_status_t rs_sdio_data_configure(rs_sdio_instance_t instance, const rs_sdio_data_config_t *config) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif uint32_t value; if (!rs_sdio_instance_valid(instance) || (config == NULL) || (config->block_size == 0U) || @@ -532,6 +605,11 @@ rs_status_t rs_sdio_data_configure(rs_sdio_instance_t instance, } rs_status_t rs_sdio_data_start(rs_sdio_instance_t instance) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif if (!rs_sdio_instance_valid(instance) || rs_sdio_busy(instance) || (rs_sdio_irq_clear(instance, RS_SDIO_IRQ_DATA_DONE | RS_SDIO_IRQ_DATA_ERROR) != RS_OK) || (rs_sdio_error_clear(instance, RS_SDIO_ERROR_DATA_CRC | RS_SDIO_ERROR_DATA_TIMEOUT | @@ -582,6 +660,11 @@ static rs_status_t rs_sdio_pio_store(rs_sdio_instance_t instance, const uint8_t rs_status_t rs_sdio_pio_write(rs_sdio_instance_t instance, const void *buffer, size_t byte_count, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif const uint8_t *bytes = (const uint8_t *)buffer; rs_status_t status; size_t offset = 0U; @@ -612,6 +695,11 @@ rs_status_t rs_sdio_pio_write(rs_sdio_instance_t instance, const void *buffer, s rs_status_t rs_sdio_pio_read(rs_sdio_instance_t instance, void *buffer, size_t byte_count, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif uint8_t *bytes = (uint8_t *)buffer; size_t offset = 0U; @@ -645,6 +733,11 @@ rs_status_t rs_sdio_pio_read(rs_sdio_instance_t instance, void *buffer, size_t b } rs_status_t rs_sdio_data_wait(rs_sdio_instance_t instance, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif rs_status_t status; if (!rs_sdio_instance_valid(instance)) { @@ -664,6 +757,11 @@ rs_status_t rs_sdio_data_wait(rs_sdio_instance_t instance, rs_timeout_t timeout) rs_status_t rs_sdio_dma_setup(rs_sdio_instance_t instance, rs_sdio_descriptor_t *descriptors, uint16_t count, uint32_t total_bytes) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif uintptr_t address; if (!rs_sdio_instance_valid(instance) || (total_bytes == 0U) || @@ -686,6 +784,11 @@ rs_status_t rs_sdio_dma_setup(rs_sdio_instance_t instance, rs_sdio_descriptor_t } rs_status_t rs_sdio_dma_start(rs_sdio_instance_t instance) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif /* * DATA_START is the coordinated DMA/data-engine launch. Keep this * compatibility entry point as an alias so software cannot start a DMA @@ -695,6 +798,11 @@ rs_status_t rs_sdio_dma_start(rs_sdio_instance_t instance) { } rs_status_t rs_sdio_dma_abort(rs_sdio_instance_t instance) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif if (!rs_sdio_instance_valid(instance)) { return RS_EINVAL; } @@ -702,6 +810,11 @@ rs_status_t rs_sdio_dma_abort(rs_sdio_instance_t instance) { } rs_status_t rs_sdio_dma_status_get(rs_sdio_instance_t instance, rs_sdio_dma_status_t *status) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif uint32_t dma_error; if (!rs_sdio_instance_valid(instance) || (status == NULL) || @@ -720,6 +833,11 @@ rs_status_t rs_sdio_dma_status_get(rs_sdio_instance_t instance, rs_sdio_dma_stat } rs_status_t rs_sdio_dma_wait(rs_sdio_instance_t instance, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif rs_status_t status; if (!rs_sdio_instance_valid(instance)) { @@ -738,6 +856,11 @@ rs_status_t rs_sdio_dma_wait(rs_sdio_instance_t instance, rs_timeout_t timeout) } rs_status_t rs_sdio_irq_enable(rs_sdio_instance_t instance, uint32_t mask) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif if (!rs_sdio_instance_valid(instance) || ((mask & ~RS_SDIO_IRQ_ALL) != 0U)) { return RS_EINVAL; } @@ -745,6 +868,11 @@ rs_status_t rs_sdio_irq_enable(rs_sdio_instance_t instance, uint32_t mask) { } rs_status_t rs_sdio_irq_pending(rs_sdio_instance_t instance, uint32_t *mask) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif if (!rs_sdio_instance_valid(instance) || (mask == NULL)) { return RS_EINVAL; } @@ -752,6 +880,11 @@ rs_status_t rs_sdio_irq_pending(rs_sdio_instance_t instance, uint32_t *mask) { } rs_status_t rs_sdio_irq_clear(rs_sdio_instance_t instance, uint32_t mask) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif if (!rs_sdio_instance_valid(instance) || ((mask & ~RS_SDIO_IRQ_ALL) != 0U)) { return RS_EINVAL; } @@ -759,6 +892,11 @@ rs_status_t rs_sdio_irq_clear(rs_sdio_instance_t instance, uint32_t mask) { } rs_status_t rs_sdio_irq_test(rs_sdio_instance_t instance, uint32_t mask) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif if (!rs_sdio_instance_valid(instance) || ((mask & ~RS_SDIO_IRQ_ALL) != 0U)) { return RS_EINVAL; } @@ -766,6 +904,11 @@ rs_status_t rs_sdio_irq_test(rs_sdio_instance_t instance, uint32_t mask) { } rs_status_t rs_sdio_error_clear(rs_sdio_instance_t instance, uint32_t mask) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif if (!rs_sdio_instance_valid(instance) || ((mask & ~RS_SDIO_ERROR_ALL) != 0U)) { return RS_EINVAL; } @@ -773,6 +916,11 @@ rs_status_t rs_sdio_error_clear(rs_sdio_instance_t instance, uint32_t mask) { } rs_status_t rs_sdio_controller_selftest(rs_sdio_instance_t instance) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif rs_sdio_status_t status; uint32_t ip_id; uint32_t version; @@ -916,7 +1064,9 @@ static rs_status_t rs_sd_memory_try_high_speed(rs_sdio_instance_t instance, return RS_EIO; } if (rs_sdio_clock_set(instance, source_clock_hz, - (source_clock_hz >= 100000000U) ? 50000000U : 36000000U, + rs_sdio_transfer_clock(source_clock_hz, (source_clock_hz >= 100000000U) + ? 50000000U + : 36000000U), &clock) == RS_OK) { info->actual_clock_hz = clock.actual_hz; info->high_speed = true; @@ -928,6 +1078,11 @@ static rs_status_t rs_sd_memory_try_high_speed(rs_sdio_instance_t instance, rs_status_t rs_sd_memory_initialize(rs_sdio_instance_t instance, uint32_t source_clock_hz, rs_sd_memory_info_t *info, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif rs_sdio_response_t response; rs_sdio_clock_t clock; uint32_t payload; @@ -1008,7 +1163,9 @@ rs_status_t rs_sd_memory_initialize(rs_sdio_instance_t instance, uint32_t source if (status != RS_OK) { return status; } - if ((rs_sdio_response_field(&response, 31U, 16U, &payload) != RS_OK) || + /* RESP retains the complete 48-bit frame: the R6 payload is [39:8], + * so its upper sixteen RCA bits are [39:24], above the status/CRC. */ + if ((rs_sdio_response_field(&response, 39U, 24U, &payload) != RS_OK) || (payload > UINT16_MAX)) { return RS_EFORMAT; } @@ -1046,7 +1203,8 @@ rs_status_t rs_sd_memory_initialize(rs_sdio_instance_t instance, uint32_t source info->bus_width_fallback = true; info->bus_width = RS_SDIO_BUS_WIDTH_1; } - status = rs_sdio_clock_set(instance, source_clock_hz, 36000000U, &clock); + status = rs_sdio_clock_set(instance, source_clock_hz, + rs_sdio_transfer_clock(source_clock_hz, 36000000U), &clock); if (status != RS_OK) { return status; } @@ -1150,12 +1308,22 @@ static rs_status_t rs_sd_memory_transfer(rs_sdio_instance_t instance, rs_status_t rs_sd_memory_read_blocks(rs_sdio_instance_t instance, const rs_sd_memory_info_t *info, uint32_t block, uint32_t count, void *buffer, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif return rs_sd_memory_transfer(instance, info, block, count, buffer, false, timeout); } rs_status_t rs_sd_memory_write_blocks(rs_sdio_instance_t instance, const rs_sd_memory_info_t *info, uint32_t block, uint32_t count, const void *buffer, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif return rs_sd_memory_transfer(instance, info, block, count, (void *)buffer, true, timeout); } @@ -1175,6 +1343,11 @@ static rs_status_t rs_sdio_function_command(rs_sdio_instance_t instance, uint8_t rs_status_t rs_sdio_function_cmd52_read(rs_sdio_instance_t instance, uint8_t function, uint32_t address, uint8_t *data, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif rs_sdio_cmd52_t command = { .function = function, .address = address, @@ -1203,6 +1376,11 @@ rs_status_t rs_sdio_function_cmd52_read(rs_sdio_instance_t instance, uint8_t fun rs_status_t rs_sdio_function_cmd52_write(rs_sdio_instance_t instance, uint8_t function, uint32_t address, uint8_t data, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif const rs_sdio_cmd52_t command = { .function = function, .address = address, @@ -1223,6 +1401,11 @@ rs_status_t rs_sdio_function_cmd52_write(rs_sdio_instance_t instance, uint8_t fu rs_status_t rs_sdio_function_initialize(rs_sdio_instance_t instance, uint32_t source_clock_hz, rs_sdio_function_info_t *info, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif rs_sdio_response_t response; rs_sdio_clock_t clock; uint32_t payload; @@ -1345,8 +1528,10 @@ rs_status_t rs_sdio_function_initialize(rs_sdio_instance_t instance, uint32_t so } else { info->high_speed_fallback = true; } - status = rs_sdio_clock_set(instance, source_clock_hz, - high_speed_enabled ? 24000000U : 25000000U, &clock); + status = rs_sdio_clock_set( + instance, source_clock_hz, + rs_sdio_transfer_clock(source_clock_hz, high_speed_enabled ? 24000000U : 25000000U), + &clock); if (status != RS_OK) { return status; } @@ -1356,6 +1541,11 @@ rs_status_t rs_sdio_function_initialize(rs_sdio_instance_t instance, uint32_t so rs_status_t rs_sdio_function_enable(rs_sdio_instance_t instance, rs_sdio_function_info_t *info, uint8_t function, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif uint8_t enable; uint8_t ready; uint32_t attempts; @@ -1394,6 +1584,11 @@ rs_status_t rs_sdio_function_cmd53_transfer(rs_sdio_instance_t instance, uint8_t uint32_t address, void *buffer, size_t byte_count, bool write, bool block_mode, bool fixed_address, uint16_t block_size, rs_timeout_t timeout) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif rs_sdio_cmd53_t command; rs_sdio_data_config_t data_config; rs_sdio_response_t response; @@ -1482,6 +1677,11 @@ rs_status_t rs_sdio_function_cmd53_transfer(rs_sdio_instance_t instance, uint8_t } rs_status_t rs_sdio_function_irq_enable(rs_sdio_instance_t instance, bool enable) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif uint32_t mask; if (!rs_sdio_instance_valid(instance) || @@ -1501,6 +1701,11 @@ rs_status_t rs_sdio_function_irq_enable(rs_sdio_instance_t instance, bool enable } rs_status_t rs_sdio_function_irq_pending(rs_sdio_instance_t instance, bool *pending) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif uint32_t mask; if ((pending == NULL) || (rs_sdio_irq_pending(instance, &mask) != RS_OK)) { @@ -1511,5 +1716,10 @@ rs_status_t rs_sdio_function_irq_pending(rs_sdio_instance_t instance, bool *pend } rs_status_t rs_sdio_function_irq_ack(rs_sdio_instance_t instance) { +#ifdef RS_SOC_TINY + if (instance == RS_SDIO_INSTANCE_1) { + return RS_ENOTSUP; + } +#endif return rs_sdio_irq_clear(instance, RS_SDIO_IRQ_DAT1); } diff --git a/crt/src/hal/sdio_math.c b/crt/src/hal/sdio_math.c index 997c63d8..37bf7263 100644 --- a/crt/src/hal/sdio_math.c +++ b/crt/src/hal/sdio_math.c @@ -136,7 +136,9 @@ rs_status_t rs_sd_memory_parse_csd(const rs_sdio_response_t *response, rs_sd_mem return RS_EINVAL; } block_length = UINT32_C(1) << read_bl_len; - capacity_bytes = ((uint64_t)c_size + UINT64_C(1)) * (UINT64_C(1) << (c_size_mult + 2U)) * + /* C_SIZE_MULT is a three-bit CSD field: the shift is at most nine. + * Keep the factor RV32-native; the enclosing product remains 64-bit. */ + capacity_bytes = ((uint64_t)c_size + UINT64_C(1)) * (UINT32_C(1) << (c_size_mult + 2U)) * (uint64_t)block_length; capacity_blocks = capacity_bytes / (uint64_t)RS_SD_MEMORY_BLOCK_SIZE; info->card_type = RS_SD_MEMORY_SDSC; diff --git a/crt/src/hal/uart.c b/crt/src/hal/uart.c index 88f92081..16035267 100644 --- a/crt/src/hal/uart.c +++ b/crt/src/hal/uart.c @@ -84,6 +84,12 @@ static rs_status_t rs_uart_flow_gpio_configure(const rs_uart_config_t *config) { }; rs_status_t status = RS_OK; +#ifdef RS_SOC_TINY + status = rs_gpio_configure(8U, &pin_config); + if (status == RS_OK) { + status = rs_gpio_configure(9U, &pin_config); + } +#endif if (config->auto_cts_enable) { status = rs_gpio_configure(RS_UART_CTS_GPIO, &pin_config); } @@ -106,6 +112,11 @@ rs_status_t rs_uart_configure(const rs_uart_config_t *config, rs_timeout_t timeo return RS_EINVAL; } +#ifdef RS_SOC_TINY + if (config->auto_cts_enable || config->auto_rts_enable) { + return RS_ENOTSUP; + } +#endif RS_UART_CTRL = 0U; while (((RS_UART_STATUS & (RS_UART_STATUS_TX_BUSY | RS_UART_STATUS_RX_BUSY)) != 0U) && (timeout != 0U)) { diff --git a/crt/src/hal/ws2812.c b/crt/src/hal/ws2812.c index 47c5cf94..6fd811a5 100644 --- a/crt/src/hal/ws2812.c +++ b/crt/src/hal/ws2812.c @@ -419,7 +419,11 @@ rs_status_t rs_ws2812_dma_service(uint64_t now) { return rs_ws2812_dma_finish(); } if ((transmitter_flags & RS_WS2812_STATUS_BUSY) == 0U) { - return rs_ws2812_dma_fail(RS_EIO); + /* Completion may occur between the interrupt and status reads, and + * sticky DONE can lag the core's idle transition. Observe terminal + * events on the next bounded service call; never refill an idle core. + * Errors, aborts and the deadline remain checked on every call. */ + return RS_OK; } if (rs_ws2812_transfer.remaining == 0U) { rs_ws2812_write_register(RS_WS2812_INTR_STATE_OFFSET, RS_WS2812_INTR_FIFO_LOW); diff --git a/dependencies/dependencies.lock.json b/dependencies/dependencies.lock.json index 29b7957f..37b9ebc3 100644 --- a/dependencies/dependencies.lock.json +++ b/dependencies/dependencies.lock.json @@ -158,7 +158,7 @@ "cluster_common": {"url": "https://github.com/retroSoC/common.git", "revision": "964a54d70cb78e394ba9a25c05f2aa2b02941fb7", "destination": "rtl/managed/clusterip/common", "license": "NOASSERTION"}, "cluster_crc": {"url": "https://github.com/retroSoC/crc.git", "revision": "8c1a5664c4d23d005c1afad432372a13ca967cc8", "destination": "rtl/managed/clusterip/crc", "license": "MulanPSL-2.0"}, "cluster_ps2": {"url": "https://github.com/retroSoC/ps2.git", "revision": "2097a0d41b07573ff0453d65ba9a1670b3df7b9c", "destination": "rtl/managed/clusterip/ps2", "license": "MulanPSL-2.0"}, - "cluster_pwm": {"url": "https://github.com/retroSoC/pwm.git", "revision": "79ae32f725e848a1e7f6134847b3fb032ed64006", "destination": "rtl/managed/clusterip/pwm", "license": "MulanPSL-2.0"}, + "cluster_pwm": {"url": "https://github.com/retroSoC/pwm.git", "revision": "38efaf5e20f084a00148920087c60cadab61839b", "destination": "rtl/managed/clusterip/pwm", "license": "MulanPSL-2.0"}, "cluster_rng": {"url": "https://github.com/retroSoC/rng.git", "revision": "36f3e0774d8f3c4b597d3db8a96ca3992532b61e", "destination": "rtl/managed/clusterip/rng", "license": "MulanPSL-2.0"}, "cluster_rtc": {"url": "https://github.com/retroSoC/rtc.git", "revision": "32548c5cbcdf39e09d0880fb611f9dd47940ea88", "destination": "rtl/managed/clusterip/rtc", "license": "NOASSERTION"}, "cluster_wdg": {"url": "https://github.com/retroSoC/wdg.git", "revision": "f5d39fa246054d678d34195824d943367dfcce44", "destination": "rtl/managed/clusterip/wdg", "license": "MulanPSL-2.0"} diff --git a/docs/engineering.md b/docs/engineering.md index 9c7bb55c..41a3ed59 100644 --- a/docs/engineering.md +++ b/docs/engineering.md @@ -3,8 +3,10 @@ ## Supported Profiles Tiny's [2026-10-07 specification default](ip/tiny-soc.md#ics55-default-platform-and-final-timing-gate-2026-10-07) -is ICS55/PLL with SAFE24 boot. The executable default keeps the PLL parked off; -PLL-driven SYS and physical qualification remain later work. The table +is ICS55/PLL with SAFE24 boot. The PLL starts parked off; the +[R2-P7 implementation](ip/tiny-soc-r2-clock-reset.md) adds explicit software +transitions with separate functional evidence. Physical qualification remains +pending. The table describes executable profiles, not qualified operating rates. For Tiny and its SPI/PIO-lite/PPALite extensions only, pre-final post-synthesis timing closure is observational. Retain STA attempts/failures and all required diff --git a/docs/ip/i2s.md b/docs/ip/i2s.md index cf3dbbcc..25d3758d 100644 --- a/docs/ip/i2s.md +++ b/docs/ip/i2s.md @@ -105,6 +105,15 @@ change. in `[23:0]`. `TKEEP` and `TSTRB` are `4'hF`. I2S RX does not assert `TLAST`. DMA asserts `TLAST` on the final programmed TX word. +The P6 integration regression uses alternating sample least-significant bits +in both 16- and 24-bit pin-level loopback. It exposed a receiver defect that +combined the preceding sample's last bit with the next sample. The corrected +receiver appends the bit sampled at the LRCK transition to the preceding +sample's shifted bits and registers completion after that value is available. +This preserves the host register/stream ABI and does not add slave mode. +The register frontend uses PWRITE for transfer direction. In particular, a +write with zero byte strobes cannot be reinterpreted as a side-effecting RX read. + ## HAL Sequence Mini applications should use `rs_i2s_configure()`, `rs_i2s_enable()`, and either diff --git a/docs/ip/sdio.md b/docs/ip/sdio.md index 0365806b..208b0d36 100644 --- a/docs/ip/sdio.md +++ b/docs/ip/sdio.md @@ -180,3 +180,47 @@ The standalone IP does not allocate memory, perform cache maintenance, implement an IOMMU/firewall, or provide a hosted/runtime C API. The SoC integration only supplies the two native AXI/APB/IRQ/pad paths; the SDK driver is maintained by a separate workstream. + +## Tiny SAFE24 integration + +Tiny R2-P6 connects instance 0 to external AXI owner 2, its independent APB +branch and IRQ10, and GPIO0..5. Instance 1 remains absent and its public SDK +operations return `RS_ENOTSUP` before MMIO. These bindings do not replace the +Mini mappings above. The 24 MHz input permits at most 12 MHz SDCLK; enumeration +still starts at 400 kHz and the SDK caps later protocol requests at input/2. + +R6 is retained as the complete 48-bit frame in RESP. RCA therefore occupies +bits 39:24, above the payload status and CRC/end bits. The shared initialization +helper uses this field rather than bits 31:16; no register or public API changes. + +The command sender enables its first output bit at its scheduled launch. A +command admitted on an already running SDCLK rising edge must not expose an +extra start bit. Directed pad sampling covers repeated command admission at +both half-cycle phases in Icarus and Verilator. The card model collects the +complete 48-bit frame after its start bit, including the pulled-high end bit +after host output release; host output-enable is not the frame length oracle. + +The transmit wait indication also covers the last bit of a word when its +successor is unavailable. The core pauses SDCLK before the next word's launch, +while retaining the current driven bit and draining the high half-cycle. This +prevents an idle bit from becoming the next word's MSB under memory backpressure. +A 1024-byte four-lane DMA case inserts 24 system cycles between read beats and +checks every card byte, CRC status and descriptor retirement through real +three-state lines. Inactive lanes stay pulled high in the card's token/tail checks. + +Card-to-memory DMA retains descriptor ownership across every AXI burst, rather +than publishing DONE after the first 64 bytes. Receive backpressure stops the +next SDCLK launch as soon as a complete word is held; a registered stop loses +the next card bit. The roundtrip fixture delays descriptor fetch as well as +payload supply, checks all 1024 received bytes and successful final writeback. +Its card emits all sixteen CRC bits in order; earlier manual-model tests did +not reject the skipped CRC bit. These fixes preserve the native one-read/one-write +capacity, descriptor layout, register ABI and interrupt/error lifecycle. + +The existing verification card keeps its manually scheduled standalone mode. +Tiny selects its optional SDHC enumeration responses with 1024 512-byte blocks +and default-speed-only capability. Its board fixture checks the observed wire +command, direction, width and length, then calls the existing data-stimulus API +at DATA_START. All payload and response bits cross the real pads. This checks +the repository controller/SDK contract; it does not qualify arbitrary card +response timing, SD protocol compliance, a physical card or an electrical rate. diff --git a/docs/ip/tiny-soc-r2-clock-reset.md b/docs/ip/tiny-soc-r2-clock-reset.md new file mode 100644 index 00000000..6fb820a5 --- /dev/null +++ b/docs/ip/tiny-soc-r2-clock-reset.md @@ -0,0 +1,385 @@ +# Tiny R2-P7 clock/reset implementation and evidence + +This phase starts at `43a3c8d453b6d41df3536719ebc6d1f4521bc7cd`, against frozen +specification blob `7e19de49d49efaafacccfa85671e314d53dd15a4`. Implementation +is authorized; phase acceptance still requires review of the completed gates. +No P8 implementation, commit, push, or physical frequency qualification follows. + +## Product and ownership + +`configs/ci/ics55-tiny.mk` remains the default: TINY, ICS55, PLL present, +SAFE24 boot. The single RCU-owned PLL starts off. Software may request the +functional PLL192/PLL240 profiles after draining clients. These are digital +simulation capabilities, not characterized silicon operating points. +`configs/ci/ihp130-tiny.mk` remains the explicit no-PLL 24 MHz compatibility +profile. `ihp130-tiny-xin48.mk` and `ihp130-tiny-xin96.mk` add declared external +input variants, both normalizing the boot/control reference to 24 MHz. + +CPU, all 32 main SRAM macros, bank frontends, fabric and SDIO share active SYS. +Only the CPU leaf stops for WFI. XPI data/control use MEM. DMA, shared serial +and security IP and the six private Crypto macros use PCLK. Crypto capacity +and erasure policy are unchanged. AON owns RCU, CLINT and RTC/WDG functional +timekeeping; AUDIO is optional at boot. The historical ASIC wrapper and frozen +QFN64/pad contract are retained under the existing P6 qualification boundary. + +Tiny owns the clock backend, admission holders, APB/AXI crossings, peripheral +lifecycle controller, CPU gate, CLKOUT and SYSCTRL ABI. Common clock mux, +divider, reset barrier, synchronizers and CDC primitives are reused without +editing managed sources. New optional shared-IP lifecycle outputs preserve +Mini defaults; the debug wrapper's separate system reset is opt-in. Managed +PWM/CRC lifecycle mirrors observe successful writes at their existing ABI. +The already approved PWM lock remains +`38efaf5e20f084a00148920087c60cadab61839b`. + +RCU occupies `SYSCTRL+0x100..0x15c`, IRQ31. Handwritten SVH/C definitions +have a 24-register parity test. `rcu.h` supplies bounded Tiny-specific profile, +snapshot, gate, local/system reset, cause/fault and CLKOUT operations. Invalid, +unsupported, busy, timeout and hardware-error results are distinct. No new +MISRA Required-rule deviation is introduced. Future targets 14–17 remain +absent; no SPI, PIO-lite, DVP, PPALite or PSRAM rollout is included. + +## Lifecycle and failure boundaries + +Clock requests block new admissions after command acceptance, drain accepted +CPU/local/AXI/APB traffic, park on REF24, configure conservative divisors, +qualify the source, then commit rate snapshots before unblocking. Stalled +addresses already accepted into admission holders retain VALID/payload. +Unaccepted requests may remain pending and resume after switching. An +insufficient transition budget fails before changing the committed profile. +PLL lock timeout publishes the complete SAFE24 fallback. PLL stuck-low/high +requires a system/CDC reset before forced mux recovery; XIN loss needs source +restoration and external reset. + +Local resets hold for five actual leaf edges and release through five-edge +synchronizers and bus barriers. I2S additionally requires actual AUDIO-side +reset readiness; missing AUDIO cannot manufacture RESET_DONE. Gate changes +wait for idle; CPU-only WFI permission does not pause unrelated DMA. Current +non-CPU lifecycle commands conservatively drain all central-DMA work before +changing the selected targets. This can reject a request while an unrelated +DMA stream remains active; it does not stop that stream by gating its clock. +Warm reset retains cause/fault/debug state, aborts interrupted debug commands, +restores SAFE24, resets functional RTC/WDG state and preserves main SRAM data. + +The 2026-10-14 P7 review correction keeps the XPI fabric target and AXI bridge +source endpoint on SYS reset while the XPI/MEM endpoint uses its MEM leaf reset. +The later response-conservation correction uses source-owned response holders: +already presented B/R responses remain stable and retire once, while missing +read beats receive DECERR and aborted writes drain W before B. Admission allows +one outstanding request in each direction. Payload source reset uses the +five-edge link barrier; the response holders reset only with the source. +An unfinished warm clear holds isolation until destination reset so that late +responses cannot enter another transaction. Production DMA/XPI clear inputs +remain tied low. A failed drain does not trigger an invented autonomous AXI +timeout or peripheral reset. The focused tests now cover these protocol +boundaries in both simulators; earlier 36-case results did not establish them. + +Source-only reset has a separate production precondition. The DMA source +may reset locally only after its engine and accepted admission/fabric work +drain. The controller blocks new admissions, waits at least 16 REF24 cycles +for the synchronized observations, and leaves the source running on drain +timeout. XPI has no independent SYS-source reset: the common system reset +also asserts its MEM endpoint reset. The standalone AXI bridge does not +cancel a live remote slave's work on an arbitrary source-only reset and must +not be reused with that assumption. + +The focused `tiny_p7_source_reset_tb` extracts the production controller, +admission, bridge, synchronizers and reset bindings into its fixture and uses +the real DMA AXI master. It checks stalled addresses, partial writes, late +responses, held read responses, timeout, recovery and common reset at all five +declared domain ratios. A scoreboard independent of local reset detects the +deliberately broken-drain negative case. Unrelated clients and the completion +mailbox are idle; the target is a behavioral slave. This supplies executable +integration-contract coverage, not a general arbitrary-reset proof or physical +qualification. The new evidence is under `06-00.../meta/tiny-r2-p7-source-reset/` +as detailed in the verification ledger; production RTL and backend inputs are +unchanged by this follow-up. + +The follow-up validation commands are: + +```sh +source .cache/retrosoc/development/tiny-r2-p1/activate.sh +p7_reset_evidence=build/ics55-tiny-2026-10-14-06-00-ff4b5cef6eb5/meta/tiny-r2-p7-source-reset +PYTHONDONTWRITEBYTECODE=1 python3 scripts/run_flow.py --tool pytest \ + --log "$p7_reset_evidence/directed.log" \ + --result "$p7_reset_evidence/result-directed.json" -- \ + python3 -m pytest -q tests/test_tiny_r2_p7.py \ + --basetemp="$p7_reset_evidence/directed-tests" +make --silent CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 \ + rtl-format-check rtl-style-check-all check-clock-reset-domains +ruff check . +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55 --dry-run +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 --dry-run +git diff --check +``` + +These paths identify the retained execution, not an instruction to overwrite +it: use a fresh evidence directory for any retry. `source.json` binds the new +tests and `verification-update.json` compares them to the original `06-00` +snapshot. Only the two verification files differ; all 1728 other technical +inputs and tool identities match. No firmware, full-SoC, synthesis, netlist or +STA flow is repeated for this verification-only correction. The earlier +full-suite Pytest and full-Icarus limitations below remain explicit. + +The first post-fix physical smoke campaign below is historical. Each PDK has +its own profile/configuration digest. These results precede the final response +holders; current source evidence is recorded in the verification ledger. + +| PDK | Variant | Synthesis | Netlist boot | STA | +| --- | --- | --- | --- | --- | +| ICS55 | `build/ics55-tiny-2026-10-14-01-20-ff4b5cef6eb5` | PASS, 2122.685 s | PASS, 1656.024 s, `SIM_TEST_PASS Tiny netlist boot` | PASS observation; WNS max/min `-144.40/-0.31 ns`, TNS max/min `-4859636.50/-45.04` | +| IHP130 | `build/ihp130-tiny-2026-10-14-01-20-8232fa11e7f3` | PASS, 1925.337 s | PASS, 586.861 s, `SIM_TEST_PASS Tiny netlist boot` | PASS observation; WNS max/min `-257.81/-0.47 ns`, TNS max/min `-9807803.00/-100.68` | + +The current frozen-source `06-00` campaign supersedes the historical table for +post-fix qualification: + +| PDK | Variant | Synthesis | Netlist boot | STA | +| --- | --- | --- | --- | --- | +| ICS55 | `build/ics55-tiny-2026-10-14-06-00-ff4b5cef6eb5` | PASS, 2108.388 s | PASS, 1655.980 s, `SIM_TEST_PASS Tiny netlist boot` | PASS observation; WNS max/min `-152.03/-0.31 ns`, TNS max/min `-5622052.50/-44.14` | +| IHP130 | `build/ihp130-tiny-2026-10-14-06-00-8232fa11e7f3` | PASS, 1934.515 s | PASS, 578.869 s, `SIM_TEST_PASS Tiny netlist boot` | PASS observation; WNS max/min `-253.43/-0.49 ns`, TNS max/min `-10035324.00/-153.37` | + +The STA markers explicitly identify SAFE24/observational analysis; neither +variant qualifies an active PLL, routed implementation, PVT corner or product +operating point. The `04-00` source candidate was later rejected by the +response-conservation tests. The `06-00` source-bound post-fix campaign is the +current qualification run and all requested synthesis, netlist and STA gates +pass. Its measured results are recorded in the ledger. Full firmware Icarus +remains deferred to the final Tiny phase. + +Source/divisor/enable for CLKOUT are committed together, cross to the selected +source, and finish the current output high pulse before disabling. GPIO28 ALT0 +and GPIO23 ALT1 consume the same generator. Illegal/live changes return +PSLVERR and leave the previous configuration unchanged. + +## Validation sequence and commands + +Activate the locked environment before these commands: + +```sh +source .cache/retrosoc/development/tiny-r2-p1/activate.sh +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55 --dry-run +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 --dry-run +python3 -m pytest -q tests/test_tiny_r2_p7.py tests/test_tiny.py tests/test_tiny_r2_p4.py tests/test_tiny_r2_p5.py +make sw-format-check sw-policy-check sw-host-test +make rtl-format-check rtl-style-check-all rtl-readiness-check check-clock-reset-domains +ruff check . +python3 scripts/dependency_lock.py --lock dependencies/dependencies.lock.json +python3 -m pytest -q +git diff --check +``` + +The direct long-flow commands use the selected regression's targets with +larger explicit wall-time budgets. The identical Tiny nightly matrix does not +need a second execution. Every row below runs once per PDK, with +`CONFIG=configs/ci/-tiny.mk SOC=TINY PDK= APP=ci_smoke HAVE_SVA=YES`: + +| Purpose | BUILD_TIMESTAMP | Remaining Make arguments | +| --- | --- | --- | +| P7 Verilator | `2026-10-13-17-05` | `TINY_P7_ACCEPTANCE=YES SIMU=VERILATOR SOC_SIM_TIME=3600 TINY_SIM_TIMEOUT=6000000 TINY_SIM_ARGS=+p7_monitor sim` | +| P7 Icarus | `2026-10-13-17-06` | `TINY_P7_ACCEPTANCE=YES SIMU=IVERILOG TINY_SIM_TIMEOUT=6000000 TINY_SIM_ARGS=+p7_monitor 'TINY_RUN=timeout --foreground 14400s $(VVP) $(SIM_BUILD_ROOT)/simv' sim` | +| Physical macro synthesis | `2026-10-13-17-15` | `HAVE_SVA=NO SYNTH=YOSYS YOSYS_TIMEOUT=10800 synth` | +| Full firmware/JTAG, P7 watchdog-cause adaptation, Verilator | `2026-10-13-18-25` | `SIMU=VERILATOR SOC_SIM_TIME=7200 TINY_SIM_TIMEOUT=30000000 TINY_SIM_ARGS=+tiny_jtag_smoke sim` | +| Full firmware/JTAG, P7 watchdog-cause adaptation, Icarus | `2026-10-13-18-26` | `SIMU=IVERILOG TINY_SIM_TIMEOUT=30000000 TINY_SIM_ARGS=+tiny_jtag_smoke 'TINY_RUN=timeout --foreground 36000s $(VVP) $(SIM_BUILD_ROOT)/simv' sim` | +| Netlist boot, after synthesis | `2026-10-13-17-15` | `HAVE_SVA=NO SIMU=IVERILOG netsim-boot` | +| Observational core STA, after synthesis | `2026-10-13-17-15` | `HAVE_SVA=NO 'STA_BUILD_ROOT=$(VARIANT_ROOT)/sta/opensta-p7-v3' STA=OPENSTA sta` | + +Warning/metric collection uses each corresponding variant. New EDA signatures +remain observations; no warning baseline or metric policy is edited. Compiler +warning policy and Tiny functional verdicts remain required. + +After correcting the external-input POR reference, the actual-SoC P7 image is +also run with `CONFIG=configs/ci/ihp130-tiny-xin48.mk` and +`CONFIG=configs/ci/ihp130-tiny-xin96.mk`, `SOC=TINY PDK=IHP130 APP=ci_smoke +TINY_P7_ACCEPTANCE=YES HAVE_SVA=YES TINY_SIM_ARGS=+p7_monitor +TINY_SIM_TIMEOUT=24000000`. Verilator uses timestamp `2026-10-13-18-40`, +`SIMU=VERILATOR SOC_SIM_TIME=7200 sim`; final Icarus uses `2026-10-13-20-05`, +`SIMU=IVERILOG 'TINY_RUN=timeout --foreground 21600s $(VVP) $(SIM_BUILD_ROOT)/simv' sim`. +The larger cycle budget counts raw XIN edges, not SYS/REF24 cycles. These are +additional source configurations, not repetitions of a single benchmark. + +The initial XIN48/96 Icarus attempts (`18-41`) produced a premature terminal +write but failed the independent P7 lifecycle coverage checks, so they remain +FAIL. A short actual-SoC comparison reproduced unknown RCU cause/fault state +with the old reset declaration initialized low at time zero. REF24 has no edges +while its input divider is reset; an explicit external RESET_N assertion at +1 ns, before the first XIN edge, initializes the asynchronous state correctly. +Both old-stimulus negative probes fail and both asserted-reset probes pass, +under `20-05.../meta/cold-reset/`. The testbench now checks exact known cold +state and rejects an unknown terminal pass/code. This is a verification-stimulus +repair, with no forced DUT state or synthesizable RTL change. The Verilator +source cases and existing 24 MHz runs are retained separately; only the two +failed Icarus source cases are replayed, with the same firmware hashes. + +## Workloads and provenance + +The separate `TINY_P7_ACCEPTANCE=YES` configuration key selects the short P7 +firmware without replacing the original acceptance application. It exercises +all supported profile selections for the chosen backend, 128-word SRAM +retention, atomic rate readback, PWM rate readback, CLINT monotonicity and an +independent PCLK timer measurement over at least 200 CLINT ticks. The timer +check allows bounded APB read skew but rejects /2 or /4 domain mistakes. +It gates/ungates/resets timer0, copies 1024 words through DMA while CPU WFI is +armed, wakes on CLINT, and requests software reset with a retained `.noinit` +sentinel. The observer must see actual CPU gated cycles, DMA responses during +that interval, supported profile commits and exactly one software reset. + +Tiny PASS requires command success, TEST_STATUS/SIM_TEST_PASS, and absence of +FAILED/FATAL/assertion failed/%Error/SIM_TEST_FAIL/SIM_TEST_TIMEOUT. P7 also +requires `P7_LIFECYCLE_PASS`. The old full firmware additionally retains its +P6 peripheral/wire/AXI records and JTAG checks. UART output is diagnostic only. + +The completed P7 firmware results below match between Verilator and Icarus. +All eight images use the same retained binary. Cycles count raw XIN edges and +must not be compared as CPU cycles across different XIN configurations. + +| Source/profile | Terminal cycles in each simulator | Actual gated SYS cycles | DMA responses while gated | +| --- | ---: | ---: | ---: | +| ICS55 XIN24, PLL192/240/SAFE24 | 3,525,681 | 71,614 | 64 | +| IHP130 XIN24, external/SAFE24 | 3,521,913 | 71,623 | 64 | +| IHP130 XIN48, external/SAFE24 | 7,038,466 | 71,635 | 64 | +| IHP130 XIN96, external/SAFE24 | 14,071,429 | 71,615 | 64 | + +Each run also records exactly one software reset and verifies SRAM retention. + +The source record is +`build/ics55-tiny-2026-10-13-17-05-f3e42d54c18b/meta/tiny-r2-p7/source.json`. +It retains repository state, the exact specification, locked tool identities, +source hashes, patch and a source archive, including untracked implementation +files. `scripts/tiny_r2_p7.py verify-source` checks the retained source set. +The Make formatter subsequently changed only the terminal newline in eight +Make/configuration files. `make-format-equivalence.json` checks every changed +file byte-for-byte except that final newline; RTL, firmware, tests and tool +scripts are identical. `formatted/source.json` retains the final formatting +snapshot separately, without overwriting the source consumed by running jobs. +Subsequent validation corrections update the commercial six-domain SAFE24 +adapter/test and connect the top-level POR counter to normalized REF24. The +external-source fixture now requires five actual REF24 edges at 24/48/96 MHz +and compares old/new reset-tree outputs at 24 MHz in both simulators (seven +checks pass). For every retained long-flow configuration, XIN is 24 MHz and +the backend directly wires XIN to REF24, so that one port reconnection is an +exact alias substitution. The original source remains the provenance of those +runs; the documented equivalence and focused higher-input-rate tests cover +the correction without repeating default-profile simulations. +Build variants retain effective configuration, generated filelists, firmware +ELF/bin/hex/map, simulator binaries, command logs and structured terminal +results. Match binaries by SHA-256 across simulators, and retain flags and +plusargs with results; do not mix these images with compiler/layout experiments. + +The four P7 binaries (both PDKs and both simulators) have SHA-256 +`0a536fbbae9832de7ba5a82f7688a147b96a57435a66249b5e8de18bad1c01c5`. +The initial full ICS55 firmware matched between simulators at +`e4cb5965b9b9ddb1135a0a672327113ed8b378ace56e1a4ee771374309c18a15`; +the initial full IHP130 firmware matched at +`048bd15221fcbd25986abaa2d973e74d101f3a8ac8f328a6169442ba8acabacb`. +The corrected watchdog-acceptance full images match between simulators at +ICS55 `69b8582099c8735e81014b9c3f5685d9212a58c18b820a8c9a611a6f64a2f6c4` +and IHP130 `efc0b403f32669332e942cb63ee9bb2b035a6e462e68e0a5a1524dea01c6c298`. + +P7 has its own structural netlist audit. It requires 32 main and six private +Crypto physical SRAM macros, main-SRAM/CPU SYS equality, the CPU-only gate, +Crypto PCLK binding and exactly one real ICS55 PLL driving the SYS mux. +The audit binds configuration digest, netlist/JSON and audit-script hashes. +The strict historical P1 parked-PLL/direct-SYS audit is unchanged. + +Core STA analyzes SAFE24 boot, with explicit mux/PLL case analysis. Retain +clocks, setup/hold, coverage, recovery/removal and constraint-violation reports. +It excludes pad/board/routed/PVT and active-PLL qualification. Negative slack +is observational before the final combined product campaign, not evidence of +qualification and not permission to omit synthesis or netlist functionality. +The commercial adapter mirrors the same six-domain SAFE24 inventory and +requires explicit mux/PLL case selections; its host tests pass. Internal /2, +/4 and CLKOUT-source clocks supplement the six root domains. Settled mux gate +state is constrained because a select request alone does not propagate through +the mux's sequential handshake in static analysis. The initial and second STA +attempts remain under `sta/opensta` and `sta/opensta-p7-v2`; the final observation +is under `sta/opensta-p7-v3`. No licensed +commercial execution, active-PLL SDC, routed extraction, board IO or package +qualification is supplied by those host tests. + +Historical `2026-10-13-17-15` SAFE24 observations, in ns, are ICS55 setup/hold WNS −147.48/−0.31, +TNS −4758121.50/−45.30; IHP130 WNS −275.45/−0.50, +TNS −9958118.00/−102.84. The worst setup cone begins at the fabric reset leaf, +whose direct fanout is over 10,000; no reset false path was added to hide it. +The v3 coverage reports retain excluded pad IO budgets, 42 unclocked ICS55 PLL +monitor/mux pins while that source is off, and 5 ICS55 / 6 IHP130 control pins +with multiple clock tags. These are explicit limits of this settled SAFE24 +observation, not complete-mode timing coverage or signoff. These values and +coverage counts belong to that historical netlist; the current `06-00` WNS/TNS +are in the table above, with their own retained coverage reports. + +The single full Pytest attempt records 1696 passed, 6 failed, 60 setup errors +and one optional `pypdf` skip. Two failures were the commercial domain policy; +60 setup errors and one failure shared the missing `RS_EBUSY=-8` SDK return +entry in the publication's source-checked table. Both are corrected; the +commercial/STA tests pass and the publication subset records 110 passed/one +skip. The remaining three Icarus failures used a test module collected before +the external-reset fixture acquired its `tiny_reset_tree` dependency; the +updated test command explicitly supplies it and all seven external-source/reset +checks pass. Keep that original full-suite result and these focused reruns; +do not report an unexecuted clean full-suite pass. Only the shared return-code +table changes in the publication; no source identity, PDF, edition or release +qualification is advanced. + +## Interrupted and historical attempts + +Earlier October 13 build directories are development snapshots. In particular, +the `15-45-f3e42d54c18b` Verilator attempt passed at 3,281,032 cycles and observed +71,612 gated cycles, 64 DMA responses while gated and one warm reset. Later +corrections mean that result is not final-source acceptance. Earlier synthesis +attempts failed obsolete P1 checks; a temporary weakened P1 audit is invalid +evidence and was restored. No current physical result may be inferred from it. + +The first final-campaign synthesis attempt (`17-07`, SVA enabled) failed during +Slang elaboration on unsupported temporal assertion syntax. It did not run +mapping. The `17-15` retry uses the existing regression's `HAVE_SVA=NO` synthesis +configuration; behavioral/SVA verification remains separate and unchanged. + +The `17-08` complete Verilator runs passed all first-boot peripherals, then +incorrectly repeated the firmware after watchdog reset and failed recovery +code 26 on the second pass. The old firmware inferred warm boot from the +watchdog's functional `reset_count`; P7 intentionally resets that state. The +corrected firmware checks retained RCU cause bit 2, verifies that watchdog +functional state was cleared and SAFE24 restored, and requires an SRAM `.noinit` +cookie set only after all first-boot tests. This preserves the complete test +sequence and watchdog acceptance rather than restoring obsolete retention. +The corresponding `17-09` Icarus runs were cancelled as obsolete; controller +exit 130 is retained in `controller-cancellation.json`, separately from any +unfinished child result. Corrected full runs use `18-25` / `18-26` and source +evidence under `build/ics55-tiny-2026-10-13-18-25-f3e42d54c18b/meta/tiny-r2-p7/`. + +The corrected full Icarus attempts under `18-26` reached the watchdog-reset +stage after reporting all first-boot peripherals, but both exhausted the +36,000-second wall-clock budget with exit 124 and no terminal PASS. Those +results remain FAIL. A focused test uses the real WDG APB wrapper, command +mailboxes, Tiny APB bridge, clock backend, reset tree and RCU, and verifies two +successive hardware watchdog resets, retained RCU cause and cleared functional +state in both simulators. It does not replace the full firmware gate. +The exact retained `simv` and firmware were replayed under +`sim/iverilog/attempt-2026-10-14-04-50/` with `timeout --foreground 72000s vvp -i`, +the same `+max_cycles=30000000 +tiny_jtag_smoke` and firmware path. The +maintainer then intentionally interrupted both replays with exit 130 and moved +this long-running full-Icarus gate to the final Tiny phase. The logs and +interrupted `result-sim.json` records are retained as unrun/partial evidence; +they cannot satisfy the P7 terminal verdict. `-i` enabled unbuffered standard +IO; it did not change the design or acceptance workload. No synthesis, +simulator compilation or firmware build was repeated for the replay. + +PIDs are diagnostics in a particular execution namespace. Absence from another +tool invocation's process list does not prove termination. Keep long commands +attached to their execution sessions and use terminal result JSON plus strict +log checks. `run_flow.py` records child/parent PIDs and closes results on caught +exceptions; SIGKILL or host loss can still leave a running record. Such a record +never counts as PASS. NFS clock-skew warnings are retained in the build logs. +Cancellation now writes an interrupted checkpoint before process-group cleanup +and ignores a second termination signal during that cleanup. The final result +records the child's exit code and whether cleanup remains pending; the repeated +signal/TERM-ignoring-child regression passes. An external executor can still +kill a process before it handles a signal, so controller cancellation evidence +must not be mistaken for a fabricated child exit result. + +The ledger records the final pass/fail/unrun matrix. Human review of that matrix, +the diff and remaining qualification boundaries is the P7 acceptance gate. +The generated `audit.py` under the corrected full-run evidence directory reads +all source configurations' terminal results, strict Tiny markers, P7 lifecycle +observations, unchanged P6 workload/wire records, JTAG, paired firmware hashes, +macro audit, netlist boot and STA artifacts. It launches no hardware flow and +writes `acceptance.json`; a pending/missing/failed input cannot produce PASS. diff --git a/docs/ip/tiny-soc-r2-concurrent-fabric.md b/docs/ip/tiny-soc-r2-concurrent-fabric.md new file mode 100644 index 00000000..68ca60e3 --- /dev/null +++ b/docs/ip/tiny-soc-r2-concurrent-fabric.md @@ -0,0 +1,402 @@ +# TINY-R2-P5 concurrent fabric + +This implements the approved P5 preflight on +`3131762b35e0ebfcd196395f955e7c1005bd18e7`, using frozen specification blob +`7e19de49d49efaafacccfa85671e314d53dd15a4`. The default is Tiny/ICS55 SAFE24 +with its present PLL parked off; IHP130/no PLL remains compatibility. +The [ledger](tiny-soc-verification.md) separates phase evidence from approval. +Historical campaigns and their limitations remain below. The P1 correction +has its own source and validation scope; no P6, frequency or physical +qualification is implied. + +## P1 protocol correction (2026-10-11) + +This correction starts from committed `4e6ab1a6fb445521703f0bfa412d3a1e8a38cd69` +and the unchanged frozen specification. The preceding `09-30` campaign's +`final-evidence.json` records two reset protocol failures and stale publication +bindings even though ordinary regressions and matched-image replay passed. +Those results do not qualify this corrected RTL. + +Target reset cancels the downstream session while preserving the live +upstream session. A reset sampled in Address/Response latches the abort path; +releasing the target cannot replay its cancelled address or data. Already +captured R/B responses remain in the Common spills with unchanged VALID, +payload, ID and order. Only fabric/system reset clears those buffers. The +downstream stability assertions follow the target's own reset; upstream +stability assertions remain enabled throughout target-only reset. + +A nine-bit count tracks captured read beats, including buffered and already +retired beats. Missing DECERR beats append through the same spill until the +original ARLEN+1 count is met; RLAST belongs only to the final beat. AbortWrite +accepts/discards remaining upstream W beats before creating one DECERR B. +Captured-B state prevents duplicate responses and preserves an already held +successful completion. Fault metadata is captured from the first erroneous +response beat, preserving earlier SLVERR over a subsequent reset DECERR; +fault counting remains once per terminal transaction. Fully captured successful +responses do not become reset faults. + +The reset fixture drives stimulus away from sampling edges and independently +checks R/B stability, exact beat counts, W-before-B ordering, one completion, +credit release and first-error metadata. Its 28 cases include 16-beat SRAM/XPI +traffic, a production APB bridge, three production DMA-master drain cases, +target reset held through completion and early target release. A separate +formal mode varies target reset arbitrarily and proves response/ownership, +beat-count and W/B ordering properties by induction. The normal formal mode +and its fixed-response expectations remain intact. Publication bindings retain +complete terminal/fault predicates, with negative tests for premature +completion and response flushing; the reviewed publication SHA is unchanged. + +Evidence belongs under +`build/ics55-tiny-2026-10-11-23-35-b4aa59f3e5d9/meta/tiny-r2-p5-p1/`. +Final focused checks precede one full Pytest and one PR execution per Tiny +PDK. No matched-image replay is required to claim this bounded correctness +repair; historical workload measurements retain their source attribution. +Validation completed on 2026-10-12; the ledger retains human review pending. +Runtime peripheral-reset controls/quiescence remain P7, and the existing +five-edge reset leaves remain unchanged. No shared DMA, public ABI, profile, +dependency, warning baseline or metrics policy changes are included. + +The final technical input digest is +`f670789a2379a8d2e6c59f5ecd714ca0237bb6f1cfe749a2f29b0794d356b5f0`. +`source.json` preserves the 2011 inputs and locked tools; `evidence.json` +rechecks their identity and binds all gate/fixture/flow artifacts. Focused +validation passes 101 tests; full Pytest passes 1685 with one optional `pypdf` +skip and no failures. Counts overlap. ICS55/IHP130 PR commands each ran once; +their Tiny nightly dry-run matrices match, so the same gates were not repeated. +All six ordinary simulations satisfy command success, the Tiny success marker +and forbidden-marker checks. The scoped reset fixture and formal checks are +additional protocol evidence, not physical or XPI-device qualification. + +| PDK / balanced recipe | Cells | Library area | Max WNS / TNS (ns) | Min WNS / TNS (ns) | +| --- | ---: | ---: | ---: | ---: | +| ICS55 | 245037 | 1910664.100799 | -70.04 / -1151596.25 | 0 / 0 | +| IHP130 | 259394 | 9911158.202394 | -127.01 / -3120300.50 | 0 / 0 | + +Current physical-flow observations are under +`build/ics55-tiny-2026-10-11-23-35-42d5beccb726/` and +`build/ihp130-tiny-2026-10-11-23-35-bff91e3529ba/`, in `meta/metrics.json` +and `sta/opensta/{timing_metrics.rpt,p4-clock-bindings.rpt,opensta.log}`. +Both mapping checks retain four groups, 32 macros and CPU/main SRAM on SYS. +Timing remains observational and unclosed; library area is not routed die area +and zero observed min violations do not constitute PVT/hold signoff. Lint +signature/count sets are identical to `09-30` (470/414 new against the unchanged +ICS55/IHP130 baselines, zero increased). Successful ABC Liberty-cache fallback +and NFS clock-skew observations remain logged. No baselines were regenerated. + +Commands used the locked activation, `BUILD_TIMESTAMP=2026-10-11-23-35` and +`JOBS=3`. The `run_flow.py` JSON/log pairs in the correction evidence root retain +every exact command: `focused`, `quality`, `pytest-full`, `regress-ics55`, +`regress-ihp130`, `metrics-ics55`, `metrics-ihp130`, four `dry-run-*` records +and `audit-result`. The principal acceptance commands were: + +```sh +source .cache/retrosoc/development/tiny-r2-p1/activate.sh +export BUILD_TIMESTAMP=2026-10-11-23-35 JOBS=3 +make rtl-format-check rtl-style-check-all rtl-readiness-check-all +ruff check . +python3 -m pytest -q --tb=short --basetemp=build/ics55-tiny-2026-10-11-23-35-b4aa59f3e5d9/meta/tiny-r2-p5-p1/tests-full +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55 +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 APP=ci_smoke SYNTH=YOSYS STA=OPENSTA metrics +make CONFIG=configs/ci/ihp130-tiny.mk SOC=TINY PDK=IHP130 APP=ci_smoke SYNTH=YOSYS STA=OPENSTA metrics +git diff --check +``` + +New executions must select a fresh timestamp/evidence directory. New matched +workload replay, compiler experiments, full-function C netlist qualification, +PDF build/visual promotion, CDC/RDC, extracted PVT/MMMC and place/route were +not run. No performance, new operating-frequency or complete-product claim is +made by this correction. Error completion does not roll back downstream writes +or peripheral side effects that committed before reset. + +## Target-local reset repair (2026-10-11) + +This is the superseded first attempt. Its focused tests passed but omitted +required protocol checks; the later `09-30` campaign and independent monitors +rejected it. The following description records that attempted behavior, not +the corrected implementation above. + +The P5 review found that a target reset was previously used only as an +admission enable while an accepted controller session remained live. The +attempt carried the explicit per-target reset leaf into each controller. +Admission and downstream AXI VALID/W/RREADY are blocked while that leaf is +asserted. An accepted Address or Response session changes to an abort response +and returns one DECERR RLAST or B response with the saved owner and ID; the +response remains held until the upstream terminal handshake. Response spill +registers flush during the target reset, so an old downstream response cannot +be replayed. The normal terminal handshake releases the fabric owner credit +and records the reset termination as one fault event. + +The focused reset-isolation fixture covers an accepted SRAM read held before +response and an accepted XPI/APB write held before W, under both Icarus and +Verilator. It checks DECERR termination, no stale response replay, and owner +credit release. These local reruns are separate from the earlier matched-image +campaign; the campaign evidence and PPA/STA attribution remain unchanged until +a new approved evidence capture is made. The retained focused test output is +under `build/ics55-tiny-2026-10-10-18-58-b4aa59f3e5d9/meta/tiny-r2-p5/reset-repair-tests/`. + +## Ownership and bounds + +The three ingress slots are CPU slow path 0, central DMA 1 and reserved SDIO 2. +Only slots 0/1 have product producers. CPU has one combined read/write credit; +DMA and the reserved interface each have one read and one write credit. +The real DMA already reserves complete write data before starting a burst; +its shared scheduler and all request/channel assignments are unchanged. + +Targets 0–3 directly attach the existing SRAM group frontends through Common +AXI connectors. Target 4 is XPI memory. Targets 5–20 follow the existing APB +peripheral order, with GPIO's two windows sharing its one branch. Targets 21/22 +are the unchanged DECERR/SLVERR responders. The generator emits the decoder +and sixteen single-peripheral bridge instances from canonical topology. +There is no product-wide APB or external transaction lock. + +Each target admits one combined transaction and saves owner, direction and +address attributes on the upstream address handshake. Admission reserves both +target and ingress direction, even while the downstream address is stalled. +Owner round robin advances on admission; the chosen owner's direction +preference alternates when both directions compete. W waits for saved AW +ownership and downstream AW acceptance, and stops after the terminal data beat. +Bounded Common response spill registers isolate held R/B values. Ownership and +ingress credit last through the upstream B/RLAST handshake, including buffered +terminal responses. A continuously eligible owner can be bypassed by at most +two other admissions; progress requires clocks and peer completion. + +Local CPU I/D, group arbitration, P4 response reservations and main SRAM macro +paths are unchanged. The local uncontended bound remains three SYS cycles, +and the P4 external SRAM streaming fixture still requires one beat per cycle. +All new logic uses SYS; APB bridges follow their existing peripheral reset +leaves. Target reset must be released for admission. System reset invalidates +all buffered sessions without clearing SRAM payload. Hart reset drains CPU +work only. Runtime peripheral resets still require P7's quiescence controls. + +## Faults and public compatibility + +Each target retains its first failed captured read beat's address/response +until terminal upstream completion; a successful final beat cannot erase it. +Write errors retain the accepted AW address. Six external direction events and +two local CPU events feed the collector. CPU frontend errors carry SLVERR; +external errors propagated through AHB are not counted again locally. + +The collector increments the existing count by every simultaneous event, +using four-bit multiplicity and a 33-bit saturating sum. It selects metadata +by ascending owner, CPU instruction before data, then read before write. +Equal keys use stable input order. SYSCTRL preserves a valid first snapshot; +W1C clears validity only and a simultaneous hardware event wins over clear. +FAULT_MASTER adds reserved owner 2 without changing offset or owner 0/1 meanings. +FAULT_DETAIL remains raw AXI response 2/3. The existing HAL byte-sized owner +field needs no change. No SDK API, register generator, interrupt, DMA channel, +capability or pad assignment is added. + +The old CPU/DMA wait registers retain their predicate: a requesting owner with +no address handshake in that cycle counts once. R2_P5_FABRIC records separate +active-target maxima, DMA simultaneous read/write ownership and simultaneous +read/write beat handshakes. These are testbench observations. The original +P3/P4 record formats and evidence retain their own identities. + +## Verification and reproducibility + +Focused protocol tests use all three owner slots, saved-address perturbations, +W-before-AW, held addresses/data/responses, independent control branches, +same-target contention and 96 deterministic randomized reads. Mixed-response +bursts align two terminal faults while their final responses are OKAY. +SYSCTRL tests cover multi-event count, owner 2, hardware-over-W1C and saturation. +The saturation test seeds the Common counter register, then exercises the +real adder and readback; it does not simulate billions of events. + +The system fixture instantiates production retrosoc_tiny, CPU, DMA, SRAM, +bridges and SYSCTRL. Only the XPI boundary is replaced with a controllable +verification endpoint and a fixture-only reset ROM. Real DMA copies B2 to B3 +with concurrent read/write beats and CPU readback checks. With an XPI mapped +response held, SRAM code and SYSCTRL access continue. A held XPI APB write +then verifies independent controls and hart-reset isolation. Finally CPU aborts +a held DMA read, checks that BUSY survives accepted work, releases the response +and verifies ABORTED. Ordinary regressions retain real pin-level XPI/NOR models. +This fixture is neither a product Boot ROM nor an XPI-device qualification. + +The first fixture attempted hart reset while holding the XPI memory response; +an accepted CPU instruction also needed that target, so reset correctly waited +for drain. The corrected isolation case retains DMA at the independent APB +branch and permits CPU progress. Other development attempts preserve sv2v +diagnostics separately from converted RTL and observe declared interface +instances rather than module-modport aliases for Icarus compatibility. + +Yosys induction checks production target ownership, response retention, +terminal-fault attribution and service-count fairness. A combinational proof +checks the collector's count and lexicographic metadata selection for arbitrary +event vectors. These are scoped control proofs, not full-chip formal signoff. + +The P5 replay entrypoint uses the unchanged P3 flat/banked images referenced by +the repaired P4 report: +`build/ics55-tiny-2026-10-10-15-15-b4aa59f3e5d9/meta/tiny-r2-p4/report.json`. +The old reports retain their original 4db27524-based snapshot attribution; +preflight matched all 1972 technical inputs to the committed P5 starting point. +P5 report identity, source/configuration/tool hashes and observations are new. +Parameters stay 1024 words, four jobs, 4096 CPU iterations and seed 0x12345678. +Each Verilator image runs once and banked Icarus once; all banked observations +must agree. There is no frozen overall speedup requirement or compiler/layout +experiment. Different-bank concurrency is proved separately because the +retained banked placement concentrates DMA storage in B3. + +Fresh development evidence begins at +`build/ics55-tiny-2026-10-10-18-25-b4aa59f3e5d9/meta/tiny-r2-p5/`. +The completed campaign and its monitor-only follow-up are recorded below. +Documentation remains here rather than in generated directories. +The Tiny draft/source extractor follows P5 routing and the fault field width; +its reviewed publication SHA and PDF status are not advanced. + +## Completed campaign and monitor correction + +The final evidence root is +`build/ics55-tiny-2026-10-10-18-58-b4aa59f3e5d9/meta/tiny-r2-p5/`. +`source.json` binds the initial 2010-input campaign to combined ICS55/IHP130 +digest `e85f4245883bf35f152d607480adf48edcf49debadb46d8b0bde92050a792b11`. +Both Tiny PR regressions, full Pytest and the first three image executions +passed on that snapshot. The initial aggregation in `report-result.{json,log}` +correctly failed: Icarus reported active-target maxima one greater than +Verilator in all eight banked windows, while every other sample was identical. + +The locked Icarus 13 installation miscounts the temporary expression +`$countones(~a_23_bit_vector)`. `countones_probe.sv` and its retained log +demonstrate an expected count 1 returning 3/6, while a named vector and explicit +bit counting return 1. The correction counts the declared 23 bits explicitly, +rejects unknown target states, and adds an opt-in count 0..23 self-check. No +postprocessing subtraction, relaxed comparison, tool upgrade or DUT fix is used. + +`monitor-source.json` captures the final combined digest +`0f3ed21f6fd6e932ff2532aab8c9964e71cf4b972e4c27e2c8ca6ab1f22c1015`. +Its only technical difference from the initial capture is +`rtl/tiny/dv/retrosoc_tiny_tb.sv`, inside P5 opt-in observation/self-check code. +The DUT, firmware, verification fixtures, policies and tool inputs are identical. +The ordinary regression commands do not enable this branch, and netlist boot +excludes it. Their results, full-suite results and PPA retain their original +snapshot attribution; they were not rerun or relabeled as new executions. +The audit checks this exact single-file delta and the original command flags. + +Both Verilator images and banked Icarus were rebuilt/replayed after the fix, +and the monitor self-check passed on both new models. The final ICS55-only +replay input digest is +`df14713ed7177d988d0e3fa0a038984589584788eda994dd7480f01a8dfcab7c`. +`report-final.{json,log}` and `report.json` confirm exact equality of all banked +observations across simulators, including the corrected active-target statistic. +`monitor-diagnosis.json` preserves the original discrepancy, logs and input +identity. Final model copies are retained beside their attempt records and +bound by `model-backup-*.json`; the initial Icarus model is separately retained. + +| Gate | Retained result | Scope/outcome | +| --- | --- | --- | +| Focused development suite | Earlier `18-25` root, `focused-final.{json,log}` | 106 passed before final campaign | +| Initial quality | `quality.{json,log}` | Ruff, diff, RTL format/style/readiness pass; 11.524 s | +| Full Pytest | `pytest-full.{json,log}` | 1679 passed, optional `pypdf` skip; 3867.891 s; original snapshot | +| Tiny ICS55 PR | `regress-tiny-ics55.{json,log}` | Pass; 4273.573 s; original snapshot | +| Tiny IHP130 PR | `regress-tiny-ihp130.{json,log}` | Pass; 3028.340 s; original snapshot | +| Initial image executions | `replay-verilator` / `replay-iverilog` results | Commands pass; 609.465/5952.278 s; cross-simulator aggregation fails as above | +| Monitor follow-up quality | `monitor-quality.{json,log}` | Pass; 11.714 s | +| Count self-checks | `monitor-check-{verilator,iverilog}.{json,log}` | Pass; instrumentation-only, not firmware acceptance | +| Final image executions | `replay-verilator-monitor` / `replay-iverilog-monitor` results | Pass; 618.974/5978.016 s; one run per selected image | +| Final report and audit | `report-final.{json,log}`, `report.json`, `evidence.json`, `audit.py` | Measurement complete; source-bound functional validation complete, review pending | + +All six ordinary simulations satisfy command success, configured SIM_TEST_PASS +and absence of forbidden error markers. In the final full suite, six P4 SRAM +fixtures preserve three-cycle local latency and one-cycle streaming spacing; +two P4 CPU fixtures, two P5 fabric fixtures, two real-CPU/DMA system fixtures +and three scoped formal logs pass. The system fixture reports 13 simultaneous +DMA read/write beat cycles and 27 independent control completions on both tools, +with hart-reset isolation and abort drain. SYSCTRL tests additionally check +multiple faults, reserved owner 2, raw detail, clear precedence and saturation. +These are functional results, not physical or XPI-device qualification. + +## Measurements and qualification limits + +The final runs preserve the immutable P3 image identities and compare them +with repaired P4 measurements, without changing compiler or placement: + +| Workload | Flat P4 → P5 cycles | Banked P4 → P5 cycles | +| --- | ---: | ---: | +| Empty | 69 → 69 | 66 → 66 | +| CPU | 73911 → 73911 | 73914 → 73914 | +| CPU copy | 102568 → 102568 | 86214 → 86214 | +| DMA burst1 | 91306 → 65527 | 89365 → 65502 | +| DMA burst16 | 17897 → 16974 | 17317 → 15802 | +| CPU/DMA contention | 299881 → 299990 | 299633 → 299738 | +| TCD contention | 299878 → 300027 | 299645 → 299750 | +| Chunk256 | 361285 → 361514 | 359570 → 361355 | + +The flat burst16 window records a maximum of three active targets, 124 cycles +with simultaneous DMA read/write ownership and 48 simultaneous read/write beat +cycles. Banked DMA stays in B3 and has zero DMA read/write overlap; up to two +targets are active because independent CPU control can overlap that bank. +The separate system fixture establishes different-bank DMA concurrency. +CPU/copy cycles are unchanged. Small contention/TCD increases and the 0.50% +banked chunk256 increase remain visible; no universal speedup is claimed. + +Synthesis/STA observations for the unchanged DUT use +`build/ics55-tiny-2026-10-10-18-58-42d5beccb726/` and +`build/ihp130-tiny-2026-10-10-18-58-bff91e3529ba/`. +Both pass synthesis, netlist boot and the existing 32-macro/direct-SYS binding +checks. Reports are `meta/metrics.json`, `sta/opensta/timing_metrics.rpt`, +`sta/opensta/p4-clock-bindings.rpt` and `sta/opensta/retrosoc_sta.log`. + +| PDK | Cells | Library area | Max WNS / TNS (ns) | Min WNS / TNS (ns) | +| --- | ---: | ---: | ---: | ---: | +| ICS55 | 241309 | 1906156.940799 | -64.75 / -932689.50 | 0 / 0 | +| IHP130 | 256995 | 9888378.712794 | -111.93 / -2594456.50 | 0 / 0 | + +Library area grows 5.56%/6.95% versus repaired P4. Max timing margins worsen; +timing remains unclosed and observational before the final product campaign. +Library area is not routed die area, and zero observed min violations is not +PVT/hold signoff. No new timing exception or rate is introduced. + +The unchanged warning baselines report 469/413 new signatures for ICS55/IHP130, +zero increased and zero resolved. Relative to repaired P4, two new signatures +are intentional empty `owner_idle_o`/`target_idle_o` connections; their future +lifecycle use and verification visibility are documented at the instances. +The former unused interface `aresetn` signature disappears because target +admission now consumes it. There is no new width/latch signature. The known +NFS clock-skew warning and successful ABC Liberty-cache fallback remain logged. +No warning baseline or metrics policy was changed. + +Mini behavioral compatibility retains its earlier P4 evidence and source +identity. The audit rechecks the unchanged shared/Mini/SDK/configuration inputs; +there is no new Mini execution or physical claim. No C/H implementation or +MISRA deviation changed. The P3 compiler matrix and three cold repetitions, +full C netlist campaign, PDF build/visual checks, CDC/RDC/PVT/MMMC, place/route, +high-rate qualification and P6+ work were not performed. Only the concretely +affected image measurements were repeated for the monitor defect. + +## Executed commands + +Commands used the locked activation and `BUILD_TIMESTAMP=2026-10-10-18-58`, +`JOBS=3`. `scripts/run_flow.py` retains the exact outer commands and JSON/log +results named above. New evidence is under `meta/tiny-r2-p5`; source snapshots, +tool identities, input digests, models, images and raw logs remain separate. + +```sh +source .cache/retrosoc/development/tiny-r2-p1/activate.sh +export BUILD_TIMESTAMP=2026-10-10-18-58 JOBS=3 +make rtl-format-check rtl-style-check-all rtl-readiness-check-all +ruff check . +git diff --check +python3 -m pytest -q --basetemp=build/ics55-tiny-2026-10-10-18-58-b4aa59f3e5d9/meta/tiny-r2-p5/tests-full +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55 --dry-run +python3 scripts/regress.py --root . --suite nightly --soc TINY --pdk ICS55 --dry-run +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 --dry-run +python3 scripts/regress.py --root . --suite nightly --soc TINY --pdk IHP130 --dry-run +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55 +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 SIMU=VERILATOR SOC_SIM_TIME=21600 \ + TINY_P5_REFERENCE=build/ics55-tiny-2026-10-10-15-15-b4aa59f3e5d9/meta/tiny-r2-p4/report.json \ + TINY_P5_CASES=perf-o2-plain-flat,perf-o2-plain-banked tiny-r2-p5-replay +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 SIMU=IVERILOG SOC_SIM_TIME=21600 \ + TINY_P5_REFERENCE=build/ics55-tiny-2026-10-10-15-15-b4aa59f3e5d9/meta/tiny-r2-p4/report.json \ + TINY_P5_CASES=perf-o2-plain-banked tiny-r2-p5-replay +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 tiny-r2-p5-report +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 APP=ci_smoke SYNTH=YOSYS STA=OPENSTA metrics +make CONFIG=configs/ci/ihp130-tiny.mk SOC=TINY PDK=IHP130 APP=ci_smoke SYNTH=YOSYS STA=OPENSTA metrics +PYTHONDONTWRITEBYTECODE=1 python3 build/ics55-tiny-2026-10-10-18-58-b4aa59f3e5d9/meta/tiny-r2-p5/audit.py +``` + +Tiny PR/nightly definitions matched and were not executed twice. The first +report attempt failed; only the monitor-corrected executions qualify the final +cross-simulator report. The original cold executions are not a three-repeat +qualification campaign. No commit, staging, push, PR or automatic next-phase +transition belongs to this implementation. diff --git a/docs/ip/tiny-soc-r2-local-memory.md b/docs/ip/tiny-soc-r2-local-memory.md new file mode 100644 index 00000000..76fd0200 --- /dev/null +++ b/docs/ip/tiny-soc-r2-local-memory.md @@ -0,0 +1,387 @@ +# TINY-R2-P4 local CPU/SRAM implementation + +## Approved throughput repair (2026-10-10; validated, review pending) + +The maintainer approved the eight-file P4 throughput-repair preflight after +the read-only review identified the external frontend's per-beat bubbles. +This follow-up remains P4; it does not enable P5 target concurrency. +The initial implementation and its measurements below are retained historical +evidence for the pre-repair snapshot, not qualification of the changed RTL. +Fresh evidence is under +`build/ics55-tiny-2026-10-10-15-15-b4aa59f3e5d9/meta/tiny-r2-p4/`. + +The group now reserves two response slots for its external client, counting +both queued completions and a macro read still in flight. A response pop may +free capacity on the same edge as the next issue. The macro's saved owner/bank +still selects the captured read value; FIFO data cannot be overwritten by +later local accesses. A same-client pending read prevents a simultaneous write +commit into the single-push response FIFO. Local I/D keep one operation each +and their original completion pipeline. + +The external frontend separates issued and retired read positions. W uses a +two-entry Common FIFO containing captured address, data and byte strobes. +Only captured legal beats enter arbitration. Invalid strobes or LAST retain +the existing error policy, including earlier committed beats and later legal +beats after an invalid strobe. B waits for all queued legal writes and their +commit responses. External transaction ownership still lasts through B/RLAST. +These are small register queues, not additional user SRAM or new macros. + +Acceptance adds consecutive 16-beat read/write handshakes at one SYS cycle per +beat after startup, without local competition or bus backpressure. Logs retain +first-beat latency, total transfer time and final-W-to-B delay. Directed checks +fill/drain queues, stop READY, interleave local service, retain old read values +across local writes, exercise buffered malformed writes and drop stale queued +responses on system reset. Induction checks response conservation/capacity in +addition to held-response stability and the existing service-count fairness. +The unchanged CPU debug/reset, discovery/counters and both real-PDK macro +fixtures remain required. + +Use the same immutable P3 flat/banked images and parameters. Each selected +Verilator image runs once; banked Icarus runs once. Compare both with their P3 +samples and the pre-repair P4 report. The repair requires burst-16 cycles below +36713/36489 respectively, truthful reporting of all other workload deltas and +identical banked results across simulators. It does not promise P3 cycle parity +or change compiler/layout settings. Existing shared Mini debug inputs are +unchanged by this repair; matching prior compatibility evidence is retained. + +### Repair results and provenance + +The campaign is complete for review. `source.json`, `report.json`, `audit.py` +and `evidence.json` in the fresh evidence root bind 1972 technical inputs, +six fresh Tiny regression simulations, three matched-image executions, +six real/behavioral SRAM fixtures, two CPU fixtures and control induction. +The combined ICS55/IHP130 input digest is +`58daf68fbb9dee199b3085a73ef07d22eb3de6d00ba875c462e85b76b6091a0d`; +the ICS55 replay input digest is +`a96a129ffc02d0d38fd2eee7ed8fb62893828db3602bbd4459e0a71ffce0c35f`. +Only the approved six technical files differ from the preceding P4 snapshot; +the other two changes are this runbook and the ledger. No commit or staging +operation is part of this campaign. + +All six SRAM runs record `read_gap_max=1 write_gap_max=1 beats=16`, with first +R at three cycles after AR and final R at cycle18; W begins one cycle after +AW, final B is at cycle20, four cycles after final W. These are standalone +uncontended protocol measurements, not end-to-end DMA or physical timing. +All six retain `local_latency_max=3`. The new queue checks cover full-response +backpressure, local writes to already captured read addresses, W contention, +buffered malformed writes, and both queued-R and accepted-but-uncommitted-W +system resets. CPU debug/patch/hart-reset and induction pass in the final suite. + +| Gate | Result under the fresh evidence root | Outcome | +| --- | --- | --- | +| Final Ruff, diff, RTL format/style/readiness | `quality-pass.{json,log}` | Pass | +| Full Pytest | `pytest-full.{json,log}` | 1659 passed, one optional `pypdf` skip; 3903.061 s | +| Tiny ICS55 PR | `regress-tiny-ics55.{json,log}` | Pass; 3396.035 s | +| Tiny IHP130 PR | `regress-tiny-ihp130.{json,log}` | Pass; 2340.150 s | +| Verilator, both immutable images once | `replay-verilator.{json,log}` | Pass; 390.698 s | +| Icarus, immutable banked image once | `replay-iverilog.{json,log}` | Pass; 5154.223 s | +| Cross-simulator report | `report-result.{json,log}`, `report.json` | Complete; all banked observations identical | +| Source/artifact audit | `evidence.json`, `audit.py` | Pass; review/phase acceptance pending | + +The initial `quality-final.{json,log}` is a retained failed development attempt: +the style gate required `s_req_accept`/`s_resp_accept` abbreviations. The names +were corrected before the final source snapshot and all long gates. The first +29 focused checks and additional queued-W-reset check also passed; final-suite +logs qualify the final technical snapshot. No failed attempt is relabeled. + +| Workload | Flat pre-repair P4 → repaired P4 | Banked pre-repair P4 → repaired P4 | +| --- | ---: | ---: | +| Empty | 69 → 69 | 66 → 66 | +| CPU | 73911 → 73911 | 73914 → 73914 | +| CPU copy | 102568 → 102568 | 86214 → 86214 | +| DMA burst1 | 91306 → 91306 | 89365 → 89365 | +| DMA burst16 | 36713 → 17897 | 36489 → 17317 | +| CPU/DMA contention | 303493 → 299881 | 299633 → 299633 | +| TCD contention | 303702 → 299878 | 299645 → 299645 | +| Chunk256 | 377705 → 361285 | 376970 → 359570 | + +Burst16 cycles decrease 51.25%/52.54%, satisfying the approved repair criterion. +They remain 8.34%/4.81% above the original P3 16520/16523 cycles. Burst1 still +retains the earlier P4 overhead versus P3; this repair does not claim universal +baseline parity. Flat local maximum is four cycles in contended windows, with +17697 eligible bank-conflict cycles; banked maximum is three with zero conflicts. +Uncontended local latency is still checked independently. Firmware checksums, +parameters and each image's compiler/layout identity remain unchanged. + +Fresh physical variants are +`build/ics55-tiny-2026-10-10-15-15-42d5beccb726/` and +`build/ihp130-tiny-2026-10-10-15-15-bff91e3529ba/`. +Both pass synthesis, netlist boot and common-SYS/32-macro binding checks. +Their `meta/metrics.json`, `sta/opensta/timing_metrics.rpt`, +`sta/opensta/p4-clock-bindings.rpt` and `sta/opensta/retrosoc_sta.log` retain +the matching observations: + +| PDK | Cells | Library area | Setup WNS / TNS (ns) | Hold WNS / TNS (ns) | +| --- | ---: | ---: | ---: | ---: | +| ICS55 | 207029 | 1805809.700799 | -36.88 / -489807.34 | 0 / 0 | +| IHP130 | 218773 | 9245382.228597 | -97.37 / -1640251.62 | 0 / 0 | + +Setup remains unclosed; library area is not die area and zero observed hold +slack violations are not physical signoff. Lint comparisons to the unchanged +baselines report 468/412 new signatures for ICS55/IHP130, zero increased and +zero resolved. Relative to pre-repair P4 there are exactly two additional +signatures: intentionally unused FIFO `cnt_o`, and `s_full`, which is consumed +by the verification assertions but unused in synthesis-oriented lint. No new +width/latch warning is attributed to the repair. Warning and metrics policy +remain observational and no baseline was edited. + +Commands below used the same timestamp, with `scripts/run_flow.py` retaining +outer command/result/log records. Standalone test artifacts use distinct +`tests-focused`, `tests-reset` and `tests-full` directories under this variant. + +```sh +source .cache/retrosoc/development/tiny-r2-p1/activate.sh +export BUILD_TIMESTAMP=2026-10-10-15-15 JOBS=3 +python3 -m pytest -q tests/test_tiny_r2_p4.py --basetemp=build/ics55-tiny-2026-10-10-15-15-b4aa59f3e5d9/meta/tiny-r2-p4/tests-focused +python3 -m pytest -q 'tests/test_tiny_r2_p4.py::test_four_group_local_memory[BEHAV-iverilog]' --basetemp=build/ics55-tiny-2026-10-10-15-15-b4aa59f3e5d9/meta/tiny-r2-p4/tests-reset +ruff check . +git diff --check +make rtl-format-check rtl-style-check-all rtl-readiness-check-all +python3 -m pytest -q --basetemp=build/ics55-tiny-2026-10-10-15-15-b4aa59f3e5d9/meta/tiny-r2-p4/tests-full +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55 --dry-run +python3 scripts/regress.py --root . --suite nightly --soc TINY --pdk ICS55 --dry-run +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 --dry-run +python3 scripts/regress.py --root . --suite nightly --soc TINY --pdk IHP130 --dry-run +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55 +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 SIMU=VERILATOR SOC_SIM_TIME=21600 \ + TINY_P4_REFERENCE=build/ics55-tiny-performance-2026-10-09-17-10-53cc6674d532/meta/tiny-r2-p3/report.json \ + TINY_P4_CASES=perf-o2-plain-flat,perf-o2-plain-banked tiny-r2-p4-replay +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 SIMU=IVERILOG SOC_SIM_TIME=21600 \ + TINY_P4_REFERENCE=build/ics55-tiny-performance-2026-10-09-17-10-53cc6674d532/meta/tiny-r2-p3/report.json \ + TINY_P4_CASES=perf-o2-plain-banked tiny-r2-p4-replay +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 tiny-r2-p4-report +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 APP=ci_smoke SYNTH=YOSYS STA=OPENSTA metrics +make CONFIG=configs/ci/ihp130-tiny.mk SOC=TINY PDK=IHP130 APP=ci_smoke SYNTH=YOSYS STA=OPENSTA metrics +PYTHONDONTWRITEBYTECODE=1 python3 build/ics55-tiny-2026-10-10-15-15-b4aa59f3e5d9/meta/tiny-r2-p4/audit.py +``` + +Tiny PR/nightly dry-runs match; no duplicate nightly execution was needed. +The six fresh ordinary simulations all require `SIM_TEST_PASS`, including +Tiny netlist boot, together with command success and no forbidden error marker. +Mini's two prior behavioral results retain their original source attribution; +the audit verifies that changed technical inputs are confined to Tiny and its +tests. Mini synthesis/STA, the P3 compiler matrix, additional cold repetitions, +C format/policy/host reruns, PDF generation, PVT/MMMC, CDC/RDC and place/route +were not run for this repair. No C/H code or MISRA deviation changed. The +full-suite optional PDF retrieval check remains skipped for missing `pypdf`. +No new frequency, physical qualification or P5 authorization is claimed. + +## Original implementation snapshot + +This implements the approved [P4 contract](tiny-soc.md#tiny-r2-p4---dual-port-hazard3-and-four-bank-local-sram) +from `4db27524a6282ca40d76b935415b89f043d6ea83`, specification blob +`7e19de49d49efaafacccfa85671e314d53dd15a4`. The maintainer approved the preflight +and implementation. No P5 execution, dependency update, publication release or +physical-frequency qualification is implied. Historical phase evidence remains +in the [ledger](tiny-soc-verification.md). + +## Architecture and lifecycle + +Tiny uses locked `hazard3_cpu_2port`, preserving ISA, IRQ, debug, multiply and +branch-prediction settings; A remains disabled. Separate I/D AHB frontends decode +SRAM before the tagged slow-path merge. One accepted D operation spans all +targets. Write data is captured in its AHB data phase; local stores complete +after macro commit. Uncontended local completion is at most three SYS cycles. +Slow errors retain two-cycle AHB completion. Instruction origin reaches the +existing fabric fault path: only NOR boot/NSS0 may be fetched externally, and +peripheral fetches cannot trigger register reads. + +Four 32 KiB groups cover `0x30000000..0x3001ffff`, each with eight existing +4 KiB macros and I/D/external round robin advancing only on macro issue. +Per-client registers capture read responses before macro outputs change. +External W waits and R/B backpressure do not lock local macro service. +Continuously eligible requests wait for at most two other issues, conditional +on progress rather than a wall-clock bound for stopped peers. + +Each external frontend retains one transaction through B/RLAST and alternates +read/write preference. A direction-tagged demux preserves the existing external +fabric attachment. Whole-product different-target DMA read/write concurrency +remains P5; standalone frontend concurrency is not that product qualification. + +Hart reset requests debug halt, prevents new debug instruction injection, drains +accepted local/slow operations and responses, then resets the hart/frontends. +A terminal-cycle AHB overlap must still drain. SRAM/external frontends and +unrelated DMA stay active. System reset invalidates pending responses without +clearing SRAM payload. Five-edge release and Mini's default single-port reset +behavior are preserved; shared debug only adds a pending-state output. + +## ABI, physical bindings and observations + +Shared SRAM register definitions are unchanged: 128 KiB, `BANK_COUNT=32`, +`BANK_BYTES=4096`. Public counters count external AXI events, saturating sums +of simultaneous events; stall cycles use OR across groups. Local CPU traffic +is not silently added. `R2_AHB` observes the separate I/D ports. `R2_P4_LOCAL` +records local completions, latency and address-admission waits; `R2_P4_BANK` +records macro issues, eligible-client waits and conflict cycles (two or more +eligible clients in a group). These are verification-only observations. + +Macro `g,b` is `u_soc.u_sram.gen_group[g].u_group.gen_bank[b].u_ram.u_mem`, +physical index `8*g+b`. IHP130 placement coordinates stay fixed while instance +and supply hooks track both indices. CPU and all 32 macros remain directly on +SYS24; ICS55 PLL stays off and IHP130 remains no-PLL compatibility. Mapping +checks reject stale single-port/flat-bank hierarchies. STA remains observational, +with no 96/192/240 MHz or physical signoff claim. + +The Tiny publication extractor and affected draft prose/diagram follow the new +bindings. No reviewed publication SHA, delivered PDF or release status advances. + +## Acceptance and matched images + +`tests/test_tiny_r2_p4.py` uses real IHP130/ICS55 macro models and a behavioral +model on both simulators: all macro/group boundaries, byte masks, back-to-back +AHB, independent groups, held responses, W-before-AW, bursts/malformed writes, +discovery/counter semantics and reset payload retention. Deterministic readback +uses seed `0x12345678`, 4096 write/read pairs per group. The actual CPU fixture +checks mixed 16/32-bit cross-group fetch, FENCE.I, instruction faults, JTAG +patch/resume, and reset with a held slow response plus an unrelated held B. +Yosys induction proves group control fairness and retained responses with only +macro payload abstracted. Normal Tiny firmware additionally executes code +copied through the real DMA, then CPU-patched code, after completion/FENCE.I. +Its helper is private acceptance assembly, not a new SDK API. + +`tiny-r2-p4-replay` validates retained P3 images/logs, builds a fresh P4 model, +and executes each selected image once. Image source/compiler/layout and new +RTL/model identities are separate; hardware settings must match. Original P3 +same-source/three-cold-start rules are unchanged. `tiny-r2-p4-report` revalidates +artifacts and requires identical banked-image results across the two simulators. + +The reference is +`build/ics55-tiny-performance-2026-10-09-17-10-53cc6674d532/meta/tiny-r2-p3/report.json`. +Select `perf-o2-plain-flat` and `perf-o2-plain-banked` for Verilator and the banked +image for Icarus. Parameters remain 1024 words, four jobs, 4096 CPU iterations, +seed `0x12345678`, burst-1/16 DMA, TCD and chunk-256. Compare each unchanged +binary with its own historical samples, not with a different compiler/layout. +No fixed speedup is predeclared; latency/fairness requirements remain gates. + +Development evidence begins under +`build/ics55-tiny-2026-10-10-10-05-b4aa59f3e5d9/meta/tiny-r2-p4/`. +Early fixture failures and corrected runs are retained separately. The final +campaign below completes planned functional validation and measurements; human +review/phase acceptance remain pending, including the DMA throughput tradeoff. + +## Executed campaign and provenance + +The evidence root above contains `source.json`, `evidence.json`, `audit.py` and +`report.json`. The campaign snapshot contains 1972 unchanged technical inputs, +base commit `4db27524a6282ca40d76b935415b89f043d6ea83` plus the retained patch and +new-file archive, and locked tool/executable/archive identities. Its combined +ICS55/IHP130 digest is +`9fa311a5500711c99b006eacd95cdff04a74659d52b5b46640c42c49773a7f66`. +The ICS55-only replay source set has digest +`2f0d285186f84d41422d8381d09b0a01a2afc066dbd011a11ec7a8a85433ad0d`; +these are different declared input sets, not conflicting identities. + +The final audit rechecks all captured inputs, eight ordinary regression +simulations, the three matched-image executions, six standalone memory runs, +two actual-CPU runs and the group-control induction log. Ordinary firmware and +model artifacts are hashed post-run; the campaign source hashes were captured +before execution and rechecked afterward. Image/model identities and original +P3 phase attribution are retained separately. + +| Gate | Retained result under the evidence root | Outcome | +| --- | --- | --- | +| Format/style/readiness, C policy/host, Ruff/diff | `quality-final.{json,log}` | Pass; no new MISRA deviation; partial checks, not MISRA certification | +| Full Pytest | `pytest-full.{json,log}` | 1659 passed, one optional `pypdf` environment skip; 3856.630 s | +| Tiny ICS55 PR | `regress-tiny-ics55.{json,log}` | Pass; 3295.064 s | +| Tiny IHP130 PR | `regress-tiny-ihp130.{json,log}` | Pass; 2297.373 s | +| Mini ICS55 behavioral-only PR | `regress-mini-ics55.{json,log}` | Pass; 3093.379 s; no Mini physical evidence | +| P4 Verilator replay | `replay-verilator.{json,log}` | Both selected images once; 357.537 s including build/audit | +| P4 Icarus replay | `replay-iverilog.json`, `replay-iverilog.json.log` | Banked image once; 5214.736 s including build/audit | +| Matched-image aggregation | `report-result.json`, `report.log`, `report.json` | `measurement_complete`; all banked samples match across simulators | + +The six standalone memory logs report `local_latency_max=3`, with real +simultaneous group issues. Those protocol fixtures use a 10 ns verification +clock; product replays and timing checks use SAFE24. The banked product replay +also observes three-cycle maximum local latency and zero eligible bank-conflict +cycles. No physical frequency qualification follows from functional models. + +Commands were run through `scripts/run_flow.py` for retained logs/results: + +```sh +source .cache/retrosoc/development/tiny-r2-p1/activate.sh +export BUILD_TIMESTAMP=2026-10-10-10-05 +export JOBS=3 +make rtl-format-check rtl-style-check-all rtl-readiness-check-all +make sw-format-check sw-policy-check sw-host-test +ruff check . +git diff --check +python3 -m pytest -q --basetemp=build/ics55-tiny-2026-10-10-10-05-b4aa59f3e5d9/meta/tiny-r2-p4/tests-full +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55 --dry-run +python3 scripts/regress.py --root . --suite nightly --soc TINY --pdk ICS55 --dry-run +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 --dry-run +python3 scripts/regress.py --root . --suite pr --soc MINI --pdk ICS55 --behavioral-only --dry-run +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55 +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 +JAVA_TOOL_OPTIONS=-Dsbt.server.forcestart=true python3 scripts/regress.py \ + --root . --suite pr --soc MINI --pdk ICS55 --behavioral-only +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 SIMU=VERILATOR SOC_SIM_TIME=21600 \ + TINY_P4_REFERENCE=build/ics55-tiny-performance-2026-10-09-17-10-53cc6674d532/meta/tiny-r2-p3/report.json \ + TINY_P4_CASES=perf-o2-plain-flat,perf-o2-plain-banked tiny-r2-p4-replay +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 SIMU=IVERILOG SOC_SIM_TIME=21600 \ + TINY_P4_REFERENCE=build/ics55-tiny-performance-2026-10-09-17-10-53cc6674d532/meta/tiny-r2-p3/report.json \ + TINY_P4_CASES=perf-o2-plain-banked tiny-r2-p4-replay +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 tiny-r2-p4-report +make CONFIG=configs/ci/ics55-tiny.mk SOC=TINY PDK=ICS55 APP=ci_smoke SYNTH=YOSYS STA=OPENSTA metrics +make CONFIG=configs/ci/ihp130-tiny.mk SOC=TINY PDK=IHP130 APP=ci_smoke SYNTH=YOSYS STA=OPENSTA metrics +``` + +Tiny PR and nightly expand to the same commands; nightly was not duplicated. +Each simulation requires command success, its configured terminal marker and +no forbidden-error marker. All selected ordinary targets require SIM_TEST_PASS, +including Tiny netlist boot. UART text alone is insufficient. Development +attempts are not counted as extra qualifying cold starts. + +## Measurements and remaining qualification + +Raw cycles below compare each immutable binary with its own P3 baseline. +The report also retains retired instructions, CPI inputs, empty calibration, +local waits, macro issues/conflicts and external traffic accounting. + +| Workload | Flat P3 → P4 cycles | Banked P3 → P4 cycles | +| --- | ---: | ---: | +| CPU | 73951 → 73911 | 73954 → 73914 | +| CPU copy | 127157 → 102568 | 114875 → 86214 | +| DMA burst 1 | 64577 → 91306 | 64583 → 89365 | +| DMA burst 16 | 16520 → 36713 | 16523 → 36489 | +| CPU/DMA contention | 310368 → 303493 | 310383 → 299633 | +| TCD contention | 310665 → 303702 | 310683 → 299645 | +| Chunk 256 | 373812 → 377705 | 374016 → 376970 | + +CPU-copy cycles decrease by about 19%/25%, while isolated burst-16 DMA takes +about 2.2 times the baseline cycles. The current external frontend processes +issue, completion and bus response separately for each beat; preserving local +response isolation costs streaming throughput. This regression is retained for +review, not hidden by the CPU-copy result or a changed compiler/layout. P5's +external target concurrency alone does not remove these per-beat waits. No +fixed speedup was an acceptance gate, but the tradeoff needs explicit assessment +before phase acceptance or a throughput claim. + +Actual synthesis/STA variants are +`build/ics55-tiny-2026-10-10-10-05-42d5beccb726/` and +`build/ihp130-tiny-2026-10-10-10-05-bff91e3529ba/`. +Both emit `TINY_R2_P4_BINDING_PASS` and pass netlist boot. Their raw reports are +`sta/opensta/timing_metrics.rpt`, `p4-clock-bindings.rpt`, `retrosoc_sta.log` and +`meta/metrics.json`. + +| PDK | Cells | Reported library area | Max WNS / TNS (ns) | Min WNS / TNS (ns) | +| --- | ---: | ---: | ---: | ---: | +| ICS55 | 206311 | 1803352.980799 | -40.55 / -518700.31 | -0.02 / -11.85 | +| IHP130 | 216216 | 9218095.882797 | -97.46 / -1626503.88 | -0.17 / -142.67 | + +Timing is not closed. Library area is not placed/routed die area. Lint baseline +comparisons remain non-blocking observations: Tiny ICS55 466 new signatures, +Tiny IHP130 410 new, Mini ICS55 1537 new / 118 resolved, all with zero increased. +Compared with the preceding P3 lint runs, newly observed signatures are intentional +empty named output connections: Tiny's unused master IDs, exclusive/power/fence +outputs, plus Mini's ignored `reset_pending_o`. Tiny is cacheless with committed +stores, no A/SBA and no Xh3Power, so these outputs do not add interfaces or clock +gating. Baselines and metric policy were not edited. The logs retain the known +generated-file clock skew and successful ABC Liberty fallback observations. + +The P3 eight-configuration matrix and three-cold-start campaign were not rerun. +No place/route, extracted/PVT/CDC/RDC signoff, high-rate qualification, new PDK +rollout or PDF build/visual check was performed. Publication snapshot enforcement +remains enabled; the reviewed PDF source revision was not advanced. Mini +DMA-to-WS2812 stays unsupported under option A, and product DMA/IRQ budgets +remain for a supported integration. P5 and later features are not implemented. diff --git a/docs/ip/tiny-soc-r2-p3-review-fixes.md b/docs/ip/tiny-soc-r2-p3-review-fixes.md index cc06926b..2c0568af 100644 --- a/docs/ip/tiny-soc-r2-p3-review-fixes.md +++ b/docs/ip/tiny-soc-r2-p3-review-fixes.md @@ -3,6 +3,9 @@ This record covers the two P1 findings approved for follow-up on `bab01352f8f7c80e39546b04e3ed37a3d927f475` plus the existing P3 worktree. It does not approve Mini fabric enablement or advance TINY-R2-P4. +Earlier sections retain their original development status and attribution; +the source-bound campaign below records completed validation on `3e46cc8`. +The final section separately records the subsequent two HAL race fixes. ## Retained Tiny Icarus finding: closed @@ -182,6 +185,10 @@ STA observations qualify a new physical operating point. ## Compiler-matrix execution corrections (2026-10-09) +The intermediate snapshots below are historical. The final source-bound results +later in this record supersede their in-progress status without relabeling any +failed attempt or earlier artifact. + The initial flat-image collector could not read `_stack_point` from its symbol listing. Flat placement now falls back to the retained linker map's explicit `PROVIDE (_stack_point = .)` value; the exact SRAM endpoint and 4 KiB stack @@ -286,3 +293,280 @@ the selected target's successful simulation checks. This behavioral-only run provides no Mini synthesis, netlist, STA or physical evidence. Mini lint baseline differences remain non-blocking observations, separately retained in the `56f28cd9230c` variant's `meta/rtl-lint-warnings.json`. + +## Final source-bound P3 matrix (2026-10-10) + +After the external style commit and the publication/configuration corrections +settled, the final campaign was run against commit +`3e46cc80e2d2243233d284b7cd9583714b0773bb`, source digest +`a7fc5112cd6f20df956243c4d081a6a31ede6192cd270e3c13340557b0e58776`. +Its master record is +`build/ics55-tiny-performance-2026-10-09-17-10-53cc6674d532/meta/tiny-r2-p3/matrix/attempt-3jy5g5vo/record.json`; +the final report is in the same root at `meta/tiny-r2-p3/report.json`. + +All eight cases pass image collection, normal Tiny acceptance and three cold +Verilator runs. The selected native Icarus cases also pass three cold runs: + +- `compat-os-flat`: 7464.762, 7891.159 and 7428.430 seconds; +- `perf-o2-lto-flat`: 5841.742, 5890.497 and 6149.375 seconds; +- `perf-o2-plain-banked`: 5951.447, 6345.716 and 5964.039 seconds. + +The collector validates Tiny `TEST_STATUS`/`SIM_TEST_PASS`, forbidden markers, +matched retained image/model/tool artifacts, source digests, three repetitions, +and cross-simulator sample identity. `report.json` remains +`measurement_complete`; the ledger's human phase gate is still required. + +The earlier 09-25, 15-10 and 16-45 campaigns remain historical: source changes +during execution included the `352b200` style commit, the configuration/publication +repairs, and the performance profile's trailing-newline change. Their raw passing +samples and provenance failures are retained and are not relabeled as final +evidence. The final campaign uses the complete committed corrections in `3e46cc8`. + +### Current validation and commands + +The tested primary platform is TINY/ICS55, external SAFE24 with HAVE_PLL=YES +but the PLL parked off. CPU and the existing 128 KiB main SRAM remain at SYS24. +The IHP130 compatibility profile keeps no PLL. Dependencies, warning baselines, +metrics policy, hardware integration and the frozen specification blob +`7e19de49d49efaafacccfa85671e314d53dd15a4` are unchanged by this validation. + +After activating `.cache/retrosoc/development/tiny-r2-p1/activate.sh`, the matrix +and final report commands were: + +```sh +make CONFIG=configs/benchmark/ics55-tiny-performance.mk SOC=TINY PDK=ICS55 \ + BUILD_TIMESTAMP=2026-10-09-17-10 SOC_SIM_TIME=21600 JOBS=3 \ + TINY_P3_RESUME=NO tiny-r2-p3-matrix +make CONFIG=configs/benchmark/ics55-tiny-performance.mk SOC=TINY PDK=ICS55 \ + BUILD_TIMESTAMP=2026-10-09-17-10 tiny-r2-p3-report +``` + +The current full Pytest run reports **1629 passed, 1 skipped** in 4200.10 seconds. +The one skipped PDF inspection test requires `pypdf`, absent from this environment; +it is not a hardware or simulation pass. Its command, complete log and result are +in the matrix root under `meta/tiny-r2-p3/quality/pytest.{log,json}`. The previous +4 failures and 94 fixture errors remain in their old logs; they are resolved by +the configuration declaration and publication/source-fixture corrections in +`3e46cc8`. The focused correction check also passed 149 tests with the same one +PDF skip. No PDF or publication source revision was advanced by these checks. + +`ruff check .`, dependency-lock validation and `git diff --check` pass. The +current C format/policy/host and RTL format/style/readiness checks pass; their +exact Make command and outputs are retained under +`meta/tiny-r2-p3/quality/policy.{log,json}`. These are the repository's mechanical +quality checks, not complete MISRA certification; no new deviation is recorded. + +Regression dry-runs selected TINY explicitly for both PDKs and MINI with +`--behavioral-only`. Tiny nightly resolves to the same commands as PR, so it was +not executed a second time. The final executions were: + +```sh +BUILD_TIMESTAMP=2026-10-09-19-55 python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55 +BUILD_TIMESTAMP=2026-10-09-19-56 python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 +JAVA_TOOL_OPTIONS=-Dsbt.server.forcestart=true BUILD_TIMESTAMP=2026-10-09-19-57 \ + python3 scripts/regress.py --root . --suite pr --soc MINI --pdk ICS55 --behavioral-only +``` + +All three exit zero. Current evidence roots and results are: + +| Scope | Evidence root below `build/` | Result | +| --- | --- | --- | +| Tiny ICS55 PR | `ics55-tiny-2026-10-09-19-55-42d5beccb726/` | `meta/tiny-r2-p3/regress-ics55.{log,json}`; behavioral/JTAG, netlist boot, synthesis and STA commands pass | +| Tiny IHP130 PR | `ihp130-tiny-2026-10-09-19-56-bff91e3529ba/` | `meta/tiny-r2-p3/regress-ihp130.{log,json}`; same target-specific gates pass | +| Mini ICS55 behavioral-only | `ics55-2026-10-09-19-57-d1cacf6d3b62/` | `meta/tiny-r2-p3/regress-mini.{log,json}`; SDRAM Verilator and Icarus assembly boot pass | + +Each Tiny PR's HAVE_SVA=YES Verilator acceptance has its separate configuration +variant, identified in the retained command/log. Mini's SDRAM run uses +`dc24d22ad53d` and assembly boot uses `ec9e1f2e9c2c` at the same 19-57 timestamp. +Mini behavioral-only supplies no synthesis, netlist, STA or physical evidence. + +The additional IHP130 performance/banked normal-firmware check also passes: + +```sh +make CONFIG=configs/ci/ihp130-tiny.mk SOC=TINY PDK=IHP130 \ + BUILD_TIMESTAMP=2026-10-10-00-13 SW_ISA_PROFILE=TINY_PERF SW_OPT=O2 SW_LTO=NO \ + LINK_TYPE=ld2_tiny_banked SIMU=VERILATOR firmware sim +``` + +Its root is `build/ihp130-tiny-2026-10-10-00-13-8648aa4845d9/`, with the exact +command/result at `meta/tiny-r2-p3/performance-banked.{log,json}` and strict Tiny +simulation check at `sim/verilator/result-sim-check.json`. This is normal +firmware compatibility; it is not an additional IHP130 compiler measurement matrix. + +The ICS55 platform auditor captured inputs before this execution and successfully +produced `meta/tiny-ics55-p1/report.json` in the actual APP=ci_smoke variant. This +reuses the platform audit under P3; its original phase label is unchanged. Metrics +were explicitly collected in each actual synthesis/STA variant rather than the +regression runner's final default-bringup observation directory: + +| TINY PDK | Cells | Reported library area | Max WNS (ns) | Max TNS (ns) | +| --- | ---: | ---: | ---: | ---: | +| ICS55 | 197677 | 1786686.260799 | -41.14 | -508669.53 | +| IHP130 | 208547 | 9109320.296998 | -94.28 | -1594880.00 | + +Both min WNS/TNS values are zero in these smoke reports. Full reports are at +`sta/opensta/timing_metrics.rpt` and `meta/metrics.json` within the corresponding +APP=ci_smoke roots. Library area is not placed/routed die area; negative margins +remain unclosed. No 96/192/240 MHz operation, routed/PVT signoff or physical +qualification is claimed, and no half-rate main-SRAM fallback is introduced. + +Lint comparisons remain non-blocking observations: ICS55 Tiny has 459 new +signatures relative to its baseline, IHP130 Tiny 403, and Mini ICS55 1536 new / +118 resolved; all three have zero increased signatures. Counts are not attribution +of every warning to P3. The reports are in each timestamp's lint variant +(`303f95040df3`, `2dc2e5c747e5`, and `56f28cd9230c` respectively), under +`meta/rtl-lint-warnings.json`. Baselines were not edited. + +### Software measurements and remaining boundaries + +Each row below uses the retained P3 workload (1024 words, four jobs, 4096 CPU +iterations, seed `0x12345678`). Cycle counts are raw measured windows; the report +also retains the separate empty-window calibration, retired instructions, bus +observations and CPI. These are different compiler/layout binaries, not a +matched-binary architectural speedup claim. + +| Case | BIN bytes | CPU cycles | Copy cycles | Burst-16 DMA cycles | Chunk-256 cycles | Observed stack bytes | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| compat-os-flat | 11132 | 98682 | 164096 | 18881 | 511389 | 692 | +| perf-o2-plain-flat | 10148 | 73951 | 127157 | 16520 | 373812 | 648 | +| perf-o2-lto-flat | 8592 | 73974 | 127180 | 13980 | 339167 | 552 | +| perf-o3-plain-flat | 10404 | 73951 | 127157 | 16058 | 372690 | 628 | +| perf-o3-lto-flat | 9872 | 73974 | 127180 | 13980 | 339167 | 564 | +| perf-os-plain-flat | 8000 | 74064 | 139487 | 18421 | 394248 | 692 | +| perf-os-lto-flat | 6360 | 74064 | 139484 | 14169 | 343769 | 584 | +| perf-o2-plain-banked | 10184 | 73954 | 114875 | 16523 | 374016 | 648 | + +Compare compatible Os with performance Os to hold optimization/layout fixed; +compare performance O2 with/without LTO to isolate that selection; compare the +two O2/no-LTO rows for placement. BIN bytes exclude BSS, and observed stack use +does not prove a static upper bound. Each image reserves at least 4096 stack +bytes. Bank conflicts remain unavailable because P4 hardware is not implemented. +The original P1 binaries and their historical phase/source identities are retained. + +Current Mini PIO compatibility is additionally retained at +`build/ics55-2026-10-09-20-00-d0c568d0142e/meta/tiny-r2-p3/pio-retry/evidence.json`. +The initial default 180-second attempt emits SIM_TEST_TIMEOUT despite emulator +exit zero, and fails the simulation checker. A retry of the identical model and +BIN with `--fast-flash -t 1800` passes in 323.930 seconds, including 4/16/33/65-word +frames, IRQ count 5, exact waveform/reset-low checks, underflow/abort recovery, +and observer totals `frames=6 completed=4 underflows=1 aborts=2 dma_starts=0`. +The evidence records the before/after model and BIN hashes, command, manifest, +compile/run logs and a post-build recheck of 487 compiled input sizes/mtimes. +It is explicitly a post-build audit; neither the failed timeout nor the original +unsupported Mini DMA attempt is relabeled as a passing run. + +Product-integrated WS2812 DMA/IRQ service budgets remain deferred under approved +option A to an actual supported route, including Tiny R2-P6. Full-product, +physical, silicon and publication-PDF qualification remain outside these results. +Human review and acceptance of TINY-R2-P3 are required before authorizing any +R2-P4 implementation; this execution does not automatically advance a phase. + +## Subsequent P1 HAL race fixes (2026-10-10) + +This approved follow-up starts from `3e46cc80e2d2243233d284b7cd9583714b0773bb` +and preserves the preceding uncommitted documentation/evidence updates. The +specification remains blob `7e19de49d49efaafacccfa85671e314d53dd15a4`. +Only `crt/src/hal/dma.c`, `crt/src/hal/ws2812.c` and +`tests/c/test_runtime.c` change executable/test source. No public interface, +register, RTL, profile, dependency, warning baseline or metric policy changes. + +- WS2812: completing between INTR_STATE and STATUS reads no longer produces a + false I/O error. Idle without an observed sticky DONE retains ownership and + returns to the caller; the next bounded service checks error, abort, deadline + and completion again. No refill is admitted to an idle core. DMA response + draining and source-buffer lifetime rules are unchanged. +- DMA: one outer interrupt save/restore spans legacy TCD configuration, + descriptor publication and START, including ownership rejection. Nested HAL + helpers preserve the saved mask; the blocking completion wait is outside this + critical section. Test-only IRQ hooks model a pending ISR at mask restoration + and do not appear in target builds. +- Deterministic production-HAL host tests cover an ISR trying to lease the + channel during TCD publication, rejection without modifying an existing lease, + an initially masked caller, completion between WS2812 register reads, delayed + sticky DONE, genuine error/abort and expiry while idle. Existing final-B drain, + timeout ownership and 16-word FIFO admission tests remain enabled. + +New evidence is rooted at +`build/ics55-tiny-2026-10-10-09-00-b4aa59f3e5d9/meta/tiny-r2-p3-races/`. +The timestamp is a fixed build label; each structured result records actual UTC +start/end times. `source.json` records the base revision, dirty state, locked +tool identities, input hashes, source archive and worktree patch. Its combined +ICS55/IHP130 source/test/spec digest is +`dfa171fc1adbdfc5c7c9d31ef4ac7ba1a66710caac0fb13b869ddac89186b2f7`. +This digest's input set differs from the earlier compiler-matrix input set. + +Before fixes, `host-red.{log,json}` records host test exit 10 (TCD race). +After the TCD fix alone, `host-ws-red.{log,json}` records exit 11 (WS2812 race). +Both fail the enclosing Make command. `host-green.{log,json}` passes after both +fixes; `policy.{log,json}` additionally covers the final error/abort/deadline and +interrupt-mask cases, C formatting and policy checks. `focused.{log,json}` has +156 passing tests, including P3, Tiny baseline, DMA/register parity, WS2812 RTL +and publication API behavior. No new MISRA deviation is introduced. These are +partial project checks, not complete MISRA certification. + +Commands run with the existing locked environment: + +```sh +source .cache/retrosoc/development/tiny-r2-p1/activate.sh +make sw-format-check sw-policy-check sw-host-test +python3 -m pytest -q tests/test_tiny_r2_p3.py tests/test_tiny.py \ + tests/test_tiny_r2_baseline.py tests/test_dma.py \ + tests/test_dma_register_parity.py tests/test_ws2812.py \ + tests/test_publication_api_bundle.py +ruff check . +git diff --check +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55 --dry-run +python3 scripts/regress.py --root . --suite nightly --soc TINY --pdk ICS55 --dry-run +python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 --dry-run +python3 scripts/regress.py --root . --suite pr --soc MINI --pdk ICS55 --behavioral-only --dry-run +BUILD_TIMESTAMP=2026-10-10-09-00 JOBS=3 JAVA_TOOL_OPTIONS=-Dsbt.server.forcestart=true \ + python3 scripts/regress.py --root . --suite pr --soc TINY --pdk ICS55 +BUILD_TIMESTAMP=2026-10-10-09-00 JOBS=3 JAVA_TOOL_OPTIONS=-Dsbt.server.forcestart=true \ + python3 scripts/regress.py --root . --suite pr --soc TINY --pdk IHP130 +BUILD_TIMESTAMP=2026-10-10-09-00 JOBS=3 JAVA_TOOL_OPTIONS=-Dsbt.server.forcestart=true \ + python3 scripts/regress.py --root . --suite pr --soc MINI --pdk ICS55 --behavioral-only +JAVA_TOOL_OPTIONS=-Dsbt.server.forcestart=true make CONFIG=configs/ci/ics55.mk \ + SOC=MINI PDK=ICS55 APP=ci_smoke WS2812_P3_ACCEPTANCE=YES HAVE_CSR=YES \ + LINK_TYPE=ld2_all_sram SIMU=VERILATOR VERILATOR_SIM_ARGS=--fast-flash \ + SOC_SIM_TIME=3600 BUILD_TIMESTAMP=2026-10-10-09-00 JOBS=3 firmware sim +``` + +Regression dry-runs passed; Tiny PR and nightly select identical commands, so +the long run is not duplicated. All three regression executions passed once; +their exact commands and verdicts are retained as +`regress-{tiny-ics55,tiny-ihp130,mini-ics55}.{log,json}` in the new evidence root. +Durations are 3210.491 seconds (Tiny ICS55), 2205.505 seconds (Tiny IHP130), +and 3170.591 seconds (Mini ICS55 behavioral-only). Mini PIO passes once in +423.766 seconds including build, with 4/16/33/65-word frames, five IRQs and +observer totals `frames=6 completed=4 underflows=1 aborts=2 dma_starts=0`. +Its variant is `build/ics55-2026-10-10-09-00-d0c568d0142e/`. + +`audit.py` and `evidence.json` retain the final artifact audit: all 1287 captured +source/test/spec inputs are unchanged, 487 Mini PIO compiled inputs retain their +recorded sizes/mtimes, and the firmware/model/log/result hashes are recorded. +The Mini model audit is explicitly post-build. Eight ordinary regression +simulations are independently checked against their executable success marker, +command exit and forbidden-error rules. All selected targets, including the +current Mini assembly fixture, require `SIM_TEST_PASS`; UART output is not used +to infer success. This evidence is in addition to the separate PIO audit. + +Current Tiny synthesis/netlist/STA results are in +`build/ics55-tiny-2026-10-10-09-00-42d5beccb726/` and +`build/ihp130-tiny-2026-10-10-09-00-bff91e3529ba/`. Both netlist checks pass; +STA command success does not mean timing closure. Observed max WNS/TNS remain +-41.14 ns / -508669.53 ns (ICS55) and -94.28 ns / -1594880.00 ns (IHP130), +with min WNS/TNS zero. Raw reports are `sta/opensta/timing_metrics.rpt`. +Lint baseline comparisons still fail as non-blocking regression observations: +459 new signatures for ICS55 Tiny, 403 for IHP130 Tiny, and 1536 new / 118 +resolved for Mini, with zero increased signatures. No baseline was changed. +Build logs also retain a roughly four-second generated-file clock-skew warning; +these were fresh variant builds, and the input audit passed. + +The old compiler/layout matrix, full Pytest and earlier timing observations stay +bound to `3e46cc8`; they are not reattributed to this follow-up. The compiler +matrix and full Pytest are not rerun for these isolated C changes (no build or +Python implementation changes). Product-integrated WS2812 DMA/IRQ budgets remain +deferred under option A; no Mini DMA-to-MMIO route is added. Human P3 acceptance +and separate approval are still required before R2-P4. Per the maintainer's +instruction, long checks default to a single execution; additional repetitions +need a concrete failure/inconsistency or an explicit frozen acceptance rule. diff --git a/docs/ip/tiny-soc-r2-shared-integration.md b/docs/ip/tiny-soc-r2-shared-integration.md new file mode 100644 index 00000000..9bff33b9 --- /dev/null +++ b/docs/ip/tiny-soc-r2-shared-integration.md @@ -0,0 +1,253 @@ +# Tiny R2-P6 shared integration + +Implementation in progress from `b3b30a72cd6ef5340c28e5044c96cd13258ec5a6`, +against specification blob `7e19de49d49efaafacccfa85671e314d53dd15a4`. +The maintainer approved the single-phase P6 preflight. No phase acceptance, +new operating-frequency claim, commit or publication promotion follows. + +Default `configs/ci/ics55-tiny.mk` remains SAFE24 with its PLL parked off; +`configs/ci/ihp130-tiny.mk` remains no-PLL compatibility. CPU and all 32 main +SRAM macros share SYS. Crypto adds its six private 4 KiB macros on the current +SAFE24 PCLK alias, without reducing the 128 KiB main aperture. + +## Product boundary + +The eight-channel DMA enables requests 0-8 and 12/13 (`0x31ff`). I2C1 requests +9/10, DVP11 and future requests stay unsupported. Default owners are UART0=0, +I2C0=1, camera reservation/explicit memory borrowing=2, serialized bulk=3, +Crypto=4/5 and audio=6/7. The application lease on channel 3 persists across +all WS2812 refill batches; CRC and XPI must acquire it only after release. +SPI16/17 remain outside the existing V2.0 selector ABI and are rejected by +the SDK. This does not claim hardware rejection of arbitrary raw extended +selector writes: the common DMA specification explicitly records their legacy +truncation and requires V2.2 identity/capability guards before SPI integration. + +The 20 independent APB branches occupy fabric targets 5-24. SRAM remains +0-3, XPI memory 4, and decode/protocol errors are 25/26. The original three +external owners remain; owner 2 is now the actual SDIO private DMA producer. +P5 response retention, write drain, fault routing and ownership are preserved. +The 21 system reset leaves exclude the independently released AUDIO reset; +neither an absent audio reference nor Crypto initialization holds CPU reset. + +Gen1 default GPIO routes are SDIO0-5, UART8/9, I2C10/11, audio12-17, +PWM18-25 (plus its frozen ALT1 outputs), WS26 and XPI chip selects29-31. +Unfrozen UART/I2C alternate inputs, camera, SPI and CLKOUT remain inactive. +The historical ASIC wrapper still has legacy dedicated UART/XPI-CS ports and +its old power-pad inventory. It is not a QFN64 binding. This explicitly +approved compatibility boundary leaves the complete package/power wrapper +to final physical implementation, without changing the frozen package table. + +## Shared correctness and software + +Tiny's shared DMA validator now accepts integrated streams and eight channels. +Known absent I2C1/SDIO1 and UART flow-control operations fail before MMIO. +SD/SDIO enumeration caps a protocol clock request at half the actual input +frequency; the existing low-level clock calculator still rejects invalid +requests. The bounded SDSC CSD multiplier uses a 32-bit shift factor inside +the unchanged 64-bit capacity calculation, avoiding an absent RV32 runtime +helper without replacing the locked GNU toolchain. +The SD Memory RCA extraction uses raw R6 frame bits 39:24; the preceding +31:16 selection incorrectly shifted the assigned card address by eight bits. + +The independent P6 I2S payload fixture reproduced an existing receive-bit +assembly defect in both simulators. Completion now follows capture of the +current sample's LSB at the LRCK boundary. Both widths preserve the existing +register ABI, FIFO/CDC topology and master-only integration. Shared Mini/APU +checks remain required; this is not an I2S slave extension. + +Adding partial external GPIO drivers exposed a locked-Verilator simulation +limitation: a packed `tri1` bus loses pulls on its otherwise undriven bits. +The original I2C test consequently saw released SCL/SDA low and timed out. +A minimal reproduction fails in Verilator and passes in Icarus; explicit +independent scalar board nets retain both weak pulls and driven readback in +both tools. Tiny's generated testbench bindings use those scalar nets and an +observation-only packed view. ASIC ports, pad cells, power counts and I2C RTL +are unchanged by this verification repair. + +The Tiny `HAVE_SVA=YES` flow now also emits `+define+HAVE_SVA`, which the +owned P4/P5 assertions use. Previously it only omitted the Common +`SV_ASSRT_DISABLE` define; focused fixtures explicitly enabled the owned +assertions but ordinary firmware flows did not. Mini flag behavior is unchanged. + +The first full integration reached Crypto but timed out before audio completion. +A smaller actual-SoC diagnostic isolated an I2S status load fault: Tiny's +AXI-to-APB bridge retained the preceding write strobes on reads. Read PSTRB is +now zero as required by APB4, while writes retain their byte lanes. A directed +write/read sequence checks full/partial writes with a strict receiver. +The Tiny firmware limits exception skipping to its explicit CPU probes; +unexpected later load/store faults fail immediately instead of being masked. + +Audio shutdown keeps the consumer running while aborting/draining its leased +TX DMA session. A held stream beat cannot retire into an already stopped/full +audio FIFO. Only after DMA retirement does the acceptance image disable I2S +and flush. After a missing-clock flush timeout it polls the outstanding command +through clock recovery instead of issuing another protected COMMAND write. + +SDIO's reused card model originally required manual enumeration/data stimulus; +its default ACMD41 never reports READY. The command trace confirmed the +initialization loop. An opt-in SDHC response profile and the existing explicit +data API now support the Tiny board fixture while preserving standalone defaults. +The fixture checks wire-command/data-policy agreement and drives only external +pins. Card timing/protocol/electrical qualification remains excluded. The native +Icarus parser also needs an equivalent explicit enum branch in this model. +Tiny Icarus builds now retain separate testbench/header dependencies, including +the shared card model and P6 board include. Its serial observer emits measurement +records on ordinary runs; TEST_STATUS and strict error checks remain authoritative. + +The 12 MHz command case exposed a running-clock start-bit duplication in the +shared SDIO sender. Output admission now waits for its first scheduled launch; +the independent pad sampler covers both admission phases. The card sampler +retains the complete frame across host OE release, including the pulled-high +end bit. This preserves the command/register ABI and does not extend card modes. + +The full four-lane SDIO block exposed a separate transmit pause defect: when +memory delayed the next word, a late clock stop corrupted its first bit +(`0x57` became `0xD7`) and caused CRC rejection. The shared data engine now +requests the pause before that launch; the clock wrapper honors it while +draining the current high half-cycle. A full 1024-byte fixture with read-beat +gaps checks payload, CRC and descriptor retirement. Its scalar three-state +variant also caught card-model assumptions about unused lanes being zero; +token and partial-tail checks now account for their pulled-high state. + +The receive roundtrip isolated premature descriptor completion after one +64-byte write burst. SDIO private DMA retains ownership until every payload +burst retires or an error is reported. A separately reproduced receive stop +delay shifted the next word (`0x13579BDE` to `0x26AE37BD`); immediate stop +qualification prevents the extra launch. The card's read-CRC sequencer also +skipped one bit and is corrected independently. Delayed descriptor/payload +tests check full readback, CRC, descriptor retirement and both simulators. +These repairs add no masters, channels, descriptor fields or operating modes. + +## Validation and historical publication + +Workloads keep the ordinary Tiny boot, SRAM/DMA tail, trap, interrupt, RTC, +I2C NACK, JTAG and watchdog-reset checks. P6 adds real GPIO8/9 UART echo; +eight-channel/unsupported-request checks; RNG qualification rejection and IRQ; +CRC DMA, empty input and byte tails; Crypto initialization, AES/SHA/public RSA, +channels 4/5 and zeroization. Audio uses the fixed 18.432 MHz external reference +and 48 kHz master presets, checks 256 stereo frames at both 16 and 24 bits, +and drives paced 33/65-word WS frames in each case. Channel 2 concurrently runs +bounded 256-byte jobs, then four-descriptor chains; channels 6/7 own audio. +The board independently decodes I2S/WS pins, checks FIFO/service bounds and +stops AUDIO low/high for recovery tests. SDIO uses 400 kHz enumeration/one-bit +PIO and 12 MHz four-bit DMA, transfers 1024 bytes in each DMA direction and +512 PIO bytes, overlaps a separate 1024-byte central-DMA job and checks IRQ, +CRC-error, timeout and recovery behavior. + +MCYCLE/MINSTRET measure the audio/WS service loop after prefill; elapsed CLINT +time also includes its bounded preparation. The independent AXI observers count +handshakes over the complete firmware run, including boot/control accesses; +they are not payload-only bandwidth measurements. Write counts mean accepted +strobes, not rollback guarantees. Descriptor fetch bytes, per-owner AR/AW/W/R/B +backpressure, WS service gaps and actual SDIO/DMA overlap with CPU progress +remain explicit. `scripts/tiny_r2_p6.py` retains ELF/hex/bin/map, linker/generated +software inputs, simulator binaries/filelists, manifests, logs and their hashes. +Both simulators on both PDKs must have successful commands, Tiny's terminal +`SIM_TEST_PASS`, all workload/independent-observer records and no forbidden +error marker. Component diagnostics never satisfy that collector. + +The current Tiny PR and nightly dry-run command matrices are identical for +each PDK. After focused/quality checks, execute full Pytest once and one full +PR per PDK in independent build directories; these broader gates may run in +parallel but must all finish before acceptance. Collect warnings/metrics with explicit `APP=ci_smoke`, +`SYNTH=YOSYS`, `STA=OPENSTA` and the same build timestamp. Do not rerun an +identical nightly matrix. Shared I2S and SDIO standalone/formal checks plus the +affected Mini firmware path remain separate compatibility evidence. Core STA +must execute and bind all 38 exact macros; its slack remains observational. +The legacy IHP130 LibreLane floorplan generator still places the 32 main macros +only. It has not placed/connected the six private Crypto macros or implemented +the final QFN64 wrapper. Full-chip APR/PDN/package work remains outside this +campaign; neither its old floorplan nor smoke STA establishes P6 physical closure. + +Development artifacts begin at the manually selected `2026-10-12-12-00` +build label, with subsequent diagnostic labels recorded in each command. +Those labels are not wall-clock measurements; JSON start/end times are. +Some diagnostic firmware directories were reused while finding defects and +must not be rebound to later binaries. The `11-42` snapshot's Make-format gate +failed before long validation. The fresh `2026-10-12-11-50` campaign retains +its input snapshot under `build/ics55-tiny-2026-10-12-11-50-2eb98c0e1fae/meta/tiny-r2-p6/`. +Failed attempts retain their own verdicts and are not acceptance evidence. + +Both original PR attempts passed complete Verilator/JTAG acceptance and then +stopped at native Icarus elaboration: `s_token_lanes` referenced `s_data_bytes` +before its declaration. The declaration relocation changes no expression or +other model byte. `verification-update.json` retains the second snapshot; +the collector checks that exact equivalence and rejects any DUT, firmware, +constraint, tool or other model-behavior change. Only the card model and its +collector/tests differ. Original Verilator and Mini results retain their +original source identity. Native Icarus and the remaining physical smoke stages +resume from the failed step; the original failed PR verdicts are not rewritten. +Full Pytest retains its original collection, with the equivalent model edit +during execution; the updated 48-case P6 suite separately covers the native +parser and fail-closed provenance checks. Counts overlap and must not be added. + +The one full Pytest attempt reports 1634 passed, one optional `pypdf` skip, +five failures and 60 fixture errors. These reduce to two source-consumer issues: +Mini's publication parser counted inactive Tiny enum branches as Mini channel +allocations, and the commercial Tiny overlay retained its pre-P6 two-domain +allowlist. The parser now selects and checks Mini's actual bulk aliases; no book +identity, chapter, PDF or historical evidence changes. The commercial policy +and Tcl inventory now include the canonical AUDIO root; no licensed flow or +qualified I/O run is implied. `consumer-update.json` binds this separate scope +and rejects changes to RTL, firmware, models or smoke constraints. + +Every one of the 65 original failed/error nodes passes in the explicit repair +selection. The complete affected publication/commercial suite reports 512 passed +and one optional PDF-library skip; 35 selected provenance tests also pass. +These counts overlap the full attempt and each other. There is no claimed clean +full-suite rerun: unaffected long tests were retained with their original scope. + +### Completed Verilator observations (partial campaign) + +`verilator-observations.json` and `report-functional-before-physical.json` bind +the following values to successful command/log artifacts. Each row verifies 256 +stereo frames and two WS frames, with 64 completed background jobs. These values +are functional observations, not phase acceptance or timing closure. + +| PDK | Audio bits | Service-loop cycles | Retired instructions | Elapsed including preparation (us) | +| --- | ---: | ---: | ---: | ---: | +| ICS55 | 16 | 141917 | 41351 | 8044 | +| ICS55 | 24 | 138353 | 40283 | 7892 | +| IHP130 | 16 | 141917 | 41351 | 8065 | +| IHP130 | 24 | 138350 | 40283 | 7903 | + +The independent WS service-gap maxima are 2375/2374 SYS cycles +(98.96/98.92 us), below the 4800-cycle budget; FIFO peak occupancy is 15/16. +Both runs observe 4124 cycles of actual SDIO/private-DMA versus central-DMA +overlap, with 1185 CPU instruction-fetch handshakes in that interval. SDIO's +external owner reads 1056 bytes and accepts 1032 write bytes, including its +descriptor fetch/writeback. Central-DMA descriptor fetches total 16384 bytes. +No cross-architecture speedup or physical rate is inferred from these samples. + +Both complete Icarus runs subsequently pass: ICS55 at 16036387 cycles and +IHP130 at 16035743 cycles, with the +same workload, WS, contention and bus observations as its Verilator run. +Its UART observer leaves a partial human-readable line before the testbench's +terminal `SIM_TEST_PASS Tiny cycles=...` record. `collection-update.json` +records a collector/test-only correction to accept that exact terminal token +without requiring a fresh line; JTAG-only/Mini markers, malformed cycle values +and forbidden errors remain rejected. Forty focused collection/provenance +checks pass. Raw logs, RTL, firmware, models and constraints are unchanged. + +Both netlist boot checks pass at 30030 cycles. Yosys synthesis and OpenSTA run +for both PDKs and report the exact 32 main plus 6 private Crypto macro bindings. +Observed top-cell/area values are ICS55 412515/2461020.464702 and IHP130 +433861/13320370.152627. Setup/hold remain negative: ICS55 WNSmax/WNSmin +−84.93/−0.31 ns, IHP130 −165.41/−0.51 ns. These are smoke-flow observations; +the frozen phase does not use synthesis slack as an intermediate blocking gate. +Warning checks retain new Yosys/Verilator/RTL-lint and IHP130 Icarus signatures +as non-promoted observations under the existing policy; no baseline was edited. + +The old Tiny publication remains frozen at its advertised technical revision +`9c706ba0ff1da1028c299cfff573010a86dfd7ce`. Tests execute the original +publication adapter/test snapshot `2805ea91833a6bede32f3a82df3d17d20eb5ec77` +and, separately, the later P5 contract snapshot `b3b30a72cd6ef5340c28e5044c96cd13258ec5a6`. +Neither snapshot becomes P6 publication evidence. Current P6 source must be +rejected by the historical collector. The reset mutation checks still run +against the current target RTL. No frozen chapter, reviewed SHA or old PDF is +rewritten; unavailable PDF/media inputs remain a separate publication gap. + +P7 RCU/variable clocks, PWM dependency upgrades, alternate-input ABI, slave +audio, PSRAM, DVP and later extensions remain outside this phase. Timing is +observational before complete-product qualification; exact macro/clock/source +binding and successful functional verdicts remain mandatory. diff --git a/docs/ip/tiny-soc-verification.md b/docs/ip/tiny-soc-verification.md index f95f9e08..4412e430 100644 --- a/docs/ip/tiny-soc-verification.md +++ b/docs/ip/tiny-soc-verification.md @@ -1,14 +1,486 @@ # Tiny MCU Verification Record -## TINY-R2-P3 implementation in progress (2026-10-08–09) +## TINY-R2-P7 source-reset integration contract (2026-10-14) + +The follow-up review identified a missing source-side reset proof, not a +reproduced product failure. The AXI bridge does not support arbitrarily +resetting an active source while leaving its slave session alive. Flushing +the FIFO cannot cancel a request already accepted by that slave. Production +Tiny prevents that sequence: DMA local reset requires engine, admission and +fabric drain before resetting the source; XPI's SYS source reset is a common +system reset that also resets its MEM endpoint. Neither production bridge +uses warm clear. These preconditions are part of the integration contract, +not a claim of unrestricted unilateral-reset support by the standalone bridge. + +`tests/rtl/tiny_p7_source_reset_tb.sv` uses the production DMA master, +admission, CDC, lifecycle controller and reset tree. The test extracts the +actual top-level instances, drain/block synchronizers and generated DMA/XPI +reset bindings, so a hand-copied reset policy cannot silently replace them. +An independent outstanding scoreboard survives local reset and rejects +premature reset. The AXI target is a controllable behavioral slave; unrelated +CPU/APB/SDIO work and the DMA completion mailbox are idle. This is a focused +integration test, not a full-SoC firmware or formal CDC/RDC signoff result. + +The scenarios cover stalled AR/AW, partial W, downstream W backpressure, +delayed R/B, source-held R, drain timeout without reset, post-reset reads and +writes, and common system reset during an outstanding read. The same binary +is reused at SAFE24, XIN48, XIN96, PLL192 and PLL240 domain ratios. They test +digital ordering only; the clock generators do not qualify a PLL or PDK rate. +A negative run falsifies both idle observations and must trip +`P7_SOURCE_RESET_UNDRAINED`; Icarus may return zero for sv2v's lowered fatal, +so the forbidden marker and missing PASS still make that simulation invalid. + +Evidence for this correction is retained under +`build/ics55-tiny-2026-10-14-06-00-ff4b5cef6eb5/meta/tiny-r2-p7-source-reset/`. +The first focused attempt's test-driver failure is retained in `focused-1.log`; +it is not a DUT failure or acceptance result. The final `directed.log` and +`result-directed.json` record **54 passed in 191.71 seconds**. This includes +ten positive source-reset runs (five ratios in each simulator) and two +expected-negative broken-drain runs inside the two new Pytest cases. +RTL format/style, clock/reset inventory, Ruff, both Tiny PDK PR dry-runs and +`git diff --check` pass. The verification-only source comparison is retained +in `verification-update.json`: only the two test files differ; 1728 other +technical inputs and tool identities match. The design, +firmware, configuration, dependencies and physical inputs are unchanged from +the `06-00` source snapshot; the new tests do not relabel earlier simulations +as new executions. Full Icarus firmware remains deferred to final Tiny +qualification. Human P7 acceptance is still required. + +## TINY-R2-P7 response conservation correction (2026-10-14) + +The earlier review recommendation to give abort unconditional priority over +source response retirement was incorrect. A cycle with an already exposed +normal B/R handshake followed by an abort produced two completions for one +request. Four read/write tests reproduced this in Verilator and Icarus. The +failed candidate, diagnostics and source archive are retained below +`build/ics55-tiny-2026-10-14-04-00-ff4b5cef6eb5/meta/tiny-r2-p7-response/`. +The `04-00` synthesis/netlist/STA results belong to that rejected candidate; +their successful boot does not close the protocol finding. + +The corrected bridge holds presented responses in source-owned registers. +Remote reset cannot revoke VALID or rewrite their payload under backpressure, +and a completed source handshake is never followed by a second completion. +Only missing read beats receive DECERR, with RLAST at the original burst end; +a reset write consumes the remaining W beats before emitting B. Outstanding +tracking is one transaction per direction, matching the existing Tiny paths. +The payload reset uses the five-edge source link barrier. An unfinished warm +clear quarantines the bridge until a real destination reset; a FIFO clear +cannot by itself cancel remote slave state or authorize re-use of late replies. +Production DMA/XPI clear inputs remain tied low; no new software reset API, +autonomous AXI timeout, DMA owner, or later-phase capability is introduced. + +The new tests distinguish completed/held responses from missing responses, +cover simultaneous clear and response handshakes, remote reset with READY high, +backpressure, partial bursts and late-response quarantine. That campaign's +full directed selection passed 52 cases. Earlier 36-case and rejected- +candidate passes did not cover these failures and are not proof of the repair. +The failed same-cycle candidate and diagnostics remain in the response +evidence directory. +The final input snapshot is +`build/ics55-tiny-2026-10-14-06-00-ff4b5cef6eb5/meta/tiny-r2-p7-response/final/source-final.json`. +The `06-00` backend campaign is bound to the final source snapshot and all +requested post-fix gates pass. Its variants are +`build/ics55-tiny-2026-10-14-06-00-ff4b5cef6eb5/` and +`build/ihp130-tiny-2026-10-14-06-00-8232fa11e7f3/`. ICS55 +synthesis/netlist/STA durations are `2108.388/1655.980/43.594 s`, with WNS +max/min `-152.03/-0.31 ns` and TNS max/min `-5622052.50/-44.14`; IHP130 +durations are `1934.515/578.869/47.109 s`, with WNS max/min +`-253.43/-0.49 ns` and TNS max/min `-10035324.00/-153.37`. Netlist boot +emitted `SIM_TEST_PASS Tiny netlist boot` for both PDKs. These timing values +remain observational and do not qualify closure or an operating point. +Full Icarus firmware remains deferred by the maintainer to final Tiny +qualification. It is not a current P7 gate; protocol correctness and human +phase acceptance remain required. + +## TINY-R2-P7 final verification campaign (2026-10-13) + +The [P7 runbook](tiny-soc-r2-clock-reset.md) records the current implementation, +exact profile-specific commands, workload definitions and source snapshot. +The integrated design now drives actual SYS/MEM/PCLK consumers, CPU-only WFI +gating, local reset barriers, CLKOUT and the single ICS55 PLL backend. It keeps +the 32 main SRAM macros at CPU SYS and six private Crypto macros at PCLK. +Future target capabilities remain absent. No later phase is authorized here. + +Development validation includes 68 passing Tiny P7/SYSCTRL/P5 directed tests +and a later complete 52-case P7 directed pass, +plus both simulators' real Hazard3 IRQ/debug wake, clock-pause and retained-debug +warm-reset/aborted-command cases. RCU validation now rejects invalid or BUSY +staging writes with PSLVERR, and the CLKOUT waveform test exposed and corrected +enable/divisor ordering. HAL tests cover invalid, absent, busy, gated-reset and +sticky-timeout/error boundaries. These short checks complement the final long +flows; they do not replace full firmware or netlist acceptance. + +Source evidence is retained at +`build/ics55-tiny-2026-10-13-17-05-f3e42d54c18b/meta/tiny-r2-p7/source.json`. +The final command matrix and its retained corrections are listed in the runbook. +Default ICS55/IHP130 and external XIN48/96 P7 firmware pass in both simulators. +Each profile's cycle count and WFI/DMA/reset observations match across simulators. +Both watchdog-corrected full Verilator firmware runs pass the unchanged P6 +peripheral/wire/AXI collector and JTAG. The two corresponding full Icarus +replays were intentionally interrupted with exit 130 after the maintainer +requested that this long-running gate move to the final Tiny phase; their +partial logs and interrupted result records are retained, but they are not +acceptance evidence. +Both PDKs pass synthesis, structural macro binding and Tiny netlist boot. +Current core STA remains observational and negative, with the coverage limits +explicit in the runbook. These are historical campaign results; current phase +acceptance requires the response correction above and human review. The full +Icarus firmware gate belongs to final Tiny qualification. No high-rate physical operating point is +qualified by these functional simulations or SAFE24 core STA. + +## Historical TINY-R2-P7 CDC/reset correction follow-up (2026-10-14) + +This checkpoint predates the response-conservation correction and is +superseded by the `06-00` campaign above. It is not current-source acceptance. +The P7 review identified two source-bound gaps. The XPI fabric target and bridge source side release with +the SYS reset endpoint, while the XPI/MEM side retains its MEM leaf reset. The +Tiny AXI CDC bridge synchronizes destination reset loss, invalidates its +protected request/response state and blocks new source admissions. Its original +single-DECERR completion claim was insufficient for held responses and bursts; +the later response-conservation correction supersedes that claim. The existing DMA/XPI +instances retain their explicit bridge boundaries and the XPI bridge now uses +the split SYS/MEM reset mapping. + +The new `tests/rtl/tiny_axi4_async_bridge_tb.sv` passes under both Verilator +and Icarus for normal read response, reset-aborted read/write, DECERR, +no-replay and bridge recovery. The complete +`PYTHONDONTWRITEBYTECODE=1 python3 -m pytest -q tests/test_tiny_r2_p7.py` +selection eventually passed 52 tests after the later corrections; that count +does not belong to the `01-20` implementation. `make --silent rtl-format-check rtl-style-check-all` +and `git diff --check` pass. An ICS55 default-profile Verilator `comp` also +passes; its existing PDK and simulator warnings remain observations. The +checkpoint's synthesis, netlist boot and observational STA gates passed for +both PDKs. Its historical evidence is retained under +`build/ics55-tiny-2026-10-14-01-20-ff4b5cef6eb5/` and +`build/ihp130-tiny-2026-10-14-01-20-8232fa11e7f3/`. ICS55 reports WNS +max/min `-144.40/-0.31 ns` and TNS max/min `-4859636.50/-45.04`; IHP130 +reports WNS max/min `-257.81/-0.47 ns` and TNS max/min `-9807803.00/-100.68`. +These remain observational negative timing results, not closure or operating +point qualification. The long-running full Icarus firmware gate remains +intentionally deferred to the final Tiny phase; no physical flow was run. + +## Historical initial P7 checkpoint, superseded (2026-10-13) + +The following incomplete checkpoint belongs to earlier development snapshots. +Its unconnected/missing-path statements do not describe the final campaign. + +This checkpoint starts from committed source `43a3c8d453b6d41df3536719ebc6d1f4521bc7cd`, +specification blob `7e19de49d49efaafacccfa85671e314d53dd15a4`, Tiny/ICS55 +profile `configs/ci/ics55-tiny.mk`, and the locked PWM revision +`38efaf5e20f084a00148920087c60cadab61839b`. The dependency lock validator +passes with digest `263402a34f2e315ca46ef4bf6366aab64935bd8de7abab3e469f513e988b0208`. + +The implementation adds the Tiny-owned RCU register bank and handwritten +`rcu.h`/`rcu_regs.h`/`rcu.c` contract, reports SAFE24/domain-rate snapshots, +connects committed PCLK/SYS rate reporting to the upgraded PWM and SDIO +`CLOCK_ACTUAL` paths, and adds focused APB coverage for identity, capability, +profile rejection/commit, status and write-only command behavior. Verilator +and Icarus SYSCTRL focused tests pass; ICS55 Verilator and Icarus top-level +compilation, Tiny firmware linking, C format/policy/host tests and the SDIO +suite pass. Build logs are retained under the generated +`build/ics55-tiny-2026-10-13-09-00-f3e42d54c18b/` and +`build/ics55-tiny-2026-10-13-10-30-f3e42d54c18b/` roots. + +A Tiny-owned `tiny_clock_reset_subsystem` backend is now source-bound and +filelisted. Its SAFE24/no-PLL focused test passes in both simulators and covers +five-stage SYS/MEM/PCLK reset release, external-profile handling and rejected +PLL requests. A second focused test exercises the behavioral PLL lock-timeout +path in both simulators; it does not claim successful macro lock or frequency +qualification. The backend is intentionally not advertised or wired into the +CPU/fabric yet; its SYSCTRL profile/apply handshake is now connected, while +the backend instance is now present in the Tiny top and its SYS output clocks +the CPU, fabric, SRAM, AXI interfaces and system reset tree. XPI now crosses +from SYS to the backend MEM clock through the Tiny-owned warm-flush AXI bridge. +The RCU command checks CPU/fabric/target idle observations and the XPI bridge +before accepting a transition. PCLK outputs are still not connected to all +peripheral consumers. + +The focused command is +`PYTHONDONTWRITEBYTECODE=1 python3 -m pytest -q tests/test_tiny_r2_p7.py`; +its four cases passed. Supporting gates were +`make --silent rtl-format-check rtl-style-check-all`, +`make --silent sw-format-check sw-policy-check sw-host-test`, +`PYTHONDONTWRITEBYTECODE=1 python3 -m pytest -q tests/test_script_tools.py -k 'run_flow'`, +`ruff check scripts/run_flow.py tests/test_tiny_r2_p7.py`, and +`python3 scripts/dependency_lock.py --lock dependencies/dependencies.lock.json`. +The current ICS55 top-level compile used +`source .cache/retrosoc/development/tiny-r2-p1/activate.sh && CCACHE_DISABLE=1 make --silent CONFIG=configs/ci/ics55-tiny.mk APP=ci_smoke SIMU=VERILATOR HAVE_SVA=YES BUILD_TIMESTAMP=2026-10-13-16-30 comp` +and exited zero. These are source/compile/focused checks; none is a strict +full firmware terminal verdict or physical qualification. + +The long-run diagnostic also confirmed that the emulator itself exits normally +with a Tiny timeout at a bounded cycle count and uses about 26 MiB RSS. The +earlier `status=running` records lacked a verified terminal result. The initial +diagnosis attributed this to sandbox cleanup without sufficient evidence; +that conclusion is withdrawn. Separate tool invocations can have different +PID namespaces, so process absence in one does not establish that another +command died. `run_flow.py` now records `parent_pid` and `child_pid` +in the running result and writes a failed terminal result for unexpected +Python exceptions; long runs must remain attached to one polling session. + +This is not phase acceptance. The current RTL now uses the backend SYS clock +for the system/fabric path, but does not yet provide the required executable +MEM/PCLK clock consumers, full CDC bridges, WFI leaf gating, PLL lock/loss +recovery, or target-local five-cycle reset sequences. A full Tiny Verilator firmware +run was started but did not produce a terminal `TEST_STATUS`/`SIM_TEST_PASS` +verdict and was stopped; no physical, STA or netlist qualification is claimed. +The remaining P7 clock/reset implementation and strict terminal regressions +require a follow-up correction before human phase approval. + +## TINY-R2-P6 implementation complete, human review pending (2026-10-12) + +The maintainer approved the P6 preflight on +`b3b30a72cd6ef5340c28e5044c96cd13258ec5a6`, against unchanged specification blob +`7e19de49d49efaafacccfa85671e314d53dd15a4`. The +[P6 runbook](tiny-soc-r2-shared-integration.md) records eight-channel/shared-IP +integration, the explicitly retained historical ASIC wrapper and publication, +and the separate source-bound validation scope. Development diagnostics are +not final acceptance; no P7 or physical-frequency qualification is implied. + +Initial integration exposed a shared I2S sample-LSB assembly defect and a +Verilator packed-tri1 board-model limitation. The runbook records their narrow +corrections. Directed SDIO integration additionally exposed command admission, +transmit/receive pause and multi-burst descriptor-retirement defects, plus +manual card-model gaps; their independent reproductions are recorded there. +Both Tiny PDKs have complete Verilator and Icarus functional verdicts and +netlist boots. Yosys synthesis and OpenSTA run for both PDKs with the exact +32-main plus 6-private-Crypto macro bindings. The final audit is +`build/ics55-tiny-2026-10-12-11-50-2eb98c0e1fae/meta/tiny-r2-p6/audit-result-v3.json`; +the source-bound evidence is `evidence.json` in the same directory. The +functional report retains four simulator records, all workload/AXI/device +observations and the Mini compatibility result. + +Observed setup/hold slack remains negative: ICS55 WNSmax/WNSmin −84.93/−0.31 ns +and IHP130 −165.41/−0.51 ns. Warning checks report new signatures from the +existing policy's observation flows; no baseline or metrics policy was edited. +The full Pytest attempt remains recorded as 1634 passed, one optional `pypdf` +skip, five failures and 60 fixture errors; all 65 failed/error nodes pass in +the explicit repair selection, and the affected consumer suite passes 512 with +one optional skip. These overlapping counts are not a clean full-suite rerun. +Final human review, complete-product timing closure, package/QFN64 binding and +physical signoff remain pending. Earlier P5 results and failed P6 attempts +retain their original attribution. + +## TINY-R2-P5 P1 protocol correction; validated, review pending (2026-10-12) + +The maintainer requested correction of the reset write-drain/read-retention +failures and publication-binding mismatch. The implementation starts from +`4e6ab1a6fb445521703f0bfa412d3a1e8a38cd69`; the specification remains blob +`7e19de49d49efaafacccfa85671e314d53dd15a4`. The [P5 runbook](tiny-soc-r2-concurrent-fabric.md) +describes preserved upstream spill responses, complete read/error bursts, +write drain before B, latched downstream cancellation and first-error capture. +Profiles remain Tiny/ICS55 SAFE24 with parked PLL and Tiny/IHP130 compatibility. + +Current evidence is isolated under +`build/ics55-tiny-2026-10-11-23-35-b4aa59f3e5d9/meta/tiny-r2-p5-p1/`. +The older `09-30` campaign failed overall: 1669 Pytest passes, one skip, +two failures and ten errors (publication), plus independent R stability and +W-before-B violations. Its passing ordinary regressions, PPA and replay are +historical observations, not acceptance of the repair. + +Final focused validation passes 101 tests. Full Pytest passes 1685 tests, +with one optional `pypdf` skip and no failures; these counts overlap. Both +Tiny PDK PR regressions pass, including six strict simulation verdicts, +synthesis, netlist boot and the 32-macro/common-SYS binding check. Each +simulator passes all 28 reset scenarios, including three production DMA +drain cases; normal-target, arbitrary-target-reset and fault-collector +induction/SAT checks pass. Complete terminal/fault publication bindings and +three unsafe-completion mutation tests pass without changing the reviewed +publication SHA. RTL format/style/readiness, Ruff and diff checks pass. + +`source.json` and `evidence.json` bind 2011 technical inputs to digest +`f670789a2379a8d2e6c59f5ecd714ca0237bb6f1cfe749a2f29b0794d356b5f0`, +locked tools, exact command results and retained artifacts. No warning +baseline, metric policy, shared DMA/SDK implementation or public ABI changed. +The lint signatures/counts match the preceding `09-30` campaign; the existing +baseline comparison remains a non-blocking observation. STA remains unclosed: +max WNS is -70.04 ns ICS55 and -127.01 ns IHP130. The runbook records full +area/timing observations and limitations. Matched-image replay, full-function +C netlist qualification, PDF promotion and physical qualification were not +repeated for this bounded correctness repair. P5 human review/acceptance and +all P6+ work remain separate. + +## TINY-R2-P5 target-reset isolation repair; focused validation (2026-10-11) + +Superseded attempt: later protocol checks rejected its response flushing and +early B/RLAST behavior. The four focused passes below remain historically +accurate but were insufficient to establish reset isolation. + +Following the P5 review, the fabric now receives the explicit 23-entry target +reset vector. A reset blocks new admission and downstream handshakes, flushes +response spill state, and converts any accepted target session into a held +DECERR terminal response. The existing owner credit is released only by the +upstream RLAST/B handshake, preventing stale response replay or a permanently +busy direction. No target map, public register, DMA capacity, clock profile or +later phase was changed. + +The focused command +`PYTHONDONTWRITEBYTECODE=1 python3 -m pytest -q tests/test_tiny_r2_p5.py -k 'target_reset_isolation or concurrent_fabric'` passed 4 tests: both +Icarus and Verilator reset-isolation tests and both existing concurrent-fabric +tests. `make rtl-format-check rtl-style-check-all rtl-readiness-check-all` +also passed. This is a focused implementation check; the prior P5 campaign +reports remain source-bound to their recorded snapshots and have not been +rewritten or relabeled. The retained focused logs are under +`build/ics55-tiny-2026-10-10-18-58-b4aa59f3e5d9/meta/tiny-r2-p5/reset-repair-tests/`. + +## TINY-R2-P5 implementation; validated, review pending (2026-10-10) + +The maintainer approved the single-phase P5 plan on clean commit +`3131762b35e0ebfcd196395f955e7c1005bd18e7`, specification blob +`7e19de49d49efaafacccfa85671e314d53dd15a4`. The P4 input hashes match this +starting source, but earlier reports retain their original snapshot attribution. +The [P5 runbook](tiny-soc-r2-concurrent-fabric.md) records target-local ownership, +sixteen APB branches, reserved inactive SDIO owner 2, concurrent-fault accounting +and the unchanged public ABI/clock/memory boundaries. + +The completed campaign is under +`build/ics55-tiny-2026-10-10-18-58-b4aa59f3e5d9/meta/tiny-r2-p5/`; +the earlier `18-25` root retains development attempts. Full Pytest reports +1679 passed and one optional `pypdf` skip. Both Tiny PDK PR regressions pass, +including synthesis, netlist boot and clock/macro binding. The real CPU/DMA +fixture records 13 overlapping DMA read/write beat cycles, 27 independent control +completions, hart-reset isolation and abort drain on both simulators. Scoped +target-control/fault proofs and retained P4 latency/streaming checks pass. + +Initial image executions passed but aggregation correctly failed on an Icarus 13 +temporary-expression population-count defect. The only subsequent source change +is P5 opt-in testbench instrumentation. `source.json` retains the original +snapshot for the unchanged DUT/full-suite/ordinary-regression/PPA results; +`monitor-source.json` records the corrected monitor. Only affected image +measurements and monitor checks were repeated. `monitor-diagnosis.json` retains +the reproduction and original discrepancy; no comparison rule was relaxed. + +Final `report.json` has identical banked observations across simulators. Against +repaired P4, burst1 cycles change 91306→65527 flat and 89365→65502 banked; burst16 +changes 17897→16974 and 17317→15802. CPU/copy cycles are unchanged; small +contention/TCD/chunk256 increases remain explicit. The final combined input +digest is `0f3ed21f6fd6e932ff2532aab8c9964e71cf4b972e4c27e2c8ca6ab1f22c1015`. +`evidence.json` rechecks 2010 inputs and binds both snapshot scopes, six ordinary +simulations, final matched-image runs, 15 focused/formal artifacts and reused +unchanged Mini compatibility evidence. Library area grows 5.56%/6.95%; max WNS +is -64.75 ns ICS55 and -111.93 ns IHP130. Timing remains unclosed/observational. +The runbook records exact commands, source attribution, warning observations, +measurements and unrun qualifications. Human phase acceptance is pending. +Shared DMA/Mini/SDK behavior, dependencies and warning/metrics policy remain +unchanged. No P6 execution or physical-frequency qualification is authorized. + +## TINY-R2-P4 throughput repair; validated, review pending (2026-10-10) + +Following the read-only P4 review, the maintainer approved the bounded-buffer +repair plan for the P2 external-SRAM throughput regression. The changed Tiny +group/frontend keep local I/D single-operation semantics, reserve external +response capacity and overlap burst issuance/retirement; W is captured before +macro arbitration and B follows complete commit-response drain. Common FIFO +and register dependencies, ABI, clock/macro bindings and the pre-P5 external +attachment remain unchanged. No shared Mini debug code is changed by this fix. + +Fresh evidence belongs under +`build/ics55-tiny-2026-10-10-15-15-b4aa59f3e5d9/meta/tiny-r2-p4/`. +The preceding P4 report below remains the pre-repair comparison, and does not +qualify these new changes. The final audit rechecks 1972 technical inputs, +six fresh regression simulations and three immutable-image executions. Full +Pytest reports 1659 passed and one optional `pypdf` skip. Both Tiny PDK PR +regressions pass, including synthesis, netlist boot and clock/macro bindings. +Streaming/backpressure/error/reset checks and response conservation/fairness +induction pass; all six SRAM fixtures retain three-cycle local completion and +one-cycle steady read/write beat spacing. Initial style failures and focused +development attempts remain separate from final qualifying results. + +Matched burst16 DMA cycles fall from 36713 to 17897 (flat) and 36489 to 17317 +(banked), reductions of 51.25% and 52.54%. All banked observations match across +Verilator and Icarus. CPU/copy/burst1 cycles remain unchanged from pre-repair +P4; burst1 overhead and 8.34%/4.81% burst16 overhead versus original P3 remain +explicit. The combined input digest is +`58daf68fbb9dee199b3085a73ef07d22eb3de6d00ba875c462e85b76b6091a0d`. +`evidence.json` binds the source, artifacts, comparison and reused unchanged +Mini behavioral evidence. All long gates ran once; Tiny nightly matched PR +and was not duplicated. Setup WNS remains negative (-36.88 ns ICS55, +-97.37 ns IHP130), with warning/metrics observations and baselines preserved. +The [runbook](tiny-soc-r2-local-memory.md) records exact commands, measurements +and unrun qualifications. Human review remains required; no P5 execution or +physical-frequency qualification is implied. + +## TINY-R2-P4 implementation and validation; review pending (2026-10-10) + +The maintainer approved P4 preflight and implementation on clean source +`4db27524a6282ca40d76b935415b89f043d6ea83`, specification blob +`7e19de49d49efaafacccfa85671e314d53dd15a4`. The [P4 runbook](tiny-soc-r2-local-memory.md) +records architecture, lifecycle, physical binding and matched-image boundaries. +P3 results retain their original source attribution; no historical phase or +physical qualification is automatically promoted. + +Development evidence is under +`build/ics55-tiny-2026-10-10-10-05-b4aa59f3e5d9/meta/tiny-r2-p4/`. +All planned functional gates completed: 1659 Pytest passes and one optional +`pypdf` skip; Tiny ICS55/IHP130 PR and Mini ICS55 behavioral compatibility pass. +Both Tiny netlists pass boot and the four-group/32-macro/common-SYS binding +checks. Local uncontended latency is three cycles; group-control induction +passes. Setup and hold timing remain negative and observational. + +`report.json` retains single-execution matched-binary results; the banked image +matches exactly across Verilator and Icarus. CPU-copy cycles improve about +19%/25% for flat/banked images, while isolated burst-16 DMA cycles increase +about 2.2 times. This external-frontend throughput regression requires review; +no blanket speedup or automatic phase acceptance is claimed. `evidence.json` +rechecks 1972 source inputs and binds the gate, image, model, log and report +artifacts. Exact commands, measurements and unrun qualifications are in the runbook. + +External fabric stays serialized until P5, which is not authorized. Default +Tiny stays ICS55 SAFE24 with PLL off; IHP130 is explicit compatibility. + +## TINY-R2-P3 implementation in progress (2026-10-08–10) + +The retained source-bound compiler/layout matrix is complete for commit +`3e46cc80e2d2243233d284b7cd9583714b0773bb`, source digest +`a7fc5112cd6f20df956243c4d081a6a31ede6192cd270e3c13340557b0e58776`. +All eight configurations pass ordinary Tiny acceptance and three deterministic +Verilator cold starts. `compat-os-flat`, `perf-o2-lto-flat` and +`perf-o2-plain-banked` also pass three native Icarus cold starts, with exact +cross-simulator sample identity. The structured measurement report is +`build/ics55-tiny-performance-2026-10-09-17-10-53cc6674d532/meta/tiny-r2-p3/report.json`. +It records software measurements only; it does not close phase acceptance, +physical timing, hardware bank conflict or Mini integrated-DMA gates. + +Quality evidence for that committed source is complete: full Pytest reports 1629 passed and +one environment skip for missing `pypdf`; focused P3 tests and the embedded/RTL +quality checks pass. Its Tiny PR regressions pass for ICS55 and IHP130, and +Mini ICS55 behavioral-only compatibility passes. Negative STA margins and +non-blocking lint observations remain recorded as observations. + +Exact commands, measurements, current evidence roots and remaining qualification +limits are in the final section of the [P3 review-fix record](tiny-soc-r2-p3-review-fixes.md). +Human review and acceptance remain required before any R2-P4 work. + +### Subsequent P1 race-fix follow-up + +The approved follow-up fixes WS2812 completion between register reads and +legacy TCD publication across an ISR lease acquisition. Current source is +`3e46cc8` plus the two HAL fixes and deterministic host tests; the matrix above +remains evidence for the original commit. New evidence is under +`build/ics55-tiny-2026-10-10-09-00-b4aa59f3e5d9/meta/tiny-r2-p3-races/`. +The two new tests fail before their respective fixes and pass afterward; +C format/policy/host checks and 156 focused tests pass. Mini PIO compatibility +also passes once with strict SIM_TEST_PASS and no forbidden markers. Tiny +ICS55/IHP130 PR and Mini ICS55 behavioral regressions each pass once. Final +`evidence.json` checks all 1287 source inputs, 487 Mini PIO compiled inputs and +all eight ordinary regression simulations. Lint baseline differences and +negative STA remain observations; no timing closure is claimed. See the final +review-fix section for commands, hashes, exact paths and unrun gates. + +Per the maintainer's follow-up instruction, long checks default to one execution; +repeat only for a concrete failure/inconsistency or an explicit acceptance +requirement. This follow-up does not repeat the three-cold-start compiler matrix. +It does not waive any frozen qualification requirement or reattribute old runs. + +### Historical development snapshot The 2026-10-09 execution snapshot in the [P3 review-fix record](tiny-soc-r2-p3-review-fixes.md) records all eight ordinary Tiny/Verilator compiler experiments completed, with -selected Icarus groups still running. The current-source full Pytest result is +selected Icarus groups still running at that snapshot. The then-current full Pytest result was 1531 passed, 4 failed, 94 fixture errors and 1 skipped; configuration-declaration -and publication source/fixture drift remain open. IHP130 Tiny PR completes with +and publication source/fixture drift were still open. IHP130 Tiny PR completed with non-blocking lint observations and negative STA margin. These results do not -close the remaining matrix, quality or human phase-acceptance gates. +replace the final results above or imply human phase acceptance. The [P3 review-fix record](tiny-soc-r2-p3-review-fixes.md) closes the retained banked O2 Icarus finding: all three runs completed successfully and match the @@ -28,8 +500,8 @@ is bound at underflow/abort recovery, zero DMA starts and strict TEST_STATUS/SIM_TEST_PASS. Focused checks report 96 passed; C/RTL quality, Ruff and diff checks pass. The old Mini DMA failure remains failed/unsupported evidence, not a PIO pass. -The full compiler/placement matrix, broader current-source regressions and human -phase acceptance remain pending. See the review-fix record for exact commands, +The full compiler/placement matrix and broader regressions were pending in this +initial record; human phase acceptance remains pending. See the review-fix record for exact commands, artifacts, historical attribution and unrun gates. The user explicitly approved the P3 preflight and implementation on source @@ -42,9 +514,9 @@ Original phase IDs, historical evidence and qualification limits are retained. The [P3 runbook](tiny-soc-r2-software-scheduling.md) defines compiler/layout experiments, shared finite DMA/WS2812 service, ownership and exact commands. -Implementation and validation are in progress; this entry does not claim -phase completion or performance improvement. The existing GNU toolchain is -used first, as requested; no dependency version has been changed. +The initial entry recorded implementation and validation in progress; it did not +claim phase completion or performance improvement. The existing GNU toolchain +was used as requested; no dependency version has been changed. Early development testing found a compressed-instruction acceptance bug: `rs_mcu_unmapped_probe` assembled its faulting load as 16-bit `4108`, while @@ -448,8 +920,8 @@ not a prerequisite to the architectural improvements in R2-P3 through R2-P5. | TINY-R2-P0 - Performance Contract and Roadmap Freeze | Reviewable requirements, exact old/new phase mapping, invariant package/IO counts, same-frequency CPU/main-SRAM ownership, explicit performance budgets, links and commands. Record actual documentation checks separately. | Documentation freeze only; no hardware result | | TINY-R2-P1 - Reproducible Baseline, Constraints and Measurements | Reviewed source/profile/lock/tool identity; current 24 MHz functional and workload baseline; clocks, reset endpoints, constraints and timing-exception audit; counters and comparison methodology. Historical WNS is a risk reference, not a refreshed measurement. | Functional/measurement baseline accepted by explicit P2 approval on 2026-10-06; timing, warning and physical gaps retained | | TINY-R2-P2 - Reset Distribution and CPU/SRAM Clock Feasibility | Reset distribution and payload-reset semantics; locked IHP130 main-SRAM macro timing, candidate common CPU/SRAM periods and representative paths; early clock/reset feasibility with explicit gaps. No final routed/PVT pass is implied. | Implementation and evidence ready for review; all analyzed rates retain timing failures; phase acceptance pending human review | -| TINY-R2-P3 - Software and DMA Scheduling | Compiler and placement A/B results, finite DMA ownership, burst/chunk scheduling, WS2812 refill budget and workload correctness on the available platform. | Pending | -| TINY-R2-P4 - Dual-Port Hazard3 and Four-Bank Local SRAM | Actual separate I/D paths, four independent 32 KiB bank frontends, SYS-clocked physical main-SRAM macros, local latency/fairness, FENCE.I, debug/reset and early inventory/SDC/macro-binding updates. | Pending | +| TINY-R2-P3 - Software and DMA Scheduling | Compiler and placement A/B results, finite DMA ownership, burst/chunk scheduling and available-path correctness; approved option A retains product DMA/IRQ budget qualification for the platform that supplies the route. | Matrix retained on `3e46cc8`; subsequent two P1 HAL fixes and focused/compatibility checks complete in the worktree; human review/acceptance pending | +| TINY-R2-P4 - Dual-Port Hazard3 and Four-Bank Local SRAM | Actual separate I/D paths, four independent 32 KiB bank frontends, SYS-clocked physical main-SRAM macros, local latency/fairness, FENCE.I, debug/reset and early inventory/SDC/macro-binding updates. | Implementation and planned functional validation complete on `4db2752` plus retained worktree snapshot; matched-image DMA throughput regression and human phase acceptance pending review; timing remains observational | | TINY-R2-P5 - Per-Target Concurrent Fabric | Cross-target concurrency, one combined transaction per target, central-DMA read/write overlap, AW/W binding, errors, simultaneous faults/counters and accepted-transfer drain. | Pending | | TINY-R2-P6 - Shared IP and Eight-Channel DMA Integration | Legacy P7 obligations; shared PWM reporting, I2S slave and alternate-input-route prerequisites; common ABI/HAL parity; Crypto banks/lifecycle; truthful capabilities and affected Mini compatibility. | Pending | | TINY-R2-P7 - Tiny RCU and Dual-Mode Clock/Reset Integration | Legacy P8 obligations with main SRAM in SYS and XPI in MEM; atomic rate changes, all actual CDC/reset barriers, clock failures, WFI leaf gating and reserved DVP controls. | Pending | diff --git a/physical/commercial/README.md b/physical/commercial/README.md index ba2bf84d..09c765f8 100644 --- a/physical/commercial/README.md +++ b/physical/commercial/README.md @@ -158,11 +158,17 @@ false-paths the canonical reset ports. Product overlays Qualified I/O covers JTAG, DVP (GPIO10-20), ULPI, SDRAM, SDIO, XPI, and asynchronous pads (UART0/1, remaining GPIO); a reviewed `TIMING_IO_MODE_HOOK` is mandatory in qualified mode. -- Tiny (`tiny.tcl`): SAFE24 boot — `clk_system` (external 24 MHz) and - `clk_jtag` only. The PLL_TOP macro is parked (EN=0), is not a clock source, +- Tiny (`tiny.tcl`): P7 SAFE24 boot — `clk_aon` (external 24 MHz), derived + `clk_system`, `clk_memory`, `clk_peripheral`, `clk_jtag`, and the independent + `clk_audio` (GPIO17, 18.432 MHz). Explicit case analysis selects REF24 in all + four RCU muxes and parks PLL_TOP (EN=0) for this scenario. The live design + supports software transitions, but active PLL timing is not modeled here. + In this scenario the PLL is not a clock source, and has no characterized timing arcs yet (see `docs/ip/tiny-soc.md` TINY-055); its DB/LEF/GDS/CDL views are still required for link, placement, - and LVS. Qualified I/O covers JTAG, XPI, and asynchronous pads. + and LVS. The legacy I/O template covers JTAG, XPI, and asynchronous pads; + this domain inventory update does not qualify P6 I2S/SDIO board timing or + the final QFN64 wrapper. ## Legacy-flow parity diff --git a/physical/commercial/config/products/tiny.mk b/physical/commercial/config/products/tiny.mk index a65474de..598e6a60 100644 --- a/physical/commercial/config/products/tiny.mk +++ b/physical/commercial/config/products/tiny.mk @@ -16,9 +16,9 @@ TOP := retrosoc_tiny_asic # Canonical clock domains, mirrored by tcl/common/products/tiny.tcl and # validated against rtl/tiny/integration/clock_reset_domains.json when the -# commercial timing contract is generated. Tiny boots SAFE24: the PLL_TOP -# macro is present in the netlist but parked off and is not a clock source. -COMMERCIAL_CLOCK_DOMAINS := system jtag +# commercial timing contract is generated. This adapter analyzes the SAFE24 +# boot scenario, explicitly constraining RCU mux selections and PLL enable. +COMMERCIAL_CLOCK_DOMAINS := aon system memory peripheral jtag audio # Qualified-I/O interface groups and the budget variables each group requires. COMMERCIAL_IO_INTERFACES := JTAG XPI ASYNC @@ -38,7 +38,7 @@ COMMERCIAL_IO_BUDGET_VARIABLES := \ # Tiny has no bidirectional pad-mode selection requirement. PRODUCT_REQUIRES_IO_MODE_HOOK := NO -# The Tiny PLL_TOP macro is parked (EN=0) and has no characterized timing +# In this SAFE24 scenario PLL_TOP is parked (EN=0) and has no characterized timing # arcs; doctor skips the qualified PLL-mode checks for this product. See # docs/ip/tiny-soc.md (TINY-055) for the qualification boundary. PRODUCT_PLL_MODE := parked diff --git a/physical/commercial/tcl/common/products/tiny.tcl b/physical/commercial/tcl/common/products/tiny.tcl index 247caba5..7fbd90dd 100644 --- a/physical/commercial/tcl/common/products/tiny.tcl +++ b/physical/commercial/tcl/common/products/tiny.tcl @@ -11,15 +11,14 @@ # retroSoC Tiny (retrosoc_tiny_asic) product overlay. # # Clock intent: the canonical contract (rtl/tiny/integration/ -# clock_reset_domains.json) owns two domains, system (SAFE24 external clock) -# and jtag. The ICS55 PLL_TOP macro is present but parked off (EN=0) and is -# not a clock source; it carries no characterized timing arcs yet, so no PLL -# overlay clock is created here. See docs/ip/tiny-soc.md (TINY-055). +# clock_reset_domains.json) owns REF24, derived SYS/MEM/PCLK, JTAG and GPIO17 +# AUDIO. This adapter covers SAFE24 boot only. RCU can enable the physical PLL +# in functional operation, but its active timing remains unqualified. namespace eval product {} proc product::expected_clock_domains {} { - return {system jtag} + return {aon system memory peripheral jtag audio} } proc product::custom_clock_domains {} { @@ -27,7 +26,7 @@ proc product::custom_clock_domains {} { } proc product::clock_names {} { - return {clk_system clk_jtag} + return {clk_aon clk_system clk_memory clk_peripheral clk_jtag clk_audio clk_sys_div2 clk_sys_div4 clk_clkout_source} } proc product::io_interface_groups {} { @@ -35,6 +34,31 @@ proc product::io_interface_groups {} { } proc product::apply_clock_overlays {} { + foreach divisor {2 4} { + create_generated_clock -name clk_sys_div$divisor \ + -source [flow::domain_object system] -divide_by $divisor \ + [flow::required_net_driver_pin "Tiny SYS /$divisor" \ + "u_soc/u_clock_backend/s_div${divisor}_clk"] + flow::register_clock clk_sys_div$divisor core + } + foreach mux {u_sys_mux u_mem_mux u_pclk_div2_mux u_pclk_mux} { + set_case_analysis 0 [flow::required_pins "Tiny SAFE24 mux $mux" \ + "u_soc/u_clock_backend/$mux/select_i"] + foreach {side enabled} {0 1 1 0} { + set_case_analysis $enabled [flow::required_net_driver_pin "Tiny settled clock gate" \ + "u_soc/u_clock_backend/$mux/r_enable$side"] + } + } + set_case_analysis 0 [flow::required_pins "Tiny SAFE24 PLL enable" \ + "u_soc/u_clock_backend/u_pll/gen_ecos/u_backend/u_PLL_TOP/EN"] + foreach bit {1 2} { + set_case_analysis 0 [flow::required_net_driver_pin "Tiny boot CLKOUT source" \ + "u_soc/u_clock_output/s_cfg_q\[$bit\]"] + } + create_generated_clock -name clk_clkout_source -source [flow::domain_object aon] \ + -divide_by 1 [flow::required_net_driver_pin "Tiny boot CLKOUT source clock" \ + "u_soc/u_clock_output/s_src_clk"] + flow::register_clock clk_clkout_source core } proc product::apply_io_constraints {} { diff --git a/physical/librelane/tiny/scripts/generate_chip_config.py b/physical/librelane/tiny/scripts/generate_chip_config.py index 782be724..85a9e08b 100644 --- a/physical/librelane/tiny/scripts/generate_chip_config.py +++ b/physical/librelane/tiny/scripts/generate_chip_config.py @@ -27,7 +27,7 @@ SIDE_ORDER = ("south", "east", "north", "west") EXPECTED_SIGNAL_PADS = 52 CLOCK_PORTS = ("extclk_i_pad", "jtag_tck_i_pad") SRAM_MASTER = "RM_IHPSG13_1P_1024x32_c2_bm_bist" -SRAM_INSTANCE = "u_soc.u_sram.gen_memory.gen_bank[{index}].u_ram.u_mem" +SRAM_INSTANCE = "u_soc.u_sram.gen_group[{group}].u_group.gen_bank[{bank}].u_ram.u_mem" SRAM_COLUMNS = 8 SRAM_ORIGIN = (400, 600) # Detailed routing requires an M2 track inside each bank's 0.26 um bottom-edge @@ -95,7 +95,7 @@ def sram_instances(capacity_kib: int) -> dict[str, tuple[list[int], str]]: SRAM_ORIGIN[0] + column * SRAM_PITCH[0], SRAM_ORIGIN[1] + row * SRAM_PITCH[1], ] - instances[SRAM_INSTANCE.format(index=index)] = (location, "N") + instances[SRAM_INSTANCE.format(group=index // 8, bank=index % 8)] = (location, "N") return instances @@ -122,18 +122,9 @@ def macro_config(instances: dict[str, tuple[list[int], str]], header: str) -> di def macro_hooks(instances: dict[str, tuple[list[int], str]]) -> list[str]: hooks: list[str] = [] for instance in instances: - bracket_start = instance.find("[") - if bracket_start >= 0: - bracket_end = instance.index("]", bracket_start) - instance_pattern = ( - re.escape(instance[:bracket_start]) - + ".*" - + instance[bracket_start + 1 : bracket_end] - + ".*" - + re.escape(instance[bracket_end + 1 :]) - ) - else: - instance_pattern = re.escape(instance) + # OpenDB and synthesis may escape array brackets differently. Handle + # every index (group and bank), retaining the exact non-index hierarchy. + instance_pattern = ".*".join(re.escape(part) for part in re.split(r"[\[\]]", instance)) hooks.extend( [ f"{instance_pattern} VDD VSS VDDARRAY! VSS!", diff --git a/physical/smoke/sta/opensta/generate_sdc.py b/physical/smoke/sta/opensta/generate_sdc.py index b7b1eb42..329e17ea 100644 --- a/physical/smoke/sta/opensta/generate_sdc.py +++ b/physical/smoke/sta/opensta/generate_sdc.py @@ -119,9 +119,12 @@ def render(domains: list[dict[str, Any]], reset_ports: list[str]) -> str: if source_domain is None: lines.append(f"create_clock -name clk_{name} -period {period} ${pin_variable}") else: + divide_by = sta.get("divide_by", 1) + if not isinstance(divide_by, int) or isinstance(divide_by, bool) or divide_by < 1: + raise ValueError("generated clock division must be a positive integer") lines.append( f"create_generated_clock -name clk_{name} -source ${pin_variables[source_domain]} " - f"-divide_by 1 ${pin_variable}" + f"-divide_by {divide_by} ${pin_variable}" ) lines.append("") @@ -175,10 +178,52 @@ def main() -> int: parser.add_argument("--domains", type=Path, required=True) parser.add_argument("--pin-map", type=Path, required=True) parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--xin-hz", type=int, default=24000000) args = parser.parse_args() try: domains, reset_ports = parse_document(args.domains, args.pin_map) - atomic_write(args.output, render(domains, reset_ports)) + document = json.loads(args.domains.read_text()) + p7 = document.get("clock_contract") == "TINY-R2-P7" + if p7 and args.xin_hz not in (24000000, 48000000, 96000000): + raise ValueError("unsupported Tiny external clock") + if p7 and args.xin_hz != 24000000: + aon = next(domain for domain in domains if domain["name"] == "aon") + external = {**aon, "name": "external", "sta": {**aon["sta"], + "period_ns": 1e9 / args.xin_hz}} + aon["sta"] = {"source_domain": "external", "period_ns": 1e9 / 24000000, + "divide_by": args.xin_hz // 24000000, "async_group": "core", + "pin": "u_soc.u_clock_backend.gen_ref_divided.u_ref_div/clk_o"} + domains.insert(0, external) + if p7: + # These sources run even while the muxes select their undivided + # inputs. Their handover synchronizers still require real clocks. + for divisor in (2, 4): + domains.append({"name": f"sys_div{divisor}", "sta": { + "source_domain": "system", "period_ns": 1e9 * divisor / 24000000, + "divide_by": divisor, "async_group": "core", "object_type": "net_driver", + "net": f"u_soc.u_clock_backend.s_div{divisor}_clk"}}) + domains.append({"name": "clkout_source", "sta": { + "source_domain": "aon", "period_ns": 1e9 / 24000000, + "divide_by": 1, "async_group": "core", "object_type": "net_driver", + "net": "u_soc.u_clock_output.s_src_clk"}}) + result = render(domains, reset_ports) + if p7: + result += "\n# P7 SAFE24 boot scenario; this does not qualify active PLL timing.\n" + for mux in ("u_sys_mux", "u_mem_mux", "u_pclk_div2_mux", "u_pclk_mux"): + result += (f"set_case_analysis 0 [require_pins {{P7 SAFE24 selection}} " + f"{{u_soc.u_clock_backend.{mux}/select_i}}]\n") + # select_i passes through sequential handshake state. A static + # mode must also select its settled gate state; setting only + # the request cannot prune the other live clock in STA. + for side, enabled in ((0, 1), (1, 0)): + result += (f"set_case_analysis {enabled} [require_net_driver_pin " + f"{{P7 SAFE24 settled gate}} " + f"{{u_soc.u_clock_backend.{mux}/r_enable{side}}}]\n") + result += "# CLKOUT retains its reset REF24 source in this boot scenario.\n" + for bit in (1, 2): + result += (f"set_case_analysis 0 [require_net_driver_pin {{P7 CLKOUT source}} " + f"{{u_soc.u_clock_output.s_cfg_q_{bit}_}}]\n") + atomic_write(args.output, result) except (OSError, ValueError, json.JSONDecodeError) as error: parser.error(str(error)) return 0 diff --git a/physical/smoke/sta/opensta/opensta.mk b/physical/smoke/sta/opensta/opensta.mk index e6d89433..9182bddc 100644 --- a/physical/smoke/sta/opensta/opensta.mk +++ b/physical/smoke/sta/opensta/opensta.mk @@ -16,14 +16,14 @@ include $(ROOT_PATH)/physical/smoke/sta/opensta/pdk_timing.mk $(OPENSTA_SDC): $(OPENSTA_SDC_GEN) $(OPENSTA_DOMAIN_MAP) $(OPENSTA_PIN_MAP) | manifest @mkdir -p $(dir $@) - python3 $(OPENSTA_SDC_GEN) --domains $(OPENSTA_DOMAIN_MAP) --pin-map $(OPENSTA_PIN_MAP) --output $@ + python3 $(OPENSTA_SDC_GEN) --domains $(OPENSTA_DOMAIN_MAP) --pin-map $(OPENSTA_PIN_MAP) --output $@ --xin-hz $(EXT_CLK_HZ) sta: $(OPENSTA_SDC) | manifest @mkdir -p $(STA_BUILD_ROOT) -ifeq ($(TINY_SAFE24_PLL_OFF),YES) - python3 $(ROOT_PATH)/scripts/tiny_ics55_platform.py --variant-root $(VARIANT_ROOT) verify-netlist \ +ifeq ($(SOC),TINY) + python3 $(ROOT_PATH)/scripts/tiny_r2_p7.py --variant-root $(VARIANT_ROOT) verify-netlist \ --netlist $(OPENSTA_NETLIST) --config $(OPENSTA_CONFIG) \ - --output $(SYN_BUILD_ROOT)/tiny-ics55-netlist.json + --output $(SYN_BUILD_ROOT)/tiny-r2-p7-netlist.json endif @for input in $(OPENSTA_NETLIST) $(OPENSTA_LIBERTY) $(OPENSTA_LINK_LIBS) $(OPENSTA_SRAM_LIBS) $(OPENSTA_SDC) $(OPENSTA_CONFIG); do \ test -f "$$input" || { echo "OpenSTA input missing: $$input" >&2; exit 1; }; \ @@ -45,7 +45,7 @@ endif --env OPENSTA_TOP=$(OPENSTA_TOP) --env OPENSTA_NETLIST=$(OPENSTA_NETLIST) --env OPENSTA_LIBERTY=$(OPENSTA_LIBERTY) \ --env 'OPENSTA_LINK_LIBS=$(OPENSTA_LINK_LIBS)' --env 'OPENSTA_SRAM_LIBS=$(OPENSTA_SRAM_LIBS)' \ --env OPENSTA_SDC=$(OPENSTA_SDC) --env OPENSTA_REPORT=$(OPENSTA_REPORT) \ - --env OPENSTA_METRICS=$(OPENSTA_METRICS) --env OPENSTA_TINY_ICS55=$(TINY_SAFE24_PLL_OFF) -- \ + --env OPENSTA_METRICS=$(OPENSTA_METRICS) --env OPENSTA_TINY_ICS55=$(TINY_SAFE24_PLL_OFF) --env OPENSTA_TINY_P7=$(TINY_R2_P7) -- \ $(OPENSTA) -no_init -exit -threads $(OPENSTA_THREADS) $(ROOT_PATH)/physical/smoke/sta/opensta/opensta.tcl .PHONY: sta \ No newline at end of file diff --git a/physical/smoke/sta/opensta/opensta.tcl b/physical/smoke/sta/opensta/opensta.tcl index 966a9e50..baa5d755 100644 --- a/physical/smoke/sta/opensta/opensta.tcl +++ b/physical/smoke/sta/opensta/opensta.tcl @@ -20,6 +20,13 @@ if {[info exists ::env(OPENSTA_TOP)] && $::env(OPENSTA_TOP) ne ""} { } link_design $top_design read_sdc $::env(OPENSTA_SDC) +if {$top_design eq "retrosoc_tiny_asic"} { + if {[info exists ::env(OPENSTA_TINY_P7)] && $::env(OPENSTA_TINY_P7) eq "YES"} { + source [file join [file dirname [info script]] tiny_r2_p7_checks.tcl] + } else { + source [file join [file dirname [info script]] tiny_r2_p4_checks.tcl] + } +} if {[info exists ::env(OPENSTA_TINY_ICS55)] && $::env(OPENSTA_TINY_ICS55) eq "YES"} { source [file join [file dirname [info script]] tiny_ics55_checks.tcl] } diff --git a/physical/smoke/sta/opensta/tiny_ics55_checks.tcl b/physical/smoke/sta/opensta/tiny_ics55_checks.tcl index 58642eb7..9c1c671d 100644 --- a/physical/smoke/sta/opensta/tiny_ics55_checks.tcl +++ b/physical/smoke/sta/opensta/tiny_ics55_checks.tcl @@ -4,7 +4,7 @@ set audit_dir [file dirname $::env(OPENSTA_REPORT)] set pll [get_cells -hierarchical -filter {ref_name == PLL_TOP}] set ram [get_cells -hierarchical -filter {ref_name == ics55_ecos_sram_1024x32_m8}] -if {[llength $pll] != 1 || [llength $ram] != 32} { error "Tiny ICS55 macro binding mismatch" } +if {[llength $pll] != 1 || [llength $ram] != 38} { error "Tiny ICS55 macro binding mismatch" } if {[llength [get_clocks clk_system]] != 1} { error "Tiny SYS clock missing" } report_clock_properties > $audit_dir/clocks.rpt check_setup -verbose > $audit_dir/coverage.rpt diff --git a/physical/smoke/sta/opensta/tiny_r2_p4_checks.tcl b/physical/smoke/sta/opensta/tiny_r2_p4_checks.tcl new file mode 100644 index 00000000..97d0e23a --- /dev/null +++ b/physical/smoke/sta/opensta/tiny_r2_p4_checks.tcl @@ -0,0 +1,77 @@ +# P4 structural timing prerequisites, common to both supported Tiny PDKs. +# Negative timing remains observational; missing/incorrect clock objects do not. +set p4_dir [file dirname $::env(OPENSTA_REPORT)] +set p4_ram [get_cells -hierarchical -filter {ref_name == RM_IHPSG13_1P_1024x32_c2_bm_bist}] +set p4_clock_pin A_CLK +if {[llength $p4_ram] == 0} { + set p4_ram [get_cells -hierarchical -filter {ref_name == ics55_ecos_sram_1024x32_m8}] + set p4_clock_pin CLK +} +if {[llength $p4_ram] != 38} { error "P6 requires 32 main SRAM and six private Crypto macros" } +set p6_all_ram $p4_ram +set p4_ram {} +set p6_crypto {} +set p6_crypto_expected {} +for {set bank 0} {$bank < 6} {incr bank} { + lappend p6_crypto_expected [format {u_soc.u_crypto.u_storage.gen_bank[%d].gen_macro.u_sram.u_mem} $bank] +} +set p6_crypto_seen {} +foreach cell $p6_all_ram { + set name [string map {/ .} [get_full_name $cell]] + set is_crypto 0 + foreach expected $p6_crypto_expected { + if {[string equal [string range $name end-[expr {[string length $expected]-1}] end] $expected]} { + lappend p6_crypto_seen $expected + set is_crypto 1 + } + } + if {$is_crypto} { lappend p6_crypto $cell } else { lappend p4_ram $cell } +} +if {[llength $p4_ram] != 32 || [lsort $p6_crypto_seen] ne [lsort $p6_crypto_expected]} { + error "P6 private/main SRAM hierarchy mismatch" +} +set p4_expected {} +for {set group 0} {$group < 4} {incr group} { + for {set bank 0} {$bank < 8} {incr bank} { + lappend p4_expected [format {u_soc.u_sram.gen_group[%d].u_group.gen_bank[%d].u_ram.u_mem} $group $bank] + } +} +set p4_seen {} +foreach cell $p4_ram { + set name [string map {/ .} [get_full_name $cell]] + foreach expected $p4_expected { + if {[string equal [string range $name end-[expr {[string length $expected]-1}] end] $expected]} { + lappend p4_seen $expected + } + } +} +if {[lsort $p4_seen] ne [lsort $p4_expected]} { error "P4 macro group hierarchy mismatch" } +set p4_source [get_pins -quiet {u_clock_buffer/clk_o}] +if {[llength $p4_source] != 1} { error "P4 SYS source missing" } +set p4_direct {} +foreach pin [get_fanout -from $p4_source -flat -pin_levels 1 -trace_arcs all] { + dict set p4_direct [get_full_name $pin] 1 +} +set p4_clock_report [open $p4_dir/p4-clock-bindings.rpt w] +foreach cell [concat $p4_ram $p6_crypto] { + set clocks {} + foreach pin [get_pins -of_objects $cell] { + if {[string match "*/$p4_clock_pin" [get_full_name $pin]]} { lappend clocks $pin } + } + if {[llength $clocks] != 1} { error "P4 macro clock pin missing" } + set name [get_full_name [lindex $clocks 0]] + if {![dict exists $p4_direct $name]} { error "P4 SRAM clock is not direct SYS: $name" } + puts $p4_clock_report $name +} +set p4_cpu [get_pins -hierarchical -quiet {*u_hazard3_cpu_2port*/CLK *u_hazard3_cpu_2port*/CK *u_hazard3_cpu_2port*/CKN}] +set p4_front [get_pins -hierarchical -quiet {*u_cpu_mem*/CLK *u_cpu_mem*/CK *u_cpu_mem*/CKN}] +if {[llength $p4_cpu] == 0 || [llength $p4_front] == 0} { error "P4 CPU/frontend clock endpoints missing" } +foreach pin [concat $p4_cpu $p4_front] { + set name [get_full_name $pin] + if {![dict exists $p4_direct $name]} { error "P4 CPU/frontend does not share SYS: $name" } +} +puts $p4_clock_report "CPU clock endpoints: [llength $p4_cpu]" +puts $p4_clock_report "Frontend clock endpoints: [llength $p4_front]" +close $p4_clock_report +puts "TINY_R2_P4_BINDING_PASS: four groups, 32 macros, common CPU/SRAM SYS; not timing qualification" +puts "TINY_R2_P6_BINDING_PASS: six private Crypto macros on SAFE24 PCLK; no capacity alias" diff --git a/physical/smoke/sta/opensta/tiny_r2_p7_checks.tcl b/physical/smoke/sta/opensta/tiny_r2_p7_checks.tcl new file mode 100644 index 00000000..ada232fc --- /dev/null +++ b/physical/smoke/sta/opensta/tiny_r2_p7_checks.tcl @@ -0,0 +1,22 @@ +# P7 uses a dedicated, provenance-checked Python macro/clock audit. Historical +# P1/P4 checks retain their direct-SYS/parked-PLL assumptions unchanged. +set p7_dir [file dirname $::env(OPENSTA_REPORT)] +foreach clock {clk_aon clk_system clk_memory clk_peripheral clk_audio clk_jtag clk_sys_div2 clk_sys_div4 clk_clkout_source} { + if {[llength [get_clocks -quiet $clock]] != 1} { error "P7 clock missing: $clock" } +} +set p7_pll [get_cells -hierarchical -quiet -filter {ref_name == PLL_TOP}] +if {[llength $p7_pll] > 1} { error "P7 contains multiple PLL macros" } +if {[llength $p7_pll] == 1} { + # The analysis is explicitly the cold SAFE24 boot operating state. + set p7_enable [get_pins -quiet "[get_full_name [lindex $p7_pll 0]]/EN"] + if {[llength $p7_enable] != 1} { error "P7 PLL enable pin missing" } + set_case_analysis 0 $p7_enable +} +report_clock_properties > $p7_dir/clocks.rpt +check_setup -verbose > $p7_dir/coverage.rpt +report_check_types -recovery -removal -min_pulse_width -min_period -max_slew -max_capacitance -max_fanout -violators -verbose > $p7_dir/constraint-violations.rpt +foreach domain {system memory peripheral aon} { + report_checks -path_group clk_$domain -path_delay max -group_path_count 20 -fields {capacitance slew fanout} > $p7_dir/$domain-setup.rpt + report_checks -path_group clk_$domain -path_delay min -group_path_count 20 -fields {capacitance slew fanout} > $p7_dir/$domain-hold.rpt +} +puts "TINY_R2_P7_STA_OBSERVATION: SAFE24 boot only; not active PLL, routed or PVT qualification" diff --git a/physical/smoke/syn/yosys/script/synth.tcl b/physical/smoke/syn/yosys/script/synth.tcl index 568ca6d9..8f6c9b8a 100644 --- a/physical/smoke/syn/yosys/script/synth.tcl +++ b/physical/smoke/syn/yosys/script/synth.tcl @@ -165,7 +165,7 @@ yosys tee -q -o "${report_dir}/${proj_name}_area_logic.rpt" stat -top $top_desig # final netlist yosys write_verilog -noattr -noexpr -nohex -nodec $netlist -if {$soc == "TINY" && $pdk == "ICS55"} { +if {$soc == "TINY"} { yosys write_json ${netlist}.json } diff --git a/physical/smoke/syn/yosys/yosys.mk b/physical/smoke/syn/yosys/yosys.mk index c1f49bc3..304fc307 100644 --- a/physical/smoke/syn/yosys/yosys.mk +++ b/physical/smoke/syn/yosys/yosys.mk @@ -59,7 +59,7 @@ gen_synth_filelist: $(SV_FLIST) ## Synthesize netlist using Yosys synth: $(NETLIST) | manifest -$(NETLIST): $(SV_FLIST) $(YOSYS_SCRIPTS) +$(NETLIST): $(SV_FLIST) $(YOSYS_SCRIPTS) | manifest @mkdir -p $(YOSYS_OUT) @mkdir -p $(YOSYS_TMP) @mkdir -p $(YOSYS_RPT) @@ -74,9 +74,9 @@ $(NETLIST): $(SV_FLIST) $(YOSYS_SCRIPTS) --env REPORTS=$(YOSYS_RPT) --env NETLIST=$(NETLIST) -- \ timeout --foreground --kill-after=5s $(YOSYS_TIMEOUT)s \ yosys -t --perffile $(YOSYS_BUILD)/yosys-perf.json -c $(YOSYS_DIR)/script/synth.tcl -ifeq ($(SOC)-$(PDK),TINY-ICS55) - python3 $(ROOT_PATH)/scripts/tiny_ics55_platform.py --variant-root $(VARIANT_ROOT) audit-netlist \ - --netlist $(NETLIST) --config $(NETLIST_CONFIG) --output $(YOSYS_BUILD)/tiny-ics55-netlist.json +ifeq ($(SOC),TINY) + python3 $(ROOT_PATH)/scripts/tiny_r2_p7.py --variant-root $(VARIANT_ROOT) audit-netlist \ + --netlist $(NETLIST) --config $(NETLIST_CONFIG) --output $(YOSYS_BUILD)/tiny-r2-p7-netlist.json endif $(NETLIST_DEBUG): $(NETLIST) diff --git a/publications/datasheets/system-reference.json b/publications/datasheets/system-reference.json index f940dc18..68fa3b48 100644 --- a/publications/datasheets/system-reference.json +++ b/publications/datasheets/system-reference.json @@ -4459,7 +4459,8 @@ "RS_ETIMEOUT": -4, "RS_EIO": -5, "RS_ENOTSUP": -6, - "RS_EFORMAT": -7 + "RS_EFORMAT": -7, + "RS_EBUSY": -8 }, "meanings": { "RS_OK": "Operation completed under that API contract.", @@ -4469,7 +4470,8 @@ "RS_ETIMEOUT": "Bounded operation timed out.", "RS_EIO": "I/O operation failed.", "RS_ENOTSUP": "Operation unsupported.", - "RS_EFORMAT": "Input format rejected." + "RS_EFORMAT": "Input format rejected.", + "RS_EBUSY": "Resource or lifecycle operation is busy; retry after the owning operation completes." }, "observation": "Signed API return value. Exact triggering conditions depend on the called function; the enum definition does not make every code reachable from every API.", "handling": "Check the return before using output values. No hardware W1C applies to a C return value; preserve the relevant peripheral status if diagnosis requires it.", diff --git a/publications/datasheets/tiny/chapter-review.md b/publications/datasheets/tiny/chapter-review.md index 65609ab7..df80a38b 100644 --- a/publications/datasheets/tiny/chapter-review.md +++ b/publications/datasheets/tiny/chapter-review.md @@ -11,9 +11,9 @@ All **39 frozen entries / 14 IP chapters** are reviewed below. Register, address | 3 | Configuration and Scope / 2 | Covered. IHP130, 128 KiB, 24 MHz target, four DMA channels; hardware RV32IMC versus default RV32IM firmware. | [ihp130-tiny.mk](../../../configs/ci/ihp130-tiny.mk), [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv) | Use the committed profile and identity; keep deferred interfaces and physical claims excluded. | | 4 | Reading and Evidence Conventions / 2 | Covered. Product-specific organization, navigation or revision control; no additional hardware capability. | [datasheet.typ](../../../publications/datasheets/tiny/datasheet.typ), [structure-contract.json](../../../publications/datasheets/tiny/structure-contract.json) | Retain the declared scope, complete source links and independent Tiny identity when updating this section. | | 5 | System Architecture / 17 | Covered. Product-specific organization, navigation or revision control; no additional hardware capability. | [datasheet.typ](../../../publications/datasheets/tiny/datasheet.typ), [structure-contract.json](../../../publications/datasheets/tiny/structure-contract.json) | Retain the declared scope, complete source links and independent Tiny identity when updating this section. | -| 6 | Processor and Integration / 17 | Covered. One RV32IMC MCU hart, A disabled, direct AHB-Lite/AXI32 adapter and debug reset boundary. | [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv), [mgmt_core_wrapper.sv](../../../rtl/ip/core/mgmt_core_wrapper.sv) | Retain actual wrapper parameters; do not infer firmware ISA flags from the hardware C extension. | +| 6 | Processor and Integration / 17 | Working-source P4 update: dual-port RV32IMC hart, A disabled, independent local I/D and tagged slow adapter. Page number belongs to the earlier delivered PDF. | [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv), [tiny_cpu_wrapper.sv](../../../rtl/tiny/top/tiny_cpu_wrapper.sv) | Retain actual wrapper parameters; revalidate publication pages separately before delivery. | | 7 | Clock, Reset and Operating State / 18 | Covered. System and asynchronous JTAG domains; RTC/WDG share the system clock. | [clock_reset_domains.json](../../../rtl/tiny/integration/clock_reset_domains.json), [retrosoc_tiny.sv](../../../rtl/tiny/top/retrosoc_tiny.sv) | Obtain CDC/RDC and clock/power evidence separately; do not claim independent sleep timing. | -| 8 | Bus Transactions, Arbitration and Errors / 19 | Covered. One globally active transaction, round-robin acceptance, saved ownership, bounded legal bursts and error/drain behavior. | [tiny_axi4_fabric.sv](../../../rtl/tiny/top/tiny_axi4_fabric.sv), [axi4_error_slave.sv](../../../rtl/ip/interconnect/axi4_error_slave.sv) | Keep local IP capability separate from end-to-end fabric admission; no unlimited liveness guarantee. | +| 8 | Bus Transactions, Arbitration and Errors / 19 | P5 source draft updated: target-local ownership, bounded independent DMA R/W, separate APB branches and concurrent fault count. | [tiny_axi4_fabric.sv](../../../rtl/tiny/top/tiny_axi4_fabric.sv), [tiny_axi4_target.sv](../../../rtl/tiny/top/tiny_axi4_target.sv) | Page19 belongs to the prior PDF; no new PDF or unconditional liveness claim. SDIO owner2 is reserved only. | | 9 | Memory Map and Access Conventions / 21 | Covered. Twenty canonical address windows, including alias versus physical-capacity distinctions. | [memory_map.json](../../../rtl/tiny/address_map/memory_map.json) | Reconcile every source-map change; do not use unmapped holes as scratch memory. | | 10 | Interrupt and DMA Request Routing / 22 | Covered. Fourteen allocated core causes, thirty external positions, enabled DMA request map and channel conventions. | [soc_topology.json](../../../rtl/tiny/integration/soc_topology.json), [dma_math.c](../../../crt/src/hal/dma_math.c) | Keep core input bits, external ordinals, CSR positions and DMA selectors distinct. | | 11 | Peripherals / 23 | Covered. Fourteen IP chapters and 337 source-extracted register definitions, with instance-specific routing. | [catalog.json](../../../publications/datasheets/tiny/catalog.json), [tiny_reference.py](../../../publications/tiny_reference.py) | Update source inventory, frozen register keys and rendered-coverage checks together after reviewed ABI changes. | diff --git a/publications/datasheets/tiny/content-review.md b/publications/datasheets/tiny/content-review.md index 14add1c8..e2ecf055 100644 --- a/publications/datasheets/tiny/content-review.md +++ b/publications/datasheets/tiny/content-review.md @@ -1,15 +1,21 @@ # Tiny Gen1 v0.1 DRAFT content review -The publication describes the independent IHP130 Tiny profile: one RV32IMC -Hazard3 with A disabled, RV32IM default firmware, 128 KiB SRAM, 24 MHz target, +The publication describes the independent IHP130 Tiny profile: one dual-port +RV32IMC Hazard3 with A disabled, RV32IM default firmware, 128 KiB SRAM in four +independently serviced 32 KiB groups, 24 MHz target, four DMA channels and AXI32/APB4. Rill (清溪) is the selected prototype brand. The font/palette/grid are inherited as design rules; product facts are not inherited from Mini's descriptions or qualification records. +The P4 working-source update changes CPU/local-SRAM bindings and the affected +draft architecture prose/figure. It does not advance `tiny.json.source_revision`, +rebuild or relabel a delivered PDF, or add hardware qualification. A later +publication release still requires source review, snapshot refresh and PDF checks. + | Area | Review and publication decision | Follow-up boundary | | --- | --- | --- | | CPU and startup | Publish the actual C/M extensions, disabled A, CSR-enabled firmware and Tiny guard around PSRAM setup. | Keep compiler target separate from implemented ISA; a reset vector is not a completed boot. | -| Fabric | Document one globally active CPU/DMA read or write, saved ownership, naturally aligned bounded bursts and terminal response/drain. | Do not claim arbitrary AXI4 features, independent read/write concurrency or unrestricted liveness. | +| Fabric | P5 draft: independent target ownership, one combined CPU slow operation, DMA one read/one write, 16 APB branches and lossless concurrent-fault accounting. Owner2 is reserved, inactive SDIO. | No SDIO integration, arbitrary AXI4 features or unconditional liveness claim. Snapshot/PDF revision is not advanced. | | Address/IRQ/pads | Derive all 20 regions, 14 allocated core causes and 52 logical signal pads; retain 32 GPIO alternate entries. | Logical pads are not package pin numbers or a board schematic. | | IP/register scope | Fourteen chapters, thirteen unique register families; Tiny SYSCTRL is decoded independently and ARCHINFO overrides the SoC identity. | Reject missing/extra decode and defaults belonging to other products. | | DMA | Four channels, request mask 0x07F9, no stream modes, 64-byte descriptors and source-derived field layout. | **Discovery discrepancy:** the four-bit maximum-burst field reads zero after truncating 16. Publish that value and the actual 16-beat limit separately; propose a future ABI/RTL review, do not change RTL here. | diff --git a/publications/datasheets/tiny/datasheet.typ b/publications/datasheets/tiny/datasheet.typ index cd9d68c6..1580990b 100644 --- a/publications/datasheets/tiny/datasheet.typ +++ b/publications/datasheets/tiny/datasheet.typ @@ -29,9 +29,9 @@ - JTAG debug with coordinated hart reset. *Memory and interconnect* - - *128 KiB SRAM*, 32 banks of 4 KiB. + - *128 KiB SRAM*, four independently serviced 32 KiB groups backed by 32 banks of 4 KiB. - Native AXI32 memory paths and one APB4 island. - - CPU and DMA share one active transaction globally. + - Independent local instruction/data ports and target-local external ownership. - XPI NOR boot and SRAM-resident application layout. *DMA and control* @@ -103,16 +103,18 @@ The document uses the approved Mini visual language; all product facts and sourc = System Architecture == Processor and Integration -Hazard3 is hart zero. The shared wrapper selects the C and M extensions and disables A for Tiny. Its AHB-Lite bus terminates -at a direct AXI32 adapter. JTAG supports the implemented halt/resume, register and system-bus debug path; debug hart reset -waits for the adapter to become idle instead of abandoning an accepted transaction. +Hazard3 is hart zero. The locked dual-port wrapper selects the C and M extensions and disables A for Tiny. Instruction fetch +and load/store use independent AHB ports through separate local SRAM frontends; non-SRAM accesses use the tagged slow path. +JTAG supports halt/resume, register access and halted-hart abstract-command memory access, without an independent system-bus master. +Debug hart reset first halts the hart and drains accepted local I/D and slow-path responses. SRAM and unrelated DMA work stay active. The system has no application hart, MMU/Linux platform, hardware cache-coherent multicore organization or product extension slots. CPU-visible SRAM, XPI and APB routes are the implemented interconnect. The single-hart architecture does not remove the need for software/DMA buffer ownership and fences. #architecture() #source-note("rtl/tiny/top/retrosoc_tiny.sv",title:"Tiny product integration and parameters") -#source-note("rtl/ip/core/mgmt_core_wrapper.sv",title:"CPU ISA, AHB adapter and debug wrapper") +#source-note("rtl/tiny/top/tiny_cpu_wrapper.sv",title:"Dual-port CPU ISA, AHB ports and debug wrapper") +#source-note("rtl/tiny/top/tiny_sram.sv",title:"Four local SRAM groups and external attachment") == Clock, Reset and Operating State CPU, AXI, APB, RTC and watchdog share the configured 24 MHz system clock. CLINT derives a 1 MHz tick. JTAG has its own @@ -130,9 +132,18 @@ advertised as a qualified voltage/frequency operating point. #source-note("rtl/tiny/integration/clock_reset_domains.json",title:"Clock/reset inventory and debug crossings") == Bus Transactions, Arbitration and Errors -The two initiators are CPU and DMA. They share *one active read or write transaction globally*. The fabric saves source, -direction, address and attributes, forwards the request, and retains ownership until the terminal B or RLAST handshake. -Read/write request arbitration is round-robin; it is not a throughput guarantee or an unrestricted liveness guarantee. +The two active external initiators are CPU slow path and DMA. A third ingress is reserved for future SDIO integration and +has no active producer. Each SRAM group, XPI memory endpoint and APB peripheral has independent transaction ownership. +CPU slow traffic admits one combined operation; DMA can have one read and one write outstanding across different targets. +Each target admits one combined read or write. CPU local instruction/data requests remain independently arbitrated within +each SRAM group. Saved ownership includes accepted address and response buffers and lasts through the upstream B/RLAST handshake. +Owner arbitration is round-robin with fair direction selection. Progress bounds require a running clock and peer progress. +Separate APB bridges keep independent controls outside a FIFO-waiting peripheral's transaction. + +Fault accounting counts each failed transaction once, including errors before the final read beat. Simultaneous events +increment the saturating count individually; first-fault metadata uses ascending owner, instruction before data, then read +before write. CPU is owner0, DMA owner1, and owner2 is reserved. FAULT_DETAIL retains raw AXI response2/3. Clearing validity +does not clear the count, and a simultaneous hardware event wins over clear. Memory transfers support naturally aligned 1/2/4-byte beats and up to 16 INCR beats. Single-beat FIXED is also admitted. The complete address span must remain within one decoded target and one 4 KiB page without overflow. MMIO accepts only diff --git a/publications/datasheets/tiny/diagrams.typ b/publications/datasheets/tiny/diagrams.typ index 4365a9ab..fe355048 100644 --- a/publications/datasheets/tiny/diagrams.typ +++ b/publications/datasheets/tiny/diagrams.typ @@ -26,21 +26,22 @@ }) } #let architecture() = diagram("tiny-architecture",schematic(( - (5,4,65,19,[Hazard3 · hart 0 \ RV32IMC · A disabled]), + (5,4,65,19,[Hazard3 dual-port · hart 0 \ RV32IMC · A disabled]), (104,4,63,19,[JTAG / debug transport \ External TCK domain]), - (5,37,65,15,[AHB-Lite → AXI32 adapter]), + (5,37,65,15,[I/D decode + slow adapter]), (104,37,63,15,[Four-channel DMA \ AXI32 master]), - (30,69,112,19,[Tiny AXI32 fabric \ One active transaction globally]), - (0,110,40,22,[128 KiB SRAM \ 32 × 4 KiB]), + (30,69,112,19,[Tiny AXI32 fabric \ Independent target ownership]), + (0,110,40,22,[128 KiB SRAM \ 4 × 32 KiB groups]), (44,110,40,22,[XPI read path \ Flash alias / NOR]), - (88,110,40,22,[APB4 bridge \ 16 targets]), + (88,110,40,22,[16 APB4 branches \ One per peripheral]), (132,110,40,22,[Error responders \ DECERR / SLVERR]), ),( ((104,13),(70,13)),((37,23),(37,37)), + ((5,44),(0,44),(0,103),(6,103),(6,110)), ((37,52),(37,61),(60,61),(60,69)),((135,52),(135,61),(112,61),(112,69)), ((45,88),(45,99),(20,99),(20,110)),((70,88),(70,103),(64,103),(64,110)), ((103,88),(103,103),(108,103),(108,110)),((127,88),(127,99),(152,99),(152,110)), -),height:134),[Tiny Gen1 integration. System logic runs from the configured 24 MHz clock; JTAG is asynchronous.]) +),height:134),[Tiny Gen1 integration. The left branch represents independent local I/D paths bypassing the external fabric. System logic uses the configured 24 MHz clock; JTAG is asynchronous.]) #let ip-diagram(entry) = { let children=entry.nodes.slice(1) diff --git a/publications/datasheets/tiny/source-contract.json b/publications/datasheets/tiny/source-contract.json index d2346185..ac2214fa 100644 --- a/publications/datasheets/tiny/source-contract.json +++ b/publications/datasheets/tiny/source-contract.json @@ -98,7 +98,12 @@ "uart1": 26 }, "sources": [ - "rtl/ip/core/mgmt_core_wrapper.sv", + "rtl/tiny/top/tiny_cpu_wrapper.sv", + "rtl/tiny/top/tiny_cpu_mem.sv", + "rtl/tiny/top/tiny_sram.sv", + "rtl/tiny/top/tiny_sram_group.sv", + "rtl/tiny/top/tiny_sram_axi4.sv", + "rtl/tiny/top/tiny_sram_port_if.sv", "rtl/ip/core/mgmt_debug_wrapper.sv", "rtl/ip/core/mgmt_debug_reset.sv", "rtl/ip/interconnect/ahbl2axi4.sv", @@ -112,19 +117,19 @@ "app/apps/bringup/tiny.mk", "app/apps/ci_smoke/tiny.mk", "scripts/generate_tiny.py", - "tests/test_tiny.py" + "tests/test_tiny.py", + "rtl/tiny/top/tiny_axi4_target.sv", + "rtl/tiny/top/tiny_fault_collector.sv" ], "bindings": [ { "file": "rtl/tiny/top/tiny_axi4_fabric.sv", "text": [ - "(s_selected_addr.len <= 8'd15)", - "(s_last_addr[31:12] == s_selected_addr.addr[31:12])", - "s_accept = (s_state_q == Idle) && s_selected_valid;", - "Response: if (s_terminal) s_state_d = Idle;", - "s_tgt_d = s_protocol_legal ? s_decoded_target : 3'd4;", - "assign s_protocol_legal = !s_selected_addr.lock && (s_selected_addr.size <= 3'd2) &&\n ((s_selected_addr.addr & ((32'd1 << s_selected_addr.size) - 32'd1)) == 32'd0) &&\n ((s_selected_addr.burst == 2'b01) ||\n ((s_selected_addr.burst == 2'b00) && (s_selected_addr.len == 8'd0))) &&\n (s_selected_addr.len <= 8'd15) && !s_last_addr[32] &&\n (s_last_addr[31:12] == s_selected_addr.addr[31:12]) &&\n (decode_target(\n s_last_addr[31:0]\n ) == s_decoded_target) && ((s_decoded_target != 3'd2) || (s_selected_addr.len == 8'd0));", - "assign s_terminal = (s_state_q == Response) &&\n (s_write_q ? (s_target_bvalid[s_tgt_q] && s_bready[s_owner_q]) :\n (s_target_rvalid[s_tgt_q] && s_rready[s_owner_q] && s_target_rlast[s_tgt_q]));" + " function automatic logic [4:0] checked_target(\n input logic [31:0] addr_i, input logic [7:0] len_i,\n input logic [2:0] size_i, input logic [1:0] burst_i,\n input logic lock_i, input logic instruction_i);\n logic [32:0] last_addr;\n logic [4:0] decoded;\n decoded = decode_target(addr_i);\n last_addr = {1'b0, addr_i} + (({25'd0, len_i} + 33'd1) << size_i) - 33'd1;\n if (lock_i || (size_i > 3'd2) || (len_i > 8'd15) ||\n ((addr_i & ((32'd1 << size_i) - 32'd1)) != 32'd0) ||\n !((burst_i == 2'b01) || ((burst_i == 2'b00) && (len_i == 8'd0))) ||\n last_addr[32] || (last_addr[31:12] != addr_i[31:12]) ||\n (decode_target(last_addr[31:0]) != decoded) ||\n ((decoded >= 5'd5) && (decoded <= 5'd20) && (len_i != 8'd0)))\n return 5'd22;\n if (instruction_i && !(`SOC_ADDR_IS_FLASH(addr_i) ||\n ((addr_i >= 32'h5000_0000) && (addr_i < 32'h5400_0000))))\n return ((decoded >= 5'd5) && (decoded <= 5'd20)) ? 5'd22 : 5'd21;\n return decoded;\n endfunction", + "assign s_busy_d = (s_busy_q & ~s_done) | s_admit;", + "assign owner_idle_o[owner] = !(s_busy_q[2*owner] || s_busy_q[2*owner+1]);", + "assign s_eligible[2*owner] = rst_n_i && owner_idle_o[owner] && masters[owner].arvalid && (!masters[owner].awvalid || !s_cpu_prefer_write_q);", + "assign s_eligible[2*owner+1] = rst_n_i && owner_idle_o[owner] && masters[owner].awvalid && (!masters[owner].arvalid || s_cpu_prefer_write_q);" ] }, { @@ -177,6 +182,20 @@ "RS_DMA_CH_REG(channel, RS_DMA_CH_REG_TCD_COUNT) = UINT32_C(1);", "current = (rs_dma_tcd_t *)(uintptr_t)current->next_ptr;" ] + }, + { + "file": "rtl/tiny/top/tiny_axi4_target.sv", + "text": [ + "assign s_accept = (s_req_q.state == Idle) && enable_i && s_valid;", + "assign s_terminal = s_resp_active && (s_req_q.write ? (s_b_valid && s_bready[s_req_q.owner]) : (s_r_valid && s_rready[s_req_q.owner] && s_r_data[1]));", + "assign fault_valid_o = s_terminal && s_req_q.error_seen;", + "if (!s_req_q.error_seen && (s_rin_data[3:2] != 2'b00)) begin s_req_d.error_seen = 1'b1; s_req_d.error_addr = s_req_q.read_addr; s_req_d.error_resp = s_rin_data[3:2]; end", + "assign s_abort_write_valid = (s_req_q.state == AbortResponse) && s_req_q.write && s_req_q.write_done && !s_req_q.b_captured;", + "assign s_abort_read_valid = (s_req_q.state == AbortResponse) && !s_req_q.write && (s_req_q.read_count <= {1'b0, s_saved_addr.len});", + ".flush_i(1'b0)", + "AbortWrite: begin", + "if (s_terminal) s_req_d.state = Idle;" + ] } ] } diff --git a/publications/programming_reference.py b/publications/programming_reference.py index ca4de1e9..afc9685c 100644 --- a/publications/programming_reference.py +++ b/publications/programming_reference.py @@ -125,6 +125,26 @@ def clean_source(text: str) -> str: return re.sub(r"\s+", "", re.sub(r"/\*.*?\*/|//[^\n]*", "", text, flags=re.S)) +def mini_channel_enum(text: str) -> str: + """Select Mini's channel roles, excluding the Tiny product branch. + + Helper aliases share an allocated context; they do not allocate another + channel. Keep their Mini bulk binding checked instead of parsing Tiny's + literal 6/7 values from an inactive preprocessor branch. + """ + match = re.search(r"typedef enum\s*\{([^{}]*)\}\s*rs_dma_channel_t;", text, re.S) + if match is None: + raise ValueError("missing DMA channel enumeration") + body = re.sub(r"#ifdef RS_SOC_TINY\s*\n[^#]*#else\s*\n([^#]*)#endif", r"\1", match[1]) + if "#" in body: + raise ValueError("unreviewed Mini DMA channel selection") + aliases = dict(re.findall(r"(RS_DMA_CHANNEL_\w+)\s*=\s*(RS_DMA_CHANNEL_\w+)\s*,", body)) + expected = {"RS_DMA_CHANNEL_DVP", "RS_DMA_CHANNEL_I2S_TX", "RS_DMA_CHANNEL_I2S_RX"} + if any(name not in expected or value != "RS_DMA_CHANNEL_BULK" for name, value in aliases.items()): + raise ValueError("Mini DMA helper alias binding changed") + return body + + def validate_programming(spec: dict, ids: set[str], root: Path, revision: str) -> None: for relative in dependencies(spec): path = root / relative @@ -193,7 +213,7 @@ def collect_programming(root: Path, spec: dict, ids: set[str], revision: str) -> result = copy.deepcopy(spec) sdk_text = (root / "crt/include/retrosoc/hal/dma.h").read_text() sdk = constants(sdk_text, "RS_DMA_REQUEST_") - channels = constants(sdk_text, "RS_DMA_CHANNEL_") + channels = constants(mini_channel_enum(sdk_text), "RS_DMA_CHANNEL_") # Capacity is selected by product; it is not a named channel allocation. # Keep the Mini publication's role inventory independent of Tiny's count. channels.update(constants((root / "crt/include/retrosoc/hal/dma_regs.h").read_text(), diff --git a/publications/tiny_reference.py b/publications/tiny_reference.py index 42c4a022..df366c3a 100644 --- a/publications/tiny_reference.py +++ b/publications/tiny_reference.py @@ -26,6 +26,9 @@ TOP = "rtl/tiny/top/retrosoc_tiny.sv" RESET_TREE = "rtl/tiny/top/tiny_reset_tree.sv" SYSCTRL = "rtl/tiny/top/tiny_sysctrl.sv" ARCHINFO = "rtl/tiny/top/tiny_archinfo.sv" +CPU = "rtl/tiny/top/tiny_cpu_wrapper.sv" +CPU_MEM = "rtl/tiny/top/tiny_cpu_mem.sv" +SRAM = "rtl/tiny/top/tiny_sram.sv" FAMILIES = {"xpi", "sram", "gpio", "dma", "timer", "pwm", "rtc", "wdg", "uart", "i2c", "clint", "archinfo"} MANAGED = {"publication_media", "hazard3", "cluster_common", "cluster_archinfo", "cluster_pwm", "cluster_rtc", "cluster_wdg"} SUPPORTED_SYSCTRL = { @@ -65,21 +68,30 @@ def facts(root: Path = ROOT) -> dict: if any(profile.get(key) != value for key, value in required.items()): raise ValueError("Tiny publication profile changed; review configuration and ISA boundary") top = (root / TOP).read_text(encoding="utf-8") - ram = instance_parameters(top, "onchip_ram", "u_sram") + ram_reg = instance_parameters((root / SRAM).read_text(), "onchip_ram_reg", "u_reg") + ram = {"CapacityKiB": ram_reg.get("CapacityKiB"), + "DataWidth": 8 * ram_reg.get("DataBytes", 0), "IdWidth": 1} dma = instance_parameters(top, "apb4_dma", "u_dma") - cpu = instance_parameters(top, "mgmt_core_wrapper", "u_cpu") - if ram != {"CapacityKiB": 128, "DataWidth": 32, "IdWidth": 1, "Ics55SmallBanks": 1}: + cpu = instance_parameters(top, "tiny_cpu_wrapper", "u_cpu") + if ram != {"CapacityKiB": 128, "DataWidth": 32, "IdWidth": 1} or ram_reg.get("Present") != 1: raise ValueError("Tiny SRAM parameters changed") - # The publication remains the reviewed IHP130 compatibility profile; - # selecting ICS55's 4 KiB geometry does not alter its reported SRAM facts. - ram = {key: value for key, value in ram.items() if key != "Ics55SmallBanks"} + # P4 changes local routing, not physical geometry or the book's IHP130 + # compatibility selection. Snapshot enforcement still precedes publication. + topology = read_topology(root / TOPOLOGY, root / MAP) + require_snippets(root, TOP, ["tiny_sram u_sram", "gen_sram_attachment", "axi4_connector u_connector", + ".ID_WIDTH(1)", ".DATA_WIDTH(32)"]) + require_snippets(root, SRAM, ["group < 4", "tiny_sram_group u_group"]) if dma != {"NumChannels": 4, "MaxBurstBeats": 16, "FifoDepth": 32, "RequestMask": 0x7F9, "EnableStreams": 0}: raise ValueError("Tiny DMA parameters changed") - if cpu != {"ExternalIrqCount": 30, "EnableAtomics": 0, "TwoCycleBusErrors": 1, "ResetSyncStages": 5}: + if cpu != {"ExternalIrqCount": 30, "EnableAtomics": 0, "ResetSyncStages": 5}: raise ValueError("Tiny CPU integration changed") require_snippets(root, RESET_TREE, [".STAGE(5)", "assign cpu_ready_rst_n_o = &leaf_rst_n_o;"]) - require_snippets(root, "rtl/ip/core/mgmt_core_wrapper.sv", [".EXTENSION_C(1)", ".EXTENSION_A(EnableAtomics)"]) - require_snippets(root, TOP, ["u_masters_axi4_if[2]", ".axi4(u_masters_axi4_if[0])", + require_snippets(root, CPU, ["hazard3_cpu_2port", ".EXTENSION_C(1)", ".EXTENSION_A(EnableAtomics)"]) + require_snippets(root, CPU_MEM, [".TwoCycleErrors(1'b1)", "ErrorFirst", "ErrorLast"]) + require_snippets(root, TOP, ["u_masters_axi4_if[3]", "u_targets_axi4_if[23]", + "assign u_masters_axi4_if[2].arvalid = 1'b0;", + "assign u_masters_axi4_if[2].awvalid = 1'b0;", + ".slow_axi4(u_masters_axi4_if[0])", ".axi4(u_masters_axi4_if[1])", ".cfg_apb4(u_sram_apb4_if)", "assign s_tick = s_tick_count_q == 5'd23;"]) require_snippets(root, SYSCTRL, ["(apb4.paddr[1:0] != 2'd0)", "(apb4.pstrb != 4'hf)", @@ -99,6 +111,7 @@ def facts(root: Path = ROOT) -> dict: raise ValueError("Tiny discovery parameters changed") require_snippets(root, ARCHINFO, ["32'h5449_4e59 : u_info_apb4_if.prdata", ".device_id_valid_i(1'b0)", ".device_id_read_enable_i(1'b0)"]) return {"profile": profile, "sram": ram, "dma": dma, "cpu": cpu, "archinfo": arch, + "fabric": topology["fabric"], "hardware_isa": "RV32IMC", "firmware_isa": profile["ISA"], "soc_id": 0x54494E59, "system_hz": int(profile["EXT_CLK_HZ"]), "timebase_hz": int(profile["CLINT_TIMEBASE_HZ"])} @@ -130,7 +143,7 @@ def sysctrl_registers(root: Path) -> dict: offsets = {r["symbol"]: int(r["offset"], 0) for r in read(root / MAP)["sysctrl_registers"]} definitions = { "FAULT_STATUS": ("RW1C", "Sticky first fault. Bit 0 clears pending; a new fault can win after clear.", [("PENDING", 0, 0, "A fault record is pending."), ("WRITE", 1, 1, "Faulting transaction was a write."), ("CLASS", 2, 4, "1: decode error; 2: protocol/target error.")]), - "FAULT_MASTER": ("RO", "Master of the captured first fault.", [("MASTER", 0, 0, "0 CPU, 1 DMA.")]), + "FAULT_MASTER": ("RO", "Master of the captured first fault.", [("MASTER", 0, 1, "0 CPU, 1 DMA, 2 reserved SDIO ingress; SDIO is not integrated.")]), "FAULT_DETAIL": ("RO", "Captured AXI response code.", [("RESPONSE", 0, 1, "AXI RRESP/BRESP encoding.")]), "PERF_CTRL": ("RW / command", "Enable counters, clear counters/snapshots or capture a coherent pair. Clear takes precedence over snapshot.", [("ENABLE", 0, 0, "Counter enable state."), ("CLEAR", 1, 1, "Write-one pulse; reads zero."), ("SNAPSHOT", 2, 2, "Write-one pulse; reads zero.")]), "TEST_STATUS": ("RW sticky", "First full-word valid result is sticky until system reset; write mask 0x8000FF01.", [("PASS", 0, 0, "Pass when valid is set."), ("CODE", 8, 15, "Application-defined result code."), ("VALID", 31, 31, "The first valid write locks the result.")]), diff --git a/rtl/ip/core/mgmt_core_wrapper.sv b/rtl/ip/core/mgmt_core_wrapper.sv index 6afa424c..51bacb5b 100644 --- a/rtl/ip/core/mgmt_core_wrapper.sv +++ b/rtl/ip/core/mgmt_core_wrapper.sv @@ -72,36 +72,37 @@ module mgmt_core_wrapper #( .JtagIdcode (`SOC_JTAG_IDCODE), .ResetSyncStages(ResetSyncStages) ) u_mgmt_debug_wrapper ( - .clk_i (clk_i), - .rst_n_i (rst_n_i), - .bridge_idle_i (s_ahbl_idle), - .jtag_tck_i (jtag_tck_i), - .jtag_tms_i (jtag_tms_i), - .jtag_tdi_i (jtag_tdi_i), - .jtag_trst_n_i (jtag_trst_n_i), - .jtag_tdo_o (jtag_tdo_o), - .core_rst_n_o (s_core_rst_n), - .dbg_req_halt_o (s_dbg_req_halt), - .dbg_req_halt_on_reset_o (s_dbg_req_halt_on_reset), - .dbg_req_resume_o (s_dbg_req_resume), - .dbg_halted_i (s_dbg_halted), - .dbg_running_i (s_dbg_running), - .dbg_data0_rdata_o (s_dbg_data0_rdata), - .dbg_data0_wdata_i (s_dbg_data0_wdata), - .dbg_data0_wen_i (s_dbg_data0_wen), - .dbg_instr_data_o (s_dbg_instr_data), - .dbg_instr_data_vld_o (s_dbg_instr_data_vld), - .dbg_instr_data_rdy_i (s_dbg_instr_data_rdy), + .clk_i(clk_i), + .rst_n_i(rst_n_i), + .bridge_idle_i(s_ahbl_idle), + .jtag_tck_i(jtag_tck_i), + .jtag_tms_i(jtag_tms_i), + .jtag_tdi_i(jtag_tdi_i), + .jtag_trst_n_i(jtag_trst_n_i), + .jtag_tdo_o(jtag_tdo_o), + .core_rst_n_o(s_core_rst_n), + .reset_pending_o(), // Mini retains its existing reset policy. + .dbg_req_halt_o(s_dbg_req_halt), + .dbg_req_halt_on_reset_o(s_dbg_req_halt_on_reset), + .dbg_req_resume_o(s_dbg_req_resume), + .dbg_halted_i(s_dbg_halted), + .dbg_running_i(s_dbg_running), + .dbg_data0_rdata_o(s_dbg_data0_rdata), + .dbg_data0_wdata_i(s_dbg_data0_wdata), + .dbg_data0_wen_i(s_dbg_data0_wen), + .dbg_instr_data_o(s_dbg_instr_data), + .dbg_instr_data_vld_o(s_dbg_instr_data_vld), + .dbg_instr_data_rdy_i(s_dbg_instr_data_rdy), .dbg_instr_caught_exception_i(s_dbg_instr_caught_exception), - .dbg_instr_caught_ebreak_i (s_dbg_instr_caught_ebreak), - .dbg_sbus_addr_o (s_dbg_sbus_addr), - .dbg_sbus_write_o (s_dbg_sbus_write), - .dbg_sbus_size_o (s_dbg_sbus_size), - .dbg_sbus_vld_o (s_dbg_sbus_vld), - .dbg_sbus_rdy_i (s_dbg_sbus_rdy), - .dbg_sbus_err_i (s_dbg_sbus_err), - .dbg_sbus_wdata_o (s_dbg_sbus_wdata), - .dbg_sbus_rdata_i (s_dbg_sbus_rdata) + .dbg_instr_caught_ebreak_i(s_dbg_instr_caught_ebreak), + .dbg_sbus_addr_o(s_dbg_sbus_addr), + .dbg_sbus_write_o(s_dbg_sbus_write), + .dbg_sbus_size_o(s_dbg_sbus_size), + .dbg_sbus_vld_o(s_dbg_sbus_vld), + .dbg_sbus_rdy_i(s_dbg_sbus_rdy), + .dbg_sbus_err_i(s_dbg_sbus_err), + .dbg_sbus_wdata_o(s_dbg_sbus_wdata), + .dbg_sbus_rdata_i(s_dbg_sbus_rdata) ); hazard3_cpu_1port #( diff --git a/rtl/ip/core/mgmt_debug_reset.sv b/rtl/ip/core/mgmt_debug_reset.sv index 9505eabc..526d568f 100644 --- a/rtl/ip/core/mgmt_debug_reset.sv +++ b/rtl/ip/core/mgmt_debug_reset.sv @@ -19,12 +19,15 @@ module mgmt_debug_reset #( input logic reset_req_i, input logic bridge_idle_i, output logic core_rst_n_o, + output logic reset_pending_o, output logic reset_done_o ); logic s_pending_d, s_pending_q; logic s_reset_active_d, s_reset_active_q; + assign reset_pending_o = reset_req_i || s_pending_q || s_reset_active_q; + always_comb begin s_pending_d = s_pending_q; s_reset_active_d = s_reset_active_q; diff --git a/rtl/ip/core/mgmt_debug_wrapper.sv b/rtl/ip/core/mgmt_debug_wrapper.sv index 5d6bad1c..6c4faa68 100644 --- a/rtl/ip/core/mgmt_debug_wrapper.sv +++ b/rtl/ip/core/mgmt_debug_wrapper.sv @@ -11,11 +11,13 @@ // Hazard3 JTAG-DTM and Debug Module integration. The Debug Module is in the // management-core clock domain and exposes abstract commands only (no SBA). module mgmt_debug_wrapper #( - parameter logic [31:0] JtagIdcode = 32'hDEAD_BEEF, - parameter int ResetSyncStages = 3 + parameter logic [31:0] JtagIdcode = 32'hDEAD_BEEF, + parameter int ResetSyncStages = 3, + parameter bit SeparateSystemReset = 1'b0 ) ( input logic clk_i, input logic rst_n_i, + input logic system_rst_n_i, input logic bridge_idle_i, input logic jtag_tck_i, input logic jtag_tms_i, @@ -23,6 +25,7 @@ module mgmt_debug_wrapper #( input logic jtag_trst_n_i, output logic jtag_tdo_o, output logic core_rst_n_o, + output logic reset_pending_o, output logic dbg_req_halt_o, output logic dbg_req_halt_on_reset_o, @@ -154,12 +157,13 @@ module mgmt_debug_wrapper #( mgmt_debug_reset #( .ResetSyncStages(ResetSyncStages) ) u_mgmt_debug_reset ( - .clk_i (clk_i), - .rst_n_i (rst_n_i), - .reset_req_i (s_sys_reset_req || s_hart_reset_req), - .bridge_idle_i(bridge_idle_i), - .core_rst_n_o (core_rst_n_o), - .reset_done_o (s_reset_done) + .clk_i (clk_i), + .rst_n_i (SeparateSystemReset ? system_rst_n_i : rst_n_i), + .reset_req_i (s_sys_reset_req || s_hart_reset_req), + .bridge_idle_i (bridge_idle_i), + .core_rst_n_o (core_rst_n_o), + .reset_pending_o(reset_pending_o), + .reset_done_o (s_reset_done) ); endmodule diff --git a/rtl/ip/peripheral/apb4_dma.sv b/rtl/ip/peripheral/apb4_dma.sv index 9cc49bdd..b5b5a68b 100644 --- a/rtl/ip/peripheral/apb4_dma.sv +++ b/rtl/ip/peripheral/apb4_dma.sv @@ -18,6 +18,7 @@ module apb4_dma #( parameter bit EnableStreams = 1'b1 ) ( // verilog_format: off -- integration ports retain established peripheral endpoint names. + output logic lifecycle_idle_o, input logic clk_i, input logic rst_n_i, output logic dma_xfer_done_o, @@ -179,4 +180,7 @@ module apb4_dma #( .crypto_in_axis (crypto_in_axis), .crypto_out_axis (crypto_out_axis) ); + logic s_bus_busy; + assign s_bus_busy = axi4.awvalid || axi4.arvalid || axi4.wvalid; + assign lifecycle_idle_o = !(|s_busy) && !(|s_start) && !s_bus_busy; endmodule diff --git a/rtl/ip/peripheral/apb4_timer.sv b/rtl/ip/peripheral/apb4_timer.sv index 5db235bd..6fca13af 100644 --- a/rtl/ip/peripheral/apb4_timer.sv +++ b/rtl/ip/peripheral/apb4_timer.sv @@ -10,6 +10,7 @@ module apb4_timer ( // verilog_format: off -- preserve reviewed column alignment + output logic lifecycle_idle_o, input logic clk_i, input logic rst_n_i, input logic debug_halted_i, @@ -89,4 +90,5 @@ module apb4_timer ( .one_shot_done_o (s_one_shot_done) ); + assign lifecycle_idle_o = !s_en; endmodule diff --git a/rtl/ip/security/apb4_crypto.sv b/rtl/ip/security/apb4_crypto.sv index f766b5a8..8580dbcc 100644 --- a/rtl/ip/security/apb4_crypto.sv +++ b/rtl/ip/security/apb4_crypto.sv @@ -7,6 +7,7 @@ module apb4_crypto #( parameter logic [5:0] DropResponseMask = 6'd0, parameter bit DuplicateResponse = 1'b0 ) ( + output logic lifecycle_idle_o, input logic clk_i, rst_n_i, output logic dma_input_proc_o, @@ -43,9 +44,13 @@ module apb4_crypto #( logic [11:0] s_offset; logic [31:0] s_wdata; logic [ 3:0] s_strb; - assign s_offset = s_regs_q.offset; - assign s_wdata = s_regs_q.data; - assign s_strb = s_regs_q.mask; + logic s_transport_busy; + assign s_transport_busy = (|s_fifo_busy) || (|s_engine_pending_q) || s_aes_key_busy; + assign lifecycle_idle_o = !(|s_busy) && !s_maintenance_busy && !s_transport_busy; + + assign s_offset = s_regs_q.offset; + assign s_wdata = s_regs_q.data; + assign s_strb = s_regs_q.mask; crypto_mem_req_t [5:0] s_engine_req, s_maintenance_req, s_store_req; crypto_mem_resp_t [5:0] s_store_resp, s_engine_resp, s_maintenance_resp; logic [ 6:0] s_mem_stat; diff --git a/rtl/ip/serial/apb4_i2c.sv b/rtl/ip/serial/apb4_i2c.sv index ee367af9..26372198 100644 --- a/rtl/ip/serial/apb4_i2c.sv +++ b/rtl/ip/serial/apb4_i2c.sv @@ -13,6 +13,7 @@ module apb4_i2c #( parameter int RxFifoDepth = 16 ) ( // verilog_format: off -- preserve reviewed column alignment + output logic lifecycle_idle_o, input logic clk_i, input logic rst_n_i, output logic dma_tx_stall_o, @@ -66,10 +67,12 @@ module apb4_i2c #( logic s_recovery_failed_event; logic s_core_cmd_flush; - assign i2c.scl_o = 1'b0; - assign i2c.sda_o = 1'b0; - assign i2c.scl_oe_o = s_scl_pull_low; - assign i2c.sda_oe_o = s_sda_pull_low; + assign lifecycle_idle_o = !s_en && !s_busy && !s_recovery_active; + + assign i2c.scl_o = 1'b0; + assign i2c.sda_o = 1'b0; + assign i2c.scl_oe_o = s_scl_pull_low; + assign i2c.sda_oe_o = s_sda_pull_low; i2c_filter u_i2c_filter ( .clk_i (clk_i), diff --git a/rtl/ip/serial/apb4_i2s.sv b/rtl/ip/serial/apb4_i2s.sv index be1a54f8..969de72a 100644 --- a/rtl/ip/serial/apb4_i2s.sv +++ b/rtl/ip/serial/apb4_i2s.sv @@ -41,6 +41,8 @@ module apb4_i2s ( output logic tx_underrun_evt_o, output logic rx_overrun_evt_o, output logic rx_flush_busy_o, + output logic lifecycle_idle_o, + output logic lifecycle_reset_ready_o, apb4_if.slave apb4, axi4_stream_if.sink tx_axis, axi4_stream_if.source rx_axis, @@ -120,6 +122,36 @@ module apb4_i2s ( logic s_tx_underrun_sys; logic s_rx_overrun_sys; logic s_tx_flush_sent_d, s_tx_flush_sent_q; + logic s_cfg_ready; + logic [31:0] s_cfg_sent_d, s_cfg_sent_q; + logic s_audio_used_d, s_audio_used_q; + + // A disabled host which has never enabled its PHY may change host clock + // even when AUDIO is absent. After use, require the disabled configuration + // to reach AUDIO, empty FIFOs, and completed flush acknowledgements. + assign s_cfg_sent_d = s_cfg_ready ? s_cfg_sys : s_cfg_sent_q; + assign s_audio_used_d = s_audio_used_q || s_cfg_sys[`APB4_I2S__CFG_ENABLE]; + assign lifecycle_reset_ready_o = s_tx_src_ready && !s_tx_flush_busy && !s_rx_flush_busy; + assign lifecycle_idle_o = !s_cfg_sys[`APB4_I2S__CFG_ENABLE] && + !s_stream_tx_en && !s_stream_rx_en && + (!s_audio_used_q || ((s_cfg_sent_q == s_cfg_sys) && s_cfg_ready && + s_tx_level == 0 && s_rx_level == 0 && !s_tx_flush_busy && !s_rx_flush_busy)); + dffr #( + .DATA_WIDTH(32) + ) u_cfg_sent ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_cfg_sent_d), + .dat_o (s_cfg_sent_q) + ); + dffr #( + .DATA_WIDTH(1) + ) u_audio_used ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_audio_used_d), + .dat_o (s_audio_used_q) + ); assign s_cmd_sys = {s_cmd_rx_flush, s_cmd_tx_flush}; assign s_tx_src_clear = s_cmd_tx_flush && !s_tx_flush_sent_q && !s_tx_src_clear_busy; @@ -191,7 +223,7 @@ module apb4_i2s ( .src_rst_n_i(rst_n_i), .src_data_i (s_cfg_sys), .src_valid_i(1'b1), - .src_ready_o(), + .src_ready_o(s_cfg_ready), .dst_clk_i (clk_aud_i), .dst_rst_n_i(rst_aud_n_i), .dst_data_o (s_cfg_aud), diff --git a/rtl/ip/serial/apb4_uart.sv b/rtl/ip/serial/apb4_uart.sv index a3ab46cb..f29f25e2 100644 --- a/rtl/ip/serial/apb4_uart.sv +++ b/rtl/ip/serial/apb4_uart.sv @@ -13,6 +13,7 @@ module apb4_uart #( parameter int RxFifoDepth = 64 ) ( // verilog_format: off -- preserve reviewed column alignment + output logic lifecycle_idle_o, input logic clk_i, input logic rst_n_i, output logic dma_tx_stall_o, @@ -53,8 +54,10 @@ module apb4_uart #( logic s_tx_flow_blocked; logic s_cts_change; - assign uart.tx_o = s_tx; - assign uart.irq_o = s_irq; + assign lifecycle_idle_o = !s_tx_busy && !s_rx_active && !s_tx_data_valid && !s_rx_en && !s_break; + + assign uart.tx_o = s_tx; + assign uart.irq_o = s_irq; uart_reg #( .TxFifoDepth(TxFifoDepth), diff --git a/rtl/ip/serial/apb4_ws2812.sv b/rtl/ip/serial/apb4_ws2812.sv index e0a365d7..0b117927 100644 --- a/rtl/ip/serial/apb4_ws2812.sv +++ b/rtl/ip/serial/apb4_ws2812.sv @@ -12,6 +12,7 @@ module apb4_ws2812 #( parameter int TxFifoDepth = 16 ) ( // verilog_format: off -- preserve reviewed column alignment + output logic lifecycle_idle_o, input logic clk_i, input logic rst_n_i, apb4_if.slave apb4, @@ -33,8 +34,10 @@ module apb4_ws2812 #( logic s_done, s_underflow, s_aborted; logic s_irq, s_dat; - assign ws2812.dat_o = s_dat; - assign ws2812.irq_o = s_irq; + assign lifecycle_idle_o = !s_busy && !s_reset_active && !s_start && !s_data_valid; + + assign ws2812.dat_o = s_dat; + assign ws2812.irq_o = s_irq; ws2812_reg #( .TxFifoDepth(TxFifoDepth) diff --git a/rtl/ip/serial/i2s_recv.sv b/rtl/ip/serial/i2s_recv.sv index 1b18bbeb..b9e4be7a 100644 --- a/rtl/ip/serial/i2s_recv.sv +++ b/rtl/ip/serial/i2s_recv.sv @@ -24,14 +24,14 @@ module i2s_recv ( logic s_lrck_trg; logic [4:0] s_bit_cnt_d, s_bit_cnt_q; logic [4:0] s_bit_num; - logic s_first_bit_d, s_first_bit_q; + logic s_done_d, s_done_q; logic [23:0] s_recv_data_d, s_recv_data_q; logic [23:0] s_data_d, s_data_q; assign s_bit_num = bitmode_i ? 5'd23 : 5'd15; assign data_o = s_data_q; - assign done_o = s_bit_cnt_q == s_bit_num; + assign done_o = s_done_q; assign s_lrck_trg = lrck_i ^ s_lrck_dely_q; @@ -89,26 +89,25 @@ module i2s_recv ( ); - always_comb begin - s_first_bit_d = s_first_bit_q; - if (s_lrck_trg) s_first_bit_d = adcdat_i; - end - dffer #( + // I2S changes LRCK one bit before the next sample's MSB. The bit + // sampled on that transition is the current sample's LSB, not the next + // sample's first bit. Publish completion after data_o has captured it. + assign s_done_d = sclk_pos_i && s_lrck_trg; + dffr #( .DATA_WIDTH(1) - ) u_first_bit_dffer ( + ) u_done_dffr ( .clk_i (clk_i), .rst_n_i(rst_n_i), - .en_i (sclk_pos_i), - .dat_i (s_first_bit_d), - .dat_o (s_first_bit_q) + .dat_i (s_done_d), + .dat_o (s_done_q) ); always_comb begin s_data_d = s_data_q; - if ((!s_lrck_trg) && (s_bit_cnt_q == (s_bit_num - 1'b1))) begin - if (bitmode_i) s_data_d = {s_recv_data_d[22:0], s_first_bit_q}; - else s_data_d = {8'd0, s_recv_data_d[14:0], s_first_bit_q}; + if (s_lrck_trg) begin + if (bitmode_i) s_data_d = {s_recv_data_q[22:0], adcdat_i}; + else s_data_d = {8'd0, s_recv_data_q[14:0], adcdat_i}; end end dffer #( diff --git a/rtl/ip/serial/i2s_reg.sv b/rtl/ip/serial/i2s_reg.sv index 5b246ec4..db6cd72a 100644 --- a/rtl/ip/serial/i2s_reg.sv +++ b/rtl/ip/serial/i2s_reg.sv @@ -92,7 +92,7 @@ module i2s_reg ( endfunction assign s_req = apb4.psel && apb4.penable && !s_ready_q; - assign s_write = |apb4.pstrb; + assign s_write = apb4.pwrite; assign s_accept = s_req; assign s_offset = apb4.paddr[11:0]; assign s_aligned = apb4.paddr[1:0] == 2'b00; diff --git a/rtl/ip/storage/apb4_sdio.sv b/rtl/ip/storage/apb4_sdio.sv index fbb3237e..fc872bb6 100644 --- a/rtl/ip/storage/apb4_sdio.sv +++ b/rtl/ip/storage/apb4_sdio.sv @@ -11,17 +11,20 @@ `timescale 1ns / 1ps module apb4_sdio #( - parameter int InputClockHz = 72_000_000, - parameter int AddrWidth = 32, - parameter int DataWidth = 32, - parameter int DescCount = 16 + parameter int InputClockHz = 72_000_000, + parameter bit UseRuntimeClockHz = 1'b0, + parameter int AddrWidth = 32, + parameter int DataWidth = 32, + parameter int DescCount = 16 ) ( // verilog_format: off -- preserve the APB, AXI, and pad column alignment + output logic lifecycle_idle_o, input logic clk_i, input logic rst_n_i, apb4_if.slave apb4, axi4_if.master dma_axi4, - sdio_if.dut sdio + sdio_if.dut sdio, + input logic [31:0] clock_hz_i // verilog_format: on ); logic s_busy; @@ -80,6 +83,10 @@ module apb4_sdio #( logic s_pio_read_consume; sdio_if u_sdio_core_if (); + logic s_bus_busy; + assign s_bus_busy = dma_axi4.awvalid || dma_axi4.arvalid || dma_axi4.wvalid; + assign lifecycle_idle_o = !s_busy && !s_clock_enable && !s_bus_busy; + assign u_sdio_core_if.cmd_di_i = sdio.cmd_di_i; assign u_sdio_core_if.dat_di_i = sdio.dat_di_i; assign sdio.sck_o = u_sdio_core_if.sck_o; @@ -149,14 +156,16 @@ module apb4_sdio #( ); sdio_core #( - .InputClockHz(InputClockHz), - .AddrWidth (AddrWidth), - .DataWidth (DataWidth), - .DescCount (DescCount) + .InputClockHz (InputClockHz), + .UseRuntimeClockHz(UseRuntimeClockHz), + .AddrWidth (AddrWidth), + .DataWidth (DataWidth), + .DescCount (DescCount) ) u_sdio_core ( .clk_i (clk_i), .rst_n_i (rst_n_i), .host_enable_i (s_host_enable), + .clock_hz_i (clock_hz_i), .host_irq_enable_i (s_host_irq_enable), .clock_enable_i (s_clock_enable), .half_period_i (s_half_period), diff --git a/rtl/ip/storage/apb4_xpi.sv b/rtl/ip/storage/apb4_xpi.sv index b07e4d01..14320df7 100644 --- a/rtl/ip/storage/apb4_xpi.sv +++ b/rtl/ip/storage/apb4_xpi.sv @@ -20,6 +20,7 @@ module apb4_xpi ( // verilog_format: off -- preserve reviewed port alignment + output logic lifecycle_idle_o, input logic clk_i, input logic rst_n_i, input logic dma_xfer_done_i, @@ -176,6 +177,11 @@ module apb4_xpi ( logic s_unused_dma_xfer_done; logic s_unused_mm_perf_split; + logic s_protocol_idle; + assign s_protocol_idle = !s_mm_busy && !s_indirect_busy && !s_core_busy; + assign lifecycle_idle_o = s_protocol_idle && + (s_owner_q == OwnerNone) && s_tx_fifo_empty && s_rx_fifo_empty; + assign s_owner_q = xpi_owner_e'(s_owner_bits_q); assign s_selected_req_valid = s_mm_busy ? s_mm_req_valid : (s_indirect_req_valid ? s_indirect_req_valid : s_mm_req_valid); diff --git a/rtl/ip/storage/sdio_command.sv b/rtl/ip/storage/sdio_command.sv index 1ad70bb3..bc22b3f2 100644 --- a/rtl/ip/storage/sdio_command.sv +++ b/rtl/ip/storage/sdio_command.sv @@ -88,7 +88,10 @@ module sdio_command #( assign s_frame_crc_error = s_frame_crc != s_resp_shift_next[7:1]; assign s_frame_index_error = s_resp_shift_next[45:40] != s_cmd_index_q; - assign cmd_oe_o = (s_state_q == Send) || (s_tx_final_hold_q && !sample_tick_i); + // Admission can coincide with an SDCLK rising edge. Do not expose the + // start bit until the first scheduled launch, or the card sees it twice. + assign cmd_oe_o = ((s_state_q == Send) && (s_tx_started_q || launch_tick_i)) || + (s_tx_final_hold_q && !sample_tick_i); assign cmd_do_o = ((s_state_q == Send) || s_tx_final_hold_q) ? s_tx_shift_q[47] : 1'b1; assign busy_o = (s_state_q != Idle) && (s_state_q != Complete); assign done_o = s_done_q; diff --git a/rtl/ip/storage/sdio_core.sv b/rtl/ip/storage/sdio_core.sv index dc179701..833832d4 100644 --- a/rtl/ip/storage/sdio_core.sv +++ b/rtl/ip/storage/sdio_core.sv @@ -11,15 +11,17 @@ `timescale 1ns / 1ps module sdio_core #( - parameter int InputClockHz = 72_000_000, - parameter int AddrWidth = 32, - parameter int DataWidth = 32, - parameter int DescCount = 16 + parameter int InputClockHz = 72_000_000, + parameter bit UseRuntimeClockHz = 1'b0, + parameter int AddrWidth = 32, + parameter int DataWidth = 32, + parameter int DescCount = 16 ) ( // verilog_format: off -- preserve the host, status, and external bus groups input logic clk_i, input logic rst_n_i, input logic host_enable_i, + input logic [31:0] clock_hz_i, input logic host_irq_enable_i, input logic clock_enable_i, input logic [15:0] half_period_i, @@ -102,7 +104,7 @@ module sdio_core #( logic s_data_tx_last; logic s_data_rx_valid; logic s_data_rx_ready; - logic s_data_rx_stall_q; + logic s_data_rx_stall; logic [ 31:0] s_data_rx_data; logic [ 3:0] s_data_rx_strb; logic s_data_rx_last; @@ -174,8 +176,8 @@ module sdio_core #( assign s_clock_en = host_enable_i && clock_enable_i && (s_cmd_busy || s_data_busy || s_dma_busy || cmd_start_i || data_start_i) && - !s_data_rx_stall_q && - !(s_data_busy && s_data_tx_wait && (s_data_oe == 4'b0000)); + !s_data_rx_stall && + !(s_data_busy && s_data_tx_wait); assign s_total_bytes = {16'd0, block_size_i} * {16'd0, block_count_i}; assign s_abort_request = host_abort_i || dma_abort_i || (s_dma_done && s_dma_err) || @@ -200,6 +202,9 @@ module sdio_core #( assign s_dma_data_in_strb = s_data_rx_strb; assign s_dma_data_in_last = s_data_rx_last; assign s_data_rx_ready = data_dma_enable_i ? s_dma_data_in_ready : !s_pio_valid_q; + // ReadEmit begins on the sampling edge. Stop before the very next launch + // if its word cannot retire; registering this condition loses one card bit. + assign s_data_rx_stall = s_data_rx_valid && !s_data_rx_ready; assign s_core_busy = s_cmd_busy || s_data_busy || s_dma_busy; assign busy_o = s_core_busy; assign s_pio_tx_pop = !data_dma_enable_i && s_data_tx_valid && s_data_tx_ready; @@ -233,7 +238,9 @@ module sdio_core #( assign bytes_done_o = s_dma_bytes_done; assign dma_error_addr_o = s_dma_err_addr; assign dma_error_o = {24'd0, s_dma_err_code}; - assign clock_actual_o = (half_period_i == 16'd0) ? 32'd0 : InputClockHz / (2 * half_period_i); + logic [31:0] s_input_clock_hz; + assign s_input_clock_hz = UseRuntimeClockHz && (clock_hz_i != 32'd0) ? clock_hz_i : InputClockHz; + assign clock_actual_o = (half_period_i == 16'd0) ? 32'd0 : s_input_clock_hz / (2 * half_period_i); assign status_o = { 23'd0, s_clock_running, @@ -401,7 +408,6 @@ module sdio_core #( s_dat1_meta_q <= 1'b1; s_dat1_sync_q <= 1'b1; s_dat1_seen_q <= 1'b0; - s_data_rx_stall_q <= 1'b0; s_pio_rdata_q <= '0; s_pio_valid_q <= 1'b0; s_pio_tx_rd_q <= '0; @@ -418,10 +424,9 @@ module sdio_core #( s_pio_tx_strb_q[index] <= '0; end end else begin - s_dat1_meta_q <= sdio.dat_di_i[1]; - s_dat1_sync_q <= s_dat1_meta_q; - s_dat1_seen_q <= s_dat1_seen_d; - s_data_rx_stall_q <= s_data_rx_valid && !s_data_rx_ready; + s_dat1_meta_q <= sdio.dat_di_i[1]; + s_dat1_sync_q <= s_dat1_meta_q; + s_dat1_seen_q <= s_dat1_seen_d; if (data_start_i) begin s_pio_tx_rd_q <= '0; diff --git a/rtl/ip/storage/sdio_data.sv b/rtl/ip/storage/sdio_data.sv index f520cf3b..6a6b0972 100644 --- a/rtl/ip/storage/sdio_data.sv +++ b/rtl/ip/storage/sdio_data.sv @@ -160,7 +160,12 @@ module sdio_data #( assign tx_ready_o = (s_state_q == WriteWaitWord) || (s_state_q == WriteNextWord) || ((s_state_q == WritePayload) && launch_tick_i && s_tx_word_final && (s_tx_total_q < s_target_bytes_q)); - assign tx_wait_o = (s_state_q == WriteWaitWord) || (s_state_q == WriteNextWord); + // Stop before launching the next word if memory has not supplied it. + // Waiting only after that launch exposes an idle bit to the card and + // corrupts the next word's MSB when the clock resumes. + assign tx_wait_o = (s_state_q == WriteWaitWord) || (s_state_q == WriteNextWord) || + ((s_state_q == WritePayload) && s_tx_word_final && + (s_tx_total_q < s_target_bytes_q) && !tx_valid_i); assign rx_valid_o = (s_state_q == ReadEmit); assign rx_data_o = s_rx_pending_data_q; assign rx_strb_o = s_rx_pending_strb_q; diff --git a/rtl/ip/storage/sdio_dma.sv b/rtl/ip/storage/sdio_dma.sv index 8cea0c60..e92c4b9d 100644 --- a/rtl/ip/storage/sdio_dma.sv +++ b/rtl/ip/storage/sdio_dma.sv @@ -516,11 +516,18 @@ module sdio_dma #( s_err_addr_d = s_payload_addr + s_burst_offset; end if (s_write_final_sent_q) begin - s_writeback_control_d = (s_desc_control_q & - ~(32'd1 << sdio_pkg::SDIO_DESC_OWN)) | - (s_fault_d ? (32'd1 << sdio_pkg::SDIO_DESC_ERROR) : - (32'd1 << sdio_pkg::SDIO_DESC_DONE)); - s_state_d = WritebackStart; + // A retired AXI burst is not necessarily the whole descriptor. + // Keep ownership while subsequent bursts receive its remaining + // bytes, just as the memory-to-card direction does. + if (s_fault_d || (s_desc_progress_d >= s_desc_count_q)) begin + s_writeback_control_d = (s_desc_control_q & + ~(32'd1 << sdio_pkg::SDIO_DESC_OWN)) | + (s_fault_d ? (32'd1 << sdio_pkg::SDIO_DESC_ERROR) : + (32'd1 << sdio_pkg::SDIO_DESC_DONE)); + s_state_d = WritebackStart; + end else begin + s_state_d = PayloadWriteStart; + end end end end diff --git a/rtl/mk/software.mk b/rtl/mk/software.mk index c5611759..7815caed 100644 --- a/rtl/mk/software.mk +++ b/rtl/mk/software.mk @@ -123,6 +123,7 @@ CRT_SRCS := $(ROOT_PATH)/crt/arch/riscv/startup.S \ $(ROOT_PATH)/crt/src/hal/clint.c \ $(ROOT_PATH)/crt/src/hal/clock.c \ $(ROOT_PATH)/crt/src/hal/sysctrl.c \ + $(ROOT_PATH)/crt/src/hal/rcu.c \ $(ROOT_PATH)/crt/src/hal/uart_math.c \ $(ROOT_PATH)/crt/src/hal/uart.c \ $(ROOT_PATH)/crt/src/hal/gpio_math.c \ @@ -182,7 +183,8 @@ CFLAGS += -DRS_SOC_TINY TINY_CRT_NAMES := startup.S clzsi2.c divdi3.c ffssi2.c udivdi3.c umoddi3.c \ stdlib.c string.c console.c printf.c archinfo.c clint.c uart_math.c uart.c \ gpio_math.c gpio.c timer_math.c timer.c pwm.c rtc.c wdg.c watchdog.c i2c_math.c i2c.c \ - dma_math.c dma.c onchip_sram.c xpi.c + dma_math.c dma.c onchip_sram.c xpi.c rng.c crc.c ws2812_math.c ws2812.c \ + crypto.c crypto_lifecycle.c i2s_math.c i2s.c sdio_math.c sdio.c rcu.c CRT_SRCS := $(foreach source,$(CRT_SRCS),$(if $(filter $(notdir $(source)),$(TINY_CRT_NAMES)),$(source))) CRT_SRCS += $(ROOT_PATH)/crt/src/hal/sysctrl.c endif @@ -285,12 +287,12 @@ asm: $(MPW_VARIANT_DEP) $(MEMORY_MAP_STAMP) cp $(SW_BUILD_DIR)/asm/hello-asm.bin $(SW_BUILD_DIR)/$(ASM_FIRMWARE_NAME).bin cp $(SW_BUILD_DIR)/asm/hello-asm.txt $(SW_BUILD_DIR)/$(ASM_FIRMWARE_NAME)_all.txt +endif + $(CRYPTO_CONSTANTS_HEADER): $(ROOT_PATH)/scripts/crypto_constants.py $(ROOT_PATH)/scripts/data/crypto_constants.json $(PYTHON) $(ROOT_PATH)/scripts/crypto_constants.py --root $(ROOT_PATH) --output $(SW_BUILD_DIR)/include -endif - -CRYPTO_CONSTANTS_DEP := $(if $(filter TINY,$(SOC)),,$(CRYPTO_CONSTANTS_HEADER)) +CRYPTO_CONSTANTS_DEP := $(CRYPTO_CONSTANTS_HEADER) $(FIRMWARE_ELF): $(MPW_VARIANT_DEP) $(MEMORY_MAP_STAMP) $(SOC_TOPOLOGY_STAMP) $(SOC_IRQ_METADATA) $(USER_EXTENSIONS_STAMP) $(ARCHINFO_METADATA_STAMP) $(VERSION_HEADER) $(CRYPTO_CONSTANTS_DEP) $(SRC_PATH) $(SW_HEADERS) $(LDS_PATH) \ $(ROOT_PATH)/rtl/mk/software.mk $(APP_MK) diff --git a/rtl/rtl_readiness.json b/rtl/rtl_readiness.json index 616992fc..d2973028 100644 --- a/rtl/rtl_readiness.json +++ b/rtl/rtl_readiness.json @@ -1,6 +1,40 @@ { "schema_version": 1, "targets": [ + { + "name": "tiny-r2-p5", + "paths": ["rtl/tiny/top"], + "status": "prototype", + "baseline_revision": "3131762b35e0ebfcd196395f955e7c1005bd18e7", + "configuration_digest": null, + "configuration_profiles": ["configs/ci/ics55-tiny.mk", "configs/ci/ihp130-tiny.mk"], + "required_evidence": [], + "synthesis_intent": { + "registers": "Common registers and bounded response spill registers per external target", + "reset": "accepted CPU work drains before hart reset; system reset invalidates all sessions", + "clock_enable": "all fabric, APB, CPU and SRAM logic remains on direct SAFE24 SYS", + "memory": "unchanged 32 SRAM macros; no global SRAM transaction lock", + "pipeline": "one combined transaction per target, CPU one combined, DMA/SDIO one per direction" + }, + "waivers": [] + }, + { + "name": "tiny-r2-p4", + "paths": ["rtl/tiny/top"], + "status": "prototype", + "baseline_revision": "4db27524a6282ca40d76b935415b89f043d6ea83", + "configuration_digest": null, + "configuration_profiles": ["configs/ci/ics55-tiny.mk", "configs/ci/ihp130-tiny.mk"], + "required_evidence": [], + "synthesis_intent": { + "registers": "Common registers; independent I/D request state and per-group responses", + "reset": "five-edge release; hart halt/drain leaves SRAM and unrelated DMA active", + "clock_enable": "CPU and all 32 main-SRAM macros use direct SAFE24 SYS", + "memory": "four groups of eight tc_sram_1024x32 macros; no payload reset or extra SRAM", + "pipeline": "AHB capture, macro issue, registered response; uncontended completion within three cycles" + }, + "waivers": [] + }, { "name": "owned-rtl", "paths": [ diff --git a/rtl/rtl_style_audit.json b/rtl/rtl_style_audit.json index 1dc7ba28..7cfbeac7 100644 --- a/rtl/rtl_style_audit.json +++ b/rtl/rtl_style_audit.json @@ -439,8 +439,26 @@ "tiny_archinfo.sv", "tiny_axi42apb4.sv", "tiny_axi4_fabric.sv", + "tiny_axi4_target.sv", + "tiny_cpu_mem.sv", + "tiny_cpu_wrapper.sv", + "tiny_fault_collector.sv", + "tiny_clock_reset_subsystem.sv", + "tiny_axi4_async_bridge.sv", + "tiny_apb_async_bridge.sv", + "tiny_axi4_admission.sv", + "tiny_managed_lifecycle.sv", + "tiny_peripheral_leaf.sv", + "tiny_peripheral_control.sv", + "tiny_clock_output.sv", + "tiny_clock_gate.sv", "tiny_reset_tree.sv", - "tiny_sysctrl.sv" + "tiny_sram.sv", + "tiny_sram_axi4.sv", + "tiny_sram_group.sv", + "tiny_sram_port_if.sv", + "tiny_sysctrl.sv", + "tiny_rcu_regs.svh" ] }, "reviewed_boundaries": [ diff --git a/rtl/tech/tc_pll.sv b/rtl/tech/tc_pll.sv index 434178e4..81eb3830 100644 --- a/rtl/tech/tc_pll.sv +++ b/rtl/tech/tc_pll.sv @@ -8,6 +8,7 @@ // MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. +`timescale 1ns / 1ps module tc_pll #( parameter bit UseIcs55EcosPll = 1'b0, parameter bit Ics55Parked = 1'b0 @@ -28,10 +29,10 @@ module tc_pll #( logic [2:0] s_lock_count_d, s_lock_count_q; logic s_lock_d, s_lock_q; logic s_apply_seen_d, s_apply_seen_q; - logic s_cfg_apply; - logic s_force_lock_fail; - time s_last_ref_edge; - time s_ref_period; + logic s_cfg_apply; + logic s_force_lock_fail; + realtime s_last_ref_edge; + realtime s_ref_period; function automatic int pll_multiplier(input logic [2:0] cfg_sel); case (cfg_sel) @@ -55,14 +56,14 @@ module tc_pll #( always @(posedge fref_i) begin if (s_last_ref_edge != 0) begin - s_ref_period = $time - s_last_ref_edge; + s_ref_period = $realtime - s_last_ref_edge; end - s_last_ref_edge = $time; + s_last_ref_edge = $realtime; end always begin @(posedge fref_i); - wait (pll_lock_o); + wait (pll_lock_o && s_ref_period > 0.0); while (pll_lock_o) begin #(s_ref_period / (2 * pll_multiplier(s_cfg_sel_q))); if (pll_lock_o) begin diff --git a/rtl/tiny/Makefile b/rtl/tiny/Makefile index d327bb2f..f0947d58 100644 --- a/rtl/tiny/Makefile +++ b/rtl/tiny/Makefile @@ -60,6 +60,7 @@ OPENSTA_CONFIG := $(SYN_BUILD_ROOT)/out/$(RTL_NAME)_yosys.config NETLIST_PATH := $(OPENSTA_NETLIST) TINY_CONVERTED := $(SIM_BUILD_ROOT)/converted_soc.v TINY_DEPFILE := $(SIM_BUILD_ROOT)/sources.d +TINY_TB_DEPFILE := $(SIM_BUILD_ROOT)/testbench.d TINY_LINT_LOG := $(VARIANT_ROOT)/lint/verilator/lint.log SOC_SIM_TIME ?= 1800 TINY_SIM_TIMEOUT ?= 20000000 @@ -69,7 +70,7 @@ IVERILOG ?= iverilog VVP ?= vvp include $(ROOT_PATH)/rtl/mk/software.mk --include $(TINY_DEPFILE) +-include $(TINY_DEPFILE) $(TINY_TB_DEPFILE) $(TINY_CONVERTED): $(FILELIST_STAMP) @mkdir -p $(@D) @@ -95,6 +96,9 @@ $(SIM_BUILD_ROOT)/simv: $(TINY_CONVERTED) $(wildcard $(RTL_PATH)/dv/*.sv) --log $(SIM_BUILD_ROOT)/compile.log --result $(SIM_BUILD_ROOT)/result-compile.json -- \ $(IVERILOG) -g2012 -gno-specify -s $(RTL_TOP) -o $@ -f $(GENERATED_FL_DIR)/def.fl \ -f $(GENERATED_FL_DIR)/inc.fl -f $(PDK_FILELIST) $(TINY_TB_FLIST) $(TINY_CONVERTED) + $(PYTHON) $(ROOT_PATH)/scripts/rtl/filelist_deps.py -f $(GENERATED_FL_DIR)/def.fl \ + -f $(GENERATED_FL_DIR)/inc.fl $(TINY_TB_FLIST) \ + --target $@ --output $(TINY_TB_DEPFILE) comp: $(SIM_BUILD_ROOT)/simv TINY_RUN := $(VVP) $(SIM_BUILD_ROOT)/simv endif @@ -200,4 +204,35 @@ tiny-r2-p3-matrix: | manifest tiny-r2-p3-report: $(PYTHON) $(TINY_P3_TOOL) --root $(ROOT_PATH) --variant-root $(VARIANT_ROOT) report -.PHONY: tiny-r2-p3-image tiny-r2-p3-sim tiny-r2-p3-matrix tiny-r2-p3-report \ No newline at end of file +.PHONY: tiny-r2-p3-image tiny-r2-p3-sim tiny-r2-p3-matrix tiny-r2-p3-report + +# P4 preserves old image identity and replays each selected image exactly once. +TINY_P4_REFERENCE ?= +TINY_P4_CASES ?= perf-o2-plain-flat,perf-o2-plain-banked +tiny-r2-p4-replay: | manifest + $(PYTHON) $(ROOT_PATH)/scripts/tiny_r2_p4.py --root $(ROOT_PATH) --variant-root $(VARIANT_ROOT) \ + --reference $(TINY_P4_REFERENCE) --cases $(TINY_P4_CASES) --simulator $(SIM_TOOL_NAME) \ + --vvp $(VVP) --timeout $(SOC_SIM_TIME) -- \ + make --no-print-directory -j1 -B CONFIG=$(CONFIG_PATH) BUILD_TIMESTAMP=$(BUILD_TIMESTAMP) \ + $(foreach var,$(CONFIG_KEY_VARS),$(var)=$($(var))) SIMU=$(SIMU) SYNTH=NONE STA=NONE comp + +tiny-r2-p4-report: + $(PYTHON) $(ROOT_PATH)/scripts/tiny_r2_p4.py --root $(ROOT_PATH) --variant-root $(VARIANT_ROOT) --report + +.PHONY: tiny-r2-p4-replay tiny-r2-p4-report + +# P5 compares the same P3 binaries to retained P4 observations, with its own +# phase identity and evidence root. The historical P3/P4 commands stay intact. +TINY_P5_REFERENCE ?= +TINY_P5_CASES ?= perf-o2-plain-flat,perf-o2-plain-banked +tiny-r2-p5-replay: | manifest + $(PYTHON) $(ROOT_PATH)/scripts/tiny_r2_p5.py --root $(ROOT_PATH) --variant-root $(VARIANT_ROOT) \ + --reference $(TINY_P5_REFERENCE) --cases $(TINY_P5_CASES) --simulator $(SIM_TOOL_NAME) \ + --vvp $(VVP) --timeout $(SOC_SIM_TIME) -- \ + make --no-print-directory -j1 -B CONFIG=$(CONFIG_PATH) BUILD_TIMESTAMP=$(BUILD_TIMESTAMP) \ + $(foreach var,$(CONFIG_KEY_VARS),$(var)=$($(var))) SIMU=$(SIMU) SYNTH=NONE STA=NONE comp + +tiny-r2-p5-report: + $(PYTHON) $(ROOT_PATH)/scripts/tiny_r2_p5.py --root $(ROOT_PATH) --variant-root $(VARIANT_ROOT) --report + +.PHONY: tiny-r2-p5-replay tiny-r2-p5-report \ No newline at end of file diff --git a/rtl/tiny/README.md b/rtl/tiny/README.md index e172e693..9cdf0d8d 100644 --- a/rtl/tiny/README.md +++ b/rtl/tiny/README.md @@ -7,7 +7,20 @@ Use `make CONFIG=configs/ci/ics55-tiny.mk setup` followed by `make CONFIG=configs/ci/ics55-tiny.mk firmware sim`. IHP130/no PLL remains explicit compatibility through `configs/ci/ihp130-tiny.mk`. See the [platform runbook](../../docs/ip/tiny-ics55-platform.md) for source-bound -validation; backend frequency tests are not SYS switching or timing qualification. +validation of the historical SAFE24 platform. The +[R2-P7 runbook](../../docs/ip/tiny-soc-r2-clock-reset.md) describes the integrated +RCU, runtime domains and separate acceptance evidence. Backend frequency tests +alone are not full-SoC or physical qualification. + +The [R2-P4 implementation](../../docs/ip/tiny-soc-r2-local-memory.md) connects the +official dual-port CPU to four independently serviced 32 KiB local SRAM groups. +The 32 physical macros and register ABI are unchanged. Only non-SRAM CPU work +uses the tagged slow path. The [P5 implementation](../../docs/ip/tiny-soc-r2-concurrent-fabric.md) +replaces external serialization with independent target ownership and APB +branches. The [P6 candidate](../../docs/ip/tiny-soc-r2-shared-integration.md) +activates the SDIO ingress, eight-channel DMA, RNG/CRC/WS2812/Crypto and the +existing I2S master path. Its validation and acceptance remain in the ledger. +Source presence alone is not completed phase or physical qualification. `top` owns product integration, `address_map`, `pin_map` and `integration` own canonical address, pad, IRQ and clock/reset inputs, `filelist` selects sources, @@ -43,8 +56,8 @@ final timing closure is mandatory. Software/DMA scheduling is `TINY-R2-P3`, loca `TINY-R2-P9`, system qualification `TINY-R2-P10` and complete-product physical qualification `TINY-R2-P11` (historical IHP130 title; now default ICS55). Legacy `TINY-P0` through `TINY-P12` remain in -the contract history. The committed RTL/profile still implements the initial -24 MHz/no-PLL/four-channel design; no faster CPU/SRAM or PLL timing support +the contract history. The P6 candidate retains SAFE24 with eight DMA channels; +ICS55's present PLL stays off. No faster CPU/SRAM or PLL timing support follows from this freeze or the `HAVE_PLL` selector alone. The R2-P2 candidate keeps that executable clock/memory configuration and adds @@ -88,8 +101,8 @@ FIFOs, APB4 `0x1001D000..0x1001DFFF`, IRQ25 and RCU target16. DMA V2.2 adds paced fixed-MMIO requests `SPI_TX=16` / `SPI_RX=17`; payload MMIO retains single-beat transactions. TX borrows channel3 and RX channel2 only after prior owners fully drain and release them. No extra AXI master, DMA channel, -SRAM capacity or pad is added. None of these paths exists in the current -24 MHz/no-PLL/four-channel RTL/profile. +SRAM capacity or pad is added. SPI and its requests remain absent from the +SAFE24 P6 candidate. The frozen Gen1 routes add only GPIO27 ALT0 SCK, GPIO28 ALT1 MOSI, GPIO30 ALT1 MISO and GPIO31 ALT1 CS_N. Write-only display mode uses ordinary GPIO30 diff --git a/rtl/tiny/address_map/memory_map.json b/rtl/tiny/address_map/memory_map.json index 0036f910..51f5aea5 100644 --- a/rtl/tiny/address_map/memory_map.json +++ b/rtl/tiny/address_map/memory_map.json @@ -300,15 +300,6 @@ "public": true, "user_access": "none" }, - { - "symbol": "APB4_I2C1", - "base": "0x10011000", - "size": "0x00001000", - "route": "apb4_periph", - "kind": "active", - "public": true, - "user_access": "rw" - }, { "symbol": "APB4_GPIO_ADMIN", "base": "0x10014000", @@ -327,15 +318,6 @@ "public": true, "user_access": "none" }, - { - "symbol": "APB4_UART1", - "base": "0x10018000", - "size": "0x00001000", - "route": "apb4_periph", - "kind": "active", - "public": true, - "user_access": "rw" - }, { "symbol": "APB4_ARCHINFO", "base": "0x20000000", @@ -399,6 +381,60 @@ "public": true, "user_access": "rw", "burst": "incr16" + }, + { + "symbol": "APB4_RNG", + "base": "0x20001000", + "size": "0x00001000", + "route": "apb4_system", + "kind": "active", + "public": true, + "user_access": "none" + }, + { + "symbol": "APB4_CRC", + "base": "0x20006000", + "size": "0x00001000", + "route": "apb4_system", + "kind": "active", + "public": true, + "user_access": "none" + }, + { + "symbol": "APB4_WS2812", + "base": "0x10008000", + "size": "0x00001000", + "route": "apb4_periph", + "kind": "active", + "public": true, + "user_access": "none" + }, + { + "symbol": "APB4_CRYPTO", + "base": "0x1000C000", + "size": "0x00001000", + "route": "apb4_periph", + "kind": "active", + "public": true, + "user_access": "none" + }, + { + "symbol": "APB4_I2S", + "base": "0x10007000", + "size": "0x00001000", + "route": "apb4_periph", + "kind": "active", + "public": true, + "user_access": "none" + }, + { + "symbol": "APB4_SDIO0", + "base": "0x1000F000", + "size": "0x00001000", + "route": "apb4_periph", + "kind": "active", + "public": true, + "user_access": "none" } ], "legacy_rib": false, diff --git a/rtl/tiny/dv/retrosoc_tiny_tb.sv b/rtl/tiny/dv/retrosoc_tiny_tb.sv index 8adbfac3..1cef5b6f 100644 --- a/rtl/tiny/dv/retrosoc_tiny_tb.sv +++ b/rtl/tiny/dv/retrosoc_tiny_tb.sv @@ -1,3 +1,4 @@ +`include "tiny_topology.svh" // Copyright (c) 2026 Yuchi Miao // SPDX-License-Identifier: MulanPSL-2.0 `timescale 1ns / 1ps @@ -5,27 +6,34 @@ module retrosoc_tiny_tb; logic s_clk_driver = 1'b0; wire s_clk = s_clk_driver; - logic s_reset_driver = 1'b0; + // Assert RESET_N after time zero. With a divided REF24 there are no REF + // edges while reset is held, so a declaration initialized low is not an + // observable asynchronous assertion for every four-state simulator process. + logic s_reset_driver = $test$plusargs("p7_legacy_reset_level") ? 1'b0 : 1'b1; wire s_rst_n = s_reset_driver; wire s_uart0_tx, s_uart1_tx; - wire s_uart0_rx = 1'b1; - wire s_uart1_rx = 1'b1; - logic s_jtag_tck_driver = 1'b0; - logic s_jtag_tms_driver = 1'b1; - logic s_jtag_tdi_driver = 1'b0; - wire s_jtag_tck = s_jtag_tck_driver; - wire s_jtag_tms = s_jtag_tms_driver; - wire s_jtag_tdi = s_jtag_tdi_driver; - wire s_jtag_trst_n = s_rst_n; - wire s_jtag_tdo; - tri1 [31:0] s_gpio; - wire s_xpi_sck; - wire [ 3:0] s_xpi_nss; - tri [ 3:0] s_xpi_data; - integer s_cycles; - integer s_max_cycles = 20000000; - integer s_done_cycles = 0; - logic [ 7:0] s_uart_byte; + wire s_uart0_rx = 1'b1; + wire s_uart1_rx = 1'b1; + logic s_jtag_tck_driver = 1'b0; + logic s_jtag_tms_driver = 1'b1; + logic s_jtag_tdi_driver = 1'b0; + wire s_jtag_tck = s_jtag_tck_driver; + wire s_jtag_tms = s_jtag_tms_driver; + wire s_jtag_tdi = s_jtag_tdi_driver; + wire s_jtag_trst_n = s_rst_n; + wire s_jtag_tdo; + `include "tiny_tb_gpio.svh" + wire s_xpi_sck; + wire [3:0] s_xpi_nss; + tri [3:0] s_xpi_data; + integer s_cycles; +`ifndef RETROSOC_SOC__TINY_NETLIST + `include "tiny_p6_devices.svh" + `include "tiny_p7_observers.svh" +`endif + integer s_max_cycles = 20000000; + integer s_done_cycles = 0; + logic [7:0] s_uart_byte; `include "tiny_jtag_smoke.svh" `ifdef RETROSOC_SOC__TINY_NETLIST @@ -43,8 +51,12 @@ module retrosoc_tiny_tb; wire [7:0] s_test_code = u_dut.s_test_code; `endif - always #20.833333 s_clk_driver = !s_clk_driver; + always #(500000000.0 / `SOC_EXT_CLK_HZ) s_clk_driver = !s_clk_driver; initial begin + if (!$test$plusargs("p7_legacy_reset_level")) begin + #1; + s_reset_driver = 1'b0; + end if ($value$plusargs("max_cycles=%d", s_max_cycles)) begin end repeat (20) @(posedge s_clk); @@ -53,7 +65,8 @@ module retrosoc_tiny_tb; for (s_cycles = 0; s_cycles < s_max_cycles; s_cycles = s_cycles + 1) begin @(posedge s_clk); if (u_dut.s_test_done) begin - if (!u_dut.s_test_pass) $fatal(1, "SIM_TEST_FAIL code=%0d", s_test_code); + if (u_dut.s_test_pass !== 1'b1 || (^s_test_code) === 1'bx) + $fatal(1, "SIM_TEST_FAIL code=%0d", s_test_code); s_done_cycles = s_done_cycles + 1; if (s_done_cycles >= 128 && s_jtag_checks_done) begin $display("SIM_TEST_PASS Tiny cycles=%0d", s_cycles); @@ -64,24 +77,15 @@ module retrosoc_tiny_tb; if (s_done_cycles != 128) $fatal(1, "SIM_TEST_TIMEOUT Tiny cycles=%0d", s_cycles); end -`ifdef RETROSOC_SOC__TINY_NETLIST +`ifdef SIMU_IVERILOG localparam bit ObserveUartAlways = 1'b1; `else localparam bit ObserveUartAlways = 1'b0; `endif -`ifdef SIMU_IVERILOG - localparam bit ObserveUartBaseline = 1'b1; -`else - localparam bit ObserveUartBaseline = 1'b0; -`endif - // The Verilator RTL already prints accepted TX bytes. The Icarus observer is - // opt-in for measurements; SYSCTRL remains the authoritative verdict. + // The Verilator RTL prints accepted TX bytes. Icarus observes serial output for + // all firmware measurements; SYSCTRL remains the authoritative verdict. initial begin - if (ObserveUartAlways || (ObserveUartBaseline && ($test$plusargs( - "tiny_r2_baseline" - ) || $test$plusargs( - "tiny_r2_p3" - )))) begin + if (ObserveUartAlways) begin forever begin @(negedge s_uart0_tx); #1627.604; @@ -230,57 +234,181 @@ module retrosoc_tiny_tb; `undef _BASELINE_AXI end - // The baseline has one AHB port. HPROT[0] distinguishes instruction/data; - // these observations do not assert future independent local-I/D latency. - longint unsigned s_ahb_cycle = 0; - longint unsigned s_ahb_start = 0; - longint unsigned s_ahb_accept[2], s_ahb_done[2], s_ahb_latency[2]; - logic s_ahb_pending = 1'b0, s_ahb_measured = 1'b0; - logic s_ahb_data = 1'b0, s_ahb_previous_enable = 1'b0; - integer s_ahb_window = 0; - wire s_ahb_enable = u_dut.u_soc.s_perf_enable && s_baseline_enabled; - `define _BASELINE_AHB u_dut.u_soc.u_cpu.u_ahbl_if - always @(posedge s_clk) begin - s_ahb_cycle = s_ahb_cycle + 1; - if (!u_dut.u_soc.s_rst_n) begin - s_ahb_pending = 1'b0; - s_ahb_measured = 1'b0; - s_ahb_previous_enable = 1'b0; - s_ahb_window = 0; - end else if (s_baseline_enabled) begin - if (s_ahb_enable && !s_ahb_previous_enable) begin - s_ahb_window = s_ahb_window + 1; - s_ahb_measured = 1'b0; - for (int kind = 0; kind < 2; kind++) begin - s_ahb_accept[kind] = 0; - s_ahb_done[kind] = 0; - s_ahb_latency[kind] = 0; - end + // P5-only observations supplement the unchanged P3/P4 record formats. They + // are testbench measurements, not new software-visible performance counters. + longint unsigned s_p5_overlap = 0, s_p5_beat_overlap = 0; + integer s_p5_window = 0, s_p5_active_max = 0; + logic s_p5_previous_enable = 0; + wire s_p5_enable = s_baseline_enabled && u_dut.u_soc.s_perf_enable; + // Icarus 13 miscounts the temporary expression $countones(~a_23_bit_vector). + // Count declared bits explicitly and reject unknowns rather than correcting + // a report afterward. This function changes only opt-in P5 observations. + function automatic integer p5_active_targets( + input logic [`RETROSOC_TINY__TARGET_COUNT-1:0] idle_i); + integer count; + count = 0; + for ( + integer target_index = 0; target_index < `RETROSOC_TINY__TARGET_COUNT; target_index++ + ) begin + if (idle_i[target_index] === 1'b0) count++; + else if (idle_i[target_index] !== 1'b1) $fatal(1, "SIM_TEST_FAIL P5 unknown target state"); + end + return count; + endfunction + initial begin : p5_monitor_check + if ($test$plusargs("tiny_r2_p5_monitor_check")) begin + for (integer active = 0; active <= `RETROSOC_TINY__TARGET_COUNT; active++) begin + if (p5_active_targets( + {`RETROSOC_TINY__TARGET_COUNT{1'b1}} ^ ((`RETROSOC_TINY__TARGET_COUNT'(1) << active) - `RETROSOC_TINY__TARGET_COUNT'(1)) + ) != active) + $fatal(1, "SIM_TEST_FAIL P5 monitor population count"); end - if (`_BASELINE_AHB.hready) begin - if (s_ahb_pending && s_ahb_measured && s_ahb_enable) begin - s_ahb_done[s_ahb_data] = s_ahb_done[s_ahb_data] + 1; - if ((s_ahb_cycle - s_ahb_start) > s_ahb_latency[s_ahb_data]) - s_ahb_latency[s_ahb_data] = s_ahb_cycle - s_ahb_start; - end - s_ahb_pending = `_BASELINE_AHB.htrans[1]; - s_ahb_measured = s_ahb_pending && s_ahb_enable; - if (s_ahb_measured) begin - s_ahb_data = `_BASELINE_AHB.hprot[0]; - s_ahb_start = s_ahb_cycle; - s_ahb_accept[s_ahb_data] = s_ahb_accept[s_ahb_data] + 1; - end - end - if (!s_ahb_enable && s_ahb_previous_enable) begin - for (int kind = 0; kind < 2; kind++) begin - $display("R2_AHB window=%0d kind=%0d accepted=%0d completed=%0d latency_max=%0d", - s_ahb_window, kind, s_ahb_accept[kind], s_ahb_done[kind], s_ahb_latency[kind]); - end - end - s_ahb_previous_enable = s_ahb_enable; + $display("SIM_TEST_PASS Tiny P5 monitor-check counts=0..27; instrumentation only"); + $finish; end end - `undef _BASELINE_AHB + always @(posedge s_clk) begin + if (!u_dut.u_soc.s_rst_n) begin + s_p5_previous_enable = 0; + s_p5_window = 0; + end else if ($test$plusargs("tiny_r2_p5")) begin + if (s_p5_enable && !s_p5_previous_enable) begin + s_p5_window++; + s_p5_overlap = 0; + s_p5_beat_overlap = 0; + s_p5_active_max = 0; + end + if (s_p5_enable) begin + if (p5_active_targets(u_dut.u_soc.u_fabric.target_idle_o) > s_p5_active_max) + s_p5_active_max = p5_active_targets(u_dut.u_soc.u_fabric.target_idle_o); + if (u_dut.u_soc.u_fabric.s_busy_q[2] && u_dut.u_soc.u_fabric.s_busy_q[3]) s_p5_overlap++; + if (u_dut.u_soc.u_masters_axi4_if[1].rvalid && u_dut.u_soc.u_masters_axi4_if[1].rready && + u_dut.u_soc.u_masters_axi4_if[1].wvalid && u_dut.u_soc.u_masters_axi4_if[1].wready) + s_p5_beat_overlap++; + end + if (!s_p5_enable && s_p5_previous_enable) + $display( + "R2_P5_FABRIC window=%0d active_targets_max=%0d dma_rw_overlap_cycles=%0d dma_rw_beat_overlap_cycles=%0d", + s_p5_window, + s_p5_active_max, + s_p5_overlap, + s_p5_beat_overlap + ); + s_p5_previous_enable = s_p5_enable; + end + end + + // Verification-only per-group issue/conflict accounting. A bank conflict is + // two or more eligible macro clients in one cycle, not an AXI address stall. + for (genvar group = 0; group < 4; group++) begin : gen_bank_observer + longint unsigned s_issues[3], s_waits[3]; + longint unsigned s_conflicts = 0; + logic s_previous_enable = 0; + integer s_window = 0; + wire s_enable = u_dut.u_soc.s_perf_enable && s_baseline_enabled; + wire [2:0] s_req = u_dut.u_soc.u_sram.gen_group[group].u_group.s_req; + wire [2:0] s_grant = u_dut.u_soc.u_sram.gen_group[group].u_group.s_grant; + always @(posedge s_clk) begin + if (!u_dut.u_soc.s_rst_n) begin + s_previous_enable = 0; + s_window = 0; + end else if (s_baseline_enabled) begin + if (s_enable && !s_previous_enable) begin + s_window = s_window + 1; + s_conflicts = 0; + for (int client = 0; client < 3; client++) begin + s_issues[client] = 0; + s_waits[client] = 0; + end + end + if (s_enable) begin + if ((s_req & (s_req - 3'd1)) != 3'd0) s_conflicts = s_conflicts + 1; + for (int client = 0; client < 3; client++) begin + if (s_grant[client]) s_issues[client] = s_issues[client] + 1; + if (s_req[client] && !s_grant[client]) s_waits[client] = s_waits[client] + 1; + end + end + if (!s_enable && s_previous_enable) + $display( + "R2_P4_BANK window=%0d group=%0d issues_i=%0d issues_d=%0d issues_external=%0d conflicts=%0d wait_i=%0d wait_d=%0d wait_external=%0d", + s_window, + group, + s_issues[0], + s_issues[1], + s_issues[2], + s_conflicts, + s_waits[0], + s_waits[1], + s_waits[2] + ); + s_previous_enable = s_enable; + end + end + end + + // P4 retains the original per-kind record format, now observed on two + // independent ports. External AXI counts remain external traffic only. + for (genvar port_index = 0; port_index < 2; port_index++) begin : gen_ahb_observer + longint unsigned s_cycle = 0, s_start = 0; + longint unsigned s_accepted = 0, s_completed = 0, s_latency = 0; + longint unsigned s_local_accepted = 0, s_local_completed = 0, s_local_latency = 0; + longint unsigned s_admission_wait = 0; + logic s_pending = 0, s_measured = 0, s_local = 0, s_previous_enable = 0; + integer s_window = 0; + wire s_enable = u_dut.u_soc.s_perf_enable && s_baseline_enabled; + `define _P4_AHB u_dut.u_soc.u_cpu_ahbl_if[port_index] + always @(posedge s_clk) begin + s_cycle = s_cycle + 1; + if (!u_dut.u_soc.s_core_rst_n) begin + s_pending = 0; + s_measured = 0; + s_previous_enable = 0; + s_window = 0; + end else if (s_baseline_enabled) begin + if (s_enable && !s_previous_enable) begin + s_window = s_window + 1; + s_measured = 0; + s_accepted = 0; + s_completed = 0; + s_latency = 0; + s_local_accepted = 0; + s_local_completed = 0; + s_local_latency = 0; + s_admission_wait = 0; + end + if (s_enable && `_P4_AHB.htrans[1] && !`_P4_AHB.hready) + s_admission_wait = s_admission_wait + 1; + if (`_P4_AHB.hready) begin + if (s_pending && s_measured && s_enable) begin + s_completed = s_completed + 1; + if ((s_cycle - s_start) > s_latency) s_latency = s_cycle - s_start; + if (s_local) begin + s_local_completed = s_local_completed + 1; + if ((s_cycle - s_start) > s_local_latency) s_local_latency = s_cycle - s_start; + end + end + s_pending = `_P4_AHB.htrans[1]; + s_measured = s_pending && s_enable; + if (s_measured) begin + s_start = s_cycle; + s_accepted = s_accepted + 1; + s_local = (`_P4_AHB.haddr >= 32'h30000000) && (`_P4_AHB.haddr < 32'h30020000); + if (s_local) s_local_accepted = s_local_accepted + 1; + end + end + if (!s_enable && s_previous_enable) begin + $display("R2_AHB window=%0d kind=%0d accepted=%0d completed=%0d latency_max=%0d", + s_window, port_index, s_accepted, s_completed, s_latency); + $display( + "R2_P4_LOCAL window=%0d port=%0d accepted=%0d completed=%0d latency_max=%0d admission_wait=%0d", + s_window, port_index, s_local_accepted, s_local_completed, s_local_latency, + s_admission_wait); + end + s_previous_enable = s_enable; + end + end + `undef _P4_AHB + end `endif retrosoc_tiny_asic u_dut ( diff --git a/rtl/tiny/dv/tiny_p6_devices.svh b/rtl/tiny/dv/tiny_p6_devices.svh new file mode 100644 index 00000000..460bee1e --- /dev/null +++ b/rtl/tiny/dv/tiny_p6_devices.svh @@ -0,0 +1,332 @@ +// Verification-only external devices on the Gen1 GPIO routes. No DUT state is driven. +logic s_p6_io_trace_started = 0; +always @(negedge u_dut.u_soc.s_i2c0_clk) begin + if ($test$plusargs("p6_debug_io")) begin + if (u_dut.u_soc.u_i2c0.s_cmd_valid && !s_p6_io_trace_started) begin + s_p6_io_trace_started = 1; + $display("P6_IO_DIAGNOSTIC start pads=%b raw=%b%b filtered=%b%b drive=%b%b", s_gpio[11:10], + u_dut.u_soc.u_i2c0_if.sda_i, u_dut.u_soc.u_i2c0_if.scl_i, + u_dut.u_soc.u_i2c0.s_sda_filtered, u_dut.u_soc.u_i2c0.s_scl_filtered, + u_dut.u_soc.u_i2c0.s_sda_pull_low, u_dut.u_soc.u_i2c0.s_scl_pull_low); + $fflush(); + end + if (u_dut.u_soc.u_i2c0.s_addr_nack_event || u_dut.u_soc.u_i2c0.s_bus_timeout_event || + u_dut.u_soc.u_i2c0.s_cmd_timeout_event || u_dut.u_soc.u_i2c0.s_stretch_timeout_event || + u_dut.u_soc.u_i2c0.s_arb_lost_event) begin + $display( + "P6_IO_DIAGNOSTIC nack=%b bus_timeout=%b command_timeout=%b stretch=%b arbitration=%b pads=%b drive=%b%b", + u_dut.u_soc.u_i2c0.s_addr_nack_event, u_dut.u_soc.u_i2c0.s_bus_timeout_event, + u_dut.u_soc.u_i2c0.s_cmd_timeout_event, u_dut.u_soc.u_i2c0.s_stretch_timeout_event, + u_dut.u_soc.u_i2c0.s_arb_lost_event, s_gpio[11:10], u_dut.u_soc.u_i2c0.s_sda_pull_low, + u_dut.u_soc.u_i2c0.s_scl_pull_low); + $finish; // Diagnostic only: deliberately no SIM_TEST_PASS verdict. + end + end +end +`define _P6_WS u_dut.u_soc.u_ws2812 +logic s_p6_uart_rx = 1; +logic [7:0] s_p6_uart_byte; +logic [7:0] s_p6_uart_packet [4]; +integer s_p6_uart_index = 0, s_p6_uart_routes = 0; +assign s_gpio_9 = s_p6_uart_rx; +initial begin + forever begin + @(negedge s_gpio_8); + #1627.604; + for (integer bit_index = 0; bit_index < 8; bit_index++) begin + s_p6_uart_byte[bit_index] = s_gpio_8; + #1085.069; + end + if (s_p6_uart_index != 0 || s_p6_uart_byte == 8'ha5) begin + s_p6_uart_packet[s_p6_uart_index] = s_p6_uart_byte; + s_p6_uart_index++; + if (s_p6_uart_index == 4) begin + if (s_p6_uart_packet[0] != 8'ha5 || s_p6_uart_packet[1] != 8'h5a || + s_p6_uart_packet[2] != 8'h96 || s_p6_uart_packet[3] != 8'h69) + $fatal(1, "SIM_TEST_FAIL P6 UART GPIO8 framing/data"); + for (integer byte_index = 0; byte_index < 4; byte_index++) begin + s_p6_uart_rx = 0; + #1085.069; + for (integer bit_index = 0; bit_index < 8; bit_index++) begin + s_p6_uart_rx = s_p6_uart_packet[byte_index][bit_index]; + #1085.069; + end + s_p6_uart_rx = 1; + #1085.069; + end + s_p6_uart_index = 0; + s_p6_uart_routes++; + end + end + end +end +integer s_p6_frames = 0, s_p6_completed = 0, s_p6_underflows = 0, s_p6_aborts = 0; +longint unsigned s_p6_sdio_overlap = 0, s_p6_overlap_fetches = 0; +always @(posedge u_dut.u_soc.s_exec_clk) begin + if (u_dut.u_soc.u_dma.s_busy[2] && u_dut.u_soc.u_sdio.s_dma_status[0]) begin + s_p6_sdio_overlap++; + if (u_dut.u_soc.u_cpu_ahbl_if[0].hready && u_dut.u_soc.u_cpu_ahbl_if[0].htrans[1]) + s_p6_overlap_fetches++; + end +end +integer s_p6_word = 0, s_p6_bit = 0, s_p6_bit_cycle = 0, s_p6_reset_cycles = 0; +integer s_p6_ws_wait = 0, s_p6_ws_wait_max = 0, s_p6_fifo_max = 0; +integer s_p6_clock_stops = 0; +integer s_p6_wire_cases = 0, s_p6_wire_matched = 0, s_p6_wire_examined = 0; +integer s_p6_wire_edges = 0; +logic s_p6_wire_found = 0, s_p6_wire_boundary = 0, s_p6_wire_lrck = 0; +logic [23:0] s_p6_wire_shift = 0; +logic [31:0] s_p6_wire_word; +wire s_p6_audio_enabled = u_dut.u_soc.u_i2s.u_i2s_core.enable_i; +wire s_p6_audio_wide = u_dut.u_soc.u_i2s.u_i2s_core.s_bitmode; +// Decode actual BCLK/LRCLK/DOUT pads, independently of the receive engine. +always @(posedge s_gpio[12] or negedge s_p6_audio_enabled) begin + if (!s_p6_audio_enabled) begin + if (s_p6_wire_found) begin + if (s_p6_wire_matched != 512) $fatal(1, "SIM_TEST_FAIL P6 truncated I2S wire payload"); + s_p6_wire_cases++; + $display("R2_P6_I2S_WIRE bits=%0d samples=%0d", s_p6_audio_wide ? 24 : 16, s_p6_wire_matched); + end + s_p6_wire_matched = 0; + s_p6_wire_examined = 0; + s_p6_wire_edges = 0; + s_p6_wire_found = 0; + s_p6_wire_boundary = 0; + s_p6_wire_lrck = 0; + s_p6_wire_shift = 0; + end else begin + if (s_gpio[14] !== 1'b0 && s_gpio[14] !== 1'b1) $fatal(1, "SIM_TEST_FAIL P6 unknown I2S DOUT"); + s_p6_wire_shift = {s_p6_wire_shift[22:0], s_gpio[14]}; + s_p6_wire_edges++; + if (s_gpio[13] != s_p6_wire_lrck) begin + if (s_p6_wire_boundary && s_p6_wire_edges != (s_p6_audio_wide ? 24 : 16)) + $fatal(1, "SIM_TEST_FAIL P6 I2S slot length"); + s_p6_wire_boundary = 1; + s_p6_wire_edges = 0; + s_p6_wire_word = s_p6_audio_wide ? {8'd0, s_p6_wire_shift} : {16'd0, s_p6_wire_shift[15:0]}; + if (!s_p6_wire_found && s_p6_wire_word == (s_p6_audio_wide ? 32'h120020 : 32'h2020)) + s_p6_wire_found = 1; + if (s_p6_wire_found && s_p6_wire_matched < 512) begin + if (s_p6_wire_word != (s_p6_audio_wide ? 32'h120020 : 32'h2020) + 32'(s_p6_wire_matched)) + $fatal(1, "SIM_TEST_FAIL P6 I2S wire ordering/data index=%0d", s_p6_wire_matched); + s_p6_wire_matched++; + end + s_p6_wire_examined++; + if (!s_p6_wire_found && s_p6_wire_examined > 40) + $fatal(1, "SIM_TEST_FAIL P6 missing I2S wire payload"); + s_p6_wire_lrck = s_gpio[13]; + end + end +end +logic s_p6_busy_previous = 0; +logic [23:0] s_p6_pixel; +logic s_p6_expected_high; +logic s_p6_audio_reference = 1'b0; +logic s_p6_audio_running = 1'b0; +initial begin + repeat (200) @(negedge u_dut.u_soc.s_ref_clk); + if (!u_dut.u_soc.s_cpu_ready_rst_n) + $fatal(1, "SIM_TEST_FAIL CPU boot waited for absent AUDIO clock"); + s_p6_audio_running = 1'b1; +end +// GPIO27 is a verification-board control only after the four normal WS frames. +// Stop low and high in separate cases, then restore the source without DUT forcing. +always @(posedge s_gpio[27]) begin + if (s_p6_frames == 4 && s_p6_clock_stops < 2) begin + wait (s_p6_audio_reference == (s_p6_clock_stops != 0)); + s_p6_audio_running = 0; + repeat (4800) @(negedge u_dut.u_soc.s_ref_clk); + s_p6_clock_stops++; + s_p6_audio_running = 1; + end +end +always @(negedge u_dut.u_soc.s_ws2812_clk) begin + if (`_P6_WS.s_busy && !s_p6_busy_previous) begin + s_p6_frames++; + s_p6_word = 0; + s_p6_bit = 0; + s_p6_bit_cycle = 0; + s_p6_reset_cycles = 0; + end + if (`_P6_WS.s_underflow) s_p6_underflows++; + if (`_P6_WS.s_aborted) s_p6_aborts++; + if (`_P6_WS.s_busy && !`_P6_WS.s_reset_active && s_p6_frames <= 4) begin + s_p6_pixel = 24'(s_p6_word * 32'h010203); + s_p6_expected_high = s_p6_bit_cycle < (s_p6_pixel[23-s_p6_bit] ? 17 : 8); + if (s_gpio[26] !== s_p6_expected_high) + $fatal( + 1, + "SIM_TEST_FAIL Tiny P6 WS pad waveform frame=%0d word=%0d bit=%0d cycle=%0d", + s_p6_frames, + s_p6_word, + s_p6_bit, + s_p6_bit_cycle + ); + s_p6_bit_cycle++; + if (s_p6_bit_cycle == 30) begin + s_p6_bit_cycle = 0; + s_p6_bit++; + if (s_p6_bit == 24) begin + s_p6_bit = 0; + s_p6_word++; + end + end + if (`_P6_WS.u_ws2812_reg.s_fifo_level > s_p6_fifo_max) + s_p6_fifo_max = integer'(`_P6_WS.u_ws2812_reg.s_fifo_level); + if (`_P6_WS.u_ws2812_reg.s_fifo_low && !`_P6_WS.u_ws2812_reg.s_tx_push) s_p6_ws_wait++; + else s_p6_ws_wait = 0; + if (s_p6_ws_wait > s_p6_ws_wait_max) s_p6_ws_wait_max = s_p6_ws_wait; + if (s_p6_ws_wait > 4800 || s_p6_fifo_max > 16) + $fatal(1, "SIM_TEST_FAIL Tiny P6 WS service budget/occupancy"); + end else s_p6_ws_wait = 0; + if (`_P6_WS.s_reset_active) begin + if (s_gpio[26] !== 1'b0) $fatal(1, "SIM_TEST_FAIL Tiny P6 WS reset-low"); + s_p6_reset_cycles++; + end + if (!`_P6_WS.s_busy && s_p6_busy_previous) begin + if (s_p6_reset_cycles < 7200) $fatal(1, "SIM_TEST_FAIL Tiny P6 WS reset interval"); + if (s_p6_frames <= 4) begin + if (s_p6_word != ((s_p6_frames % 2) ? 33 : 65) || s_p6_bit != 0 || s_p6_bit_cycle != 0) + $fatal(1, "SIM_TEST_FAIL Tiny P6 WS exact frame length"); + s_p6_completed++; + end + end + s_p6_busy_previous = `_P6_WS.s_busy; +end +final begin + if (u_dut.s_test_done && u_dut.s_test_pass && s_p6_frames != 0) begin + if (s_p6_sdio_overlap == 0 || s_p6_overlap_fetches == 0) + $fatal(1, "SIM_TEST_FAIL P6 missing actual SDIO/DMA/CPU contention"); + $display("R2_P6_CONTENTION sdio_dma_cycles=%0d cpu_fetches=%0d", s_p6_sdio_overlap, + s_p6_overlap_fetches); + if (s_p6_completed != 4 || s_p6_underflows != 1 || s_p6_aborts != 1 || + s_p6_clock_stops != 2 || s_p6_wire_cases != 2 || s_p6_uart_routes != 1) + $fatal(1, "SIM_TEST_FAIL Tiny P6 incomplete device coverage"); + $display( + "R2_P6_DEVICES ws_frames=%0d ws_wait_max=%0d fifo_max=%0d underflows=%0d aborts=%0d clock_stops=%0d", + s_p6_completed, s_p6_ws_wait_max, s_p6_fifo_max, s_p6_underflows, s_p6_aborts, + s_p6_clock_stops); + end +end +`undef _P6_WS +// Counts describe observed bus handshakes over the complete acceptance run. +// Read bytes require OKAY; write bytes are accepted W strobes, not rollback claims. +for (genvar owner = 0; owner < 3; owner++) begin : gen_p6_axi_observer + longint unsigned read_bytes = 0, write_bytes = 0, descriptor_bytes = 0; + longint unsigned ar_wait = 0, aw_wait = 0, w_wait = 0, r_wait = 0, b_wait = 0; + longint unsigned ar_max = 0, aw_max = 0, w_max = 0, r_max = 0, b_max = 0; + logic pending_read = 0, pending_write = 0, descriptor = 0; + logic [2:0] read_size = 0; + `define _P6_AXI u_dut.u_soc.u_masters_axi4_if[owner] + always @(posedge u_dut.u_soc.s_exec_clk) begin + if (!u_dut.u_soc.s_fabric_rst_n) begin + pending_read = 0; + pending_write = 0; + end else begin + if (`_P6_AXI.arvalid && `_P6_AXI.arready) begin + if (pending_read) $fatal(1, "SIM_TEST_FAIL P6 duplicate read admission"); + pending_read = 1; + read_size = `_P6_AXI.arsize; + descriptor = owner == 1 && u_dut.u_soc.u_dma.s_tcd_count[64+:32] != 0 && + `_P6_AXI.araddr >= u_dut.u_soc.u_dma.s_tcd_head[64+:32] && + `_P6_AXI.araddr < u_dut.u_soc.u_dma.s_tcd_head[64+:32] + 32'd256; + end + if (`_P6_AXI.rvalid && `_P6_AXI.rready) begin + if (!pending_read) $fatal(1, "SIM_TEST_FAIL P6 unsolicited read response"); + if (`_P6_AXI.rresp == 0) begin + read_bytes += 64'd1 << read_size; + if (descriptor) descriptor_bytes += 64'd1 << read_size; + end + if (`_P6_AXI.rlast) pending_read = 0; + end + if (`_P6_AXI.awvalid && `_P6_AXI.awready) begin + if (pending_write) $fatal(1, "SIM_TEST_FAIL P6 duplicate write admission"); + pending_write = 1; + end + if (`_P6_AXI.wvalid && `_P6_AXI.wready) begin + if (!pending_write) $fatal(1, "SIM_TEST_FAIL P6 W without saved AW"); + for (integer lane = 0; lane < 4; lane++) if (`_P6_AXI.wstrb[lane]) write_bytes++; + end + if (`_P6_AXI.bvalid && `_P6_AXI.bready) begin + if (!pending_write) $fatal(1, "SIM_TEST_FAIL P6 unsolicited write response"); + pending_write = 0; + end + ar_wait = `_P6_AXI.arvalid && !`_P6_AXI.arready ? ar_wait + 1 : 0; + aw_wait = `_P6_AXI.awvalid && !`_P6_AXI.awready ? aw_wait + 1 : 0; + w_wait = `_P6_AXI.wvalid && !`_P6_AXI.wready ? w_wait + 1 : 0; + r_wait = `_P6_AXI.rvalid && !`_P6_AXI.rready ? r_wait + 1 : 0; + b_wait = `_P6_AXI.bvalid && !`_P6_AXI.bready ? b_wait + 1 : 0; + if (ar_wait > ar_max) ar_max = ar_wait; + if (aw_wait > aw_max) aw_max = aw_wait; + if (w_wait > w_max) w_max = w_wait; + if (r_wait > r_max) r_max = r_wait; + if (b_wait > b_max) b_max = b_wait; + end + end + final begin + if (u_dut.s_test_done && u_dut.s_test_pass && s_p6_frames != 0) begin + if (pending_read || pending_write) $fatal(1, "SIM_TEST_FAIL P6 unretired accepted work"); + $display( + "R2_P6_AXI owner=%0d read_bytes=%0d write_bytes=%0d descriptor_bytes=%0d ar_stall_max=%0d aw_stall_max=%0d w_stall_max=%0d r_stall_max=%0d b_stall_max=%0d", + owner, read_bytes, write_bytes, descriptor_bytes, ar_max, aw_max, w_max, r_max, b_max); + end + end + `undef _P6_AXI +end +always #27.126736111 if (s_p6_audio_running) s_p6_audio_reference = !s_p6_audio_reference; +assign s_gpio_17 = s_p6_audio_reference; +// The first integration case is an external codec loopback, across actual pads. +// Independent wire decoding and error injection supplement firmware readback. +assign s_gpio_15 = s_gpio_14; +wire s_p6_card_cmd; +wire [3:0] s_p6_card_data; +wire s_p6_card_irq; +sdio_native_card_model #( + .MemoryInitialization(1'b1), + .BlockCount (1024) +) u_p6_card ( + .rst_n_i (s_rst_n), + .sck_i (s_gpio[0]), + .cmd_oe_i(u_dut.u_soc.u_sdio_if.cmd_oe_o), + .cmd_do_i(s_gpio[1]), + .cmd_do_o(s_p6_card_cmd), + .dat_oe_i(u_dut.u_soc.u_sdio_if.dat_oe_o), + .dat_do_i(s_gpio[5:2]), + .dat_do_o(s_p6_card_data), + .irq_o (s_p6_card_irq) +); +assign s_gpio_1 = u_dut.u_soc.u_sdio_if.cmd_oe_o ? 1'bz : s_p6_card_cmd; +assign s_gpio_2 = u_dut.u_soc.u_sdio_if.dat_oe_o[0] ? 1'bz : s_p6_card_data[0]; +assign s_gpio_3 = u_dut.u_soc.u_sdio_if.dat_oe_o[1] ? 1'bz : s_p6_card_data[1]; +assign s_gpio_4 = u_dut.u_soc.u_sdio_if.dat_oe_o[2] ? 1'bz : s_p6_card_data[2]; +assign s_gpio_5 = u_dut.u_soc.u_sdio_if.dat_oe_o[3] ? 1'bz : s_p6_card_data[3]; +always @(negedge u_dut.u_soc.s_sdio_clk) begin + // Reuse the standalone card's explicit data-stimulus API. The wire command + // fixes direction/address/length; host DATA_START only schedules admission. + // This is repository controller integration evidence, not card compliance. + if (u_dut.u_soc.u_sdio.s_data_start) begin + if (u_p6_card.s_last_cmd != 17 && u_p6_card.s_last_cmd != 18 && u_p6_card.s_last_cmd != 25) + $fatal(1, "SIM_TEST_FAIL P6 unexpected SDIO data command"); + if (u_dut.u_soc.u_sdio.s_block_size != 512 || + u_dut.u_soc.u_sdio.s_block_count != (u_p6_card.s_last_cmd == 17 ? 1 : 2) || + u_dut.u_soc.u_sdio.s_data_direction != (u_p6_card.s_last_cmd == 25) || + u_dut.u_soc.u_sdio.s_bus_width != (u_p6_card.s_memory_width4 ? 1 : 0)) + $fatal(1, "SIM_TEST_FAIL P6 SDIO command/data configuration disagreement"); + u_p6_card.set_data_delay(1); + if (u_p6_card.s_last_cmd == 25) + u_p6_card.arm_write(integer'(u_p6_card.s_last_arg * 512), 1024, u_p6_card.s_memory_width4, + 1'b0); + else + u_p6_card.arm_read(integer'(u_p6_card.s_last_arg * 512), + u_p6_card.s_last_cmd == 17 ? 512 : 1024, u_p6_card.s_memory_width4, 1'b0); + end + if ($test$plusargs("p6_debug_sdio") && u_dut.u_soc.u_sdio.s_cmd_start) begin + $display("P6_SDIO_DIAGNOSTIC cmd=%0d argument=%h status=%h", u_dut.u_soc.u_sdio.s_cmd_index, + u_dut.u_soc.u_sdio.s_cmd_arg, u_dut.u_soc.u_sdio.s_status); + $fflush(); + end + if (u_dut.u_soc.u_sdio.s_cmd_start && u_dut.u_soc.u_sdio.s_cmd_index == 13) begin + if (u_dut.u_soc.u_sdio.s_cmd_arg == 32'hbadc0de0) u_p6_card.inject_next_crc_error(); + if (u_dut.u_soc.u_sdio.s_cmd_arg == 32'hbadc0de1) u_p6_card.inject_next_timeout(); + end +end diff --git a/rtl/tiny/dv/tiny_p7_observers.svh b/rtl/tiny/dv/tiny_p7_observers.svh new file mode 100644 index 00000000..9c04a5dc --- /dev/null +++ b/rtl/tiny/dv/tiny_p7_observers.svh @@ -0,0 +1,65 @@ +// P7-only assertions over production paths. No DUT state is written. +logic s_p7_monitor = 0; +longint unsigned s_p7_gated_cycles = 0, s_p7_dma_gated_responses = 0; +integer s_p7_warm_resets = 0; +logic [3:0] s_p7_profiles = 0; +initial s_p7_monitor = $test$plusargs("p7_monitor"); +initial begin + if ($test$plusargs("p7_monitor") || $test$plusargs("p7_cold_reset_probe")) begin + wait (s_rst_n === 1'b0); + @(posedge s_rst_n); + wait (u_dut.u_soc.s_cpu_boot_ready === 1'b1); + repeat (2) @(negedge u_dut.u_soc.s_ref_clk); + // Case comparisons require the exact known reset values in both simulators. + if (u_dut.u_soc.u_sysctrl.s_reset_cause_q !== 32'd1 || + u_dut.u_soc.u_sysctrl.s_rcu_fault_q !== 32'd0 || + u_dut.u_soc.s_backend_current !== 2'd0 || u_dut.u_soc.s_gate_request !== 14'd0) + $fatal( + 1, + "SIM_TEST_FAIL P7 cold reset state cause=%h fault=%h profile=%h gates=%h", + u_dut.u_soc.u_sysctrl.s_reset_cause_q, + u_dut.u_soc.u_sysctrl.s_rcu_fault_q, + u_dut.u_soc.s_backend_current, + u_dut.u_soc.s_gate_request + ); + if ($test$plusargs("p7_cold_reset_probe")) begin + $display("P7_COLD_RESET_PASS"); + $finish; // Diagnostic only; never emits the full Tiny acceptance marker. + end + end +end +always @(posedge u_dut.u_soc.s_exec_clk) begin + if (s_p7_monitor && u_dut.u_soc.s_actual_gate[0]) begin + s_p7_gated_cycles++; + if (u_dut.u_soc.u_masters_axi4_if[1].bvalid && u_dut.u_soc.u_masters_axi4_if[1].bready) + s_p7_dma_gated_responses++; + end +end +always @(posedge u_dut.u_soc.s_ref_clk) begin + if (s_p7_monitor && u_dut.u_soc.s_clock_done) begin + s_p7_profiles[u_dut.u_soc.s_backend_current] = 1; + $display("P7_CLOCK_COMMIT profile=%0d sys=%0d mem=%0d pclk=%0d", u_dut.u_soc.s_backend_current, + u_dut.u_soc.s_backend_sys_hz, u_dut.u_soc.s_backend_mem_hz, + u_dut.u_soc.s_backend_pclk_hz); + $fflush(); + end + if (s_p7_monitor && u_dut.u_soc.s_system_reset) begin + s_p7_warm_resets++; + $display("P7_SOFTWARE_RESET request=%0d", s_p7_warm_resets); + $fflush(); + end +end +final begin + if (s_p7_monitor && u_dut.s_test_done && u_dut.s_test_pass) begin + if (s_p7_gated_cycles == 0 || s_p7_dma_gated_responses == 0 || s_p7_warm_resets != 1) + $fatal(1, "SIM_TEST_FAIL P7 WFI/DMA/software-reset coverage"); +`ifdef HAVE_PLL + if ((s_p7_profiles & 4'b1101) != 4'b1101) $fatal(1, "SIM_TEST_FAIL P7 PLL profile coverage"); +`else + if ((s_p7_profiles & 4'b0011) != 4'b0011) + $fatal(1, "SIM_TEST_FAIL P7 external profile coverage"); +`endif + $display("P7_LIFECYCLE_PASS gated_cycles=%0d dma_responses_while_gated=%0d warm_resets=%0d", + s_p7_gated_cycles, s_p7_dma_gated_responses, s_p7_warm_resets); + end +end diff --git a/rtl/tiny/filelist/clusterip.fl b/rtl/tiny/filelist/clusterip.fl index 33e91aeb..06ca583e 100644 --- a/rtl/tiny/filelist/clusterip.fl +++ b/rtl/tiny/filelist/clusterip.fl @@ -22,3 +22,10 @@ /clusterip/wdg/rtl/wdg_core.sv /clusterip/wdg/rtl/wdg_reg.sv /clusterip/wdg/rtl/apb4_wdg.sv +/clusterip/rng/rtl/rng_core.sv +/clusterip/rng/rtl/rng_reg.sv +/clusterip/rng/rtl/rng_deterministic_source.sv +/clusterip/rng/rtl/apb4_rng.sv +/clusterip/crc/rtl/crc_core.sv +/clusterip/crc/rtl/crc_reg.sv +/clusterip/crc/rtl/apb4_crc.sv diff --git a/rtl/tiny/filelist/commonip.fl b/rtl/tiny/filelist/commonip.fl index d603e2c4..f11c72ee 100644 --- a/rtl/tiny/filelist/commonip.fl +++ b/rtl/tiny/filelist/commonip.fl @@ -6,6 +6,7 @@ /clusterip/common/rtl/interface/axi4_stream_if.sv /clusterip/common/rtl/interface/ram_if.sv /clusterip/common/rtl/utils/register.sv +/clusterip/common/rtl/utils/xchecker.sv /clusterip/common/rtl/utils/spill_register.sv /clusterip/common/rtl/stream/round_robin_arbiter.sv /clusterip/common/rtl/utils/lfsr.sv @@ -18,5 +19,7 @@ /clusterip/common/rtl/cdc/cdc_2phase.sv /clusterip/common/rtl/clkrst/rst_sync.sv /clusterip/common/rtl/clkrst/counter.sv +/clusterip/common/rtl/clock/safe_clock_mux.sv +/clusterip/common/rtl/clock/clock_divider.sv /ip/util/soc_common_fifo.sv /ip/util/soc_common_cdc.sv diff --git a/rtl/tiny/filelist/inc.fl b/rtl/tiny/filelist/inc.fl index 6bfcb184..8fe3ddaa 100644 --- a/rtl/tiny/filelist/inc.fl +++ b/rtl/tiny/filelist/inc.fl @@ -17,3 +17,6 @@ +incdir+/ip/serial +incdir+/ip/peripheral +incdir+/ip/core ++incdir+/ip/security ++incdir+/clusterip/rng/rtl ++incdir+/clusterip/crc/rtl diff --git a/rtl/tiny/filelist/ip.fl b/rtl/tiny/filelist/ip.fl index 41aeb470..766a200b 100644 --- a/rtl/tiny/filelist/ip.fl +++ b/rtl/tiny/filelist/ip.fl @@ -47,3 +47,40 @@ /ip/core/mgmt_debug_wrapper.sv /ip/core/mgmt_core_wrapper.sv /ip/peripheral/gpio_pad_bridge.sv +/ip/storage/sdio_if.sv +/ip/storage/sdio_pkg.sv +/ip/storage/sdio_define.svh +/ip/storage/sdio_crc7.sv +/ip/storage/sdio_crc16.sv +/ip/storage/sdio_clock.sv +/ip/storage/sdio_command.sv +/ip/storage/sdio_data.sv +/ip/storage/sdio_dma_descriptor.sv +/ip/serial/i2s_pkg.sv +/ip/serial/i2s_reg.sv +/ip/serial/i2s_clkgen.sv +/ip/serial/i2s_recv.sv +/ip/serial/i2s_send.sv +/ip/serial/i2s_core.sv +/ip/serial/apb4_i2s.sv +/ip/serial/ws2812_if.sv +/ip/serial/ws2812_reg.sv +/ip/serial/ws2812_core.sv +/ip/serial/apb4_ws2812.sv +/ip/security/crypto_pkg.sv +/ip/security/crypto_mem_pkg.sv +/ip/security/crypto_define.svh +/ip/security/crypto_sram_store.sv +/ip/security/crypto_scrubber.sv +/ip/security/crypto_clearable_fifo.sv +/ip/security/crypto_mem_ctrl.sv +/ip/security/crypto_aes_sram_core.sv +/ip/security/crypto_aes_sram_engine.sv +/ip/security/crypto_sha2_sram_engine.sv +/ip/security/crypto_montgomery_sram.sv +/ip/security/crypto_rsa_sram_core.sv +/ip/security/apb4_crypto.sv +/ip/storage/sdio_dma.sv +/ip/storage/sdio_reg.sv +/ip/storage/sdio_core.sv +/ip/storage/apb4_sdio.sv diff --git a/rtl/tiny/filelist/tb.fl b/rtl/tiny/filelist/tb.fl index 106d6b51..da7d4e7d 100644 --- a/rtl/tiny/filelist/tb.fl +++ b/rtl/tiny/filelist/tb.fl @@ -1,2 +1,3 @@ /dv/tiny_qspi_nor.sv +/tests/rtl/sdio_native_model.sv /dv/retrosoc_tiny_tb.sv diff --git a/rtl/tiny/filelist/top.fl b/rtl/tiny/filelist/top.fl index 08b0c7ab..288d99ff 100644 --- a/rtl/tiny/filelist/top.fl +++ b/rtl/tiny/filelist/top.fl @@ -1,7 +1,24 @@ +/top/tiny_axi4_target.sv /top/tiny_axi4_fabric.sv +/top/tiny_fault_collector.sv /top/tiny_axi42apb4.sv /top/tiny_sysctrl.sv /top/tiny_archinfo.sv /top/tiny_reset_tree.sv +/top/tiny_clock_reset_subsystem.sv +/top/tiny_axi4_async_bridge.sv +/top/tiny_apb_async_bridge.sv +/top/tiny_axi4_admission.sv +/top/tiny_managed_lifecycle.sv +/top/tiny_peripheral_leaf.sv +/top/tiny_peripheral_control.sv +/top/tiny_clock_output.sv +/top/tiny_clock_gate.sv +/top/tiny_sram_port_if.sv +/top/tiny_sram_group.sv +/top/tiny_sram_axi4.sv +/top/tiny_sram.sv +/top/tiny_cpu_mem.sv +/top/tiny_cpu_wrapper.sv /top/retrosoc_tiny.sv /top/retrosoc_tiny_asic.sv diff --git a/rtl/tiny/integration/clock_reset_domains.json b/rtl/tiny/integration/clock_reset_domains.json index e9bb824d..ffc83066 100644 --- a/rtl/tiny/integration/clock_reset_domains.json +++ b/rtl/tiny/integration/clock_reset_domains.json @@ -1,31 +1,90 @@ { "schema_version": 2, - "inactive_macros": { - "ics55_pll": { - "cell": "PLL_TOP", - "scope": "TINY-ICS55-P1: present, EN=0, no SYS connection", - "active_clock_domain": false, - "qualification": "no characterized timing arcs; full RCU remains R2-P7" - } + "external_fabric": { + "phase": "TINY-R2-P7", + "clock_domain": "system", + "target_controllers": "u_soc.u_fabric.gen_target[%d].u_controller", + "target_count": 27, + "apb_bridges": "SYS frontends; acknowledged per-endpoint crossings to AON, PCLK and MEM", + "fabric_reset": "fabric leaf; target reset blocks admission and aborts accepted target work with DECERR", + "hart_reset": "CPU-only accepted-work drain; DMA and all target responses stay active", + "runtime_peripheral_reset": "RCU blocks admissions, drains accepted work, holds reset for five local edges, waits for local release and bus barriers" + }, + "local_memory": { + "phase": "TINY-R2-P4", + "clock_domain": "system", + "cpu_instance": "u_soc.u_cpu.u_hazard3_cpu_2port", + "frontend_instance": "u_soc.u_cpu_mem", + "macro_instance": "u_soc.u_sram.gen_group[{group}].u_group.gen_bank[{bank}].u_ram.u_mem", + "groups": 4, + "macros_per_group": 8, + "hart_reset": "halt then drain accepted I/D and slow responses; SRAM/DMA stay active", + "main_sram_payload_reset": "none", + "local_uncontended_max_cycles": 3 }, "reset_ports": [ "ext_rst_n_i_pad", "jtag_trst_n_i_pad" ], "domains": [ + { + "name": "aon", + "clock": "s_ref_clk", + "reset": "ref_rst_n_o", + "reset_source": "rst_n_i", + "reset_primitive": "rst_sync", + "path": "rtl/tiny/top/tiny_clock_reset_subsystem.sv", + "instance": "u_ref_rst", + "sta": { + "source_port": "extclk_i_pad", + "pin": "u_clock_buffer/clk_o", + "period_ns": 41.666666667, + "async_group": "core" + } + }, { "name": "system", - "clock": "clk_i", + "clock": "s_backend_sys_clk", "reset": "system_rst_n_o", "reset_source": "s_system_req_n", "reset_primitive": "rst_sync", "path": "rtl/tiny/top/tiny_reset_tree.sv", "instance": "u_system_rst_sync", "sta": { - "source_port": "extclk_i_pad", - "pin": "u_clock_buffer/clk_o", + "source_domain": "aon", + "pin": "u_soc.u_clock_backend.u_sys_mux/clk_o", "period_ns": 41.666666667, - "async_group": "system" + "async_group": "core" + } + }, + { + "name": "memory", + "clock": "s_backend_mem_clk", + "reset": "s_backend_mem_rst_n", + "reset_source": "rst_n_i", + "reset_primitive": "rst_sync", + "path": "rtl/tiny/top/tiny_clock_reset_subsystem.sv", + "instance": "u_mem_rst", + "sta": { + "source_domain": "system", + "pin": "u_soc.u_clock_backend.u_mem_mux/clk_o", + "period_ns": 41.666666667, + "async_group": "core" + } + }, + { + "name": "peripheral", + "clock": "s_backend_pclk", + "reset": "pclk_rst_n_o", + "reset_source": "rst_n_i", + "reset_primitive": "rst_sync", + "path": "rtl/tiny/top/tiny_clock_reset_subsystem.sv", + "instance": "u_pclk_rst", + "sta": { + "source_domain": "system", + "pin": "u_soc.u_clock_backend.u_pclk_mux/clk_o", + "period_ns": 41.666666667, + "async_group": "core" } }, { @@ -39,31 +98,152 @@ "sta": { "source_port": "jtag_tck_i_pad", "object_type": "port", - "period_ns": 100.0, + "period_ns": 100, "async_group": "jtag" } + }, + { + "name": "audio", + "clock": "s_audio_clk", + "reset": "s_audio_rst_n", + "reset_source": "s_i2s_rst_n", + "reset_primitive": "rst_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", + "instance": "u_audio_rst_sync", + "sta": { + "source_port": "gpio_17_io_pad", + "pin": "u_soc.u_audio_clock_buffer/clk_o", + "period_ns": 54.253472222, + "async_group": "audio" + } } ], "reset_distribution": { "release_edges": 5, "por": "u_soc.u_reset_tree.u_por_rst_sync", "system": "u_soc.u_reset_tree.u_system_rst_sync", - "system_source": "por_rst_n_o && !watchdog_reset_req_i", + "system_source": "por_rst_n_o && !watchdog_reset_req_i && backend_reset_n_i", "leaf_instance_pattern": "u_soc.u_reset_tree.gen_leaf[%d].u_leaf_rst_sync", "leaf_names": [ - "fabric", "gpio", "uart0", "uart1", "timer0", "timer1", "i2c0", "i2c1", - "xpi", "dma", "sysctrl", "clint", "sram", "archinfo", "pwm", "rtc", "wdg" + "fabric", + "gpio", + "uart0", + "timer0", + "timer1", + "i2c0", + "xpi", + "dma", + "sysctrl", + "clint", + "sram", + "archinfo", + "pwm", + "rtc", + "wdg", + "rng", + "crc", + "ws2812", + "crypto", + "i2s", + "sdio" ], - "cpu_prerequisite": "all leaf resets released", + "cpu_prerequisite": "all global leaf resets and actual APB/DMA/XPI/RCU barriers; boot readiness latches until the next system reset", "hart": "u_soc.u_cpu.u_mgmt_debug_wrapper.u_mgmt_debug_reset.u_core_rst_sync", "jtag": "u_soc.u_cpu.u_mgmt_debug_wrapper.u_jtag_rst_sync", "dmi": "u_soc.u_cpu.u_mgmt_debug_wrapper.u_dmi_rst_sync", - "watchdog_functional_reset": "POR only; APB uses wdg leaf", + "watchdog_functional_reset": "REF24 five-edge release after backend retains the watchdog reset event; PCLK APB uses the wdg leaf", "main_sram_payload_reset": "none", - "crypto": "not integrated; physical erasure contract unchanged", - "clock_domain": "system" + "crypto": "six private macro banks; reset invalidates then scrubs/readbacks; CPU does not wait for READY", + "clock_domain": "domain-specific", + "peripheral": "tiny_peripheral_leaf; five running hold edges and five release edges; I2S additionally requires actual audio-side barrier readiness" }, "crossings": [ + { + "name": "debug_pclk_status", + "source": "system", "destination": "peripheral", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_debug_pclk_sync" + }, + { + "name": "debug_aon_status", + "source": "system", "destination": "aon", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_debug_ref_sync" + }, + { + "name": "rtc_wake", + "source": "aon", "destination": "system", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_rtc_wake_sync" + }, + { + "name": "hart_reset_event", + "source": "system", "destination": "aon", "primitive": "cdc_2phase", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_hart_event_cdc" + }, + { + "name": "peripheral_interrupts", + "source": "peripheral", "destination": "system", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_irq_sync", + "notes": "sticky interrupt levels; this vector also contains AON/MEM and synchronous SYS bits" + }, + { + "name": "wfi_permission", + "source": "aon", "destination": "system", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_wfi_gate_sync" + }, + { + "name": "admission_block_system", + "source": "aon", "destination": "system", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_block_sys" + }, + { + "name": "admission_block_dma", + "source": "aon", "destination": "peripheral", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_block_pclk" + }, + { + "name": "gate_reset_disabled", + "source": "aon", "destination": "system", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_disabled_sync" + }, + { + "name": "peripheral_idle", + "source": "peripheral", "destination": "aon", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_periph_idle_sync" + }, + { + "name": "xpi_idle", + "source": "memory", "destination": "aon", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_xpi_idle_sync" + }, + { + "name": "dma_ready", + "source": "peripheral", "destination": "system", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_dma_ready_sync" + }, + { + "name": "dma_retirement", + "source": "peripheral", "destination": "system", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_dma_idle_sync" + }, + { + "name": "fabric_retirement", + "source": "system", "destination": "aon", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_bus_idle_ref" + }, + { + "name": "gate_acknowledgements", + "source": "peripheral", "destination": "aon", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_gate_feedback" + }, + { + "name": "reset_hold_acknowledgements", + "source": "peripheral", "destination": "aon", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_held_feedback" + }, + { + "name": "reset_release_acknowledgements", + "source": "system", "destination": "aon", "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", "instance": "u_ready_feedback" + }, { "name": "debug_dmi", "source": "jtag", @@ -72,6 +252,17 @@ "path": "rtl/managed/hazard3/hdl/debug/dtm/hazard3_jtag_dtm_core.v", "instance": "inst_hazard3_apb_async_bridge" }, + { + "name": "xpi_mem_axi", + "source": "system", + "destination": "memory", + "primitive": "tiny_axi4_async_bridge", + "path": "rtl/tiny/top/retrosoc_tiny.sv", + "instance": "u_xpi_mem_cdc", + "source_reset": "s_fabric_rst_n", + "destination_reset": "s_xpi_rst_n", + "reset_semantics": "five-edge link release; source-owned held responses survive destination reset; drain remaining W before B and synthesize only missing R beats with DECERR; unfinished warm clear requires destination reset before re-admission" + }, { "name": "debug_hard_reset", "source": "jtag", @@ -79,6 +270,152 @@ "primitive": "cdc_sync", "path": "rtl/ip/core/mgmt_debug_wrapper.sv", "instance": "u_dmihardreset_cdc_sync" + }, + { + "name": "i2s_configuration", + "source": "peripheral", + "destination": "audio", + "primitive": "cdc_2phase", + "path": "rtl/ip/serial/apb4_i2s.sv", + "instance": "u_i2s_cfg_cdc" + }, + { + "name": "i2s_commands", + "source": "peripheral", + "destination": "audio", + "primitive": "cdc_2phase", + "path": "rtl/ip/serial/apb4_i2s.sv", + "instance": "u_i2s_cmd_cdc" + }, + { + "name": "i2s_tx_samples", + "source": "peripheral", + "destination": "audio", + "primitive": "cdc_fifo_warm_flush", + "path": "rtl/ip/serial/apb4_i2s.sv", + "instance": "u_tx_cdc_fifo" + }, + { + "name": "i2s_rx_samples", + "source": "audio", + "destination": "peripheral", + "primitive": "cdc_fifo_warm_flush", + "path": "rtl/ip/serial/apb4_i2s.sv", + "instance": "u_rx_cdc_fifo" + }, + { + "name": "i2s_tx_occupancy", + "source": "audio", + "destination": "peripheral", + "primitive": "cdc_2phase", + "path": "rtl/ip/serial/apb4_i2s.sv", + "instance": "u_tx_pop_cnt_cdc" + }, + { + "name": "i2s_rx_occupancy", + "source": "audio", + "destination": "peripheral", + "primitive": "cdc_2phase", + "path": "rtl/ip/serial/apb4_i2s.sv", + "instance": "u_rx_push_cnt_cdc" + }, + { + "name": "dma_axi", + "source": "peripheral", + "destination": "system", + "primitive": "tiny_axi4_async_bridge", + "path": "rtl/tiny/top/retrosoc_tiny.sv", + "instance": "u_dma_cdc", + "source_reset": "s_dma_rst_n", + "destination_reset": "s_fabric_rst_n", + "reset_semantics": "five-edge link release; source-owned held responses survive destination reset; drain remaining W before B and synthesize only missing R beats with DECERR; unfinished warm clear requires destination reset before re-admission" + }, + { + "name": "rcu_commands", + "source": "system", + "destination": "aon", + "primitive": "tiny_apb_async_bridge", + "path": "rtl/tiny/top/tiny_sysctrl.sv", + "instance": "u_rcu_cdc" + }, + { + "name": "system_rate", + "source": "aon", + "destination": "system", + "primitive": "cdc_2phase", + "path": "rtl/tiny/top/retrosoc_tiny.sv", + "instance": "u_sys_rate_cdc" + }, + { + "name": "peripheral_rate", + "source": "aon", + "destination": "peripheral", + "primitive": "cdc_2phase", + "path": "rtl/tiny/top/retrosoc_tiny.sv", + "instance": "u_pclk_rate_cdc" + }, + { + "name": "dma_done", + "source": "peripheral", + "destination": "memory", + "primitive": "cdc_2phase", + "path": "rtl/tiny/top/retrosoc_tiny.sv", + "instance": "u_dma_done_cdc" + }, + { + "name": "xpi_requests", + "source": "memory", + "destination": "peripheral", + "primitive": "cdc_sync", + "path": "rtl/tiny/top/retrosoc_tiny.sv", + "instance": "u_xpi_stall_sync" } - ] + ], + "clock_contract": "TINY-R2-P7", + "macros": { + "ics55_pll": { + "cell": "PLL_TOP", + "scope": "single RCU-owned macro; off after cold reset; selectors 5/7", + "active_clock_domain": true, + "qualification": "digital activity/rate qualification only; no analog LOCK or characterized timing arcs" + } + }, + "generated_apb_crossings": { + "generator": "scripts/generate_tiny.py", + "template": "tiny_apb_bridges.svh", + "primitive": "tiny_apb_async_bridge", + "source": "system", + "destinations": { + "aon": [ + "clint", + "archinfo" + ], + "memory": [ + "xpi" + ], + "peripheral": [ + "gpio", + "uart0", + "timer0", + "timer1", + "i2c0", + "dma", + "pwm", + "rtc", + "wdg", + "rng", + "crc", + "ws2812", + "crypto", + "i2s" + ] + }, + "same_clock_isolation": [ + "sdio" + ], + "native_system": [ + "sysctrl", + "sram" + ] + } } diff --git a/rtl/tiny/integration/soc_topology.json b/rtl/tiny/integration/soc_topology.json index 004795d2..0692b805 100644 --- a/rtl/tiny/integration/soc_topology.json +++ b/rtl/tiny/integration/soc_topology.json @@ -1,9 +1,50 @@ { "schema_version": 1, + "cpu": { + "module": "hazard3_cpu_2port", + "local_ports": 2, + "data_outstanding": 1 + }, + "main_sram": { + "groups": 4, + "group_bytes": 32768, + "macros_per_group": 8, + "macro_bytes": 4096, + "base": "0x30000000", + "macro_instance": "u_soc.u_sram.gen_group[{group}].u_group.gen_bank[{bank}].u_ram.u_mem", + "external_attachment": "per-target-P5" + }, + "fabric": { + "owners": [ + "cpu_slow", + "central_dma", + "sdio" + ], + "active_owners": [ + 0, + 1, + 2 + ], + "cpu_combined_outstanding": 1, + "dma_direction_outstanding": 1, + "sdio_direction_outstanding": 1, + "target_combined_outstanding": 1, + "sram_targets": [ + 0, + 1, + 2, + 3 + ], + "xpi_target": 4, + "apb_target_base": 5, + "decode_error_target": 25, + "protocol_error_target": 26, + "target_count": 27 + }, "gpio_pins": 32, "irq_vector_width": 32, "external_irq_count": 30, - "dma_channels": 4, + "dma_channels": 8, "apb_targets": [ { "name": "gpio", @@ -18,12 +59,6 @@ "APB4_UART0" ] }, - { - "name": "uart1", - "regions": [ - "APB4_UART1" - ] - }, { "name": "timer0", "regions": [ @@ -42,12 +77,6 @@ "APB4_I2C0" ] }, - { - "name": "i2c1", - "regions": [ - "APB4_I2C1" - ] - }, { "name": "xpi", "regions": [ @@ -101,6 +130,42 @@ "regions": [ "APB4_WDG" ] + }, + { + "name": "rng", + "regions": [ + "APB4_RNG" + ] + }, + { + "name": "crc", + "regions": [ + "APB4_CRC" + ] + }, + { + "name": "ws2812", + "regions": [ + "APB4_WS2812" + ] + }, + { + "name": "crypto", + "regions": [ + "APB4_CRYPTO" + ] + }, + { + "name": "i2s", + "regions": [ + "APB4_I2S" + ] + }, + { + "name": "sdio", + "regions": [ + "APB4_SDIO0" + ] } ], "interrupts": [ @@ -134,11 +199,21 @@ "core_bit": 7, "signal": "u_i2c0_if.irq_o" }, + { + "name": "i2s", + "core_bit": 8, + "signal": "u_i2s_if.irq_o" + }, { "name": "xpi", "core_bit": 9, "signal": "s_xpi_irq" }, + { + "name": "sdio", + "core_bit": 10, + "signal": "u_sdio_if.irq_o" + }, { "name": "pwm", "core_bit": 11, @@ -154,36 +229,44 @@ "core_bit": 14, "signal": "u_wdg_if.irq_o" }, + { + "name": "rng", + "core_bit": 16, + "signal": "s_rng_irq" + }, + { + "name": "ws2812", + "core_bit": 17, + "signal": "u_ws2812_if.irq_o" + }, { "name": "gpio", "core_bit": 18, "signal": "u_gpio_if.irq_o" }, - { - "name": "i2c1", - "core_bit": 19, - "signal": "u_i2c1_if.irq_o" - }, { "name": "dma", "core_bit": 20, "signal": "s_dma_irq" }, { - "name": "uart1", - "core_bit": 26, - "signal": "u_uart1_if.irq_o" + "name": "crypto", + "core_bit": 23, + "signal": "s_crypto_irq" + }, + { + "name": "rcu", + "core_bit": 31, + "signal": "s_rcu_irq" } ], "gpio_alt_functions": [ { "pin": 0, "alt0": { - "inputs": [ - "u_uart0_if.cts_n_i" - ], - "do": "1'b0", - "oe": "1'b0" + "inputs": [], + "do": "u_sdio_if.sck_o", + "oe": "1'b1" }, "alt1": { "inputs": [], @@ -194,9 +277,11 @@ { "pin": 1, "alt0": { - "inputs": [], - "do": "u_uart0_if.rts_n_o", - "oe": "1'b1" + "inputs": [ + "u_sdio_if.cmd_di_i" + ], + "do": "u_sdio_if.cmd_do_o", + "oe": "u_sdio_if.cmd_oe_o" }, "alt1": { "inputs": [], @@ -208,10 +293,10 @@ "pin": 2, "alt0": { "inputs": [ - "u_pwm_if.sync_i" + "u_sdio_if.dat_di_i[0]" ], - "do": "1'b0", - "oe": "1'b0" + "do": "u_sdio_if.dat_do_o[0]", + "oe": "u_sdio_if.dat_oe_o[0]" }, "alt1": { "inputs": [], @@ -221,36 +306,243 @@ }, { "pin": 3, + "alt0": { + "inputs": [ + "u_sdio_if.dat_di_i[1]" + ], + "do": "u_sdio_if.dat_do_o[1]", + "oe": "u_sdio_if.dat_oe_o[1]" + }, + "alt1": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + } + }, + { + "pin": 4, + "alt0": { + "inputs": [ + "u_sdio_if.dat_di_i[2]" + ], + "do": "u_sdio_if.dat_do_o[2]", + "oe": "u_sdio_if.dat_oe_o[2]" + }, + "alt1": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + } + }, + { + "pin": 5, + "alt0": { + "inputs": [ + "u_sdio_if.dat_di_i[3]" + ], + "do": "u_sdio_if.dat_do_o[3]", + "oe": "u_sdio_if.dat_oe_o[3]" + }, + "alt1": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + } + }, + { + "pin": 6, + "alt0": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + }, + "alt1": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + } + }, + { + "pin": 7, + "alt0": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + }, + "alt1": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + } + }, + { + "pin": 8, + "alt0": { + "inputs": [], + "do": "u_uart0_if.tx_o", + "oe": "1'b1" + }, + "alt1": { + "inputs": [], + "do": "u_pwm_if.pwm_o[0]", + "oe": "u_pwm_if.oe_o[0]" + } + }, + { + "pin": 9, + "alt0": { + "inputs": [ + "u_uart0_if.rx_i" + ], + "do": "1'b0", + "oe": "1'b0" + }, + "alt1": { + "inputs": [], + "do": "u_pwm_if.pwm_o[1]", + "oe": "u_pwm_if.oe_o[1]" + } + }, + { + "pin": 10, + "alt0": { + "inputs": [ + "u_i2c0_if.scl_i" + ], + "do": "u_i2c0_if.scl_o", + "oe": "u_i2c0_if.scl_oe_o" + }, + "alt1": { + "inputs": [], + "do": "u_pwm_if.pwm_o[2]", + "oe": "u_pwm_if.oe_o[2]" + } + }, + { + "pin": 11, + "alt0": { + "inputs": [ + "u_i2c0_if.sda_i" + ], + "do": "u_i2c0_if.sda_o", + "oe": "u_i2c0_if.sda_oe_o" + }, + "alt1": { + "inputs": [], + "do": "u_pwm_if.pwm_o[3]", + "oe": "u_pwm_if.oe_o[3]" + } + }, + { + "pin": 12, + "alt0": { + "inputs": [], + "do": "u_i2s_if.sclk_o", + "oe": "1'b1" + }, + "alt1": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + } + }, + { + "pin": 13, + "alt0": { + "inputs": [], + "do": "u_i2s_if.lrck_o", + "oe": "1'b1" + }, + "alt1": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + } + }, + { + "pin": 14, + "alt0": { + "inputs": [], + "do": "u_i2s_if.dacdat_o", + "oe": "1'b1" + }, + "alt1": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + } + }, + { + "pin": 15, + "alt0": { + "inputs": [ + "u_i2s_if.adcdat_i" + ], + "do": "1'b0", + "oe": "1'b0" + }, + "alt1": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + } + }, + { + "pin": 16, + "alt0": { + "inputs": [], + "do": "u_i2s_if.mclk_o", + "oe": "1'b1" + }, + "alt1": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + } + }, + { + "pin": 17, + "alt0": { + "inputs": [ + "s_audio_pad_clk" + ], + "do": "1'b0", + "oe": "1'b0" + }, + "alt1": { + "inputs": [], + "do": "1'b0", + "oe": "1'b0" + } + }, + { + "pin": 18, "alt0": { "inputs": [], "do": "u_pwm_if.pwm_o[0]", "oe": "u_pwm_if.oe_o[0]" }, "alt1": { - "inputs": [ - "u_i2c1_if.scl_i" - ], - "do": "u_i2c1_if.scl_o", - "oe": "u_i2c1_if.scl_oe_o" + "inputs": [], + "do": "1'b0", + "oe": "1'b0" } }, { - "pin": 4, + "pin": 19, "alt0": { "inputs": [], "do": "u_pwm_if.pwm_o[1]", "oe": "u_pwm_if.oe_o[1]" }, "alt1": { - "inputs": [ - "u_i2c1_if.sda_i" - ], - "do": "u_i2c1_if.sda_o", - "oe": "u_i2c1_if.sda_oe_o" + "inputs": [], + "do": "1'b0", + "oe": "1'b0" } }, { - "pin": 5, + "pin": 20, "alt0": { "inputs": [], "do": "u_pwm_if.pwm_o[2]", @@ -263,7 +555,7 @@ } }, { - "pin": 6, + "pin": 21, "alt0": { "inputs": [], "do": "u_pwm_if.pwm_o[3]", @@ -276,37 +568,7 @@ } }, { - "pin": 7, - "alt0": { - "inputs": [ - "u_i2c0_if.scl_i" - ], - "do": "u_i2c0_if.scl_o", - "oe": "u_i2c0_if.scl_oe_o" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 8, - "alt0": { - "inputs": [ - "u_i2c0_if.sda_i" - ], - "do": "u_i2c0_if.sda_o", - "oe": "u_i2c0_if.sda_oe_o" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 9, + "pin": 22, "alt0": { "inputs": [ "u_pwm_if.fault_i" @@ -320,192 +582,27 @@ "oe": "1'b0" } }, - { - "pin": 10, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 11, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 12, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 13, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 14, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 15, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 16, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 17, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 18, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 19, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 20, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 21, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, - { - "pin": 22, - "alt0": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - }, - "alt1": { - "inputs": [], - "do": "1'b0", - "oe": "1'b0" - } - }, { "pin": 23, "alt0": { - "inputs": [], + "inputs": [ + "u_pwm_if.sync_i" + ], "do": "1'b0", "oe": "1'b0" }, "alt1": { "inputs": [], - "do": "1'b0", - "oe": "1'b0" + "do": "s_clkout", + "oe": "s_clkout_enable" } }, { "pin": 24, "alt0": { - "inputs": [], + "inputs": [ + "u_pwm_if.capture_i[0]" + ], "do": "1'b0", "oe": "1'b0" }, @@ -518,7 +615,9 @@ { "pin": 25, "alt0": { - "inputs": [], + "inputs": [ + "u_pwm_if.capture_i[1]" + ], "do": "1'b0", "oe": "1'b0" }, @@ -532,8 +631,8 @@ "pin": 26, "alt0": { "inputs": [], - "do": "1'b0", - "oe": "1'b0" + "do": "u_ws2812_if.dat_o", + "oe": "1'b1" }, "alt1": { "inputs": [], @@ -558,8 +657,8 @@ "pin": 28, "alt0": { "inputs": [], - "do": "1'b0", - "oe": "1'b0" + "do": "s_clkout", + "oe": "s_clkout_enable" }, "alt1": { "inputs": [], @@ -571,8 +670,8 @@ "pin": 29, "alt0": { "inputs": [], - "do": "1'b0", - "oe": "1'b0" + "do": "xpi.nss_o[1]", + "oe": "1'b1" }, "alt1": { "inputs": [], @@ -584,13 +683,11 @@ "pin": 30, "alt0": { "inputs": [], - "do": "1'b0", - "oe": "1'b0" + "do": "xpi.nss_o[2]", + "oe": "1'b1" }, "alt1": { - "inputs": [ - "u_pwm_if.capture_i[0]" - ], + "inputs": [], "do": "1'b0", "oe": "1'b0" } @@ -599,16 +696,22 @@ "pin": 31, "alt0": { "inputs": [], - "do": "1'b0", - "oe": "1'b0" + "do": "xpi.nss_o[3]", + "oe": "1'b1" }, "alt1": { - "inputs": [ - "u_pwm_if.capture_i[1]" - ], + "inputs": [], "do": "1'b0", "oe": "1'b0" } } - ] + ], + "dma_request_mask": "0x31ff", + "private_crypto": { + "banks": 6, + "bank_bytes": 4096, + "macro_instance": "u_soc.u_crypto.u_storage.gen_bank[{bank}].gen_macro.u_sram.u_mem", + "clock_domain": "peripheral", + "logical_domain": "PCLK" + } } diff --git a/rtl/tiny/mk/config.mk b/rtl/tiny/mk/config.mk index 86ee40ca..95c55e68 100644 --- a/rtl/tiny/mk/config.mk +++ b/rtl/tiny/mk/config.mk @@ -1,4 +1,5 @@ -# Both Tiny integrations remain SAFE24; PLL presence is not SYS selection. +# Tiny always boots from normalized SAFE24. Dynamic selection is RCU-owned. +override TINY_R2_P7 := YES ifneq ($(MINI_MODE),NONE) $(error SOC=TINY requires MINI_MODE=NONE) endif @@ -10,12 +11,12 @@ $(error SOC=TINY requires HAVE_HP=NO) endif ifeq ($(PDK),ICS55) ifneq ($(HAVE_PLL),YES) -$(error SOC=TINY PDK=ICS55 requires HAVE_PLL=YES with the PLL held off) +$(error SOC=TINY PDK=ICS55 requires HAVE_PLL=YES) endif ifeq ($(PDK_BEHAV),YES) $(error SOC=TINY PDK=ICS55 requires the locked technology models, PDK_BEHAV=NO) endif -override TINY_SAFE24_PLL_OFF := YES +override TINY_SAFE24_PLL_OFF := NO else ifneq ($(HAVE_PLL),NO) $(error SOC=TINY PDK=IHP130 requires HAVE_PLL=NO) @@ -31,11 +32,16 @@ endif ifneq ($(SRAM_SIZE_KIB),128) $(error SOC=TINY first release requires SRAM_SIZE_KIB=128) endif -ifneq ($(EXT_CLK_HZ),24000000) -$(error SOC=TINY first release requires EXT_CLK_HZ=24000000) +ifeq ($(filter $(EXT_CLK_HZ),24000000 48000000 96000000),) +$(error SOC=TINY requires external XIN of 24, 48 or 96 MHz) endif -ifneq ($(AUD_CLK_HZ),$(EXT_CLK_HZ)) -$(error SOC=TINY uses the system clock for RTC and watchdog) +ifeq ($(HAVE_PLL),YES) +ifneq ($(EXT_CLK_HZ),24000000) +$(error SOC=TINY with PLL requires EXT_CLK_HZ=24000000) +endif +endif +ifneq ($(AUD_CLK_HZ),24000000) +$(error SOC=TINY RTC and watchdog use normalized REF24) endif ifneq ($(CLINT_TIMEBASE_HZ),1000000) $(error SOC=TINY requires CLINT_TIMEBASE_HZ=1000000) @@ -56,7 +62,7 @@ ifneq ($(ISA),RV32IM) $(error SOC=TINY firmware uses ISA=RV32IM) endif ifneq ($(WS2812_P3_ACCEPTANCE),NO) -$(error SOC=TINY does not yet integrate WS2812) +$(error SOC=TINY uses its integrated P6 acceptance, not the Mini WS2812 P3 image) endif ifneq ($(filter YES,$(APU_ENABLE_P7) $(NPU_P5_ACCEPTANCE) $(NPU_P6_ACCEPTANCE)),) $(error SOC=TINY does not include multimedia accelerators) diff --git a/rtl/tiny/pin_map/pin_map.json b/rtl/tiny/pin_map/pin_map.json index be65c3cb..23a086df 100644 --- a/rtl/tiny/pin_map/pin_map.json +++ b/rtl/tiny/pin_map/pin_map.json @@ -205,38 +205,38 @@ "jtag_tdi_i_pad": "s_jtag_tdi", "jtag_trst_n_i_pad": "s_jtag_trst_n", "jtag_tdo_o_pad": "s_jtag_tdo", - "gpio_0_io_pad": "s_gpio[0]", - "gpio_1_io_pad": "s_gpio[1]", - "gpio_2_io_pad": "s_gpio[2]", - "gpio_3_io_pad": "s_gpio[3]", - "gpio_4_io_pad": "s_gpio[4]", - "gpio_5_io_pad": "s_gpio[5]", - "gpio_6_io_pad": "s_gpio[6]", - "gpio_7_io_pad": "s_gpio[7]", - "gpio_8_io_pad": "s_gpio[8]", - "gpio_9_io_pad": "s_gpio[9]", - "gpio_10_io_pad": "s_gpio[10]", - "gpio_11_io_pad": "s_gpio[11]", - "gpio_12_io_pad": "s_gpio[12]", - "gpio_13_io_pad": "s_gpio[13]", - "gpio_14_io_pad": "s_gpio[14]", - "gpio_15_io_pad": "s_gpio[15]", - "gpio_16_io_pad": "s_gpio[16]", - "gpio_17_io_pad": "s_gpio[17]", - "gpio_18_io_pad": "s_gpio[18]", - "gpio_19_io_pad": "s_gpio[19]", - "gpio_20_io_pad": "s_gpio[20]", - "gpio_21_io_pad": "s_gpio[21]", - "gpio_22_io_pad": "s_gpio[22]", - "gpio_23_io_pad": "s_gpio[23]", - "gpio_24_io_pad": "s_gpio[24]", - "gpio_25_io_pad": "s_gpio[25]", - "gpio_26_io_pad": "s_gpio[26]", - "gpio_27_io_pad": "s_gpio[27]", - "gpio_28_io_pad": "s_gpio[28]", - "gpio_29_io_pad": "s_gpio[29]", - "gpio_30_io_pad": "s_gpio[30]", - "gpio_31_io_pad": "s_gpio[31]", + "gpio_0_io_pad": "s_gpio_0", + "gpio_1_io_pad": "s_gpio_1", + "gpio_2_io_pad": "s_gpio_2", + "gpio_3_io_pad": "s_gpio_3", + "gpio_4_io_pad": "s_gpio_4", + "gpio_5_io_pad": "s_gpio_5", + "gpio_6_io_pad": "s_gpio_6", + "gpio_7_io_pad": "s_gpio_7", + "gpio_8_io_pad": "s_gpio_8", + "gpio_9_io_pad": "s_gpio_9", + "gpio_10_io_pad": "s_gpio_10", + "gpio_11_io_pad": "s_gpio_11", + "gpio_12_io_pad": "s_gpio_12", + "gpio_13_io_pad": "s_gpio_13", + "gpio_14_io_pad": "s_gpio_14", + "gpio_15_io_pad": "s_gpio_15", + "gpio_16_io_pad": "s_gpio_16", + "gpio_17_io_pad": "s_gpio_17", + "gpio_18_io_pad": "s_gpio_18", + "gpio_19_io_pad": "s_gpio_19", + "gpio_20_io_pad": "s_gpio_20", + "gpio_21_io_pad": "s_gpio_21", + "gpio_22_io_pad": "s_gpio_22", + "gpio_23_io_pad": "s_gpio_23", + "gpio_24_io_pad": "s_gpio_24", + "gpio_25_io_pad": "s_gpio_25", + "gpio_26_io_pad": "s_gpio_26", + "gpio_27_io_pad": "s_gpio_27", + "gpio_28_io_pad": "s_gpio_28", + "gpio_29_io_pad": "s_gpio_29", + "gpio_30_io_pad": "s_gpio_30", + "gpio_31_io_pad": "s_gpio_31", "uart0_tx_o_pad": "s_uart0_tx", "uart0_rx_i_pad": "s_uart0_rx", "uart1_tx_o_pad": "s_uart1_tx", @@ -261,38 +261,38 @@ "jtag_tdi_i_pad": "s_jtag_tdi", "jtag_trst_n_i_pad": "s_jtag_trst_n", "jtag_tdo_o_pad": "s_jtag_tdo", - "gpio_0_io_pad": "s_gpio[0]", - "gpio_1_io_pad": "s_gpio[1]", - "gpio_2_io_pad": "s_gpio[2]", - "gpio_3_io_pad": "s_gpio[3]", - "gpio_4_io_pad": "s_gpio[4]", - "gpio_5_io_pad": "s_gpio[5]", - "gpio_6_io_pad": "s_gpio[6]", - "gpio_7_io_pad": "s_gpio[7]", - "gpio_8_io_pad": "s_gpio[8]", - "gpio_9_io_pad": "s_gpio[9]", - "gpio_10_io_pad": "s_gpio[10]", - "gpio_11_io_pad": "s_gpio[11]", - "gpio_12_io_pad": "s_gpio[12]", - "gpio_13_io_pad": "s_gpio[13]", - "gpio_14_io_pad": "s_gpio[14]", - "gpio_15_io_pad": "s_gpio[15]", - "gpio_16_io_pad": "s_gpio[16]", - "gpio_17_io_pad": "s_gpio[17]", - "gpio_18_io_pad": "s_gpio[18]", - "gpio_19_io_pad": "s_gpio[19]", - "gpio_20_io_pad": "s_gpio[20]", - "gpio_21_io_pad": "s_gpio[21]", - "gpio_22_io_pad": "s_gpio[22]", - "gpio_23_io_pad": "s_gpio[23]", - "gpio_24_io_pad": "s_gpio[24]", - "gpio_25_io_pad": "s_gpio[25]", - "gpio_26_io_pad": "s_gpio[26]", - "gpio_27_io_pad": "s_gpio[27]", - "gpio_28_io_pad": "s_gpio[28]", - "gpio_29_io_pad": "s_gpio[29]", - "gpio_30_io_pad": "s_gpio[30]", - "gpio_31_io_pad": "s_gpio[31]", + "gpio_0_io_pad": "s_gpio_0", + "gpio_1_io_pad": "s_gpio_1", + "gpio_2_io_pad": "s_gpio_2", + "gpio_3_io_pad": "s_gpio_3", + "gpio_4_io_pad": "s_gpio_4", + "gpio_5_io_pad": "s_gpio_5", + "gpio_6_io_pad": "s_gpio_6", + "gpio_7_io_pad": "s_gpio_7", + "gpio_8_io_pad": "s_gpio_8", + "gpio_9_io_pad": "s_gpio_9", + "gpio_10_io_pad": "s_gpio_10", + "gpio_11_io_pad": "s_gpio_11", + "gpio_12_io_pad": "s_gpio_12", + "gpio_13_io_pad": "s_gpio_13", + "gpio_14_io_pad": "s_gpio_14", + "gpio_15_io_pad": "s_gpio_15", + "gpio_16_io_pad": "s_gpio_16", + "gpio_17_io_pad": "s_gpio_17", + "gpio_18_io_pad": "s_gpio_18", + "gpio_19_io_pad": "s_gpio_19", + "gpio_20_io_pad": "s_gpio_20", + "gpio_21_io_pad": "s_gpio_21", + "gpio_22_io_pad": "s_gpio_22", + "gpio_23_io_pad": "s_gpio_23", + "gpio_24_io_pad": "s_gpio_24", + "gpio_25_io_pad": "s_gpio_25", + "gpio_26_io_pad": "s_gpio_26", + "gpio_27_io_pad": "s_gpio_27", + "gpio_28_io_pad": "s_gpio_28", + "gpio_29_io_pad": "s_gpio_29", + "gpio_30_io_pad": "s_gpio_30", + "gpio_31_io_pad": "s_gpio_31", "uart0_tx_o_pad": "s_uart0_tx", "uart0_rx_i_pad": "s_uart0_rx", "uart1_tx_o_pad": "s_uart1_tx", diff --git a/rtl/tiny/top/retrosoc_tiny.sv b/rtl/tiny/top/retrosoc_tiny.sv index 7278291b..55b3add3 100644 --- a/rtl/tiny/top/retrosoc_tiny.sv +++ b/rtl/tiny/top/retrosoc_tiny.sv @@ -1,6 +1,7 @@ // Copyright (c) 2026 Yuchi Miao // SPDX-License-Identifier: MulanPSL-2.0 `include "mmap_define.svh" +`include "tiny_topology.svh" module retrosoc_tiny ( input logic clk_i, @@ -21,50 +22,136 @@ module retrosoc_tiny ( output logic [7:0] test_code_o ); logic s_por_rst_n, s_rst_n; - logic [16:0] s_leaf_rst_n; + logic [`RETROSOC_TINY__LEAF_COUNT-1:0] s_leaf_rst_n; logic s_fabric_rst_n, s_cpu_ready_rst_n; - logic s_master_rst_n [2]; - logic s_target_rst_n [5]; - logic s_debug_halted; + logic s_master_rst_n [ 3]; + logic s_target_rst_n [`RETROSOC_TINY__TARGET_COUNT]; + logic s_debug_halted; + logic s_halted_pclk, s_halted_ref, s_rtc_wake_sys; + logic s_core_rst_n, s_cpu_bus_idle, s_cpu_quiesce; + logic s_cpu_clock_idle; logic [31:0] s_irq; - logic s_fault_valid, s_fault_master, s_fault_write; - logic [31:0] s_fault_addr; - logic [ 1:0] s_fault_resp; + logic s_fault_write; + logic [ 1:0] s_fault_master; + logic [ 3:0] s_fault_count; + logic [5:0] s_external_fault_valid, s_external_fault_instruction; + logic [5:0][31:0] s_external_fault_addr; + logic [5:0][ 1:0] s_external_fault_resp; + logic [1:0] s_local_fault_valid, s_local_fault_write; + logic [ 1:0][31:0] s_local_fault_addr; + logic [31:0] s_fault_addr; + logic [ 1:0] s_fault_resp; logic s_perf_enable, s_perf_clear; logic [63:0] s_cpu_wait, s_dma_wait; logic s_timer0_irq, s_timer1_irq, s_xpi_irq, s_dma_irq; logic s_dma_done; - logic [7:0] s_dma_stall; + logic [5:0] s_dma_stall; logic [4:0] s_tick_count_d, s_tick_count_q; logic s_tick; - logic s_unused_uart1_tx_stall, s_unused_uart1_rx_stall; + logic s_audio_pad_clk, s_audio_clk, s_audio_rst_n; + logic s_rtc_func_rst_n, s_wdg_func_rst_n; + logic [31:0] s_xin_hz, s_ref_hz, s_sys_hz, s_mem_hz, s_pclk_hz, s_clint_hz; + logic [1:0] s_rcu_current; + logic s_rcu_busy; + logic s_clock_apply; + logic [1:0] s_clock_profile; + logic s_backend_busy, s_backend_fault; + logic s_backend_sys_clk, s_backend_mem_clk, s_backend_pclk; + logic s_backend_sys_rst_n, s_backend_mem_rst_n, s_backend_pclk_rst_n; + logic [31:0] s_backend_xin_hz, s_backend_ref_hz, s_backend_sys_hz; + logic [31:0] s_backend_mem_hz, s_backend_pclk_hz; + logic s_exec_clk; + logic s_ref_clk; + logic [`RETROSOC_TINY__LEAF_COUNT-1:0] s_leaf_clk; + logic [`RETROSOC_TINY__LEAF_COUNT-2:0] s_apb_cdc_idle, s_apb_cdc_ready; + logic [ 2:0] s_owner_idle; + logic [`RETROSOC_TINY__TARGET_COUNT-1:0] s_target_idle; + logic s_clock_safe; + logic s_xpi_cdc_busy; + logic s_dma_cdc_busy; + logic s_dma_done_pending_d, s_dma_done_pending_q; + logic s_dma_done_ready, s_dma_done_mem; + logic [ 1:0] s_xpi_stall_pclk; + logic [31:0] s_irq_async; + logic s_cpu_boot_ready; + logic s_dma_cdc_ready, s_dma_ready_sys, s_xpi_cdc_ready; + logic s_rcu_link_ready; + logic s_clock_block, s_block_sys, s_block_pclk; + logic s_clock_done, s_system_reset, s_backend_reset_n, s_pll_locked, s_pll_selected; + logic s_rcu_irq; + logic s_hart_reset, s_hart_seen_d, s_hart_seen_q, s_hart_pending_d, s_hart_pending_q; + logic s_hart_ack, s_hart_reset_ref; + logic [31:0] s_pclk_report_d, s_pclk_report_q, s_sys_report_d, s_sys_report_q; + logic [31:0] s_pclk_snapshot, s_sys_snapshot; + logic s_pclk_snapshot_valid, s_sys_snapshot_valid; + logic s_rate_ref_pclk_rst_n, s_rate_pclk_rst_n; + logic s_rate_ref_sys_rst_n, s_rate_sys_rst_n; + logic [31:0] s_clkout_control; + logic s_clkout, s_clkout_enable; + logic s_clkout_idle_ref; + logic [13:0] s_gate_request, s_local_reset, s_actual_gate, s_reset_held, s_reset_ready; + logic [13:0] s_gate_ref, s_held_ref, s_ready_ref, s_disabled_sys; + logic s_periph_apply, s_periph_reset_command, s_periph_block, s_periph_done; + logic [13:0] s_periph_request, s_periph_reset_done; + logic [4:0] s_periph_fault; + logic s_bus_idle_ref; + logic s_boot_release_d, s_boot_release_q; + logic s_wfi_gate_sys; + logic [13:0] s_periph_idle, s_periph_idle_ref; + logic s_xpi_idle, s_xpi_idle_ref; + logic s_pwm_write, s_crc_write; + logic [ 4:0] s_clock_fault; + logic [31:0] s_clock_timeout; + logic [ 1:0] s_backend_current; + logic s_dma_admission_idle, s_dma_idle_sys, s_sdio_admission_idle; + logic s_rng_en, s_rng_ready, s_rng_valid, s_rng_qualified, s_rng_fault, s_rng_irq; + logic [31:0] s_rng_data; + logic s_crypto_irq, s_crypto_input_ready, s_crypto_output_ready; + logic s_i2s_tx_stall, s_i2s_rx_stall; + logic s_i2s_reset_ready; + logic s_done_pclk_rst_n, s_done_mem_rst_n; `include "tiny_apb_interfaces.svh" -uart_if u_uart0_if (); - uart_if u_uart1_if (); +ahbl_if u_cpu_ahbl_if[2] ( + .hclk (s_exec_clk), + .hresetn(s_core_rst_n) + ); + tiny_sram_port_if u_cpu_local_if[2] (); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) u_sram_axi4_if[4] ( + .aclk (s_exec_clk), + .aresetn(s_sram_rst_n) + ); + uart_if u_uart0_if (); i2c_if u_i2c0_if (); - i2c_if u_i2c1_if (); pwm_if u_pwm_if (); clint_if u_clint_if (); gpio_if u_gpio_if (); user_gpio_if u_user_gpio_if (); dma_req_if u_dma_req_if (); + i2s_if u_i2s_if (); + ws2812_if u_ws2812_if (); + sdio_if u_sdio_if (); rtc_if u_rtc_if ( - .rtc_clk_i (clk_i), - .rtc_rst_n_i(s_rtc_rst_n) + .rtc_clk_i (s_ref_clk), + .rtc_rst_n_i(s_rtc_func_rst_n) ); // The watchdog reset pulse must survive resetting the bus/CPU. Its timer - // uses POR reset while its APB register domain uses the system reset. + // resets after the AON backend retains the watchdog request. wdg_if u_wdg_if ( - .wdg_clk_i (clk_i), - .wdg_rst_n_i(s_por_rst_n) + .wdg_clk_i (s_ref_clk), + .wdg_rst_n_i(s_wdg_func_rst_n) ); axi4_if #( .ADDR_WIDTH(32), .DATA_WIDTH(32), .ID_WIDTH (1), .USER_WIDTH(1) - ) u_masters_axi4_if[2] ( - .aclk (clk_i), + ) u_masters_axi4_if[3] ( + .aclk (s_exec_clk), .aresetn(s_master_rst_n) ); axi4_if #( @@ -72,52 +159,458 @@ uart_if u_uart0_if (); .DATA_WIDTH(32), .ID_WIDTH (1), .USER_WIDTH(1) - ) u_targets_axi4_if[5] ( - .aclk (clk_i), + ) u_dma_pclk_axi4_if ( + .aclk (s_dma_clk), + .aresetn(s_dma_rst_n) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) u_dma_admitted_axi4_if ( + .aclk (s_backend_pclk), + .aresetn(s_dma_rst_n) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) u_sdio_sys_axi4_if ( + .aclk (s_sdio_clk), + .aresetn(s_sdio_rst_n) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) u_targets_axi4_if[`RETROSOC_TINY__TARGET_COUNT] ( + .aclk (s_exec_clk), .aresetn(s_target_rst_n) ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) u_xpi_mem_axi4_if ( + .aclk (s_backend_mem_clk), + .aresetn(s_backend_mem_rst_n) + ); axi4_stream_if #( .DATA_WIDTH(32) ) u_i2s_tx_axis_if ( - .aclk (clk_i), + .aclk (s_dma_clk), .aresetn(s_dma_rst_n) ); axi4_stream_if #( .DATA_WIDTH(32) ) u_i2s_rx_axis_if ( - .aclk (clk_i), + .aclk (s_dma_clk), .aresetn(s_dma_rst_n) ); axi4_stream_if #( .DATA_WIDTH(32) ) u_dvp_rx_axis_if ( - .aclk (clk_i), + .aclk (s_dma_clk), .aresetn(s_dma_rst_n) ); axi4_stream_if #( .DATA_WIDTH(32) ) u_crypto_tx_axis_if ( - .aclk (clk_i), + .aclk (s_dma_clk), .aresetn(s_dma_rst_n) ); axi4_stream_if #( .DATA_WIDTH(32) ) u_crypto_rx_axis_if ( - .aclk (clk_i), + .aclk (s_dma_clk), .aresetn(s_dma_rst_n) ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_debug_pclk_sync ( + .clk_i (s_backend_pclk), + .rst_n_i(s_backend_pclk_rst_n), + .dat_i (s_debug_halted), + .dat_o (s_halted_pclk) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_debug_ref_sync ( + .clk_i (s_ref_clk), + .rst_n_i(s_por_rst_n), + .dat_i (s_debug_halted), + .dat_o (s_halted_ref) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_rtc_wake_sync ( + .clk_i (s_exec_clk), + .rst_n_i(s_fabric_rst_n), + .dat_i (u_rtc_if.wake_o), + .dat_o (s_rtc_wake_sys) + ); + rst_sync #( + .STAGE(5) + ) u_rtc_func_reset ( + .clk_i (s_ref_clk), + .rst_n_i(s_por_rst_n && s_backend_reset_n), + .rst_n_o(s_rtc_func_rst_n) + ); + rst_sync #( + .STAGE(5) + ) u_wdg_func_reset ( + .clk_i (s_ref_clk), + .rst_n_i(s_por_rst_n && s_backend_reset_n), + .rst_n_o(s_wdg_func_rst_n) + ); + assign s_hart_seen_d = s_hart_reset; + assign s_hart_pending_d = (s_hart_reset && !s_hart_seen_q) || (s_hart_pending_q && !s_hart_ack); + dffr #( + .DATA_WIDTH(2) + ) u_hart_event ( + .clk_i (s_exec_clk), + .rst_n_i(rst_n_i), + .dat_i ({s_hart_pending_d, s_hart_seen_d}), + .dat_o ({s_hart_pending_q, s_hart_seen_q}) + ); + cdc_2phase #( + .DATA_WIDTH(1) + ) u_hart_event_cdc ( + .src_clk_i (s_exec_clk), + .src_rst_n_i(rst_n_i), + .src_valid_i(s_hart_pending_q), + .src_ready_o(s_hart_ack), + .src_data_i (1'b1), + .dst_clk_i (s_ref_clk), + .dst_rst_n_i(s_por_rst_n), + .dst_valid_o(s_hart_reset_ref), + .dst_ready_i(1'b1), + .dst_data_o () + ); + cdc_reset_barrier #( + .STAGES(5) + ) u_pclk_rate_barrier ( + .clk_a_i (s_ref_clk), + .clk_b_i (s_backend_pclk), + .rst_n_i (s_por_rst_n && s_backend_pclk_rst_n), + .release_i(1'b1), + .rst_a_n_o(s_rate_ref_pclk_rst_n), + .rst_b_n_o(s_rate_pclk_rst_n) + ); + cdc_reset_barrier #( + .STAGES(5) + ) u_sys_rate_barrier ( + .clk_a_i (s_ref_clk), + .clk_b_i (s_exec_clk), + .rst_n_i (s_por_rst_n && s_backend_sys_rst_n), + .release_i(1'b1), + .rst_a_n_o(s_rate_ref_sys_rst_n), + .rst_b_n_o(s_rate_sys_rst_n) + ); + cdc_2phase #( + .DATA_WIDTH(32) + ) u_pclk_rate_cdc ( + .src_clk_i (s_ref_clk), + .src_rst_n_i(s_rate_ref_pclk_rst_n), + .src_data_i (s_backend_pclk_hz), + .src_valid_i(1'b1), + .src_ready_o(), + .dst_clk_i (s_backend_pclk), + .dst_rst_n_i(s_rate_pclk_rst_n), + .dst_data_o (s_pclk_snapshot), + .dst_valid_o(s_pclk_snapshot_valid), + .dst_ready_i(1'b1) + ); + cdc_2phase #( + .DATA_WIDTH(32) + ) u_sys_rate_cdc ( + .src_clk_i (s_ref_clk), + .src_rst_n_i(s_rate_ref_sys_rst_n), + .src_data_i (s_backend_sys_hz), + .src_valid_i(1'b1), + .src_ready_o(), + .dst_clk_i (s_exec_clk), + .dst_rst_n_i(s_rate_sys_rst_n), + .dst_data_o (s_sys_snapshot), + .dst_valid_o(s_sys_snapshot_valid), + .dst_ready_i(1'b1) + ); + assign s_pclk_report_d = s_pclk_snapshot_valid ? s_pclk_snapshot : s_pclk_report_q; + assign s_sys_report_d = s_sys_snapshot_valid ? s_sys_snapshot : s_sys_report_q; + dffrc #( + .DATA_WIDTH(32), + .RESET_VAL (32'd24_000_000) + ) u_pclk_report ( + .clk_i (s_backend_pclk), + .rst_n_i(s_backend_pclk_rst_n), + .dat_i (s_pclk_report_d), + .dat_o (s_pclk_report_q) + ); + dffrc #( + .DATA_WIDTH(32), + .RESET_VAL (32'd24_000_000) + ) u_sys_report ( + .clk_i (s_exec_clk), + .rst_n_i(s_backend_sys_rst_n), + .dat_i (s_sys_report_d), + .dat_o (s_sys_report_q) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_clkout_idle_sync ( + .clk_i (s_ref_clk), + .rst_n_i(s_por_rst_n), + .dat_i (!s_clkout_enable), + .dat_o (s_clkout_idle_ref) + ); + tiny_clock_output u_clock_output ( + .clk_ref_i (s_ref_clk), + .clk_sys_i (s_exec_clk), + .clk_mem_i (s_backend_mem_clk), + .clk_periph_i(s_backend_pclk), + .rst_n_i (s_por_rst_n && s_backend_reset_n), + .control_i (s_clkout_control), + .clk_o (s_clkout), + .enable_o (s_clkout_enable) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_wfi_gate_sync ( + .clk_i (s_exec_clk), + .rst_n_i(s_fabric_rst_n), + .dat_i (s_gate_request[0]), + .dat_o (s_wfi_gate_sys) + ); + assign s_reset_held[0] = 1'b0; + assign s_reset_ready[0] = 1'b0; + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(14) + ) u_gate_feedback ( + .clk_i (s_ref_clk), + .rst_n_i(s_por_rst_n), + .dat_i (s_actual_gate), + .dat_o (s_gate_ref) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(14) + ) u_held_feedback ( + .clk_i (s_ref_clk), + .rst_n_i(s_por_rst_n), + .dat_i (s_reset_held), + .dat_o (s_held_ref) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(14) + ) u_ready_feedback ( + .clk_i (s_ref_clk), + .rst_n_i(s_por_rst_n), + .dat_i (s_reset_ready), + .dat_o (s_ready_ref) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(14) + ) u_disabled_sync ( + .clk_i (s_exec_clk), + .rst_n_i(s_fabric_rst_n), + .dat_i (s_gate_request | s_local_reset), + .dat_o (s_disabled_sys) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_bus_idle_ref ( + .clk_i (s_ref_clk), + .rst_n_i(s_por_rst_n), + .dat_i (s_clock_safe), + .dat_o (s_bus_idle_ref) + ); + tiny_peripheral_control u_peripheral_control ( + .clk_i (s_ref_clk), + .rst_n_i (s_por_rst_n), + .warm_reset_i (!s_backend_reset_n), + .apply_i (s_periph_apply), + .reset_command_i(s_periph_reset_command), + .request_i (s_periph_request), + .idle_i (s_periph_idle_ref), + .bus_idle_i (s_bus_idle_ref), + .gate_actual_i (s_gate_ref), + .reset_held_i (s_held_ref), + .reset_ready_i (s_ready_ref), + .timeout_i (s_clock_timeout), + .gate_o (s_gate_request), + .reset_o (s_local_reset), + .block_o (s_periph_block), + .done_o (s_periph_done), + .fault_o (s_periph_fault), + .reset_done_o (s_periph_reset_done) + ); + assign s_pwm_write = u_pwm_apb4_if.psel && u_pwm_apb4_if.penable && u_pwm_apb4_if.pready && + u_pwm_apb4_if.pwrite && !u_pwm_apb4_if.pslverr && u_pwm_apb4_if.pstrb[0]; + assign s_crc_write = u_crc_apb4_if.psel && u_crc_apb4_if.penable && u_crc_apb4_if.pready && + u_crc_apb4_if.pwrite && !u_crc_apb4_if.pslverr && u_crc_apb4_if.pstrb[0]; + tiny_managed_lifecycle u_managed_lifecycle ( + .clk_i (s_backend_pclk), + .pwm_rst_n_i(s_pwm_rst_n), + .crc_rst_n_i(s_crc_rst_n), + .pwm_write_i(s_pwm_write), + .pwm_addr_i (u_pwm_apb4_if.paddr[11:0]), + .pwm_data_i (u_pwm_apb4_if.pwdata), + .crc_write_i(s_crc_write), + .crc_addr_i (u_crc_apb4_if.paddr[11:0]), + .crc_data_i (u_crc_apb4_if.pwdata), + .pwm_idle_o (s_periph_idle[6]), + .crc_idle_o (s_periph_idle[12]) + ); + assign s_periph_idle[0] = s_cpu_bus_idle; + assign s_periph_idle[1] = 1'b1; // GPIO has no independent transaction engine; USER is absent. + assign s_periph_idle[11] = !s_rng_en && !s_rng_valid; + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(14) + ) u_periph_idle_sync ( + .clk_i (s_ref_clk), + .rst_n_i(s_por_rst_n), + .dat_i (s_periph_idle), + .dat_o (s_periph_idle_ref) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_xpi_idle_sync ( + .clk_i (s_ref_clk), + .rst_n_i(s_por_rst_n), + .dat_i (s_xpi_idle), + .dat_o (s_xpi_idle_ref) + ); + assign s_exec_clk = s_backend_sys_clk; + assign s_leaf_clk[0] = s_exec_clk; + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_dma_ready_sync ( + .clk_i (s_exec_clk), + .rst_n_i(s_fabric_rst_n), + .dat_i (s_dma_cdc_ready), + .dat_o (s_dma_ready_sys) + ); + assign s_boot_release_d = s_boot_release_q || (s_cpu_ready_rst_n && + (&s_apb_cdc_ready) && s_xpi_cdc_ready && s_dma_ready_sys && s_rcu_link_ready); + dffr #( + .DATA_WIDTH(1) + ) u_boot_release ( + .clk_i (s_exec_clk), + .rst_n_i(s_fabric_rst_n), + .dat_i (s_boot_release_d), + .dat_o (s_boot_release_q) + ); + rst_sync #( + .STAGE(5) + ) u_boot_reset ( + .clk_i (s_exec_clk), + .rst_n_i(s_cpu_ready_rst_n && s_boot_release_q), + .rst_n_o(s_cpu_boot_ready) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(2) + ) u_xpi_stall_sync ( + .clk_i (s_dma_clk), + .rst_n_i(s_dma_rst_n), + .dat_i (s_dma_stall[1:0]), + .dat_o (s_xpi_stall_pclk) + ); + // Central DMA emits one completion pulse. Retain it until MEM acknowledges; + // a new completion coincident with acknowledgement wins over clearing. + assign s_dma_done_pending_d = s_dma_done || (s_dma_done_pending_q && !s_dma_done_ready); + dffr #( + .DATA_WIDTH(1) + ) u_dma_done_pending ( + .clk_i (s_dma_clk), + .rst_n_i(s_dma_rst_n), + .dat_i (s_dma_done_pending_d), + .dat_o (s_dma_done_pending_q) + ); + cdc_reset_barrier #( + .STAGES(5) + ) u_dma_done_barrier ( + .clk_a_i (s_dma_clk), + .clk_b_i (s_backend_mem_clk), + .rst_n_i (s_dma_rst_n && s_xpi_rst_n), + .release_i(1'b1), + .rst_a_n_o(s_done_pclk_rst_n), + .rst_b_n_o(s_done_mem_rst_n) + ); + cdc_2phase #( + .DATA_WIDTH(1) + ) u_dma_done_cdc ( + .src_clk_i (s_dma_clk), + .src_rst_n_i(s_done_pclk_rst_n), + .src_valid_i(s_dma_done_pending_q), + .src_ready_o(s_dma_done_ready), + .src_data_i (1'b1), + .dst_clk_i (s_backend_mem_clk), + .dst_rst_n_i(s_done_mem_rst_n), + .dst_valid_o(s_dma_done_mem), + .dst_ready_i(1'b1), + .dst_data_o () + ); + assign s_clock_safe = s_cpu_clock_idle && (&s_owner_idle) && (&s_target_idle) && + (&s_apb_cdc_idle) && s_dma_idle_sys && s_sdio_admission_idle && !s_xpi_cdc_busy; + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_block_sys ( + .clk_i (s_exec_clk), + .rst_n_i(s_fabric_rst_n), + .dat_i (s_clock_block || s_periph_block), + .dat_o (s_block_sys) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_block_pclk ( + .clk_i (s_dma_clk), + .rst_n_i(s_dma_rst_n), + .dat_i (s_clock_block || s_periph_block), + .dat_o (s_block_pclk) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_dma_idle_sync ( + .clk_i (s_exec_clk), + .rst_n_i(s_fabric_rst_n), + .dat_i (s_dma_admission_idle && !s_dma_done_pending_q && s_dma_done_ready), + .dat_o (s_dma_idle_sys) + ); assign s_fabric_rst_n = s_leaf_rst_n[0]; - assign s_master_rst_n = '{s_cpu_ready_rst_n, s_dma_rst_n}; - assign s_target_rst_n = '{ - s_sram_rst_n, - s_xpi_rst_n, - s_fabric_rst_n, - s_fabric_rst_n, - s_fabric_rst_n - }; - - tiny_reset_tree u_reset_tree ( - .clk_i (clk_i), + assign s_master_rst_n = '{s_cpu_ready_rst_n, s_fabric_rst_n, s_sdio_rst_n}; + // The fabric-side XPI target is SYS-clocked; only the MEM endpoint uses the + // XPI leaf reset. The async bridge invalidates accepted work if MEM resets. + assign s_target_rst_n[4] = s_fabric_rst_n; + assign s_target_rst_n[`RETROSOC_TINY__DECODE_ERROR] = s_fabric_rst_n; + assign s_target_rst_n[`RETROSOC_TINY__PROTOCOL_ERROR] = s_fabric_rst_n; + tiny_reset_tree #( + .LeafCount (`RETROSOC_TINY__LEAF_COUNT), + .SplitDomains(1'b1) + ) u_reset_tree ( + .clk_i (s_exec_clk), + .por_clk_i (s_ref_clk), + .leaf_clk_i (s_leaf_clk), + .backend_reset_n_i (s_backend_reset_n), .rst_n_i (rst_n_i), .watchdog_reset_req_i(u_wdg_if.reset_req_o), .por_rst_n_o (s_por_rst_n), @@ -130,19 +623,22 @@ uart_if u_uart0_if (); dffr #( .DATA_WIDTH(5) ) u_tick_reg ( - .clk_i (clk_i), - .rst_n_i(s_fabric_rst_n), + .clk_i (s_ref_clk), + .rst_n_i(s_clint_rst_n), .dat_i (s_tick_count_d), .dat_o (s_tick_count_q) ); - mgmt_core_wrapper #( - .ExternalIrqCount (30), - .EnableAtomics (1'b0), - .TwoCycleBusErrors(1'b1), - .ResetSyncStages (5) + tiny_cpu_wrapper #( + .ExternalIrqCount(30), + .EnableAtomics (1'b0), + .ResetSyncStages (5), + .EnableWfiGate (1'b1), + .PreserveDebug (1'b1) ) u_cpu ( - .clk_i (clk_i), - .rst_n_i (s_cpu_ready_rst_n), + .clk_i (s_exec_clk), + .rst_n_i (s_cpu_boot_ready), + .debug_rst_n_i (rst_n_i), + .hart_reset_o (s_hart_reset), .irq_i (s_irq), .jtag_tck_i (jtag_tck_i), .jtag_tms_i (jtag_tms_i), @@ -150,87 +646,250 @@ uart_if u_uart0_if (); .jtag_trst_n_i (jtag_trst_n_i), .jtag_tdo_o (jtag_tdo_o), .debug_halted_o(s_debug_halted), - .axi4 (u_masters_axi4_if[0]) + .bus_idle_i (s_cpu_bus_idle), + .wfi_gate_i (s_wfi_gate_sys), + .gated_o (s_actual_gate[0]), + .core_rst_n_o (s_core_rst_n), + .quiesce_o (s_cpu_quiesce), + .cpu (u_cpu_ahbl_if) + ); + tiny_cpu_mem #( + .ClockPause(1'b1) + ) u_cpu_mem ( + .clk_i (s_exec_clk), + .rst_n_i (s_core_rst_n), + .quiesce_i (s_cpu_quiesce), + .clock_pause_i (s_block_sys), + .clock_idle_o (s_cpu_clock_idle), + .idle_o (s_cpu_bus_idle), + .local_fault_valid_o(s_local_fault_valid), + .local_fault_addr_o (s_local_fault_addr), + .local_fault_write_o(s_local_fault_write), + .cpu (u_cpu_ahbl_if), + .local_ports (u_cpu_local_if), + .slow_axi4 (u_masters_axi4_if[0]) ); tiny_axi4_fabric u_fabric ( - .clk_i (clk_i), - .rst_n_i (s_fabric_rst_n), - .masters (u_masters_axi4_if), - .targets (u_targets_axi4_if), - .perf_enable_i (s_perf_enable), - .perf_clear_i (s_perf_clear), - .fault_valid_o (s_fault_valid), - .fault_addr_o (s_fault_addr), - .fault_master_o(s_fault_master), - .fault_write_o (s_fault_write), - .fault_resp_o (s_fault_resp), - .cpu_wait_o (s_cpu_wait), - .dma_wait_o (s_dma_wait) + .clk_i (s_exec_clk), + .rst_n_i (s_fabric_rst_n), + .target_rst_n_i (s_target_rst_n), + .masters (u_masters_axi4_if), + .targets (u_targets_axi4_if), + .perf_enable_i (s_perf_enable), + .perf_clear_i (s_perf_clear), + .fault_valid_o (s_external_fault_valid), + .fault_addr_o (s_external_fault_addr), + .fault_resp_o (s_external_fault_resp), + .fault_instruction_o(s_external_fault_instruction), + .owner_idle_o (s_owner_idle), + .target_idle_o (s_target_idle), + .cpu_wait_o (s_cpu_wait), + .dma_wait_o (s_dma_wait) ); - onchip_ram #( - .CapacityKiB (128), - .DataWidth (32), - .IdWidth (1), - .Ics55SmallBanks(1'b1) - ) u_sram ( - .clk_i (clk_i), - .rst_n_i (s_sram_rst_n), - .mem_axi4 (u_targets_axi4_if[0]), - .cfg_apb4 (u_sram_apb4_if), - .perf_enable_i(s_perf_enable), - .perf_clear_i (s_perf_clear) + for (genvar group = 0; group < 4; group++) begin : gen_sram_attachment + assign s_target_rst_n[group] = s_sram_rst_n; + axi4_connector u_connector ( + .source(u_targets_axi4_if[group]), + .sink (u_sram_axi4_if[group]) + ); + end + tiny_sram u_sram ( + .clk_i (s_exec_clk), + .rst_n_i (s_sram_rst_n), + .local_ports (u_cpu_local_if), + .external_ports(u_sram_axi4_if), + .cfg_apb4 (u_sram_apb4_if), + .perf_enable_i (s_perf_enable), + .perf_clear_i (s_perf_clear) + ); + tiny_axi4_async_bridge #( + .DataWidth(32), + .IdWidth (1) + ) u_dma_cdc ( + .src_clk_i (s_dma_clk), + .src_rst_n_i (s_dma_rst_n), + .dst_clk_i (s_exec_clk), + .dst_rst_n_i (s_fabric_rst_n), + .clear_i (1'b0), + .clear_busy_o(s_dma_cdc_busy), + .ready_o (s_dma_cdc_ready), + .epoch_o (), + .src_axi4 (u_dma_admitted_axi4_if), + .dst_axi4 (u_masters_axi4_if[1]) + ); + tiny_axi4_admission u_dma_admission ( + .clk_i (s_dma_clk), + .rst_n_i(s_dma_rst_n), + .block_i(s_block_pclk), + .idle_o (s_dma_admission_idle), + .source (u_dma_pclk_axi4_if), + .sink (u_dma_admitted_axi4_if) + ); + tiny_axi4_admission u_sdio_admission ( + .clk_i (s_sdio_clk), + .rst_n_i(s_sdio_rst_n), + .block_i(s_block_sys), + .idle_o (s_sdio_admission_idle), + .source (u_sdio_sys_axi4_if), + .sink (u_masters_axi4_if[2]) + ); + tiny_axi4_async_bridge #( + .DataWidth(32), + .IdWidth (1) + ) u_xpi_mem_cdc ( + .src_clk_i (s_exec_clk), + .src_rst_n_i (s_fabric_rst_n), + .dst_clk_i (s_backend_mem_clk), + .dst_rst_n_i (s_xpi_rst_n), + .clear_i (1'b0), + .clear_busy_o(s_xpi_cdc_busy), + .ready_o (s_xpi_cdc_ready), + .epoch_o (), + .src_axi4 (u_targets_axi4_if[4]), + .dst_axi4 (u_xpi_mem_axi4_if) ); apb4_xpi u_xpi ( - .clk_i (clk_i), - .rst_n_i (s_xpi_rst_n), - .mem_axi4 (u_targets_axi4_if[1]), - .apb4 (u_xpi_apb4_if), - .xpi (xpi), - .dma_xfer_done_i(s_dma_done), - .dma_tx_stall_o (s_dma_stall[0]), - .dma_rx_stall_o (s_dma_stall[1]), - .irq_o (s_xpi_irq) + .lifecycle_idle_o(s_xpi_idle), + .clk_i (s_backend_mem_clk), + .rst_n_i (s_xpi_rst_n), + .mem_axi4 (u_xpi_mem_axi4_if), + .apb4 (u_xpi_apb4_if), + .xpi (xpi), + .dma_xfer_done_i (s_dma_done_mem), + .dma_tx_stall_o (s_dma_stall[0]), + .dma_rx_stall_o (s_dma_stall[1]), + .irq_o (s_xpi_irq) ); - tiny_axi42apb4 u_apb_bridge ( - .clk_i (clk_i), - .rst_n_i(s_fabric_rst_n), - .axi4 (u_targets_axi4_if[2]), - `include "tiny_apb_connections.svh" - ); - axi4_error_slave #( + `include "tiny_apb_bridges.svh" +axi4_error_slave #( .Response(2'b11) ) u_decode_error ( - .clk_i (clk_i), + .clk_i (s_exec_clk), .rst_n_i(s_fabric_rst_n), - .axi4 (u_targets_axi4_if[3]) + .axi4 (u_targets_axi4_if[`RETROSOC_TINY__DECODE_ERROR]) ); axi4_error_slave #( .Response(2'b10) ) u_protocol_error ( - .clk_i (clk_i), + .clk_i (s_exec_clk), .rst_n_i(s_fabric_rst_n), - .axi4 (u_targets_axi4_if[4]) + .axi4 (u_targets_axi4_if[`RETROSOC_TINY__PROTOCOL_ERROR]) ); - tiny_sysctrl u_sysctrl ( - .clk_i (clk_i), - .rst_n_i (s_sysctrl_rst_n), - .apb4 (u_sysctrl_apb4_if), - .fault_valid_i (s_fault_valid), - .fault_addr_i (s_fault_addr), - .fault_write_i (s_fault_write), - .fault_master_i(s_fault_master), - .fault_resp_i (s_fault_resp), - .cpu_wait_i (s_cpu_wait), - .dma_wait_i (s_dma_wait), - .rtc_wake_i (u_rtc_if.wake_o), - .perf_enable_o (s_perf_enable), - .perf_clear_o (s_perf_clear), - .test_done_o (test_done_o), - .test_pass_o (test_pass_o), - .test_code_o (test_code_o) + tiny_fault_collector u_fault_collector ( + .local_valid_i (s_local_fault_valid), + .local_addr_i (s_local_fault_addr), + .local_write_i (s_local_fault_write), + .external_valid_i (s_external_fault_valid), + .external_addr_i (s_external_fault_addr), + .external_resp_i (s_external_fault_resp), + .external_instruction_i(s_external_fault_instruction), + .count_o (s_fault_count), + .addr_o (s_fault_addr), + .master_o (s_fault_master), + .write_o (s_fault_write), + .resp_o (s_fault_resp) + ); +`ifdef HAVE_PLL + localparam bit TinyPllPresent = 1'b1; +`else + localparam bit TinyPllPresent = 1'b0; +`endif + tiny_sysctrl #( + .PllPresent (TinyPllPresent), + .PllProfiles(TinyPllPresent), + .XinHzValue (`SOC_EXT_CLK_HZ), + .TargetMask (14'h3fff) + ) u_sysctrl ( + .clk_i (s_exec_clk), + .rst_n_i (s_sysctrl_rst_n), + .clk_ref_i (s_ref_clk), + .rst_ref_n_i (s_por_rst_n), + .rcu_link_ready_o(s_rcu_link_ready), + .apb4 (u_sysctrl_apb4_if), + .fault_valid_i (s_fault_count != 4'd0), + .fault_count_i (s_fault_count), + .fault_addr_i (s_fault_addr), + .fault_write_i (s_fault_write), + .fault_master_i (s_fault_master), + .fault_resp_i (s_fault_resp), + .cpu_wait_i (s_cpu_wait), + .dma_wait_i (s_dma_wait), + .rtc_wake_i (s_rtc_wake_sys), + .clock_done_i (s_clock_done), + .periph_done_i (s_periph_done), + .periph_fault_i (s_periph_fault), + .gate_actual_i (s_gate_ref), + .reset_done_i (s_periph_reset_done), + .periph_apply_o (s_periph_apply), + .periph_reset_o (s_periph_reset_command), + .periph_request_o(s_periph_request), + .clkout_control_o(s_clkout_control), + .clkout_idle_i (s_clkout_idle_ref), + .clock_fault_i (s_clock_fault), + .clock_current_i (s_backend_current), + .pll_locked_i (s_pll_locked), + .pll_selected_i (s_pll_selected), + .warm_reset_i (!s_backend_reset_n), + .system_reset_o (s_system_reset), + .watchdog_reset_i(u_wdg_if.reset_req_o), + .hart_reset_i (s_hart_reset_ref), + .rcu_irq_o (s_rcu_irq), + .clock_timeout_o (s_clock_timeout), + .perf_enable_o (s_perf_enable), + .perf_clear_o (s_perf_clear), + .test_done_o (test_done_o), + .test_pass_o (test_pass_o), + .test_code_o (test_code_o), + .xin_hz_o (s_xin_hz), + .ref_hz_o (s_ref_hz), + .sys_hz_o (s_sys_hz), + .mem_hz_o (s_mem_hz), + .pclk_hz_o (s_pclk_hz), + .clint_hz_o (s_clint_hz), + .rcu_current_o (s_rcu_current), + .rcu_busy_o (s_rcu_busy), + .clock_apply_o (s_clock_apply), + .clock_profile_o (s_clock_profile) + ); + tiny_clock_reset_subsystem #( + .RefClkHz(`SOC_EXT_CLK_HZ), + .HavePll (TinyPllPresent) + ) u_clock_backend ( + .ref_clk_i (clk_i), + .rst_n_i (rst_n_i), + .idle_i (s_clock_safe), + .clients_idle_i ((&s_periph_idle_ref[13:1]) && s_xpi_idle_ref), + .warm_reset_i (u_wdg_if.reset_req_o), + .system_request_i (s_system_reset), + .command_ack_i (s_cpu_clock_idle && s_target_idle[`RETROSOC_TINY__TARGET_SYSCTRL]), + .timeout_i (s_clock_timeout), + .ref_clk_o (s_ref_clk), + .ref_rst_n_o (), + .system_reset_n_o (s_backend_reset_n), + .block_o (s_clock_block), + .done_o (s_clock_done), + .fault_code_o (s_clock_fault), + .pll_locked_o (s_pll_locked), + .pll_selected_o (s_pll_selected), + .profile_i (s_clock_profile), + .apply_i (s_clock_apply), + .current_profile_o(s_backend_current), + .busy_o (s_backend_busy), + .fault_o (s_backend_fault), + .sys_clk_o (s_backend_sys_clk), + .mem_clk_o (s_backend_mem_clk), + .pclk_o (s_backend_pclk), + .sys_rst_n_o (s_backend_sys_rst_n), + .mem_rst_n_o (s_backend_mem_rst_n), + .pclk_rst_n_o (s_backend_pclk_rst_n), + .xin_hz_o (s_backend_xin_hz), + .ref_hz_o (s_backend_ref_hz), + .sys_hz_o (s_backend_sys_hz), + .mem_hz_o (s_backend_mem_hz), + .pclk_hz_o (s_backend_pclk_hz) ); tiny_archinfo u_archinfo ( - .clk_i (clk_i), + .clk_i (s_ref_clk), .rst_n_i(s_archinfo_rst_n), .apb4 (u_archinfo_apb4_if) ); @@ -239,7 +898,7 @@ uart_if u_uart0_if (); .HasPullUp (gpio_pad_caps_pkg::HasPullUp), .HasPullDown (gpio_pad_caps_pkg::HasPullDown) ) u_gpio ( - .clk_i (clk_i), + .clk_i (s_gpio_clk), .rst_n_i (s_gpio_rst_n), .apb4 (u_gpio_apb4_if), .gpio (u_gpio_if), @@ -251,73 +910,71 @@ uart_if u_uart0_if (); ); assign u_user_gpio_if.do_o = '0; assign u_user_gpio_if.oe_o = '0; - assign u_uart0_if.rx_i = uart0_rx_i; assign uart0_tx_o = u_uart0_if.tx_o; - assign u_uart1_if.rx_i = uart1_rx_i; - assign u_uart1_if.cts_n_i = 1'b0; - assign uart1_tx_o = u_uart1_if.tx_o; - assign u_wdg_if.debug_halted_i = s_debug_halted; + // Legacy ASIC observation pads are retained, not a Gen1 package binding. + assign uart1_tx_o = 1'b1; + assign u_uart0_if.cts_n_i = 1'b0; + assign u_wdg_if.debug_halted_i = s_halted_ref; `include "tiny_gpio.svh" `include "tiny_irq.svh" -apb4_uart u_uart0 ( - .clk_i (clk_i), - .rst_n_i (s_uart0_rst_n), - .apb4 (u_uart0_apb4_if), - .uart (u_uart0_if), - .dma_tx_stall_o(s_dma_stall[2]), - .dma_rx_stall_o(s_dma_stall[3]) +cdc_sync #( + .STAGE (2), + .DATA_WIDTH(32) + ) u_irq_sync ( + .clk_i (s_exec_clk), + .rst_n_i(s_fabric_rst_n), + .dat_i (s_irq_async), + .dat_o (s_irq) + ); + apb4_uart u_uart0 ( + .lifecycle_idle_o(s_periph_idle[2]), + .clk_i (s_uart0_clk), + .rst_n_i (s_uart0_rst_n), + .apb4 (u_uart0_apb4_if), + .uart (u_uart0_if), + .dma_tx_stall_o (s_dma_stall[2]), + .dma_rx_stall_o (s_dma_stall[3]) ); apb4_i2c u_i2c0 ( - .clk_i (clk_i), - .rst_n_i (s_i2c0_rst_n), - .apb4 (u_i2c0_apb4_if), - .i2c (u_i2c0_if), - .dma_tx_stall_o(s_dma_stall[4]), - .dma_rx_stall_o(s_dma_stall[5]) + .lifecycle_idle_o(s_periph_idle[3]), + .clk_i (s_i2c0_clk), + .rst_n_i (s_i2c0_rst_n), + .apb4 (u_i2c0_apb4_if), + .i2c (u_i2c0_if), + .dma_tx_stall_o (s_dma_stall[4]), + .dma_rx_stall_o (s_dma_stall[5]) ); apb4_timer u_timer0 ( - .clk_i (clk_i), - .rst_n_i (s_timer0_rst_n), - .debug_halted_i(s_debug_halted), - .apb4 (u_timer0_apb4_if), - .irq_o (s_timer0_irq) - ); - apb4_uart u_uart1 ( - .clk_i (clk_i), - .rst_n_i (s_uart1_rst_n), - .apb4 (u_uart1_apb4_if), - .uart (u_uart1_if), - .dma_tx_stall_o(s_unused_uart1_tx_stall), - .dma_rx_stall_o(s_unused_uart1_rx_stall) - ); - apb4_i2c u_i2c1 ( - .clk_i (clk_i), - .rst_n_i (s_i2c1_rst_n), - .apb4 (u_i2c1_apb4_if), - .i2c (u_i2c1_if), - .dma_tx_stall_o(s_dma_stall[6]), - .dma_rx_stall_o(s_dma_stall[7]) + .lifecycle_idle_o(s_periph_idle[4]), + .clk_i (s_timer0_clk), + .rst_n_i (s_timer0_rst_n), + .debug_halted_i (s_halted_pclk), + .apb4 (u_timer0_apb4_if), + .irq_o (s_timer0_irq) ); apb4_timer u_timer1 ( - .clk_i (clk_i), - .rst_n_i (s_timer1_rst_n), - .debug_halted_i(s_debug_halted), - .apb4 (u_timer1_apb4_if), - .irq_o (s_timer1_irq) + .lifecycle_idle_o(s_periph_idle[5]), + .clk_i (s_timer1_clk), + .rst_n_i (s_timer1_rst_n), + .debug_halted_i (s_halted_pclk), + .apb4 (u_timer1_apb4_if), + .irq_o (s_timer1_irq) ); apb4_clint u_clint ( - .clk_i (clk_i), + .clk_i (s_ref_clk), .rst_n_i (s_clint_rst_n), .timebase_tick_i(s_tick), .apb4 (u_clint_apb4_if), .clint (u_clint_if) ); apb4_pwm #( - .PCLK_HZ(24_000_000) + .PCLK_HZ (24_000_000), + .UseRuntimeClockHz(1'b1) ) u_pwm ( - .debug_halted_i(s_debug_halted), + .debug_halted_i(s_halted_pclk), .apb4 (u_pwm_apb4_if), - .pwm (u_pwm_if) + .pwm (u_pwm_if), + .pclk_hz_i (s_pclk_report_q) ); apb4_rtc #( .RTC_CLOCK_HZ(24_000_000) @@ -333,45 +990,38 @@ apb4_uart u_uart0 ( .wdg (u_wdg_if) ); apb4_dma #( - .NumChannels (4), + .NumChannels (8), .MaxBurstBeats(16), .FifoDepth (32), - .RequestMask (16'h07f9), - .EnableStreams(1'b0) + .RequestMask (16'h31ff), + .EnableStreams(1'b1) ) u_dma ( - .clk_i (clk_i), - .rst_n_i (s_dma_rst_n), - .dma_xfer_done_o(s_dma_done), - .irq_o (s_dma_irq), - .hw_trg (u_dma_req_if), - .apb4 (u_dma_apb4_if), - .axi4 (u_masters_axi4_if[1]), - .i2s_tx_axis (u_i2s_tx_axis_if), - .i2s_rx_axis (u_i2s_rx_axis_if), - .dvp_rx_axis (u_dvp_rx_axis_if), - .crypto_in_axis (u_crypto_tx_axis_if), - .crypto_out_axis(u_crypto_rx_axis_if) + .lifecycle_idle_o(s_periph_idle[9]), + .clk_i (s_dma_clk), + .rst_n_i (s_dma_rst_n), + .dma_xfer_done_o (s_dma_done), + .irq_o (s_dma_irq), + .hw_trg (u_dma_req_if), + .apb4 (u_dma_apb4_if), + .axi4 (u_dma_pclk_axi4_if), + .i2s_tx_axis (u_i2s_tx_axis_if), + .i2s_rx_axis (u_i2s_rx_axis_if), + .dvp_rx_axis (u_dvp_rx_axis_if), + .crypto_in_axis (u_crypto_tx_axis_if), + .crypto_out_axis (u_crypto_rx_axis_if) ); - assign u_dma_req_if.i2s_tx_proc = 1'b0; - assign u_dma_req_if.i2s_rx_proc = 1'b0; - assign u_dma_req_if.crypto_in_proc = 1'b0; - assign u_dma_req_if.crypto_out_proc = 1'b0; - assign u_dma_req_if.qspi_tx_proc = !s_dma_stall[0]; - assign u_dma_req_if.qspi_rx_proc = !s_dma_stall[1]; + assign u_dma_req_if.i2s_tx_proc = !s_i2s_tx_stall; + assign u_dma_req_if.i2s_rx_proc = !s_i2s_rx_stall; + assign u_dma_req_if.crypto_in_proc = s_crypto_input_ready; + assign u_dma_req_if.crypto_out_proc = s_crypto_output_ready; + assign u_dma_req_if.qspi_tx_proc = !s_xpi_stall_pclk[0]; + assign u_dma_req_if.qspi_rx_proc = !s_xpi_stall_pclk[1]; assign u_dma_req_if.uart_tx_proc = !s_dma_stall[2]; assign u_dma_req_if.uart_rx_proc = !s_dma_stall[3]; assign u_dma_req_if.i2c0_tx_proc = !s_dma_stall[4]; assign u_dma_req_if.i2c0_rx_proc = !s_dma_stall[5]; - assign u_dma_req_if.i2c1_tx_proc = !s_dma_stall[6]; - assign u_dma_req_if.i2c1_rx_proc = !s_dma_stall[7]; - assign u_i2s_rx_axis_if.tvalid = '0; - assign u_i2s_rx_axis_if.tdata = '0; - assign u_i2s_rx_axis_if.tstrb = '0; - assign u_i2s_rx_axis_if.tkeep = '0; - assign u_i2s_rx_axis_if.tlast = '0; - assign u_i2s_rx_axis_if.tid = '0; - assign u_i2s_rx_axis_if.tdest = '0; - assign u_i2s_rx_axis_if.tuser = '0; + assign u_dma_req_if.i2c1_tx_proc = 1'b0; + assign u_dma_req_if.i2c1_rx_proc = 1'b0; assign u_dvp_rx_axis_if.tvalid = '0; assign u_dvp_rx_axis_if.tdata = '0; assign u_dvp_rx_axis_if.tstrb = '0; @@ -380,14 +1030,87 @@ apb4_uart u_uart0 ( assign u_dvp_rx_axis_if.tid = '0; assign u_dvp_rx_axis_if.tdest = '0; assign u_dvp_rx_axis_if.tuser = '0; - assign u_crypto_rx_axis_if.tvalid = '0; - assign u_crypto_rx_axis_if.tdata = '0; - assign u_crypto_rx_axis_if.tstrb = '0; - assign u_crypto_rx_axis_if.tkeep = '0; - assign u_crypto_rx_axis_if.tlast = '0; - assign u_crypto_rx_axis_if.tid = '0; - assign u_crypto_rx_axis_if.tdest = '0; - assign u_crypto_rx_axis_if.tuser = '0; - assign u_i2s_tx_axis_if.tready = 1'b0; - assign u_crypto_tx_axis_if.tready = 1'b0; + + // Host logic remains SAFE24. Audio reset waits for its own clock, never CPU boot. + tc_clk_buf u_audio_clock_buffer ( + .clk_i(s_audio_pad_clk), + .clk_o(s_audio_clk) + ); + rst_sync #( + .STAGE(5) + ) u_audio_rst_sync ( + .clk_i (s_audio_clk), + .rst_n_i(s_i2s_rst_n), + .rst_n_o(s_audio_rst_n) + ); + apb4_i2s u_i2s ( + .lifecycle_reset_ready_o(s_i2s_reset_ready), + .lifecycle_idle_o (s_periph_idle[7]), + .clk_i (s_i2s_clk), + .rst_n_i (s_i2s_rst_n), + .clk_aud_i (s_audio_clk), + .rst_aud_n_i (s_audio_rst_n), + .dma_tx_stall_o (s_i2s_tx_stall), + .dma_rx_stall_o (s_i2s_rx_stall), + .tx_underrun_evt_o (), // Sticky status and IRQ retain these events. + .rx_overrun_evt_o (), + .rx_flush_busy_o (), // No APU consumer in Tiny. + .apb4 (u_i2s_apb4_if), + .tx_axis (u_i2s_tx_axis_if), + .rx_axis (u_i2s_rx_axis_if), + .i2s (u_i2s_if) + ); + apb4_sdio #( + .InputClockHz (24_000_000), + .UseRuntimeClockHz(1'b1) + ) u_sdio ( + .lifecycle_idle_o(s_periph_idle[8]), + .clk_i (s_sdio_clk), + .rst_n_i (s_sdio_rst_n), + .apb4 (u_sdio_apb4_if), + .dma_axi4 (u_sdio_sys_axi4_if), + .sdio (u_sdio_if), + .clock_hz_i (s_sys_report_q) + ); + apb4_ws2812 u_ws2812 ( + .lifecycle_idle_o(s_periph_idle[10]), + .clk_i (s_ws2812_clk), + .rst_n_i (s_ws2812_rst_n), + .apb4 (u_ws2812_apb4_if), + .ws2812 (u_ws2812_if) + ); + apb4_crypto u_crypto ( + .lifecycle_idle_o (s_periph_idle[13]), + .clk_i (s_crypto_clk), + .rst_n_i (s_crypto_rst_n), + .dma_input_proc_o (s_crypto_input_ready), + .dma_output_proc_o(s_crypto_output_ready), + .irq_o (s_crypto_irq), + .apb4 (u_crypto_apb4_if), + .crypto_in_axis (u_crypto_tx_axis_if), + .crypto_out_axis (u_crypto_rx_axis_if) + ); + apb4_crc u_crc (.apb4(u_crc_apb4_if)); + rng_deterministic_source u_rng_source ( + .clk_i (s_rng_clk), + .rst_n_i (s_rng_rst_n), + .enable_i (s_rng_en), + .ready_i (s_rng_ready), + .valid_o (s_rng_valid), + .data_o (s_rng_data), + .qualified_o(s_rng_qualified), + .fault_o (s_rng_fault) + ); + apb4_rng #( + .FIFO_DEPTH(8) + ) u_rng ( + .entropy_enable_o (s_rng_en), + .entropy_ready_o (s_rng_ready), + .entropy_valid_i (s_rng_valid), + .entropy_data_i (s_rng_data), + .entropy_qualified_i(s_rng_qualified), + .entropy_fault_i (s_rng_fault), + .irq_o (s_rng_irq), + .apb4 (u_rng_apb4_if) + ); endmodule diff --git a/rtl/tiny/top/retrosoc_tiny_asic.sv b/rtl/tiny/top/retrosoc_tiny_asic.sv index ffa3ad18..0c89657b 100644 --- a/rtl/tiny/top/retrosoc_tiny_asic.sv +++ b/rtl/tiny/top/retrosoc_tiny_asic.sv @@ -14,22 +14,6 @@ module retrosoc_tiny_asic ( /* verilator lint_on UNUSEDSIGNAL */ gpio_if u_gpio_if (); xpi_if u_xpi_if (); -`ifdef HAVE_PLL - // P1 preserves the macro but supplies no request or reset release. SYS is - // always the external SAFE24 path; RCU source switching belongs to R2-P7. - tc_pll #( - .UseIcs55EcosPll(1'b1), - .Ics55Parked (1'b1) - ) u_pll ( - .fref_i (s_ext_clk), - .rst_n_i (1'b0), - .cfg_sel_i (3'd5), - .cfg_apply_i (1'b0), - .pll_capable_o(), - .pll_lock_o (), - .pll_clk_o () - ); -`endif `include "retrosoc_asic_pad_bindings.svh" tc_clk_buf u_clock_buffer ( .clk_i(s_ext_clk), diff --git a/rtl/tiny/top/tiny_apb_async_bridge.sv b/rtl/tiny/top/tiny_apb_async_bridge.sv new file mode 100644 index 00000000..948fc3d6 --- /dev/null +++ b/rtl/tiny/top/tiny_apb_async_bridge.sv @@ -0,0 +1,156 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// One outstanding SYS control request. Either endpoint reset flushes both +// mailboxes. A destination reset aborts the accepted source transfer with an +// error; it never replays it when the destination returns. The SYS endpoint +// remains clocked so a stopped/disabled peripheral is still reachable. +module tiny_apb_async_bridge ( + input logic src_clk_i, + input logic src_rst_n_i, + input logic dst_clk_i, + input logic dst_rst_n_i, + input logic disabled_i, + output logic idle_o, + output logic ready_o, + apb4_if.slave src_apb4, + apb4_if.master dst_apb4 +); + typedef enum logic [1:0] { + SrcIdle, + SrcSend, + SrcWait, + SrcResponse + } src_state_e; + typedef enum logic [1:0] { + DstIdle, + DstSetup, + DstAccess, + DstResponse + } dst_state_e; + typedef struct packed { + src_state_e state; + logic [71:0] req; + logic [32:0] resp; + } src_t; + typedef struct packed { + dst_state_e state; + logic [71:0] req; + logic [32:0] resp; + } dst_t; + src_t s_src_d, s_src_q; + dst_t s_dst_d, s_dst_q; + logic s_link_src_rst_n, s_link_dst_rst_n; + logic s_req_ready, s_req_valid, s_resp_ready, s_resp_valid; + logic [71:0] s_req_data; + logic [32:0] s_resp_data; + + cdc_reset_barrier #( + .STAGES(5) + ) u_barrier ( + .clk_a_i (src_clk_i), + .clk_b_i (dst_clk_i), + .rst_n_i (src_rst_n_i && dst_rst_n_i), + .release_i(1'b1), + .rst_a_n_o(s_link_src_rst_n), + .rst_b_n_o(s_link_dst_rst_n) + ); + cdc_2phase #( + .DATA_WIDTH(72) + ) u_req_cdc ( + .src_clk_i (src_clk_i), + .src_rst_n_i(s_link_src_rst_n), + .src_data_i (s_src_q.req), + .src_valid_i(s_src_q.state == SrcSend), + .src_ready_o(s_req_ready), + .dst_clk_i (dst_clk_i), + .dst_rst_n_i(s_link_dst_rst_n), + .dst_data_o (s_req_data), + .dst_valid_o(s_req_valid), + .dst_ready_i(s_dst_q.state == DstIdle) + ); + cdc_2phase #( + .DATA_WIDTH(33) + ) u_resp_cdc ( + .src_clk_i (dst_clk_i), + .src_rst_n_i(s_link_dst_rst_n), + .src_data_i (s_dst_q.resp), + .src_valid_i(s_dst_q.state == DstResponse), + .src_ready_o(s_resp_ready), + .dst_clk_i (src_clk_i), + .dst_rst_n_i(s_link_src_rst_n), + .dst_data_o (s_resp_data), + .dst_valid_o(s_resp_valid), + .dst_ready_i(s_src_q.state == SrcWait) + ); + assign ready_o = s_link_src_rst_n && s_req_ready; + assign idle_o = (s_src_q.state == SrcIdle) && s_req_ready && !s_resp_valid; + assign src_apb4.pready = s_src_q.state == SrcResponse; + assign src_apb4.pslverr = s_src_q.resp[32]; + assign src_apb4.prdata = s_src_q.resp[31:0]; + assign {dst_apb4.paddr, dst_apb4.pprot, dst_apb4.pwrite, + dst_apb4.pwdata, dst_apb4.pstrb} = s_dst_q.req; + assign dst_apb4.psel = s_link_dst_rst_n && + ((s_dst_q.state == DstSetup) || (s_dst_q.state == DstAccess)); + assign dst_apb4.penable = s_link_dst_rst_n && (s_dst_q.state == DstAccess); + + always_comb begin + s_src_d = s_src_q; + unique case (s_src_q.state) + SrcIdle: + if (src_apb4.psel && src_apb4.penable) begin + s_src_d.req = { + src_apb4.paddr, src_apb4.pprot, src_apb4.pwrite, src_apb4.pwdata, src_apb4.pstrb + }; + s_src_d.resp = {1'b1, 32'd0}; + s_src_d.state = disabled_i ? SrcResponse : SrcSend; + end + SrcSend: if (s_req_ready) s_src_d.state = SrcWait; + SrcWait: + if (s_resp_valid) begin + s_src_d.resp = s_resp_data; + s_src_d.state = SrcResponse; + end + SrcResponse: s_src_d.state = SrcIdle; + default: s_src_d.state = SrcIdle; + endcase + if (((s_src_q.state == SrcSend) || (s_src_q.state == SrcWait)) && !s_link_src_rst_n) begin + s_src_d.resp = {1'b1, 32'd0}; + s_src_d.state = SrcResponse; + end + end + always_comb begin + s_dst_d = s_dst_q; + unique case (s_dst_q.state) + DstIdle: + if (s_req_valid) begin + s_dst_d.req = s_req_data; + s_dst_d.state = DstSetup; + end + DstSetup: s_dst_d.state = DstAccess; + DstAccess: + if (dst_apb4.pready) begin + s_dst_d.resp = {dst_apb4.pslverr, dst_apb4.prdata}; + s_dst_d.state = DstResponse; + end + DstResponse: if (s_resp_ready) s_dst_d.state = DstIdle; + default: s_dst_d.state = DstIdle; + endcase + end + dffr #( + .DATA_WIDTH($bits(src_t)) + ) u_src ( + .clk_i (src_clk_i), + .rst_n_i(src_rst_n_i), + .dat_i (s_src_d), + .dat_o (s_src_q) + ); + dffr #( + .DATA_WIDTH($bits(dst_t)) + ) u_dst ( + .clk_i (dst_clk_i), + .rst_n_i(s_link_dst_rst_n), + .dat_i (s_dst_d), + .dat_o (s_dst_q) + ); +endmodule diff --git a/rtl/tiny/top/tiny_axi42apb4.sv b/rtl/tiny/top/tiny_axi42apb4.sv index 4cd6fdc3..d0b48b2d 100644 --- a/rtl/tiny/top/tiny_axi42apb4.sv +++ b/rtl/tiny/top/tiny_axi42apb4.sv @@ -4,10 +4,10 @@ // APB setup/access are separate cycles; rejected bursts never assert PSEL. module tiny_axi42apb4 ( - input logic clk_i, - input logic rst_n_i, - axi4_if.slave axi4, - `include "tiny_apb_ports.svh" + input logic clk_i, + input logic rst_n_i, + axi4_if.slave axi4, + apb4_if.master apb4 ); typedef enum logic [2:0] { Idle, @@ -30,8 +30,6 @@ module tiny_axi42apb4 ( logic [1:0] s_resp_d, s_resp_q; logic [7:0] s_len_d, s_len_q; logic [7:0] s_beat_d, s_beat_q; - logic [15:0] s_select, s_ready, s_error; - logic [15:0][31:0] s_rdata; logic s_access, s_enable, s_done, s_err; logic [31:0] s_read_data; @@ -51,18 +49,18 @@ module tiny_axi42apb4 ( assign axi4.rvalid = (s_state_q == ReadResponse) || (s_state_q == ReadError); assign s_access = (s_state_q == Setup) || (s_state_q == Access); assign s_enable = s_state_q == Access; - assign s_done = |(s_select & s_ready); - assign s_err = |(s_select & s_error); - - always_comb begin - s_select = '0; - `include "tiny_apb_decode.svh" -s_read_data = '0; - for (int unsigned target = 0; target < 16; target++) begin - if (s_select[target]) s_read_data = s_rdata[target]; - end - end - `include "tiny_apb_wiring.svh" + // The fabric owns peripheral decoding. Each bridge owns only this APB + // endpoint, including its setup/access and retained response state. + assign s_done = apb4.pready; + assign s_err = apb4.pslverr; + assign s_read_data = apb4.prdata; + assign apb4.paddr = s_addr_q; + assign apb4.pwrite = s_write_q; + assign apb4.pwdata = s_wdata_q; + assign apb4.pstrb = s_write_q ? s_wstrb_q : 4'd0; + assign apb4.pprot = 3'd0; + assign apb4.psel = s_access; + assign apb4.penable = s_enable; always_comb begin s_state_d = s_state_q; @@ -111,12 +109,7 @@ s_read_data = '0; s_state_d = WriteResponse; end else s_state_d = Setup; end - Setup: begin - if (s_select == 16'd0) begin - s_resp_d = 2'b11; - s_state_d = s_write_q ? WriteResponse : ReadResponse; - end else s_state_d = Access; - end + Setup: s_state_d = Access; Access: if (s_done) begin s_resp_d = s_err ? 2'b10 : 2'b00; diff --git a/rtl/tiny/top/tiny_axi4_admission.sv b/rtl/tiny/top/tiny_axi4_admission.sv new file mode 100644 index 00000000..5824180a --- /dev/null +++ b/rtl/tiny/top/tiny_axi4_admission.sv @@ -0,0 +1,153 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// One retained address and terminal response per direction. Blocking affects +// only upstream acceptance; already accepted addresses remain VALID downstream +// and write data/responses drain. Unaccepted requests never enter idle accounting. +module tiny_axi4_admission ( + input logic clk_i, + input logic rst_n_i, + input logic block_i, + output logic idle_o, + axi4_if.slave source, + axi4_if.master sink +); + typedef struct packed { + logic [0:0] id; + logic [31:0] addr; + logic [7:0] len; + logic [2:0] size; + logic [1:0] burst; + logic [0:0] lock; + logic [3:0] cache; + logic [2:0] prot; + logic [3:0] qos; + logic [3:0] region; + logic [0:0] user; + } address_t; + address_t s_aw_d, s_aw_q, s_ar_d, s_ar_q; + logic [1:0] s_pending_d, s_pending_q, s_sent_d, s_sent_q; + assign source.awready = rst_n_i && !block_i && !s_pending_q[0]; + assign source.arready = rst_n_i && !block_i && !s_pending_q[1]; + assign sink.awvalid = s_pending_q[0] && !s_sent_q[0]; + assign sink.arvalid = s_pending_q[1] && !s_sent_q[1]; + assign sink.awid = s_aw_q.id; + assign sink.awaddr = s_aw_q.addr; + assign sink.awlen = s_aw_q.len; + assign sink.awsize = s_aw_q.size; + assign sink.awburst = s_aw_q.burst; + assign sink.awlock = s_aw_q.lock; + assign sink.awcache = s_aw_q.cache; + assign sink.awprot = s_aw_q.prot; + assign sink.awqos = s_aw_q.qos; + assign sink.awregion = s_aw_q.region; + assign sink.awuser = s_aw_q.user; + assign sink.arid = s_ar_q.id; + assign sink.araddr = s_ar_q.addr; + assign sink.arlen = s_ar_q.len; + assign sink.arsize = s_ar_q.size; + assign sink.arburst = s_ar_q.burst; + assign sink.arlock = s_ar_q.lock; + assign sink.arcache = s_ar_q.cache; + assign sink.arprot = s_ar_q.prot; + assign sink.arqos = s_ar_q.qos; + assign sink.arregion = s_ar_q.region; + assign sink.aruser = s_ar_q.user; + assign sink.wvalid = source.wvalid && s_pending_q[0] && + (s_sent_q[0] || (sink.awvalid && sink.awready)); + assign source.wready = sink.wready && s_pending_q[0] && + (s_sent_q[0] || (sink.awvalid && sink.awready)); + assign sink.wdata = source.wdata; + assign sink.wstrb = source.wstrb; + assign sink.wlast = source.wlast; + assign sink.wuser = source.wuser; + assign sink.bready = source.bready; + assign sink.rready = source.rready; + assign source.bid = sink.bid; + assign source.bresp = sink.bresp; + assign source.buser = sink.buser; + assign source.bvalid = sink.bvalid; + assign source.rid = sink.rid; + assign source.rdata = sink.rdata; + assign source.rresp = sink.rresp; + assign source.rlast = sink.rlast; + assign source.ruser = sink.ruser; + assign source.rvalid = sink.rvalid; + assign idle_o = s_pending_q == 0; + always_comb begin + s_aw_d = s_aw_q; + s_ar_d = s_ar_q; + s_pending_d = s_pending_q; + s_sent_d = s_sent_q; + if (source.bvalid && source.bready) begin + s_pending_d[0] = 1'b0; + s_sent_d[0] = 1'b0; + end + if (source.rvalid && source.rready && source.rlast) begin + s_pending_d[1] = 1'b0; + s_sent_d[1] = 1'b0; + end + if (sink.awvalid && sink.awready) s_sent_d[0] = 1'b1; + if (sink.arvalid && sink.arready) s_sent_d[1] = 1'b1; + if (source.awvalid && source.awready) begin + s_pending_d[0] = 1'b1; + s_aw_d.id = source.awid; + s_aw_d.addr = source.awaddr; + s_aw_d.len = source.awlen; + s_aw_d.size = source.awsize; + s_aw_d.burst = source.awburst; + s_aw_d.lock = source.awlock; + s_aw_d.cache = source.awcache; + s_aw_d.prot = source.awprot; + s_aw_d.qos = source.awqos; + s_aw_d.region = source.awregion; + s_aw_d.user = source.awuser; + end + if (source.arvalid && source.arready) begin + s_pending_d[1] = 1'b1; + s_ar_d.id = source.arid; + s_ar_d.addr = source.araddr; + s_ar_d.len = source.arlen; + s_ar_d.size = source.arsize; + s_ar_d.burst = source.arburst; + s_ar_d.lock = source.arlock; + s_ar_d.cache = source.arcache; + s_ar_d.prot = source.arprot; + s_ar_d.qos = source.arqos; + s_ar_d.region = source.arregion; + s_ar_d.user = source.aruser; + end + end + dffr #( + .DATA_WIDTH($bits(address_t)) + ) u_aw ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_aw_d), + .dat_o (s_aw_q) + ); + dffr #( + .DATA_WIDTH($bits(address_t)) + ) u_ar ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_ar_d), + .dat_o (s_ar_q) + ); + dffr #( + .DATA_WIDTH(2) + ) u_pending ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_pending_d), + .dat_o (s_pending_q) + ); + dffr #( + .DATA_WIDTH(2) + ) u_sent ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_sent_d), + .dat_o (s_sent_q) + ); +endmodule diff --git a/rtl/tiny/top/tiny_axi4_async_bridge.sv b/rtl/tiny/top/tiny_axi4_async_bridge.sv new file mode 100644 index 00000000..c95a39b8 --- /dev/null +++ b/rtl/tiny/top/tiny_axi4_async_bridge.sv @@ -0,0 +1,432 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +module tiny_axi4_async_bridge #( + parameter int unsigned AddrWidth = 32, + parameter int unsigned DataWidth = 32, + parameter int unsigned IdWidth = 1, + parameter int unsigned UserWidth = 1, + parameter int unsigned FifoDepth = 4 +) ( + // verilog_format: off -- preserve the source/destination clock boundary columns + input logic src_clk_i, + input logic src_rst_n_i, + input logic dst_clk_i, + input logic dst_rst_n_i, + input logic clear_i, + output logic clear_busy_o, + output logic ready_o, + output logic [7:0] epoch_o, + axi4_if.slave src_axi4, + axi4_if.master dst_axi4 + // verilog_format: on +); + localparam int unsigned StrbWidth = DataWidth / 8; + localparam int unsigned AwWidth = IdWidth + AddrWidth + 8 + 3 + 2 + 1 + 4 + 3 + 4 + 4 + UserWidth; + localparam int unsigned WWidth = DataWidth + StrbWidth + 1 + UserWidth; + localparam int unsigned BWidth = IdWidth + 2 + UserWidth; + localparam int unsigned ArWidth = AwWidth; + localparam int unsigned RWidth = IdWidth + DataWidth + 2 + 1 + UserWidth; + + logic [AwWidth-1:0] s_aw_src_data; + logic [AwWidth-1:0] s_aw_dst_data; + logic [ WWidth-1:0] s_w_src_data; + logic [ WWidth-1:0] s_w_dst_data; + logic [ BWidth-1:0] s_b_dst_data; + logic [ BWidth-1:0] s_b_src_data; + logic [ArWidth-1:0] s_ar_src_data; + logic [ArWidth-1:0] s_ar_dst_data; + logic [ RWidth-1:0] s_r_dst_data; + logic [ RWidth-1:0] s_r_src_data; + logic s_clear_src; + logic s_clear_dst; + logic s_clear_q; + logic s_clear_dst_q; + logic s_clear_src_pulse; + logic s_clear_dst_pulse; + logic s_dst_rst_n_src_q; + logic s_dst_reset_evt; + logic [7:0] s_epoch_d, s_epoch_q; + logic s_aw_src_clear_busy, s_aw_dst_clear_busy; + logic s_w_src_clear_busy, s_w_dst_clear_busy; + logic s_b_src_clear_busy, s_b_dst_clear_busy; + logic s_ar_src_clear_busy, s_ar_dst_clear_busy; + logic s_r_src_clear_busy, s_r_dst_clear_busy; + logic s_dst_busy_src; + logic s_link_src_rst_n, s_link_dst_rst_n; + logic s_src_link_rst_n; + logic s_src_accept; + logic s_aw_src_ready_i, s_w_src_ready_i, s_ar_src_ready_i; + logic s_b_src_valid, s_r_src_valid; + logic s_write_pending_d, s_write_pending_q; + logic s_read_pending_d, s_read_pending_q; + logic s_abort_write_d, s_abort_write_q; + logic s_abort_read_d, s_abort_read_q; + logic [IdWidth-1:0] s_write_id_d, s_write_id_q; + logic [IdWidth-1:0] s_read_id_d, s_read_id_q; + logic [8:0] s_write_left_d, s_write_left_q, s_read_left_d, s_read_left_q; + logic [BWidth-1:0] s_b_hold_d, s_b_hold_q; + logic [RWidth-1:0] s_r_hold_d, s_r_hold_q; + logic s_b_hold_valid_d, s_b_hold_valid_q, s_r_hold_valid_d, s_r_hold_valid_q; + logic s_b_take, s_r_take, s_abort_evt; + logic s_clear_wait_reset_d, s_clear_wait_reset_q; + + cdc_reset_barrier #( + .STAGES(5) + ) u_link_barrier ( + .clk_a_i (src_clk_i), + .clk_b_i (dst_clk_i), + .rst_n_i (src_rst_n_i && dst_rst_n_i), + .release_i(1'b1), + .rst_a_n_o(s_link_src_rst_n), + .rst_b_n_o(s_link_dst_rst_n) + ); + assign s_src_link_rst_n = s_link_src_rst_n; + assign s_src_accept = s_src_link_rst_n && + !clear_busy_o && !s_abort_write_q && !s_abort_read_q && !s_abort_evt && !s_clear_wait_reset_q; + assign ready_o = s_src_accept; + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_busy_sync ( + .clk_i(src_clk_i), + .rst_n_i(src_rst_n_i), + .dat_i(s_aw_dst_clear_busy || s_w_dst_clear_busy || s_b_dst_clear_busy || + s_ar_dst_clear_busy || s_r_dst_clear_busy || !s_link_dst_rst_n), + .dat_o(s_dst_busy_src) + ); + + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_clear_src_sync ( + .clk_i (src_clk_i), + .rst_n_i(src_rst_n_i), + .dat_i (clear_i), + .dat_o (s_clear_src) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_clear_sync ( + .clk_i (dst_clk_i), + .rst_n_i(dst_rst_n_i), + .dat_i (clear_i), + .dat_o (s_clear_dst) + ); + assign s_clear_src_pulse = s_clear_src && !s_clear_q; + assign s_clear_dst_pulse = s_clear_dst && !s_clear_dst_q; + assign s_dst_reset_evt = s_dst_rst_n_src_q && !s_link_src_rst_n; + assign s_abort_evt = s_dst_reset_evt || s_clear_src_pulse; + + assign src_axi4.awready = s_src_accept && !s_write_pending_q && s_aw_src_ready_i; + // Accepted write data must drain even when its destination session is lost. + // Never report B until all AWLEN+1 source beats have been consumed. + assign src_axi4.wready = s_write_pending_q && s_write_left_q != 0 && + (s_abort_write_q || (s_src_accept && s_w_src_ready_i)); + assign src_axi4.arready = s_src_accept && !s_read_pending_q && s_ar_src_ready_i; + + // These registers belong to the surviving source session. Remote reset may + // discard FIFO payload, but cannot revoke VALID or rewrite a held response. + assign {src_axi4.bid, src_axi4.bresp, src_axi4.buser} = s_b_hold_q; + assign {src_axi4.rid, src_axi4.rdata, src_axi4.rresp, + src_axi4.rlast, src_axi4.ruser} = s_r_hold_q; + assign src_axi4.bvalid = s_b_hold_valid_q; + assign src_axi4.rvalid = s_r_hold_valid_q; + assign s_b_take = (!s_b_hold_valid_q || src_axi4.bready) && s_write_pending_q && + !(src_axi4.bvalid && src_axi4.bready) && + s_write_left_q == 0 && !s_abort_write_q && !s_abort_evt && s_src_accept; + assign s_r_take = (!s_r_hold_valid_q || src_axi4.rready) && s_read_pending_q && + !(src_axi4.rvalid && src_axi4.rready && src_axi4.rlast) && + !s_abort_read_q && !s_abort_evt && s_src_accept; + + always_comb begin + s_write_pending_d = s_write_pending_q; + s_read_pending_d = s_read_pending_q; + s_abort_write_d = s_abort_write_q; + s_abort_read_d = s_abort_read_q; + s_write_id_d = s_write_id_q; + s_read_id_d = s_read_id_q; + s_write_left_d = s_write_left_q; + s_read_left_d = s_read_left_q; + s_b_hold_d = s_b_hold_q; + s_r_hold_d = s_r_hold_q; + s_b_hold_valid_d = s_b_hold_valid_q; + s_r_hold_valid_d = s_r_hold_valid_q; + s_clear_wait_reset_d = s_clear_wait_reset_q; + if (src_axi4.awvalid && src_axi4.awready) begin + s_write_pending_d = 1'b1; + s_write_id_d = src_axi4.awid; + s_write_left_d = {1'b0, src_axi4.awlen} + 9'd1; + end + if (src_axi4.arvalid && src_axi4.arready) begin + s_read_pending_d = 1'b1; + s_read_id_d = src_axi4.arid; + s_read_left_d = {1'b0, src_axi4.arlen} + 9'd1; + end + if (src_axi4.wvalid && src_axi4.wready) s_write_left_d = s_write_left_q - 9'd1; + if (src_axi4.bvalid && src_axi4.bready) begin + s_write_pending_d = 1'b0; + s_abort_write_d = 1'b0; + s_b_hold_valid_d = 1'b0; + end + if (src_axi4.rvalid && src_axi4.rready) begin + s_r_hold_valid_d = 1'b0; + s_read_left_d = s_read_left_q - 9'd1; + if (src_axi4.rlast) begin + s_read_pending_d = 1'b0; + s_abort_read_d = 1'b0; + end + end + // A source handshake is irrevocable. Abort only the work still pending + // after that handshake; do not create a second terminal response. + if (s_abort_evt) begin + s_abort_write_d = s_write_pending_d; + s_abort_read_d = s_read_pending_d; + end + // A FIFO clear alone cannot cancel a transaction already accepted by a + // remote AXI slave. Return local errors, but quarantine late responses + // until an actual destination reset establishes a new session. Normal + // drained clears and already exposed terminal responses need no reset. + if (s_clear_src_pulse && + ((s_write_pending_d && !s_b_hold_valid_q) || + (s_read_pending_d && !(s_r_hold_valid_q && src_axi4.rlast)))) + s_clear_wait_reset_d = 1'b1; + if (s_dst_reset_evt) s_clear_wait_reset_d = 1'b0; + if (s_b_take && s_b_src_valid) begin + s_b_hold_d = s_b_src_data; + s_b_hold_valid_d = 1'b1; + end else if (!s_b_hold_valid_d && s_abort_write_d && s_write_left_d == 0) begin + s_b_hold_d = {s_write_id_q, 2'b11, {UserWidth{1'b0}}}; + s_b_hold_valid_d = 1'b1; + end + if (s_r_take && s_r_src_valid) begin + s_r_hold_d = s_r_src_data; + s_r_hold_valid_d = 1'b1; + end else if (!s_r_hold_valid_d && s_abort_read_d && s_read_left_d != 0) begin + s_r_hold_d = { + s_read_id_q, {DataWidth{1'b0}}, 2'b11, (s_read_left_d == 9'd1), {UserWidth{1'b0}} + }; + s_r_hold_valid_d = 1'b1; + end + end + + assign s_aw_src_data = { + src_axi4.awid, + src_axi4.awaddr, + src_axi4.awlen, + src_axi4.awsize, + src_axi4.awburst, + src_axi4.awlock, + src_axi4.awcache, + src_axi4.awprot, + src_axi4.awqos, + src_axi4.awregion, + src_axi4.awuser + }; + assign { + dst_axi4.awid, + dst_axi4.awaddr, + dst_axi4.awlen, + dst_axi4.awsize, + dst_axi4.awburst, + dst_axi4.awlock, + dst_axi4.awcache, + dst_axi4.awprot, + dst_axi4.awqos, + dst_axi4.awregion, + dst_axi4.awuser + } = s_aw_dst_data; + + cdc_fifo_warm_flush #( + .DATA_WIDTH (AwWidth), + .BUFFER_DEPTH(FifoDepth) + ) u_aw_fifo ( + .src_clk_i (src_clk_i), + .src_rst_n_i (s_src_link_rst_n), + .src_clear_i (s_clear_src_pulse), + .src_clear_busy_o(s_aw_src_clear_busy), + .src_data_i (s_aw_src_data), + .src_valid_i (src_axi4.awvalid && s_src_accept && !s_write_pending_q), + .src_ready_o (s_aw_src_ready_i), + .dst_clk_i (dst_clk_i), + .dst_rst_n_i (dst_rst_n_i), + .dst_clear_busy_o(s_aw_dst_clear_busy), + .dst_data_o (s_aw_dst_data), + .dst_valid_o (dst_axi4.awvalid), + .dst_ready_i (dst_axi4.awready) + ); + + assign s_w_src_data = {src_axi4.wdata, src_axi4.wstrb, src_axi4.wlast, src_axi4.wuser}; + assign {dst_axi4.wdata, dst_axi4.wstrb, dst_axi4.wlast, dst_axi4.wuser} = s_w_dst_data; + + cdc_fifo_warm_flush #( + .DATA_WIDTH (WWidth), + .BUFFER_DEPTH(FifoDepth) + ) u_w_fifo ( + .src_clk_i(src_clk_i), + .src_rst_n_i(s_src_link_rst_n), + .src_clear_i(s_clear_src_pulse), + .src_clear_busy_o(s_w_src_clear_busy), + .src_data_i(s_w_src_data), + .src_valid_i(src_axi4.wvalid && s_src_accept && s_write_pending_q && s_write_left_q != 0), + .src_ready_o(s_w_src_ready_i), + .dst_clk_i(dst_clk_i), + .dst_rst_n_i(dst_rst_n_i), + .dst_clear_busy_o(s_w_dst_clear_busy), + .dst_data_o(s_w_dst_data), + .dst_valid_o(dst_axi4.wvalid), + .dst_ready_i(dst_axi4.wready) + ); + + assign s_b_dst_data = {dst_axi4.bid, dst_axi4.bresp, dst_axi4.buser}; + cdc_fifo_warm_flush #( + .DATA_WIDTH (BWidth), + .BUFFER_DEPTH(FifoDepth) + ) u_b_fifo ( + .src_clk_i (dst_clk_i), + .src_rst_n_i (dst_rst_n_i), + .src_clear_i (s_clear_dst_pulse), + .src_clear_busy_o(s_b_dst_clear_busy), + .src_data_i (s_b_dst_data), + .src_valid_i (dst_axi4.bvalid), + .src_ready_o (dst_axi4.bready), + .dst_clk_i (src_clk_i), + .dst_rst_n_i (s_src_link_rst_n), + .dst_clear_busy_o(s_b_src_clear_busy), + .dst_data_o (s_b_src_data), + .dst_valid_o (s_b_src_valid), + .dst_ready_i (s_b_take) + ); + + assign s_ar_src_data = { + src_axi4.arid, + src_axi4.araddr, + src_axi4.arlen, + src_axi4.arsize, + src_axi4.arburst, + src_axi4.arlock, + src_axi4.arcache, + src_axi4.arprot, + src_axi4.arqos, + src_axi4.arregion, + src_axi4.aruser + }; + assign { + dst_axi4.arid, + dst_axi4.araddr, + dst_axi4.arlen, + dst_axi4.arsize, + dst_axi4.arburst, + dst_axi4.arlock, + dst_axi4.arcache, + dst_axi4.arprot, + dst_axi4.arqos, + dst_axi4.arregion, + dst_axi4.aruser + } = s_ar_dst_data; + + cdc_fifo_warm_flush #( + .DATA_WIDTH (ArWidth), + .BUFFER_DEPTH(FifoDepth) + ) u_ar_fifo ( + .src_clk_i (src_clk_i), + .src_rst_n_i (s_src_link_rst_n), + .src_clear_i (s_clear_src_pulse), + .src_clear_busy_o(s_ar_src_clear_busy), + .src_data_i (s_ar_src_data), + .src_valid_i (src_axi4.arvalid && s_src_accept && !s_read_pending_q), + .src_ready_o (s_ar_src_ready_i), + .dst_clk_i (dst_clk_i), + .dst_rst_n_i (dst_rst_n_i), + .dst_clear_busy_o(s_ar_dst_clear_busy), + .dst_data_o (s_ar_dst_data), + .dst_valid_o (dst_axi4.arvalid), + .dst_ready_i (dst_axi4.arready) + ); + + assign s_r_dst_data = { + dst_axi4.rid, dst_axi4.rdata, dst_axi4.rresp, dst_axi4.rlast, dst_axi4.ruser + }; + cdc_fifo_warm_flush #( + .DATA_WIDTH (RWidth), + .BUFFER_DEPTH(FifoDepth) + ) u_r_fifo ( + .src_clk_i (dst_clk_i), + .src_rst_n_i (dst_rst_n_i), + .src_clear_i (s_clear_dst_pulse), + .src_clear_busy_o(s_r_dst_clear_busy), + .src_data_i (s_r_dst_data), + .src_valid_i (dst_axi4.rvalid), + .src_ready_o (dst_axi4.rready), + .dst_clk_i (src_clk_i), + .dst_rst_n_i (s_src_link_rst_n), + .dst_clear_busy_o(s_r_src_clear_busy), + .dst_data_o (s_r_src_data), + .dst_valid_o (s_r_src_valid), + .dst_ready_i (s_r_take) + ); + + assign clear_busy_o = s_aw_src_clear_busy || s_w_src_clear_busy || + s_b_src_clear_busy || s_ar_src_clear_busy || + s_r_src_clear_busy || s_dst_busy_src; + assign s_epoch_d = ((s_clear_src_pulse && !clear_busy_o) || s_dst_reset_evt) ? + s_epoch_q + 1'b1 : s_epoch_q; + assign epoch_o = s_epoch_q; + + always_ff @(posedge src_clk_i or negedge src_rst_n_i) begin + if (!src_rst_n_i) begin + s_clear_q <= 1'b0; + s_dst_rst_n_src_q <= 1'b0; + s_epoch_q <= '0; + s_write_pending_q <= 1'b0; + s_read_pending_q <= 1'b0; + s_abort_write_q <= 1'b0; + s_abort_read_q <= 1'b0; + s_write_id_q <= '0; + s_read_id_q <= '0; + s_write_left_q <= '0; + s_read_left_q <= '0; + s_b_hold_q <= '0; + s_r_hold_q <= '0; + s_b_hold_valid_q <= 1'b0; + s_r_hold_valid_q <= 1'b0; + s_clear_wait_reset_q <= 1'b0; + end else begin + s_clear_q <= s_clear_src; + s_dst_rst_n_src_q <= s_link_src_rst_n; + s_epoch_q <= s_epoch_d; + s_write_pending_q <= s_write_pending_d; + s_read_pending_q <= s_read_pending_d; + s_abort_write_q <= s_abort_write_d; + s_abort_read_q <= s_abort_read_d; + s_write_id_q <= s_write_id_d; + s_read_id_q <= s_read_id_d; + s_write_left_q <= s_write_left_d; + s_read_left_q <= s_read_left_d; + s_b_hold_q <= s_b_hold_d; + s_r_hold_q <= s_r_hold_d; + s_b_hold_valid_q <= s_b_hold_valid_d; + s_r_hold_valid_q <= s_r_hold_valid_d; + s_clear_wait_reset_q <= s_clear_wait_reset_d; + end + end + + always_ff @(posedge dst_clk_i or negedge dst_rst_n_i) begin + if (!dst_rst_n_i) begin + s_clear_dst_q <= 1'b0; + end else begin + s_clear_dst_q <= s_clear_dst; + end + end + +`ifndef SYNTHESIS + initial begin + if ((AddrWidth < 1) || (DataWidth < 8) || ((DataWidth % 8) != 0) || + (IdWidth < 1) || (UserWidth < 1) || (FifoDepth < 2) || + ((FifoDepth & (FifoDepth - 1)) != 0)) begin + $fatal(1, "axi4_async_bridge: invalid interface or FIFO parameters"); + end + end +`endif +endmodule diff --git a/rtl/tiny/top/tiny_axi4_fabric.sv b/rtl/tiny/top/tiny_axi4_fabric.sv index a4473d48..2446037f 100644 --- a/rtl/tiny/top/tiny_axi4_fabric.sv +++ b/rtl/tiny/top/tiny_axi4_fabric.sv @@ -1,297 +1,279 @@ // Copyright (c) 2026 Yuchi Miao // SPDX-License-Identifier: MulanPSL-2.0 - `include "mmap_define.svh" +`include "tiny_topology.svh" -// One accepted transaction globally. AW and W remain independent: W is -// backpressured until the saved AW has reached its target. The owner and all -// address attributes remain registered until the terminal response handshake. +// Three active owners and topology-selected independent targets. CPU admits one +// combined operation; DMA and SDIO each admit one read and one write. IDs are +// unchanged: saved target ownership, not ID expansion, routes all responses. module tiny_axi4_fabric ( - input logic clk_i, - input logic rst_n_i, - input logic perf_enable_i, - input logic perf_clear_i, - axi4_if.slave masters [2], - axi4_if.master targets [5], - output logic fault_valid_o, - output logic [31:0] fault_addr_o, - output logic fault_master_o, - output logic fault_write_o, - output logic [ 1:0] fault_resp_o, - output logic [63:0] cpu_wait_o, - output logic [63:0] dma_wait_o + input logic clk_i, + input logic rst_n_i, + input logic perf_enable_i, + input logic perf_clear_i, + input logic target_rst_n_i[`RETROSOC_TINY__TARGET_COUNT], + axi4_if.slave masters[3], + axi4_if.master targets[`RETROSOC_TINY__TARGET_COUNT], + output logic [5:0] fault_valid_o, + output logic [5:0][31:0] fault_addr_o, + output logic [5:0][1:0] fault_resp_o, + output logic [5:0] fault_instruction_o, + output logic [2:0] owner_idle_o, + output logic [`RETROSOC_TINY__TARGET_COUNT-1:0] target_idle_o, + output logic [63:0] cpu_wait_o, + output logic [63:0] dma_wait_o ); - typedef enum logic [1:0] { - Idle, - Addr, - Response - } state_e; - typedef struct packed { - logic [31:0] addr; - logic [7:0] len; - logic [2:0] size; - logic [1:0] burst; - logic lock; - logic [3:0] cache; - logic [2:0] prot; - logic [3:0] qos; - logic [3:0] region; - logic id; - logic user; - } addr_t; - state_e s_state_d, s_state_q; - logic [1:0] s_state_bits_q; - addr_t s_addr_d, s_addr_q; - addr_t s_read_addr [2]; - addr_t s_write_addr [2]; - addr_t s_selected_addr; - logic s_owner_d, s_owner_q; - logic s_write_d, s_write_q; - logic [2:0] s_tgt_d, s_tgt_q; - logic [3:0] s_request, s_unused_grant; - logic [ 1:0] s_selected; - logic s_selected_valid; - logic s_accept; - logic s_terminal; - logic [32:0] s_last_addr; - logic [ 2:0] s_decoded_target; - logic s_protocol_legal; - logic [1:0] s_awvalid, s_arvalid, s_wvalid, s_wlast, s_bready, s_rready; - logic [1:0][31:0] s_wdata; - logic [1:0][ 3:0] s_wstrb; - logic [1:0] s_wuser; - logic [4:0] s_target_awready, s_target_arready, s_target_wready; - logic [4:0] s_target_bvalid, s_target_rvalid, s_target_rlast; - logic [4:0] s_target_bid, s_target_rid, s_target_buser, s_target_ruser; - logic [4:0][1:0] s_target_bresp, s_target_rresp; - logic [4:0][31:0] s_target_rdata; + localparam logic [4:0] DecodeErrorTarget = 5'(`RETROSOC_TINY__DECODE_ERROR); + localparam logic [4:0] ProtocolErrorTarget = 5'(`RETROSOC_TINY__PROTOCOL_ERROR); + logic [5:0] s_busy_d, s_busy_q, s_admit, s_done, s_eligible; + logic s_cpu_prefer_write_d, s_cpu_prefer_write_q; + logic [2:0][4:0] s_read_target, s_write_target; + logic [`RETROSOC_TINY__TARGET_COUNT-1:0][2:0] s_arready, s_awready, s_wready, s_rvalid, s_bvalid; + logic [`RETROSOC_TINY__TARGET_COUNT-1:0][ 2:0][36:0] s_rdata; + logic [`RETROSOC_TINY__TARGET_COUNT-1:0][ 2:0][ 3:0] s_bdata; + logic [ 2:0][36:0] s_read_return; + logic [ 2:0][ 3:0] s_write_return; + logic [2:0] s_master_arready, s_master_awready, s_master_wready, s_master_rvalid, s_master_bvalid; + logic [2:0] s_arvalid, s_awvalid; logic [1:0][63:0] s_wait_d, s_wait_q; + logic [`RETROSOC_TINY__TARGET_COUNT-1:0] s_fault_valid, s_fault_write, s_fault_instruction; + logic [`RETROSOC_TINY__TARGET_COUNT-1:0][31:0] s_fault_addr; + logic [`RETROSOC_TINY__TARGET_COUNT-1:0][1:0] s_fault_master, s_fault_resp; - function automatic logic [2:0] decode_target(input logic [31:0] addr_i); - if (`SOC_ADDR_IS_SRAM(addr_i)) return 3'd0; - if (`SOC_ADDR_IS_FLASH(addr_i) || `SOC_ADDR_IS_XPI(addr_i)) return 3'd1; - if (`SOC_ADDR_IS_APB4_PERIPH(addr_i) || `SOC_ADDR_IS_APB4_SYSTEM(addr_i)) return 3'd2; - return 3'd3; + function automatic logic [4:0] decode_target(input logic [31:0] addr_i); + if (`SOC_ADDR_IS_SRAM(addr_i)) return {3'd0, addr_i[16:15]}; + if (`SOC_ADDR_IS_FLASH(addr_i) || `SOC_ADDR_IS_XPI(addr_i)) return 5'd4; + `include "tiny_fabric_decode.svh" + return DecodeErrorTarget; + endfunction + function automatic logic [4:0] checked_target(input logic [31:0] addr_i, input logic [7:0] len_i, + input logic [2:0] size_i, input logic [1:0] burst_i, + input logic lock_i, input logic instruction_i); + logic [32:0] last_addr; + logic [ 4:0] decoded; + decoded = decode_target(addr_i); + last_addr = {1'b0, addr_i} + (({25'd0, len_i} + 33'd1) << size_i) - 33'd1; + if (lock_i || (size_i > 3'd2) || (len_i > 8'd15) || + ((addr_i & ((32'd1 << size_i) - 32'd1)) != 32'd0) || + !((burst_i == 2'b01) || ((burst_i == 2'b00) && (len_i == 8'd0))) || + last_addr[32] || (last_addr[31:12] != addr_i[31:12]) || + (decode_target( + last_addr[31:0] + ) != decoded) || ((decoded >= 5'd5) && (decoded < DecodeErrorTarget) && (len_i != 8'd0))) + return ProtocolErrorTarget; + if (instruction_i && !( + `SOC_ADDR_IS_FLASH(addr_i) + || ((addr_i >= 32'h5000_0000) && (addr_i < 32'h5400_0000)))) begin + if ((decoded >= 5'd5) && (decoded < DecodeErrorTarget)) return ProtocolErrorTarget; + return DecodeErrorTarget; + end + return decoded; endfunction - assign s_state_q = state_e'(s_state_bits_q); - assign s_request = {s_awvalid[1], s_arvalid[1], s_awvalid[0], s_arvalid[0]}; - assign s_selected_addr = s_selected[0] ? s_write_addr[s_selected[1]] : s_read_addr[s_selected[1]]; - assign s_decoded_target = decode_target(s_selected_addr.addr); - assign s_last_addr = {1'b0, s_selected_addr.addr} + - (({25'd0, s_selected_addr.len} + 33'd1) << s_selected_addr.size) - 33'd1; - assign s_protocol_legal = !s_selected_addr.lock && (s_selected_addr.size <= 3'd2) && - ((s_selected_addr.addr & ((32'd1 << s_selected_addr.size) - 32'd1)) == 32'd0) && - ((s_selected_addr.burst == 2'b01) || - ((s_selected_addr.burst == 2'b00) && (s_selected_addr.len == 8'd0))) && - (s_selected_addr.len <= 8'd15) && !s_last_addr[32] && - (s_last_addr[31:12] == s_selected_addr.addr[31:12]) && - (decode_target( - s_last_addr[31:0] - ) == s_decoded_target) && ((s_decoded_target != 3'd2) || (s_selected_addr.len == 8'd0)); - assign s_accept = (s_state_q == Idle) && s_selected_valid; - assign s_terminal = (s_state_q == Response) && - (s_write_q ? (s_target_bvalid[s_tgt_q] && s_bready[s_owner_q]) : - (s_target_rvalid[s_tgt_q] && s_rready[s_owner_q] && s_target_rlast[s_tgt_q])); - assign fault_resp_o = s_write_q ? s_target_bresp[s_tgt_q] : s_target_rresp[s_tgt_q]; - assign fault_valid_o = s_terminal && (fault_resp_o != 2'b00); - assign fault_addr_o = s_addr_q.addr; - assign fault_master_o = s_owner_q; - assign fault_write_o = s_write_q; - assign cpu_wait_o = s_wait_q[0]; - assign dma_wait_o = s_wait_q[1]; - - // Advance on acceptance: the chosen owner is saved until completion, so the - // next grant after completion starts after this source/direction. - round_robin_arbiter #( - .CLIENTS(4) - ) u_arbiter ( - .clk_i (clk_i), - .rst_n_i (rst_n_i), - .advance_i (s_accept), - .request_i (s_request), - .grant_o (s_unused_grant), - .selected_o(s_selected), - .valid_o (s_selected_valid) - ); - - always_comb begin - s_state_d = s_state_q; - s_owner_d = s_owner_q; - s_write_d = s_write_q; - s_addr_d = s_addr_q; - s_tgt_d = s_tgt_q; - unique case (s_state_q) - Idle: - if (s_accept) begin - s_owner_d = s_selected[1]; - s_write_d = s_selected[0]; - s_addr_d = s_selected_addr; - s_tgt_d = s_protocol_legal ? s_decoded_target : 3'd4; - s_state_d = Addr; - end - Addr: - if (s_write_q ? s_target_awready[s_tgt_q] : s_target_arready[s_tgt_q]) s_state_d = Response; - Response: if (s_terminal) s_state_d = Idle; - default: s_state_d = Idle; - endcase - s_wait_d = s_wait_q; - for (int unsigned master = 0; master < 2; master++) begin - if (perf_clear_i) s_wait_d[master] = '0; - else if (perf_enable_i && (s_awvalid[master] || s_arvalid[master]) && - !(s_accept && (s_selected[1] == 1'(master))) && - (s_wait_q[master] != 64'hffff_ffff_ffff_ffff)) - s_wait_d[master] = s_wait_q[master] + 64'd1; - end - end - for (genvar master = 0; master < 2; master++) begin : gen_master - assign s_write_addr[master] = { - masters[master].awaddr, - masters[master].awlen, - masters[master].awsize, - masters[master].awburst, - masters[master].awlock, - masters[master].awcache, - masters[master].awprot, - masters[master].awqos, - masters[master].awregion, - masters[master].awid, - masters[master].awuser - }; - assign s_read_addr[master] = { - masters[master].araddr, - masters[master].arlen, - masters[master].arsize, - masters[master].arburst, - masters[master].arlock, - masters[master].arcache, - masters[master].arprot, - masters[master].arqos, - masters[master].arregion, - masters[master].arid, - masters[master].aruser - }; - assign s_awvalid[master] = masters[master].awvalid; - assign s_arvalid[master] = masters[master].arvalid; - assign s_wdata[master] = masters[master].wdata; - assign s_wstrb[master] = masters[master].wstrb; - assign s_wlast[master] = masters[master].wlast; - assign s_wuser[master] = masters[master].wuser; - assign s_wvalid[master] = masters[master].wvalid; - assign s_bready[master] = masters[master].bready; - assign s_rready[master] = masters[master].rready; - assign masters[master].awready = s_accept && (s_selected == {1'(master), 1'b1}); - assign masters[master].arready = s_accept && (s_selected == {1'(master), 1'b0}); - assign masters[master].wready = (s_state_q == Response) && s_write_q && - (s_owner_q == 1'(master)) && s_target_wready[s_tgt_q]; - assign masters[master].bvalid = (s_state_q == Response) && s_write_q && - (s_owner_q == 1'(master)) && s_target_bvalid[s_tgt_q]; - assign masters[master].rvalid = (s_state_q == Response) && !s_write_q && - (s_owner_q == 1'(master)) && s_target_rvalid[s_tgt_q]; - assign masters[master].bid = s_target_bid[s_tgt_q]; - assign masters[master].bresp = s_target_bresp[s_tgt_q]; - assign masters[master].buser = s_target_buser[s_tgt_q]; - assign masters[master].rid = s_target_rid[s_tgt_q]; - assign masters[master].rdata = s_target_rdata[s_tgt_q]; - assign masters[master].rresp = s_target_rresp[s_tgt_q]; - assign masters[master].rlast = s_target_rlast[s_tgt_q]; - assign masters[master].ruser = s_target_ruser[s_tgt_q]; - end - for (genvar target = 0; target < 5; target++) begin : gen_target - assign targets[target].awaddr = s_addr_q.addr; - assign targets[target].awlen = s_addr_q.len; - assign targets[target].awsize = s_addr_q.size; - assign targets[target].awburst = s_addr_q.burst; - assign targets[target].awlock = s_addr_q.lock; - assign targets[target].awcache = s_addr_q.cache; - assign targets[target].awprot = s_addr_q.prot; - assign targets[target].awqos = s_addr_q.qos; - assign targets[target].awregion = s_addr_q.region; - assign targets[target].awid = s_addr_q.id; - assign targets[target].awuser = s_addr_q.user; - assign targets[target].awvalid = (s_state_q == Addr) && s_write_q && (s_tgt_q == 3'(target)); - assign targets[target].araddr = s_addr_q.addr; - assign targets[target].arlen = s_addr_q.len; - assign targets[target].arsize = s_addr_q.size; - assign targets[target].arburst = s_addr_q.burst; - assign targets[target].arlock = s_addr_q.lock; - assign targets[target].arcache = s_addr_q.cache; - assign targets[target].arprot = s_addr_q.prot; - assign targets[target].arqos = s_addr_q.qos; - assign targets[target].arregion = s_addr_q.region; - assign targets[target].arid = s_addr_q.id; - assign targets[target].aruser = s_addr_q.user; - assign targets[target].arvalid = (s_state_q == Addr) && !s_write_q && (s_tgt_q == 3'(target)); - assign targets[target].wdata = s_wdata[s_owner_q]; - assign targets[target].wstrb = s_wstrb[s_owner_q]; - assign targets[target].wlast = s_wlast[s_owner_q]; - assign targets[target].wuser = s_wuser[s_owner_q]; - assign targets[target].wvalid = (s_state_q == Response) && s_write_q && - (s_tgt_q == 3'(target)) && s_wvalid[s_owner_q]; - assign targets[target].bready = (s_state_q == Response) && s_write_q && - (s_tgt_q == 3'(target)) && s_bready[s_owner_q]; - assign targets[target].rready = (s_state_q == Response) && !s_write_q && - (s_tgt_q == 3'(target)) && s_rready[s_owner_q]; - assign s_target_awready[target] = targets[target].awready; - assign s_target_arready[target] = targets[target].arready; - assign s_target_wready[target] = targets[target].wready; - assign s_target_bvalid[target] = targets[target].bvalid; - assign s_target_bid[target] = targets[target].bid; - assign s_target_bresp[target] = targets[target].bresp; - assign s_target_buser[target] = targets[target].buser; - assign s_target_rvalid[target] = targets[target].rvalid; - assign s_target_rid[target] = targets[target].rid; - assign s_target_rresp[target] = targets[target].rresp; - assign s_target_rdata[target] = targets[target].rdata; - assign s_target_rlast[target] = targets[target].rlast; - assign s_target_ruser[target] = targets[target].ruser; - end + assign s_busy_d = (s_busy_q & ~s_done) | s_admit; + assign s_cpu_prefer_write_d = s_admit[0] ? 1'b1 : s_admit[1] ? 1'b0 : s_cpu_prefer_write_q; + assign cpu_wait_o = s_wait_q[0]; + assign dma_wait_o = s_wait_q[1]; dffr #( - .DATA_WIDTH(2) - ) u_state_reg ( + .DATA_WIDTH(6) + ) u_direction_busy ( .clk_i (clk_i), .rst_n_i(rst_n_i), - .dat_i (s_state_d), - .dat_o (s_state_bits_q) + .dat_i (s_busy_d), + .dat_o (s_busy_q) ); dffr #( .DATA_WIDTH(1) - ) u_owner_reg ( + ) u_cpu_direction ( .clk_i (clk_i), .rst_n_i(rst_n_i), - .dat_i (s_owner_d), - .dat_o (s_owner_q) - ); - dffr #( - .DATA_WIDTH(1) - ) u_write_reg ( - .clk_i (clk_i), - .rst_n_i(rst_n_i), - .dat_i (s_write_d), - .dat_o (s_write_q) - ); - dffr #( - .DATA_WIDTH(63) - ) u_addr_reg ( - .clk_i (clk_i), - .rst_n_i(rst_n_i), - .dat_i (s_addr_d), - .dat_o (s_addr_q) - ); - dffr #( - .DATA_WIDTH(3) - ) u_target_reg ( - .clk_i (clk_i), - .rst_n_i(rst_n_i), - .dat_i (s_tgt_d), - .dat_o (s_tgt_q) + .dat_i (s_cpu_prefer_write_d), + .dat_o (s_cpu_prefer_write_q) ); dffr #( .DATA_WIDTH(128) - ) u_wait_reg ( + ) u_wait_counters ( .clk_i (clk_i), .rst_n_i(rst_n_i), .dat_i (s_wait_d), .dat_o (s_wait_q) ); + + always_comb begin + s_master_arready = '0; + s_master_awready = '0; + s_master_wready = '0; + s_master_rvalid = '0; + s_master_bvalid = '0; + s_read_return = '0; + s_write_return = '0; + fault_valid_o = '0; + fault_addr_o = '0; + fault_resp_o = '0; + fault_instruction_o = '0; + for (int target_index = 0; target_index < `RETROSOC_TINY__TARGET_COUNT; target_index++) begin + s_master_arready |= s_arready[target_index]; + s_master_awready |= s_awready[target_index]; + s_master_wready |= s_wready[target_index]; + s_master_rvalid |= s_rvalid[target_index]; + s_master_bvalid |= s_bvalid[target_index]; + for (int owner = 0; owner < 3; owner++) begin + if (s_rvalid[target_index][owner]) s_read_return[owner] = s_rdata[target_index][owner]; + if (s_bvalid[target_index][owner]) s_write_return[owner] = s_bdata[target_index][owner]; + end + if (s_fault_valid[target_index]) begin + fault_valid_o[{s_fault_master[target_index], s_fault_write[target_index]}] = 1'b1; + fault_addr_o[{ + s_fault_master[target_index], s_fault_write[target_index] + }] = s_fault_addr[target_index]; + fault_resp_o[{ + s_fault_master[target_index], s_fault_write[target_index] + }] = s_fault_resp[target_index]; + fault_instruction_o[{ + s_fault_master[target_index], s_fault_write[target_index] + }] = s_fault_instruction[target_index]; + end + end + s_wait_d = s_wait_q; + for (int owner = 0; owner < 2; owner++) begin + if (perf_clear_i) s_wait_d[owner] = '0; + else if (perf_enable_i && (s_arvalid[owner] || s_awvalid[owner]) && + !(s_admit[2*owner] || s_admit[2*owner+1]) && (s_wait_q[owner] != 64'hffff_ffff_ffff_ffff)) + s_wait_d[owner] = s_wait_q[owner] + 64'd1; + end + end + for (genvar owner = 0; owner < 3; owner++) begin : gen_owner + assign s_arvalid[owner] = masters[owner].arvalid; + assign s_awvalid[owner] = masters[owner].awvalid; + assign owner_idle_o[owner] = !(s_busy_q[2*owner] || s_busy_q[2*owner+1]); + assign s_read_target[owner] = checked_target( + masters[owner].araddr, + masters[owner].arlen, + masters[owner].arsize, + masters[owner].arburst, + masters[owner].arlock, + (owner == 0) && masters[owner].arprot[2] + ); + assign s_write_target[owner] = checked_target( + masters[owner].awaddr, + masters[owner].awlen, + masters[owner].awsize, + masters[owner].awburst, + masters[owner].awlock, + 1'b0 + ); + if (owner == 0) begin : gen_combined_credit + assign s_eligible[2*owner] = rst_n_i && owner_idle_o[owner] && masters[owner].arvalid && + (!masters[owner].awvalid || !s_cpu_prefer_write_q); + assign s_eligible[2*owner+1] = rst_n_i && owner_idle_o[owner] && masters[owner].awvalid && + (!masters[owner].arvalid || s_cpu_prefer_write_q); + end else begin : gen_direction_credits + assign s_eligible[2*owner] = rst_n_i && !s_busy_q[2*owner] && masters[owner].arvalid; + assign s_eligible[2*owner+1] = rst_n_i && !s_busy_q[2*owner+1] && masters[owner].awvalid; + end + assign s_admit[2*owner] = masters[owner].arvalid && masters[owner].arready; + assign s_admit[2*owner+1] = masters[owner].awvalid && masters[owner].awready; + assign s_done[2*owner] = masters[owner].rvalid && masters[owner].rready && masters[owner].rlast; + assign s_done[2*owner+1] = masters[owner].bvalid && masters[owner].bready; + assign masters[owner].arready = s_master_arready[owner]; + assign masters[owner].awready = s_master_awready[owner]; + assign masters[owner].wready = s_master_wready[owner]; + assign masters[owner].rvalid = s_master_rvalid[owner]; + assign masters[owner].bvalid = s_master_bvalid[owner]; + assign {masters[owner].rid, masters[owner].rdata, masters[owner].rresp, + masters[owner].rlast, masters[owner].ruser} = s_read_return[owner]; + assign {masters[owner].bid, masters[owner].bresp, masters[owner].buser} = s_write_return[owner]; + end + for ( + genvar target_index = 0; target_index < `RETROSOC_TINY__TARGET_COUNT; target_index++ + ) begin : gen_target + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) u_routes[3] ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + for (genvar owner = 0; owner < 3; owner++) begin : gen_route + logic s_read_selected, s_write_selected; + assign s_read_selected = s_read_target[owner] == 5'(target_index); + assign s_write_selected = s_write_target[owner] == 5'(target_index); + assign u_routes[owner].arid = masters[owner].arid; + assign u_routes[owner].araddr = masters[owner].araddr; + assign u_routes[owner].arlen = masters[owner].arlen; + assign u_routes[owner].arsize = masters[owner].arsize; + assign u_routes[owner].arburst = masters[owner].arburst; + assign u_routes[owner].arlock = masters[owner].arlock; + assign u_routes[owner].arcache = masters[owner].arcache; + assign u_routes[owner].arprot = masters[owner].arprot; + assign u_routes[owner].arqos = masters[owner].arqos; + assign u_routes[owner].arregion = masters[owner].arregion; + assign u_routes[owner].aruser = masters[owner].aruser; + assign u_routes[owner].awid = masters[owner].awid; + assign u_routes[owner].awaddr = masters[owner].awaddr; + assign u_routes[owner].awlen = masters[owner].awlen; + assign u_routes[owner].awsize = masters[owner].awsize; + assign u_routes[owner].awburst = masters[owner].awburst; + assign u_routes[owner].awlock = masters[owner].awlock; + assign u_routes[owner].awcache = masters[owner].awcache; + assign u_routes[owner].awprot = masters[owner].awprot; + assign u_routes[owner].awqos = masters[owner].awqos; + assign u_routes[owner].awregion = masters[owner].awregion; + assign u_routes[owner].awuser = masters[owner].awuser; + assign u_routes[owner].arvalid = s_eligible[2*owner] && s_read_selected; + assign u_routes[owner].awvalid = s_eligible[2*owner+1] && s_write_selected; + assign u_routes[owner].wdata = masters[owner].wdata; + assign u_routes[owner].wstrb = masters[owner].wstrb; + assign u_routes[owner].wlast = masters[owner].wlast; + assign u_routes[owner].wuser = masters[owner].wuser; + assign u_routes[owner].wvalid = masters[owner].wvalid; + assign u_routes[owner].rready = masters[owner].rready; + assign u_routes[owner].bready = masters[owner].bready; + assign s_arready[target_index][owner] = u_routes[owner].arready; + assign s_awready[target_index][owner] = u_routes[owner].awready; + assign s_wready[target_index][owner] = u_routes[owner].wready; + assign s_rvalid[target_index][owner] = u_routes[owner].rvalid; + assign s_bvalid[target_index][owner] = u_routes[owner].bvalid; + assign s_rdata[target_index][owner] = { + u_routes[owner].rid, + u_routes[owner].rdata, + u_routes[owner].rresp, + u_routes[owner].rlast, + u_routes[owner].ruser + }; + assign s_bdata[target_index][owner] = { + u_routes[owner].bid, u_routes[owner].bresp, u_routes[owner].buser + }; + end + tiny_axi4_target u_controller ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .enable_i (target_rst_n_i[target_index]), + .masters (u_routes), + .target (targets[target_index]), + .idle_o (target_idle_o[target_index]), + .fault_valid_o (s_fault_valid[target_index]), + .fault_addr_o (s_fault_addr[target_index]), + .fault_master_o (s_fault_master[target_index]), + .fault_write_o (s_fault_write[target_index]), + .fault_instruction_o(s_fault_instruction[target_index]), + .fault_resp_o (s_fault_resp[target_index]) + ); + end +`ifdef HAVE_SVA + assert property (@(posedge clk_i) disable iff (!rst_n_i) !(s_busy_q[0] && s_busy_q[1])); + assert property (@(posedge clk_i) disable iff (!rst_n_i) (s_admit & s_busy_q) == 6'd0); + assert property (@(posedge clk_i) disable iff (!rst_n_i) (s_done & ~s_busy_q) == 6'd0); + for (genvar owner = 0; owner < 3; owner++) begin : gen_route_assert + logic [`RETROSOC_TINY__TARGET_COUNT-1:0] s_reads, s_writes; + for (genvar t = 0; t < `RETROSOC_TINY__TARGET_COUNT; t++) begin : gen_column + assign s_reads[t] = s_rvalid[t][owner]; + assign s_writes[t] = s_bvalid[t][owner]; + end + assert property (@(posedge clk_i) disable iff (!rst_n_i) $onehot0(s_reads)); + assert property (@(posedge clk_i) disable iff (!rst_n_i) $onehot0(s_writes)); + end +`endif endmodule diff --git a/rtl/tiny/top/tiny_axi4_target.sv b/rtl/tiny/top/tiny_axi4_target.sv new file mode 100644 index 00000000..d53b1051 --- /dev/null +++ b/rtl/tiny/top/tiny_axi4_target.sv @@ -0,0 +1,385 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// A single target owns one transaction through the upstream terminal handshake. +// Address capture reserves ownership even while the downstream target is stalled. +// Per-owner direction preference and owner round robin advance on admission. +module tiny_axi4_target ( + input logic clk_i, + input logic rst_n_i, + input logic enable_i, + axi4_if.slave masters [3], + axi4_if.master target, + output logic idle_o, + output logic fault_valid_o, + output logic [31:0] fault_addr_o, + output logic [ 1:0] fault_master_o, + output logic fault_write_o, + output logic fault_instruction_o, + output logic [ 1:0] fault_resp_o +); + typedef enum logic [2:0] { + Idle, + Address, + Response, + AbortWrite, + AbortResponse + } state_e; + typedef struct packed { + logic [31:0] addr; + logic [7:0] len; + logic [2:0] size; + logic [1:0] burst; + logic lock; + logic [3:0] cache; + logic [2:0] prot; + logic [3:0] qos; + logic [3:0] region; + logic id; + logic user; + } addr_t; + typedef struct packed { + state_e state; + logic [1:0] owner; + logic write; + addr_t addr; + logic [7:0] write_beat; + logic write_done; + logic [8:0] read_count; + logic b_captured; + logic [31:0] read_addr, error_addr; + logic [1:0] error_resp; + logic error_seen; + } request_t; + request_t s_req_d, s_req_q; + addr_t s_saved_addr, s_selected_addr; + logic [31:0] s_req_addr; + logic s_req_id; + addr_t s_read_addr[3], s_write_addr[3]; + logic [2:0] s_arvalid, s_awvalid, s_wvalid, s_wlast, s_bready, s_rready, s_wuser; + logic [2:0][31:0] s_wdata; + logic [2:0][ 3:0] s_wstrb; + logic [2:0] s_owner_req, s_grant, s_prefer_write_d, s_prefer_write_q; + logic [1:0] s_selected; + logic s_valid, s_selected_write, s_accept, s_terminal; + logic s_r_valid, s_r_ready, s_b_valid, s_b_ready; + logic [36:0] s_r_data; + logic [ 3:0] s_b_data; + logic s_read_capture, s_write_capture; + logic s_abort_read_valid, s_abort_write_valid; + logic s_resp_active, s_rin_valid, s_bin_valid; + logic [36:0] s_rin_data; + logic [ 3:0] s_bin_data; + + assign s_saved_addr = s_req_q.addr; + assign s_req_addr = s_saved_addr.addr; + assign s_req_id = s_saved_addr.id; + + assign s_selected_write = s_awvalid[s_selected] && + (s_prefer_write_q[s_selected] || !s_arvalid[s_selected]); + assign s_selected_addr = s_selected_write ? s_write_addr[s_selected] : s_read_addr[s_selected]; + assign s_accept = (s_req_q.state == Idle) && enable_i && s_valid; + assign idle_o = (s_req_q.state == Idle) && !s_r_valid && !s_b_valid; + // Target reset cancels downstream work, never an already captured upstream + // response. Append missing error beats through the same spill registers. + assign s_resp_active = (s_req_q.state == Response) || (s_req_q.state == AbortResponse); + assign s_abort_read_valid = (s_req_q.state == AbortResponse) && !s_req_q.write && + (s_req_q.read_count <= {1'b0, s_saved_addr.len}); + assign s_abort_write_valid = (s_req_q.state == AbortResponse) && s_req_q.write && + s_req_q.write_done && !s_req_q.b_captured; + assign s_terminal = s_resp_active && + (s_req_q.write ? (s_b_valid && s_bready[s_req_q.owner]) : + (s_r_valid && s_rready[s_req_q.owner] && s_r_data[1])); + assign fault_valid_o = s_terminal && s_req_q.error_seen; + assign fault_addr_o = s_req_q.error_addr; + assign fault_master_o = s_req_q.owner; + assign fault_write_o = s_req_q.write; + assign fault_instruction_o = !s_req_q.write && s_saved_addr.prot[2]; + assign fault_resp_o = s_req_q.error_resp; + assign s_rin_valid = ((s_req_q.state == Response) && enable_i && + !s_req_q.write && target.rvalid) || s_abort_read_valid; + assign s_rin_data = s_abort_read_valid ? + {s_req_id, 32'd0, 2'b11, + (s_req_q.read_count == {1'b0, s_saved_addr.len}), 1'b0} : + {target.rid, target.rdata, target.rresp, target.rlast, target.ruser}; + assign s_bin_valid = ((s_req_q.state == Response) && enable_i && + s_req_q.write && target.bvalid) || s_abort_write_valid; + assign s_bin_data = s_abort_write_valid ? {s_req_id, 2'b11, 1'b0} : + {target.bid, target.bresp, target.buser}; + assign s_read_capture = s_rin_valid && s_r_ready; + assign s_write_capture = s_bin_valid && s_b_ready; + + round_robin_arbiter #( + .CLIENTS(3) + ) u_owner_arbiter ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .advance_i (s_accept), + .request_i (s_owner_req), + .grant_o (s_grant), + .selected_o(s_selected), + .valid_o (s_valid) + ); + always_comb begin + s_prefer_write_d = s_prefer_write_q; + if (s_accept) s_prefer_write_d[s_selected] = !s_selected_write; + end + dffr #( + .DATA_WIDTH(3) + ) u_direction_preference ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_prefer_write_d), + .dat_o (s_prefer_write_q) + ); + + for (genvar owner = 0; owner < 3; owner++) begin : gen_owner + assign s_owner_req[owner] = s_arvalid[owner] || s_awvalid[owner]; + assign s_arvalid[owner] = masters[owner].arvalid; + assign s_awvalid[owner] = masters[owner].awvalid; + assign s_read_addr[owner] = { + masters[owner].araddr, + masters[owner].arlen, + masters[owner].arsize, + masters[owner].arburst, + masters[owner].arlock, + masters[owner].arcache, + masters[owner].arprot, + masters[owner].arqos, + masters[owner].arregion, + masters[owner].arid, + masters[owner].aruser + }; + assign s_write_addr[owner] = { + masters[owner].awaddr, + masters[owner].awlen, + masters[owner].awsize, + masters[owner].awburst, + masters[owner].awlock, + masters[owner].awcache, + masters[owner].awprot, + masters[owner].awqos, + masters[owner].awregion, + masters[owner].awid, + masters[owner].awuser + }; + assign s_wvalid[owner] = masters[owner].wvalid; + assign s_wdata[owner] = masters[owner].wdata; + assign s_wstrb[owner] = masters[owner].wstrb; + assign s_wlast[owner] = masters[owner].wlast; + assign s_wuser[owner] = masters[owner].wuser; + assign s_rready[owner] = masters[owner].rready; + assign s_bready[owner] = masters[owner].bready; + assign masters[owner].arready = s_accept && s_grant[owner] && !s_selected_write; + assign masters[owner].awready = s_accept && s_grant[owner] && s_selected_write; + assign masters[owner].wready = ((s_req_q.state == Response) && enable_i && s_req_q.write && + !s_req_q.write_done && (s_req_q.owner == 2'(owner)) && target.wready) || + ((s_req_q.state == AbortWrite) && !s_req_q.write_done && + (s_req_q.owner == 2'(owner))); + assign masters[owner].rvalid = s_resp_active && !s_req_q.write && + (s_req_q.owner == 2'(owner)) && s_r_valid; + assign {masters[owner].rid, masters[owner].rdata, masters[owner].rresp, + masters[owner].rlast, masters[owner].ruser} = s_r_data; + assign masters[owner].bvalid = s_resp_active && s_req_q.write && + (s_req_q.owner == 2'(owner)) && s_b_valid; + assign {masters[owner].bid, masters[owner].bresp, masters[owner].buser} = s_b_data; + end + + assign target.awaddr = s_saved_addr.addr; + assign target.awlen = s_saved_addr.len; + assign target.awsize = s_saved_addr.size; + assign target.awburst = s_saved_addr.burst; + assign target.awlock = s_saved_addr.lock; + assign target.awcache = s_saved_addr.cache; + assign target.awprot = s_saved_addr.prot; + assign target.awqos = s_saved_addr.qos; + assign target.awregion = s_saved_addr.region; + assign target.awid = s_saved_addr.id; + assign target.awuser = s_saved_addr.user; + assign target.araddr = s_saved_addr.addr; + assign target.arlen = s_saved_addr.len; + assign target.arsize = s_saved_addr.size; + assign target.arburst = s_saved_addr.burst; + assign target.arlock = s_saved_addr.lock; + assign target.arcache = s_saved_addr.cache; + assign target.arprot = s_saved_addr.prot; + assign target.arqos = s_saved_addr.qos; + assign target.arregion = s_saved_addr.region; + assign target.arid = s_saved_addr.id; + assign target.aruser = s_saved_addr.user; + assign target.awvalid = (s_req_q.state == Address) && enable_i && s_req_q.write; + assign target.arvalid = (s_req_q.state == Address) && enable_i && !s_req_q.write; + assign target.wvalid = (s_req_q.state == Response) && enable_i && s_req_q.write && + !s_req_q.write_done && s_wvalid[s_req_q.owner]; + assign target.wdata = s_wdata[s_req_q.owner]; + assign target.wstrb = s_wstrb[s_req_q.owner]; + assign target.wlast = s_wlast[s_req_q.owner]; + assign target.wuser = s_wuser[s_req_q.owner]; + assign target.rready = (s_req_q.state == Response) && enable_i && !s_req_q.write && s_r_ready; + assign target.bready = (s_req_q.state == Response) && enable_i && s_req_q.write && s_b_ready; + spill_register #( + .DATA_WIDTH(37) + ) u_read_response ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .flush_i(1'b0), + .valid_i(s_rin_valid), + .ready_o(s_r_ready), + .data_i (s_rin_data), + .valid_o(s_r_valid), + .ready_i(s_resp_active && !s_req_q.write && s_rready[s_req_q.owner]), + .data_o (s_r_data) + ); + spill_register #( + .DATA_WIDTH(4) + ) u_write_response ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .flush_i(1'b0), + .valid_i(s_bin_valid), + .ready_o(s_b_ready), + .data_i (s_bin_data), + .valid_o(s_b_valid), + .ready_i(s_resp_active && s_req_q.write && s_bready[s_req_q.owner]), + .data_o (s_b_data) + ); + always_comb begin + s_req_d = s_req_q; + unique case (s_req_q.state) + Idle: + if (s_accept) begin + s_req_d = '0; + s_req_d.state = Address; + s_req_d.owner = s_selected; + s_req_d.write = s_selected_write; + s_req_d.addr = s_selected_addr; + s_req_d.read_addr = s_selected_addr.addr; + end + Address: begin + if (!enable_i) begin + s_req_d.state = s_req_q.write ? AbortWrite : AbortResponse; + end else if (s_req_q.write ? target.awready : target.arready) s_req_d.state = Response; + end + Response: begin + if (!enable_i) begin + s_req_d.state = (s_req_q.write && !s_req_q.write_done) ? AbortWrite : AbortResponse; + end + if (target.wvalid && target.wready) begin + s_req_d.write_beat = s_req_q.write_beat + 8'd1; + if (target.wlast || (s_req_q.write_beat == s_saved_addr.len)) s_req_d.write_done = 1'b1; + end + if (s_terminal) s_req_d.state = Idle; + end + AbortWrite: begin + if (s_wvalid[s_req_q.owner] && !s_req_q.write_done) begin + s_req_d.write_beat = s_req_q.write_beat + 8'd1; + if (s_wlast[s_req_q.owner] || (s_req_q.write_beat == s_saved_addr.len)) begin + s_req_d.write_done = 1'b1; + s_req_d.state = AbortResponse; + end + end + end + AbortResponse: if (s_terminal) s_req_d.state = Idle; + default: s_req_d.state = Idle; + endcase + if (s_read_capture) begin + s_req_d.read_count = s_req_q.read_count + 9'd1; + if (s_saved_addr.burst == 2'b01) + s_req_d.read_addr = s_req_q.read_addr + (32'd1 << s_saved_addr.size); + if (!s_req_q.error_seen && (s_rin_data[3:2] != 2'b00)) begin + s_req_d.error_seen = 1'b1; + s_req_d.error_addr = s_req_q.read_addr; + s_req_d.error_resp = s_rin_data[3:2]; + end + end + if (s_write_capture) begin + s_req_d.b_captured = 1'b1; + if (!s_req_q.error_seen && (s_bin_data[2:1] != 2'b00)) begin + s_req_d.error_seen = 1'b1; + s_req_d.error_addr = s_req_addr; + s_req_d.error_resp = s_bin_data[2:1]; + end + end + end + dffr #( + .DATA_WIDTH($bits(request_t)) + ) u_request ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_req_d), + .dat_o (s_req_q) + ); + +`ifdef FORMAL + logic s_past_valid_q = 1'b0; + always_ff @(posedge clk_i) begin + s_past_valid_q <= 1'b1; + if (rst_n_i) begin + assert ($onehot0(s_grant)); + assert (!s_accept || idle_o); + assert (!fault_valid_o || s_terminal); + if (s_req_q.state != Idle) assert (s_req_q.owner < 2'd3); + end + if (s_past_valid_q && rst_n_i && $past(rst_n_i && !s_terminal && (s_req_q.state != Idle))) begin + assert (s_req_q.owner == $past(s_req_q.owner)); + assert (s_req_q.write == $past(s_req_q.write)); + assert (s_req_q.addr == $past(s_req_q.addr)); + end + end + for (genvar owner = 0; owner < 3; owner++) begin : gen_fairness + logic [1:0] s_bypass_q; + always_ff @(posedge clk_i) begin + if (!rst_n_i || !s_owner_req[owner] || (s_accept && s_grant[owner])) s_bypass_q <= '0; + else if (s_accept) s_bypass_q <= s_bypass_q + 2'd1; + if (rst_n_i) begin + assert (s_bypass_q <= 2'd2); + assert (!(s_bypass_q == 2'd2 && s_owner_req[owner] && s_accept && !s_grant[owner])); + end + if (s_past_valid_q && rst_n_i && $past(rst_n_i)) begin + if ($past(masters[owner].rvalid && !masters[owner].rready)) begin + assert (masters[owner].rvalid); + assert ({masters[owner].rid, masters[owner].rdata, masters[owner].rresp, + masters[owner].rlast} == $past( + {masters[owner].rid, masters[owner].rdata, masters[owner].rresp, masters[owner].rlast} + )); + end + if ($past(masters[owner].bvalid && !masters[owner].bready)) begin + assert (masters[owner].bvalid); + assert ({masters[owner].bid, masters[owner].bresp} == $past( + {masters[owner].bid, masters[owner].bresp} + )); + end + end + end + end +`endif +`ifdef HAVE_SVA + assert property (@(posedge clk_i) disable iff (!rst_n_i || !enable_i) + target.wvalid && !target.wready |=> target.wvalid && + $stable( + {target.wdata, target.wstrb, target.wlast, target.wuser} + )); + assert property (@(posedge clk_i) disable iff (!rst_n_i || !enable_i) + target.arvalid && !target.arready |=> target.arvalid && + $stable( + {target.araddr, target.arlen, target.arid, target.arsize, target.arburst} + )); + assert property (@(posedge clk_i) disable iff (!rst_n_i || !enable_i) + target.awvalid && !target.awready |=> target.awvalid && + $stable( + {target.awaddr, target.awlen, target.awid, target.awsize, target.awburst} + )); + for (genvar owner = 0; owner < 3; owner++) begin : gen_response_assert + assert property (@(posedge clk_i) disable iff (!rst_n_i) + masters[owner].rvalid && !masters[owner].rready |=> masters[owner].rvalid && + $stable( + {masters[owner].rid, masters[owner].rdata, masters[owner].rresp, masters[owner].rlast} + )); + assert property (@(posedge clk_i) disable iff (!rst_n_i) + masters[owner].bvalid && !masters[owner].bready |=> masters[owner].bvalid && + $stable( + {masters[owner].bid, masters[owner].bresp} + )); + end +`endif +endmodule diff --git a/rtl/tiny/top/tiny_clock_gate.sv b/rtl/tiny/top/tiny_clock_gate.sv new file mode 100644 index 00000000..26b8eae0 --- /dev/null +++ b/rtl/tiny/top/tiny_clock_gate.sv @@ -0,0 +1,21 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 +module tiny_clock_gate ( + input logic clk_i, + input logic rst_n_i, + input logic gate_i, + output logic clk_o, + output logic gated_o +); + logic s_gate_q; + ndffr #( + .DATA_WIDTH(1) + ) u_gate ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (gate_i), + .dat_o (s_gate_q) + ); + assign clk_o = clk_i && !s_gate_q; + assign gated_o = s_gate_q; +endmodule diff --git a/rtl/tiny/top/tiny_clock_output.sv b/rtl/tiny/top/tiny_clock_output.sv new file mode 100644 index 00000000..c07321eb --- /dev/null +++ b/rtl/tiny/top/tiny_clock_output.sv @@ -0,0 +1,66 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// SYSCTRL validates the source/divisor and requires disable-before-change. +// Synchronize enable to the selected source, configuring the divider while +// disabled. Both package routes consume this one output and enable. +module tiny_clock_output ( + input logic clk_ref_i, + input logic clk_sys_i, + input logic clk_mem_i, + input logic clk_periph_i, + input logic rst_n_i, + input logic [31:0] control_i, + output logic clk_o, + output logic enable_o +); + logic s_src_clk, s_enabled, s_en_req; + logic [19:0] s_cfg_d, s_cfg_q; + // Retain the source/divisor through disable acknowledgement. Source zero + // only disables the output; it does not change a still-running clock. + assign s_cfg_d = control_i[2:0] != 0 ? + {1'b1, control_i[23:8], control_i[2:0]} : {1'b0, s_cfg_q[18:0]}; + dffrc #( + .DATA_WIDTH(20), + .RESET_VAL ({1'b0, 16'd1, 3'd1}) + ) u_config ( + .clk_i (clk_ref_i), + .rst_n_i(rst_n_i), + .dat_i (s_cfg_d), + .dat_o (s_cfg_q) + ); + assign s_src_clk = s_cfg_q[2:0] == 3 ? clk_mem_i : + s_cfg_q[2:0] == 4 ? clk_periph_i : s_cfg_q[2:0] == 2 ? clk_sys_i : clk_ref_i; + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_enable_sync ( + .clk_i (s_src_clk), + .rst_n_i(rst_n_i), + .dat_i (s_cfg_q[19]), + .dat_o (s_en_req) + ); + // Finish the current output high pulse before disabling the Common divider. + ndffr #( + .DATA_WIDTH(1) + ) u_enable ( + .clk_i (s_src_clk), + .rst_n_i(rst_n_i), + .dat_i (s_en_req || clk_o), + .dat_o (s_enabled) + ); + clock_divider #( + .DIV_WIDTH(16), + .RESET_DIV(16'd1) + ) u_divider ( + .clk_i (s_src_clk), + .rst_n_i (rst_n_i), + .enable_i (s_enabled), + .div_i (s_cfg_q[18:3]), + .div_valid_i(!s_enabled), + .div_ready_o(), + .clk_o (clk_o), + .count_o () + ); + assign enable_o = s_enabled; +endmodule diff --git a/rtl/tiny/top/tiny_clock_reset_subsystem.sv b/rtl/tiny/top/tiny_clock_reset_subsystem.sv new file mode 100644 index 00000000..291ec047 --- /dev/null +++ b/rtl/tiny/top/tiny_clock_reset_subsystem.sv @@ -0,0 +1,407 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// Control runs on normalized REF24. All live changes drain first, park SYS, +// prepare conservative divisors, qualify the requested source, and commit last. +// A lost PLL resets the data session before resetting the safe mux. REF/XIN +// loss cannot be recovered by logic which depends on that same input. +module tiny_clock_reset_subsystem #( + parameter int unsigned RefClkHz = 24_000_000, + parameter bit HavePll = 1'b0, + parameter int unsigned SwitchCycles = 128 +) ( + input logic ref_clk_i, + input logic rst_n_i, + input logic [ 1:0] profile_i, + input logic apply_i, + input logic idle_i, + input logic clients_idle_i, + input logic warm_reset_i, + input logic system_request_i, + input logic command_ack_i, + input logic [31:0] timeout_i, + output logic [ 1:0] current_profile_o, + output logic busy_o, + output logic fault_o, + output logic [ 4:0] fault_code_o, + output logic done_o, + output logic block_o, + output logic ref_clk_o, + output logic ref_rst_n_o, + output logic system_reset_n_o, + output logic pll_locked_o, + output logic pll_selected_o, + output logic sys_clk_o, + output logic mem_clk_o, + output logic pclk_o, + output logic sys_rst_n_o, + output logic mem_rst_n_o, + output logic pclk_rst_n_o, + output logic [31:0] xin_hz_o, + output logic [31:0] ref_hz_o, + output logic [31:0] sys_hz_o, + output logic [31:0] mem_hz_o, + output logic [31:0] pclk_hz_o +); + typedef enum logic [3:0] { + Idle, + Drain, + Park, + Program, + Unlock, + Lock, + Divide, + Switch, + Commit, + Recover, + WarmDrain + } state_e; + typedef struct packed { + state_e state; + logic [1:0] requested; + logic [1:0] current; + logic fast; + logic mem_div; + logic [1:0] pclk_div; + logic [2:0] pll_sel; + logic pll_apply; + logic reset_n; + logic done; + logic [4:0] fault; + logic [31:0] count; + } control_t; + localparam control_t ResetValue = { + Idle, 2'd0, 2'd0, 1'b0, 1'b0, 2'd0, 3'd5, 1'b0, 1'b1, 1'b0, 5'd0, 32'd0 + }; + control_t s_ctrl_d, s_ctrl_q; + logic s_pll_capable, s_pll_lock, s_pll_clk, s_fast_clk; + logic s_div2_clk, s_div4_clk, s_pclk_mid; + logic s_mux_rst_n; + logic [31:0] s_limit, s_req_hz; + logic s_idle_ref, s_clients_idle_ref; + logic s_cmd_ack_ref; + logic s_fault_sticky_d, s_fault_sticky_q; + + if (RefClkHz == 24_000_000) begin : gen_ref_direct + assign ref_clk_o = ref_clk_i; + end else begin : gen_ref_divided + (* keep_hierarchy = 1 *) + clock_divider #( + .DIV_WIDTH(2), + .RESET_DIV(2'(RefClkHz / 48_000_000)) + ) u_ref_div ( + .clk_i (ref_clk_i), + .rst_n_i (rst_n_i), + .enable_i (1'b1), + .div_i (2'(RefClkHz / 48_000_000)), + .div_valid_i(1'b0), + .div_ready_o(), + .clk_o (ref_clk_o), + .count_o () + ); + end + rst_sync #( + .STAGE(5) + ) u_ref_rst ( + .clk_i (ref_clk_o), + .rst_n_i(rst_n_i), + .rst_n_o(ref_rst_n_o) + ); +`ifdef HAVE_PLL + tc_pll #( + .UseIcs55EcosPll(1'b1), + .Ics55Parked (1'b0) + ) u_pll ( + .fref_i (ref_clk_o), + .rst_n_i (ref_rst_n_o), + .cfg_sel_i (s_ctrl_q.pll_sel), + .cfg_apply_i (s_ctrl_q.pll_apply), + .pll_capable_o(s_pll_capable), + .pll_lock_o (s_pll_lock), + .pll_clk_o (s_pll_clk) + ); +`else + assign s_pll_capable = 1'b0; + assign s_pll_lock = 1'b0; + assign s_pll_clk = 1'b0; +`endif + assign s_fast_clk = HavePll ? s_pll_clk : ref_clk_i; + assign s_mux_rst_n = ref_rst_n_o && s_ctrl_q.reset_n; + (* keep_hierarchy = 1 *) + safe_clock_mux u_sys_mux ( + .clk0_i (ref_clk_o), + .clk1_i (s_fast_clk), + .rst_n_i (s_mux_rst_n), + .select_i(s_ctrl_q.fast), + .clk_o (sys_clk_o) + ); + // Both divided sources remain live across handover, including returning to + // SAFE24. Disabling a selected source would deadlock Common safe_clock_mux. + clock_divider #( + .DIV_WIDTH(1), + .RESET_DIV(1'b1) + ) u_div2 ( + .clk_i (sys_clk_o), + .rst_n_i (sys_rst_n_o), + .enable_i (1'b1), + .div_i (1'b1), + .div_valid_i(1'b0), + .div_ready_o(), + .count_o (), + .clk_o (s_div2_clk) + ); + clock_divider #( + .DIV_WIDTH(2), + .RESET_DIV(2'd2) + ) u_div4 ( + .clk_i (sys_clk_o), + .rst_n_i (sys_rst_n_o), + .enable_i (1'b1), + .div_i (2'd2), + .div_valid_i(1'b0), + .div_ready_o(), + .count_o (), + .clk_o (s_div4_clk) + ); + (* keep_hierarchy = 1 *) + safe_clock_mux u_mem_mux ( + .clk0_i (sys_clk_o), + .clk1_i (s_div2_clk), + .rst_n_i (s_mux_rst_n), + .select_i(s_ctrl_q.mem_div), + .clk_o (mem_clk_o) + ); + (* keep_hierarchy = 1 *) + safe_clock_mux u_pclk_div2_mux ( + .clk0_i (sys_clk_o), + .clk1_i (s_div2_clk), + .rst_n_i (s_mux_rst_n), + .select_i(s_ctrl_q.pclk_div != 0), + .clk_o (s_pclk_mid) + ); + (* keep_hierarchy = 1 *) + safe_clock_mux u_pclk_mux ( + .clk0_i (s_pclk_mid), + .clk1_i (s_div4_clk), + .rst_n_i (s_mux_rst_n), + .select_i(s_ctrl_q.pclk_div == 2), + .clk_o (pclk_o) + ); + assign system_reset_n_o = rst_n_i && s_ctrl_q.reset_n; + rst_sync #( + .STAGE(5) + ) u_sys_rst ( + .clk_i (sys_clk_o), + .rst_n_i(system_reset_n_o), + .rst_n_o(sys_rst_n_o) + ); + rst_sync #( + .STAGE(5) + ) u_mem_rst ( + .clk_i (mem_clk_o), + .rst_n_i(system_reset_n_o), + .rst_n_o(mem_rst_n_o) + ); + rst_sync #( + .STAGE(5) + ) u_pclk_rst ( + .clk_i (pclk_o), + .rst_n_i(system_reset_n_o), + .rst_n_o(pclk_rst_n_o) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_idle_sync ( + .clk_i (ref_clk_o), + .rst_n_i(ref_rst_n_o), + .dat_i (idle_i), + .dat_o (s_idle_ref) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_client_sync ( + .clk_i (ref_clk_o), + .rst_n_i(ref_rst_n_o), + .dat_i (clients_idle_i), + .dat_o (s_clients_idle_ref) + ); + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_command_ack_sync ( + .clk_i (ref_clk_o), + .rst_n_i(ref_rst_n_o), + .dat_i (command_ack_i), + .dat_o (s_cmd_ack_ref) + ); + assign s_limit = timeout_i == 0 ? 32'd1 : timeout_i; + assign s_req_hz = s_ctrl_q.requested == 2 ? 32'd192_000_000 : + s_ctrl_q.requested == 3 ? 32'd240_000_000 : + s_ctrl_q.requested == 1 ? 32'(RefClkHz) : 32'd24_000_000; + always_comb begin + s_ctrl_d = s_ctrl_q; + s_ctrl_d.done = 1'b0; + s_ctrl_d.pll_apply = 1'b0; + s_ctrl_d.fault = 5'd0; + s_ctrl_d.count = s_ctrl_q.count + 32'd1; + unique case (s_ctrl_q.state) + Idle: begin + s_ctrl_d.count = 0; + if (system_request_i) s_ctrl_d.state = WarmDrain; + else if (apply_i) begin + s_ctrl_d.requested = profile_i; + if ((HavePll && profile_i == 1) || (profile_i >= 2 && (!HavePll || !s_pll_capable))) begin + s_ctrl_d.done = 1'b1; + s_ctrl_d.fault[0] = 1'b1; + end else s_ctrl_d.state = Drain; + end + end + WarmDrain: begin + if (s_ctrl_q.count >= 16 && s_cmd_ack_ref) begin + s_ctrl_d.reset_n = 1'b0; + s_ctrl_d.fast = 1'b0; + s_ctrl_d.count = 0; + s_ctrl_d.state = Recover; + end else if (s_ctrl_q.count >= s_limit) begin + s_ctrl_d.fault[1] = 1'b1; + s_ctrl_d.done = 1'b1; + s_ctrl_d.state = Idle; + end + end + Drain: begin + // Delay covers block propagation and the accepted command's response. + if (s_limit >= SwitchCycles && s_ctrl_q.count >= 16 && + s_idle_ref && s_clients_idle_ref) begin + s_ctrl_d.fast = 1'b0; + s_ctrl_d.count = 0; + s_ctrl_d.state = Park; + end else if (s_ctrl_q.count >= s_limit) begin + s_ctrl_d.fault[1] = 1'b1; + s_ctrl_d.done = 1'b1; + s_ctrl_d.state = Idle; + end + end + Park: + if (s_ctrl_q.count >= SwitchCycles) begin + s_ctrl_d.count = 0; + s_ctrl_d.state = s_ctrl_q.requested >= 2 ? Program : Divide; + end + Program: begin + s_ctrl_d.pll_sel = s_ctrl_q.requested == 2 ? 3'd5 : 3'd7; + s_ctrl_d.pll_apply = 1'b1; + s_ctrl_d.count = 0; + s_ctrl_d.state = Unlock; + end + Unlock: + if (!s_pll_lock) begin + s_ctrl_d.count = 0; + s_ctrl_d.state = Lock; + end else if (s_ctrl_q.count >= s_limit) begin + s_ctrl_d.requested = 0; + s_ctrl_d.fault[2] = 1'b1; + s_ctrl_d.count = 0; + s_ctrl_d.state = Divide; + end + Lock: + if (s_pll_lock) begin + s_ctrl_d.count = 0; + s_ctrl_d.state = Divide; + end else if (s_ctrl_q.count >= s_limit) begin + s_ctrl_d.requested = 0; + s_ctrl_d.fault[2] = 1'b1; + s_ctrl_d.count = 0; + s_ctrl_d.state = Divide; + end + Divide: begin + s_ctrl_d.mem_div = s_req_hz > 120_000_000; + s_ctrl_d.pclk_div = s_req_hz > 120_000_000 ? 2'd2 : s_req_hz > 60_000_000 ? 2'd1 : 2'd0; + if (s_ctrl_q.count >= SwitchCycles) begin + s_ctrl_d.fast = s_ctrl_q.requested != 0; + s_ctrl_d.count = 0; + s_ctrl_d.state = Switch; + end + end + Switch: + if (s_ctrl_q.count >= SwitchCycles) begin + s_ctrl_d.current = s_ctrl_q.requested; + s_ctrl_d.state = Commit; + s_ctrl_d.count = 0; + end + Commit: + if (s_ctrl_q.count >= SwitchCycles) begin + // Keep admissions blocked while domain-local rate snapshots cross. + s_ctrl_d.done = 1'b1; + s_ctrl_d.state = Idle; + end + Recover: begin + s_ctrl_d.fast = 1'b0; + s_ctrl_d.mem_div = 1'b0; + s_ctrl_d.pclk_div = 0; + s_ctrl_d.current = 0; + if (s_ctrl_q.count >= SwitchCycles && !warm_reset_i) begin + s_ctrl_d.reset_n = 1'b1; + s_ctrl_d.state = Idle; + end + end + default: begin + s_ctrl_d = ResetValue; + s_ctrl_d.reset_n = 1'b0; + s_ctrl_d.state = Recover; + s_ctrl_d.fault[4] = 1'b1; + end + endcase + if (s_ctrl_q.state != Recover && (warm_reset_i || + (HavePll && s_ctrl_q.fast && !s_pll_lock))) begin + // Only the session-reset path may break a stopped-clock mux deadlock. + s_ctrl_d.reset_n = 1'b0; + s_ctrl_d.fast = 1'b0; + s_ctrl_d.count = 0; + s_ctrl_d.state = Recover; + if (!warm_reset_i) s_ctrl_d.fault[3] = 1'b1; + end + if ((apply_i || system_request_i) && s_ctrl_q.state != Idle) s_ctrl_d.fault[0] = 1'b1; + end + dffrc #( + .DATA_WIDTH($bits(control_t)), + .RESET_VAL (ResetValue) + ) u_control ( + .clk_i (ref_clk_o), + .rst_n_i(ref_rst_n_o), + .dat_i (s_ctrl_d), + .dat_o (s_ctrl_q) + ); + assign s_fault_sticky_d = s_fault_sticky_q || (s_ctrl_q.fault != 0); + dffr #( + .DATA_WIDTH(1) + ) u_fault_sticky ( + .clk_i (ref_clk_o), + .rst_n_i(ref_rst_n_o), + .dat_i (s_fault_sticky_d), + .dat_o (s_fault_sticky_q) + ); + assign current_profile_o = s_ctrl_q.current; + assign busy_o = s_ctrl_q.state != Idle; + assign block_o = s_ctrl_q.state != Idle; + assign fault_o = s_fault_sticky_q; + assign fault_code_o = s_ctrl_q.fault; + assign done_o = s_ctrl_q.done; + assign pll_locked_o = s_pll_lock; + assign pll_selected_o = s_ctrl_q.fast && HavePll; + assign xin_hz_o = RefClkHz; + assign ref_hz_o = 24_000_000; + assign sys_hz_o = s_ctrl_q.current == 2 ? 32'd192_000_000 : + s_ctrl_q.current == 3 ? 32'd240_000_000 : + s_ctrl_q.current == 1 ? 32'(RefClkHz) : 32'd24_000_000; + assign mem_hz_o = sys_hz_o > 120_000_000 ? sys_hz_o / 2 : sys_hz_o; + assign pclk_hz_o = sys_hz_o > 120_000_000 ? sys_hz_o / 4 : + sys_hz_o > 60_000_000 ? sys_hz_o / 2 : sys_hz_o; +`ifndef SYNTHESIS + initial begin + if (!(RefClkHz == 24_000_000 || RefClkHz == 48_000_000 || RefClkHz == 96_000_000) || + (HavePll && RefClkHz != 24_000_000) || SwitchCycles < 128) + $fatal(1, "Tiny clock source configuration is invalid"); + end +`endif +endmodule diff --git a/rtl/tiny/top/tiny_cpu_mem.sv b/rtl/tiny/top/tiny_cpu_mem.sv new file mode 100644 index 00000000..5a229270 --- /dev/null +++ b/rtl/tiny/top/tiny_cpu_mem.sv @@ -0,0 +1,289 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 +`include "mmap_define.svh" + +// Independent one-operation I/D frontends. AHB write data belongs to the cycle +// following address acceptance. Local stores complete only after macro commit. +// Only non-SRAM work enters the tagged, non-posted slow-path merge. +module tiny_cpu_mem #( + parameter bit ClockPause = 1'b0 +) ( + input logic clk_i, + input logic rst_n_i, + input logic quiesce_i, + input logic clock_pause_i, + output logic clock_idle_o, + output logic idle_o, + output logic [1:0] local_fault_valid_o, + output logic [1:0][31:0] local_fault_addr_o, + output logic [1:0] local_fault_write_o, + ahbl_if.slave cpu [2], + tiny_sram_port_if.master local_ports [2], + axi4_if.master slow_axi4 +); + typedef enum logic [2:0] { + Idle, + WriteCapture, + LocalIssue, + LocalWait, + SlowIssue, + SlowWait, + ErrorFirst, + ErrorLast + } state_e; + typedef struct packed { + state_e state; + logic [31:0] addr, data; + logic [2:0] size; + logic write; + logic external_error; + logic [3:0] strb; + } request_t; + request_t s_req_d[2], s_req_q[2]; + logic [1:0] s_terminal, s_err, s_slow_req, s_slow_grant, s_front_idle; + logic s_slow_selected, s_slow_valid, s_slow_accept; + typedef enum logic [1:0] { + SlowIdle, + SlowAddress, + SlowData, + SlowResponse + } slow_state_e; + typedef struct packed { + slow_state_e state; + logic owner, write, error; + logic [2:0] size; + logic [31:0] addr, data, rdata; + } slow_t; + slow_t s_slow_d, s_slow_q; + ahbl_if u_slow_ahbl_if ( + .hclk (clk_i), + .hresetn(rst_n_i) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) u_slow_axi4_if ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + logic s_adapter_idle; + logic s_clock_pause; + logic [1:0] s_clock_front_idle; + assign s_clock_pause = ClockPause && clock_pause_i; + // A pipeline-overlap address acknowledged with the preceding AHB response + // remains in the input register until resume. Only target admissions are + // blocked: every accepted bank/AXI operation and response still drains. + assign clock_idle_o = (&s_clock_front_idle) && (s_slow_q.state == SlowIdle) && s_adapter_idle; + + for (genvar port_index = 0; port_index < 2; port_index++) begin : gen_port + assign local_fault_valid_o[port_index] = + (s_req_q[port_index].state == ErrorLast) && !s_req_q[port_index].external_error; + assign local_fault_addr_o[port_index] = s_req_q[port_index].addr; + assign local_fault_write_o[port_index] = s_req_q[port_index].write; + assign s_slow_req[port_index] = (s_req_q[port_index].state == SlowIssue) && !s_clock_pause; + assign s_clock_front_idle[port_index] = (s_req_q[port_index].state == Idle) || + (s_req_q[port_index].state == LocalIssue) || (s_req_q[port_index].state == SlowIssue); + assign s_front_idle[port_index] = (s_req_q[port_index].state == Idle) && + (quiesce_i || !cpu[port_index].htrans[1]); + assign s_err[port_index] = (s_req_q[port_index].state == ErrorFirst) || + (s_req_q[port_index].state == ErrorLast) || + ((s_req_q[port_index].state == SlowWait) && + (s_slow_q.state == SlowResponse) && (s_slow_q.owner == 1'(port_index)) && s_slow_q.error); + assign s_terminal[port_index] = (s_req_q[port_index].state == ErrorLast) || + ((s_req_q[port_index].state == LocalWait) && local_ports[port_index].rsp_valid) || + ((s_req_q[port_index].state == SlowWait) && + (s_slow_q.state == SlowResponse) && (s_slow_q.owner == 1'(port_index)) && !s_slow_q.error); + assign cpu[port_index].hready = + ((s_req_q[port_index].state == Idle) && !quiesce_i) || s_terminal[port_index]; + assign cpu[port_index].hresp = s_err[port_index]; + assign cpu[port_index].hrdata = (s_req_q[port_index].state == LocalWait) ? + local_ports[port_index].rdata : s_slow_q.rdata; + assign local_ports[port_index].req_valid = (s_req_q[port_index].state == LocalIssue) && + !s_clock_pause; + assign local_ports[port_index].addr = s_req_q[port_index].addr; + assign local_ports[port_index].write = s_req_q[port_index].write; + assign local_ports[port_index].wdata = s_req_q[port_index].data; + assign local_ports[port_index].wstrb = s_req_q[port_index].strb; + assign local_ports[port_index].rsp_ready = s_req_q[port_index].state == LocalWait; + + always_comb begin + s_req_d[port_index] = s_req_q[port_index]; + unique case (s_req_q[port_index].state) + WriteCapture: begin + unique case (s_req_q[port_index].size) + 3'd0: begin + s_req_d[port_index].data = {24'd0, cpu[port_index].hwdata[7:0]} << + (8 * s_req_q[port_index].addr[1:0]); + s_req_d[port_index].strb = 4'b0001 << s_req_q[port_index].addr[1:0]; + end + 3'd1: begin + s_req_d[port_index].data = {16'd0, cpu[port_index].hwdata[15:0]} << + (8 * s_req_q[port_index].addr[1:0]); + s_req_d[port_index].strb = 4'b0011 << s_req_q[port_index].addr[1:0]; + end + default: begin + s_req_d[port_index].data = cpu[port_index].hwdata; + s_req_d[port_index].strb = 4'b1111; + end + endcase + s_req_d[port_index].state = + `SOC_ADDR_IS_SRAM(s_req_q[port_index].addr) + ? LocalIssue : SlowIssue; + end + LocalIssue: + if (local_ports[port_index].req_valid && local_ports[port_index].req_ready) + s_req_d[port_index].state = LocalWait; + SlowIssue: + if (s_slow_accept && s_slow_grant[port_index]) s_req_d[port_index].state = SlowWait; + SlowWait: + if (s_err[port_index]) begin + s_req_d[port_index].state = ErrorLast; + // The fabric already reports externally completed errors once. + s_req_d[port_index].external_error = 1'b1; + end + ErrorFirst: s_req_d[port_index].state = ErrorLast; + default: begin + end + endcase + if (cpu[port_index].hready) begin + s_req_d[port_index].state = Idle; + // Capture even a terminal-cycle overlap during quiesce: that address + // was acknowledged by HREADY and cannot be silently discarded. + if (cpu[port_index].htrans[1]) begin + s_req_d[port_index].addr = cpu[port_index].haddr; + s_req_d[port_index].size = cpu[port_index].hsize; + s_req_d[port_index].write = cpu[port_index].hwrite; + s_req_d[port_index].strb = '0; + s_req_d[port_index].external_error = 1'b0; + if ((cpu[port_index].htrans != 2'b10) || cpu[port_index].hmastlock || + (cpu[port_index].hsize > 3'd2) || + ((cpu[port_index].haddr & ((32'd1 << cpu[port_index].hsize) - 32'd1)) != 32'd0) || + ((port_index == 0) && (cpu[port_index].hwrite || (cpu[port_index].hsize != 3'd2)))) + s_req_d[port_index].state = ErrorFirst; + else if (cpu[port_index].hwrite) s_req_d[port_index].state = WriteCapture; + else + s_req_d[port_index].state = + `SOC_ADDR_IS_SRAM(cpu[port_index].haddr) + ? LocalIssue : SlowIssue; + end + end + end + dffr #( + .DATA_WIDTH($bits(request_t)) + ) u_request ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_req_d[port_index]), + .dat_o (s_req_q[port_index]) + ); + end + + assign idle_o = (&s_front_idle) && (s_slow_q.state == SlowIdle) && s_adapter_idle; + assign s_slow_accept = (s_slow_q.state == SlowIdle) && s_slow_valid; + round_robin_arbiter #( + .CLIENTS(2) + ) u_slow_arbiter ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .advance_i (s_slow_accept), + .request_i (s_slow_req), + .grant_o (s_slow_grant), + .selected_o(s_slow_selected), + .valid_o (s_slow_valid) + ); + always_comb begin + s_slow_d = s_slow_q; + unique case (s_slow_q.state) + SlowIdle: + if (s_slow_accept) begin + s_slow_d.owner = s_slow_selected; + s_slow_d.addr = s_req_q[s_slow_selected].addr; + s_slow_d.write = s_req_q[s_slow_selected].write; + s_slow_d.size = s_req_q[s_slow_selected].size; + // The shared AHB adapter applies lane shifting itself. + s_slow_d.data = s_req_q[s_slow_selected].data >> (8 * s_req_q[s_slow_selected].addr[1:0]); + s_slow_d.state = SlowAddress; + end + SlowAddress: if (u_slow_ahbl_if.hready) s_slow_d.state = SlowData; + SlowData: + if (u_slow_ahbl_if.hready) begin + s_slow_d.rdata = u_slow_ahbl_if.hrdata; + s_slow_d.error = u_slow_ahbl_if.hresp; + s_slow_d.state = SlowResponse; + end + SlowResponse: s_slow_d.state = SlowIdle; + default: s_slow_d.state = SlowIdle; + endcase + end + dffr #( + .DATA_WIDTH($bits(slow_t)) + ) u_slow_state ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_slow_d), + .dat_o (s_slow_q) + ); + assign u_slow_ahbl_if.haddr = s_slow_q.addr; + assign u_slow_ahbl_if.hwrite = s_slow_q.write; + assign u_slow_ahbl_if.hsize = s_slow_q.size; + assign u_slow_ahbl_if.hwdata = s_slow_q.data; + assign u_slow_ahbl_if.htrans = (s_slow_q.state == SlowAddress) ? 2'b10 : 2'b00; + assign u_slow_ahbl_if.hburst = '0; + assign u_slow_ahbl_if.hprot = {3'b001, s_slow_q.owner}; + assign u_slow_ahbl_if.hmastlock = 1'b0; + ahbl2axi4 #( + .TwoCycleErrors(1'b1) + ) u_slow_adapter ( + .ahbl (u_slow_ahbl_if), + .axi4 (u_slow_axi4_if), + .idle_o(s_adapter_idle) + ); + // Forward every channel unchanged except the saved instruction attribute. + // Tiny's fabric uses it to reject fetches before peripheral side effects. + assign slow_axi4.awid = u_slow_axi4_if.awid; + assign slow_axi4.awaddr = u_slow_axi4_if.awaddr; + assign slow_axi4.awlen = u_slow_axi4_if.awlen; + assign slow_axi4.awsize = u_slow_axi4_if.awsize; + assign slow_axi4.awburst = u_slow_axi4_if.awburst; + assign slow_axi4.awlock = u_slow_axi4_if.awlock; + assign slow_axi4.awcache = u_slow_axi4_if.awcache; + assign slow_axi4.awprot = {!s_slow_q.owner, u_slow_axi4_if.awprot[1:0]}; + assign slow_axi4.awqos = u_slow_axi4_if.awqos; + assign slow_axi4.awregion = u_slow_axi4_if.awregion; + assign slow_axi4.awuser = u_slow_axi4_if.awuser; + assign slow_axi4.awvalid = u_slow_axi4_if.awvalid; + assign slow_axi4.arid = u_slow_axi4_if.arid; + assign slow_axi4.araddr = u_slow_axi4_if.araddr; + assign slow_axi4.arlen = u_slow_axi4_if.arlen; + assign slow_axi4.arsize = u_slow_axi4_if.arsize; + assign slow_axi4.arburst = u_slow_axi4_if.arburst; + assign slow_axi4.arlock = u_slow_axi4_if.arlock; + assign slow_axi4.arcache = u_slow_axi4_if.arcache; + assign slow_axi4.arprot = {!s_slow_q.owner, u_slow_axi4_if.arprot[1:0]}; + assign slow_axi4.arqos = u_slow_axi4_if.arqos; + assign slow_axi4.arregion = u_slow_axi4_if.arregion; + assign slow_axi4.aruser = u_slow_axi4_if.aruser; + assign slow_axi4.arvalid = u_slow_axi4_if.arvalid; + assign slow_axi4.wdata = u_slow_axi4_if.wdata; + assign slow_axi4.wstrb = u_slow_axi4_if.wstrb; + assign slow_axi4.wlast = u_slow_axi4_if.wlast; + assign slow_axi4.wuser = u_slow_axi4_if.wuser; + assign slow_axi4.wvalid = u_slow_axi4_if.wvalid; + assign slow_axi4.rready = u_slow_axi4_if.rready; + assign slow_axi4.bready = u_slow_axi4_if.bready; + assign u_slow_axi4_if.awready = slow_axi4.awready; + assign u_slow_axi4_if.arready = slow_axi4.arready; + assign u_slow_axi4_if.wready = slow_axi4.wready; + assign u_slow_axi4_if.rvalid = slow_axi4.rvalid; + assign u_slow_axi4_if.rid = slow_axi4.rid; + assign u_slow_axi4_if.rdata = slow_axi4.rdata; + assign u_slow_axi4_if.rresp = slow_axi4.rresp; + assign u_slow_axi4_if.rlast = slow_axi4.rlast; + assign u_slow_axi4_if.ruser = slow_axi4.ruser; + assign u_slow_axi4_if.bvalid = slow_axi4.bvalid; + assign u_slow_axi4_if.bid = slow_axi4.bid; + assign u_slow_axi4_if.bresp = slow_axi4.bresp; + assign u_slow_axi4_if.buser = slow_axi4.buser; +endmodule diff --git a/rtl/tiny/top/tiny_cpu_wrapper.sv b/rtl/tiny/top/tiny_cpu_wrapper.sv new file mode 100644 index 00000000..20bbc7f3 --- /dev/null +++ b/rtl/tiny/top/tiny_cpu_wrapper.sv @@ -0,0 +1,243 @@ +// Copyright (c) 2023-2026 Yuchi Miao +// retroSoC is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +`include "mmap_define.svh" + +module tiny_cpu_wrapper #( + parameter int ExternalIrqCount = 30, + parameter bit EnableAtomics = 1'b0, + parameter int ResetSyncStages = 3, + parameter bit EnableWfiGate = 1'b0, + parameter bit PreserveDebug = 1'b0 +) ( + // verilog_format: off -- preserve reviewed column alignment + input logic clk_i, + input logic rst_n_i, + input logic debug_rst_n_i, + output logic hart_reset_o, + input logic [ExternalIrqCount+1:0] irq_i, + input logic jtag_tck_i, + input logic jtag_tms_i, + input logic jtag_tdi_i, + input logic jtag_trst_n_i, + output logic jtag_tdo_o, + output logic debug_halted_o, + input logic bus_idle_i, + input logic wfi_gate_i, + output logic gated_o, + output logic core_rst_n_o, + output logic quiesce_o, + ahbl_if.master cpu[2] + // verilog_format: on +); + logic s_pwrup_req; + logic s_debug_reset_n, s_abort_debug; + logic s_cpu_clk, s_cpu_clk_en, s_gate_d, s_gate_q; + logic s_reset_pending; + logic s_hart_idle; + logic s_core_rst_n; + logic s_dbg_req_halt; + logic s_dbg_req_halt_on_reset; + logic s_dbg_req_resume; + logic s_dbg_halted; + logic s_dbg_running; + logic [31:0] s_dbg_data0_rdata; + logic [31:0] s_dbg_data0_wdata; + logic s_dbg_data0_wen; + logic [31:0] s_dbg_instr_data; + logic s_dbg_instr_data_vld; + logic s_dbg_instr_data_rdy; + logic s_dbg_instr_caught_exception; + logic s_dbg_instr_caught_ebreak; + logic [31:0] s_dbg_sbus_addr; + logic s_dbg_sbus_write; + logic [ 1:0] s_dbg_sbus_size; + logic s_dbg_sbus_vld; + logic s_dbg_sbus_rdy; + logic s_dbg_sbus_err; + logic [31:0] s_dbg_sbus_wdata; + logic [31:0] s_dbg_sbus_rdata; + + assign s_debug_reset_n = PreserveDebug ? debug_rst_n_i : rst_n_i; + assign s_abort_debug = PreserveDebug && !rst_n_i; + assign hart_reset_o = s_reset_pending; + assign s_gate_d = EnableWfiGate && wfi_gate_i && !s_cpu_clk_en && bus_idle_i && + !(|irq_i) && !s_dbg_req_halt && !s_dbg_req_resume && !s_reset_pending; + (* keep_hierarchy = 1 *) + tiny_clock_gate u_cpu_gate ( + .clk_i (clk_i), + .rst_n_i(s_core_rst_n), + .gate_i (s_gate_d), + .clk_o (s_cpu_clk), + .gated_o(s_gate_q) + ); + assign gated_o = s_gate_q; + + assign debug_halted_o = s_dbg_halted; + assign core_rst_n_o = s_core_rst_n; + // Request a normal debug halt before draining/resetting the core. Never cut + // off an accepted AHB transfer to manufacture an idle indication. + assign quiesce_o = s_reset_pending && s_dbg_halted; + assign s_hart_idle = bus_idle_i && (!s_reset_pending || s_dbg_halted); + + mgmt_debug_wrapper #( + .JtagIdcode (`SOC_JTAG_IDCODE), + .ResetSyncStages (ResetSyncStages), + .SeparateSystemReset(PreserveDebug) + ) u_mgmt_debug_wrapper ( + .clk_i (clk_i), + .rst_n_i (s_debug_reset_n), + .system_rst_n_i (rst_n_i), + .bridge_idle_i (s_hart_idle), + .jtag_tck_i (jtag_tck_i), + .jtag_tms_i (jtag_tms_i), + .jtag_tdi_i (jtag_tdi_i), + .jtag_trst_n_i (jtag_trst_n_i), + .jtag_tdo_o (jtag_tdo_o), + .core_rst_n_o (s_core_rst_n), + .reset_pending_o (s_reset_pending), + .dbg_req_halt_o (s_dbg_req_halt), + .dbg_req_halt_on_reset_o (s_dbg_req_halt_on_reset), + .dbg_req_resume_o (s_dbg_req_resume), + .dbg_halted_i (s_dbg_halted), + .dbg_running_i (s_dbg_running), + .dbg_data0_rdata_o (s_dbg_data0_rdata), + .dbg_data0_wdata_i (s_dbg_data0_wdata), + .dbg_data0_wen_i (s_dbg_data0_wen && !s_abort_debug), + .dbg_instr_data_o (s_dbg_instr_data), + .dbg_instr_data_vld_o (s_dbg_instr_data_vld), + .dbg_instr_data_rdy_i (s_dbg_instr_data_rdy || s_abort_debug), + .dbg_instr_caught_exception_i(s_dbg_instr_caught_exception || s_abort_debug), + .dbg_instr_caught_ebreak_i (s_dbg_instr_caught_ebreak || s_abort_debug), + .dbg_sbus_addr_o (s_dbg_sbus_addr), + .dbg_sbus_write_o (s_dbg_sbus_write), + .dbg_sbus_size_o (s_dbg_sbus_size), + .dbg_sbus_vld_o (s_dbg_sbus_vld), + .dbg_sbus_rdy_i (s_dbg_sbus_rdy || s_abort_debug), + .dbg_sbus_err_i (s_dbg_sbus_err || s_abort_debug), + .dbg_sbus_wdata_o (s_dbg_sbus_wdata), + .dbg_sbus_rdata_i (s_dbg_sbus_rdata) + ); + + hazard3_cpu_2port #( + .RESET_VECTOR (`SOC_CPU_RESET_ADDR), + .MTVEC_INIT (32'h0000_0000), + .EXTENSION_A (EnableAtomics), + .EXTENSION_C (1), + .EXTENSION_E (0), + .EXTENSION_M (1), + .EXTENSION_ZBA (1), + .EXTENSION_ZBB (1), + .EXTENSION_ZBC (1), + .EXTENSION_ZBKB (1), + .EXTENSION_ZBKX (1), + .EXTENSION_ZBS (1), + .EXTENSION_ZCB (0), + .EXTENSION_ZCLSD (0), + .EXTENSION_ZCMP (0), + .EXTENSION_ZIFENCEI (1), + .EXTENSION_ZILSD (1), + .EXTENSION_XH3BEXTM (1), + .EXTENSION_XH3IRQ (1), + .EXTENSION_XH3PMPM (0), + .EXTENSION_XH3POWER (EnableWfiGate), + .CSR_M_MANDATORY (1), + .CSR_M_TRAP (1), + .CSR_COUNTER (1), + .U_MODE (0), + .PMP_REGIONS (0), + .PMP_GRAIN (0), + .PMP_MATCH_NAPOT (1), + .PMP_MATCH_TOR (0), + .PMP_HARDWIRED (0), + .PMP_HARDWIRED_ADDR (0), + .PMP_HARDWIRED_CFG (0), + .DEBUG_SUPPORT (1), + .BREAKPOINT_TRIGGERS(2), + .NUM_IRQS (ExternalIrqCount), + .IRQ_PRIORITY_BITS (2), + .IRQ_INPUT_BYPASS ({ExternalIrqCount{1'b0}}), + .MVENDORID_VAL (32'h0), + .MCONFIGPTR_VAL (32'h0), + .REDUCED_BYPASS (0), + .MULDIV_UNROLL (2), + .MUL_FAST (1), + .MUL_FASTER (1), + .MULH_FAST (1), + .FAST_BRANCHCMP (1), + .RESET_REGFILE (1), + .BRANCH_PREDICTOR (1), + .MTVEC_WMASK (32'hfffffffd) + ) u_hazard3_cpu_2port ( + .clk (s_cpu_clk), + .clk_always_on (clk_i), + .rst_n (s_core_rst_n), + .pwrup_req (s_pwrup_req), + .pwrup_ack (s_pwrup_req), + .clk_en (s_cpu_clk_en), + .unblock_out (), + .unblock_in (1'b0), + .i_haddr (cpu[0].haddr), + .i_hwrite (cpu[0].hwrite), + .i_htrans (cpu[0].htrans), + .i_hsize (cpu[0].hsize), + .i_hburst (cpu[0].hburst), + .i_hprot (cpu[0].hprot), + .i_hmastlock (cpu[0].hmastlock), + .i_hmaster (), + .i_hready (cpu[0].hready), + .i_hresp (cpu[0].hresp), + .i_hwdata (cpu[0].hwdata), + .i_hrdata (cpu[0].hrdata), + .d_haddr (cpu[1].haddr), + .d_hwrite (cpu[1].hwrite), + .d_htrans (cpu[1].htrans), + .d_hsize (cpu[1].hsize), + .d_hburst (cpu[1].hburst), + .d_hprot (cpu[1].hprot), + .d_hmastlock (cpu[1].hmastlock), + .d_hmaster (), + .d_hready (cpu[1].hready), + .d_hresp (cpu[1].hresp), + .d_hwdata (cpu[1].hwdata), + .d_hrdata (cpu[1].hrdata), + .d_hexcl (), + .d_hexokay (1'b1), + .fence_i_vld (), + .fence_d_vld (), + .fence_rdy (1'b1), + .dbg_req_halt (s_dbg_req_halt || s_reset_pending), + .dbg_req_halt_on_reset (s_dbg_req_halt_on_reset), + .dbg_req_resume (s_dbg_req_resume && !s_reset_pending), + .dbg_halted (s_dbg_halted), + .dbg_running (s_dbg_running), + .dbg_data0_rdata (s_dbg_data0_rdata), + .dbg_data0_wdata (s_dbg_data0_wdata), + .dbg_data0_wen (s_dbg_data0_wen), + .dbg_instr_data (s_dbg_instr_data), + .dbg_instr_data_vld (s_dbg_instr_data_vld && !s_reset_pending && !s_abort_debug), + .dbg_instr_data_rdy (s_dbg_instr_data_rdy), + .dbg_instr_caught_exception(s_dbg_instr_caught_exception), + .dbg_instr_caught_ebreak (s_dbg_instr_caught_ebreak), + .dbg_sbus_addr (s_dbg_sbus_addr), + .dbg_sbus_write (s_dbg_sbus_write), + .dbg_sbus_size (s_dbg_sbus_size), + .dbg_sbus_vld (s_dbg_sbus_vld && !s_abort_debug), + .dbg_sbus_rdy (s_dbg_sbus_rdy), + .dbg_sbus_err (s_dbg_sbus_err), + .dbg_sbus_wdata (s_dbg_sbus_wdata), + .dbg_sbus_rdata (s_dbg_sbus_rdata), + .mhartid_val ('0), + .eco_version ('0), + .irq (irq_i[ExternalIrqCount+1:2]), + .soft_irq (irq_i[0]), + .timer_irq (irq_i[1]) + ); +endmodule diff --git a/rtl/tiny/top/tiny_fault_collector.sv b/rtl/tiny/top/tiny_fault_collector.sv new file mode 100644 index 00000000..927a0b46 --- /dev/null +++ b/rtl/tiny/top/tiny_fault_collector.sv @@ -0,0 +1,60 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// Terminal transaction pulses contribute independently to the count. One +// deterministic winner supplies metadata; SYSCTRL owns validity and W1C. +module tiny_fault_collector ( + input logic [ 1:0] local_valid_i, + input logic [ 1:0][31:0] local_addr_i, + input logic [ 1:0] local_write_i, + input logic [ 5:0] external_valid_i, + input logic [ 5:0][31:0] external_addr_i, + input logic [ 5:0][ 1:0] external_resp_i, + input logic [ 5:0] external_instruction_i, + output logic [ 3:0] count_o, + output logic [31:0] addr_o, + output logic [ 1:0] master_o, + output logic write_o, + output logic [ 1:0] resp_o +); + logic [7:0] s_valid, s_write, s_instruction; + logic [7:0][31:0] s_addr; + logic [7:0][1:0] s_master, s_resp; + logic [7:0][3:0] s_rank; + logic [3:0] s_best_rank; + assign s_valid = {external_valid_i, local_valid_i}; + assign s_addr = {external_addr_i, local_addr_i}; + assign s_write[1:0] = local_write_i; + assign s_master[1:0] = '0; + assign s_resp[1:0] = {2'b10, 2'b10}; + assign s_instruction[1:0] = 2'b01; + for (genvar slot = 0; slot < 6; slot++) begin : gen_external + assign s_write[slot+2] = 1'(slot % 2); + assign s_master[slot+2] = 2'(slot / 2); + assign s_resp[slot+2] = external_resp_i[slot]; + assign s_instruction[slot+2] = (slot < 2) && external_instruction_i[slot]; + end + for (genvar slot = 0; slot < 8; slot++) begin : gen_rank + assign s_rank[slot] = {s_master[slot], !s_instruction[slot], s_write[slot]}; + end + always_comb begin + count_o = '0; + addr_o = '0; + master_o = '0; + write_o = 1'b0; + resp_o = '0; + s_best_rank = 4'hf; + for (int slot = 0; slot < 8; slot++) begin + if (s_valid[slot]) begin + count_o = count_o + 4'd1; + if (s_rank[slot] < s_best_rank) begin + s_best_rank = s_rank[slot]; + addr_o = s_addr[slot]; + master_o = s_master[slot]; + write_o = s_write[slot]; + resp_o = s_resp[slot]; + end + end + end + end +endmodule diff --git a/rtl/tiny/top/tiny_managed_lifecycle.sv b/rtl/tiny/top/tiny_managed_lifecycle.sv new file mode 100644 index 00000000..abac6f7f --- /dev/null +++ b/rtl/tiny/top/tiny_managed_lifecycle.sv @@ -0,0 +1,53 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// Product lifecycle observations for managed register-only endpoints. Track +// only successful APB writes, respecting byte enables. This conservative +// mirror never infers idle from quiet output pins or a missing clock. PWM +// requires explicit global/capture disable; CRC requires FINISH or ABORT. +module tiny_managed_lifecycle ( + input logic clk_i, + input logic pwm_rst_n_i, + input logic crc_rst_n_i, + input logic pwm_write_i, + input logic [11:0] pwm_addr_i, + input logic [31:0] pwm_data_i, + input logic crc_write_i, + input logic [11:0] crc_addr_i, + input logic [31:0] crc_data_i, + output logic pwm_idle_o, + output logic crc_idle_o +); + logic [1:0] s_pwm_d, s_pwm_q; + logic s_crc_d, s_crc_q; + always_comb begin + s_pwm_d = s_pwm_q; + if (pwm_write_i) begin + if (pwm_addr_i == 12'h000) s_pwm_d[0] = pwm_data_i[0]; + if (pwm_addr_i == 12'h400) s_pwm_d[1] = pwm_data_i[0]; + end + s_crc_d = s_crc_q; + if (crc_write_i && crc_addr_i == 12'h000) begin + if (crc_data_i[2:0] == 3'b001) s_crc_d = 1'b1; + if (crc_data_i[2:0] == 3'b010 || crc_data_i[2:0] == 3'b100) s_crc_d = 1'b0; + end + end + dffr #( + .DATA_WIDTH(2) + ) u_pwm_state ( + .clk_i (clk_i), + .rst_n_i(pwm_rst_n_i), + .dat_i (s_pwm_d), + .dat_o (s_pwm_q) + ); + dffr #( + .DATA_WIDTH(1) + ) u_crc_state ( + .clk_i (clk_i), + .rst_n_i(crc_rst_n_i), + .dat_i (s_crc_d), + .dat_o (s_crc_q) + ); + assign pwm_idle_o = s_pwm_q == 0; + assign crc_idle_o = !s_crc_q; +endmodule diff --git a/rtl/tiny/top/tiny_peripheral_control.sv b/rtl/tiny/top/tiny_peripheral_control.sv new file mode 100644 index 00000000..ac28ef87 --- /dev/null +++ b/rtl/tiny/top/tiny_peripheral_control.sv @@ -0,0 +1,133 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// Atomic Tiny gate/reset transactions on REF24. Inputs are synchronized to +// REF24 by the integration; gate masks exclude non-gateable boot infrastructure. +module tiny_peripheral_control ( + input logic clk_i, + input logic rst_n_i, + input logic warm_reset_i, + input logic apply_i, + input logic reset_command_i, + input logic [13:0] request_i, + input logic [13:0] idle_i, + input logic bus_idle_i, + input logic [13:0] gate_actual_i, + input logic [13:0] reset_held_i, + input logic [13:0] reset_ready_i, + input logic [31:0] timeout_i, + output logic [13:0] gate_o, + output logic [13:0] reset_o, + output logic block_o, + output logic done_o, + output logic [ 4:0] fault_o, + output logic [13:0] reset_done_o +); + typedef enum logic [2:0] { + Idle, + Drain, + GateWait, + Hold, + Release, + Complete + } state_e; + typedef struct packed { + state_e state; + logic reset_command; + logic [13:0] request_mask, gates, previous_gates, resets, reset_done, affected; + logic [31:0] count; + logic done; + logic [4:0] fault; + } control_t; + control_t s_ctrl_d, s_ctrl_q; + logic [13:0] s_affected; + assign s_affected = s_ctrl_q.affected; + assign gate_o = s_ctrl_q.gates; + assign reset_o = s_ctrl_q.resets; + assign reset_done_o = s_ctrl_q.reset_done; + // Arming the WFI leaf permission must not pause an unrelated DMA job. + // The CPU leaf itself waits for its accepted I/D work before stopping. + assign block_o = (s_ctrl_q.state != Idle) && (s_affected[13:1] != 0); + assign done_o = s_ctrl_q.done; + assign fault_o = s_ctrl_q.fault; + always_comb begin + s_ctrl_d = s_ctrl_q; + s_ctrl_d.done = 1'b0; + s_ctrl_d.fault = 0; + s_ctrl_d.count = s_ctrl_q.count + 32'd1; + unique case (s_ctrl_q.state) + Idle: begin + s_ctrl_d.count = 0; + if (apply_i) begin + if (reset_command_i && (request_i == 0 || request_i[0] || + (request_i & s_ctrl_q.gates) != 0)) begin + s_ctrl_d.done = 1'b1; + s_ctrl_d.fault[0] = 1'b1; + end else begin + s_ctrl_d.request_mask = request_i; + s_ctrl_d.previous_gates = s_ctrl_q.gates; + s_ctrl_d.affected = reset_command_i ? request_i : s_ctrl_q.gates ^ request_i; + s_ctrl_d.reset_command = reset_command_i; + if (reset_command_i) s_ctrl_d.reset_done = 0; + s_ctrl_d.state = Drain; + end + end + end + Drain: begin + if (s_ctrl_q.count >= 16 && ((s_affected[13:1] == 0) || (bus_idle_i && idle_i[9])) && + ((idle_i[13:1] & s_affected[13:1]) == s_affected[13:1])) begin + s_ctrl_d.count = 0; + if (s_ctrl_q.reset_command) begin + s_ctrl_d.resets = s_ctrl_q.request_mask; + s_ctrl_d.state = Hold; + end else begin + s_ctrl_d.gates = s_ctrl_q.request_mask; + s_ctrl_d.state = GateWait; + end + end else if (s_ctrl_q.count >= timeout_i) begin + s_ctrl_d.fault[1] = 1'b1; + s_ctrl_d.done = 1'b1; + s_ctrl_d.state = Idle; + end + end + GateWait: if (gate_actual_i[13:1] == s_ctrl_q.gates[13:1]) s_ctrl_d.state = Complete; + Hold: + if ((reset_held_i & s_ctrl_q.request_mask) == s_ctrl_q.request_mask) begin + s_ctrl_d.resets = 0; + s_ctrl_d.count = 0; + s_ctrl_d.state = Release; + end + Release: + if ((reset_ready_i & s_ctrl_q.request_mask) == s_ctrl_q.request_mask) begin + s_ctrl_d.reset_done = s_ctrl_q.request_mask; + s_ctrl_d.state = Complete; + end + Complete: begin + s_ctrl_d.done = 1'b1; + s_ctrl_d.state = Idle; + end + default: begin + s_ctrl_d.state = Idle; + s_ctrl_d.fault[4] = 1'b1; + end + endcase + if ((s_ctrl_q.state == GateWait || s_ctrl_q.state == Hold || s_ctrl_q.state == Release) && + s_ctrl_q.count >= timeout_i) begin + s_ctrl_d.fault[4] = 1'b1; + s_ctrl_d.done = 1'b1; + s_ctrl_d.state = Idle; + // A failed reset retains isolation; it has no RESET_DONE acknowledgement. + if (s_ctrl_q.reset_command) s_ctrl_d.resets = s_ctrl_q.request_mask; + else s_ctrl_d.gates = s_ctrl_q.previous_gates; + end + if (warm_reset_i) s_ctrl_d = '0; + end + dffr #( + .DATA_WIDTH($bits(control_t)) + ) u_control ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_ctrl_d), + .dat_o (s_ctrl_q) + ); +endmodule diff --git a/rtl/tiny/top/tiny_peripheral_leaf.sv b/rtl/tiny/top/tiny_peripheral_leaf.sv new file mode 100644 index 00000000..ff31010a --- /dev/null +++ b/rtl/tiny/top/tiny_peripheral_leaf.sv @@ -0,0 +1,55 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// The base clock and lifecycle registers never stop. A gate only stops its +// peripheral leaf, at a falling edge. Local reset asserts asynchronously; +// hold/release acknowledgements require five actual leaf clock edges. +module tiny_peripheral_leaf ( + input logic clk_i, + input logic rst_n_i, + input logic gate_i, + input logic reset_i, + output logic clk_o, + output logic rst_n_o, + output logic gated_o, + output logic held_o +); + logic s_gate_sync, s_gate_q; + logic [2:0] s_hold_d, s_hold_q; + cdc_sync #( + .STAGE (2), + .DATA_WIDTH(1) + ) u_gate_sync ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (gate_i), + .dat_o (s_gate_sync) + ); + ndffr #( + .DATA_WIDTH(1) + ) u_gate ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_gate_sync), + .dat_o (s_gate_q) + ); + assign clk_o = clk_i && !s_gate_q; + assign gated_o = s_gate_q; + assign s_hold_d = s_hold_q < 5 ? s_hold_q + 3'd1 : s_hold_q; + dffr #( + .DATA_WIDTH(3) + ) u_hold ( + .clk_i (clk_o), + .rst_n_i(rst_n_i && reset_i), + .dat_i (s_hold_d), + .dat_o (s_hold_q) + ); + assign held_o = s_hold_q == 5; + rst_sync #( + .STAGE(5) + ) u_local_reset ( + .clk_i (clk_o), + .rst_n_i(rst_n_i && !reset_i), + .rst_n_o(rst_n_o) + ); +endmodule diff --git a/rtl/tiny/top/tiny_rcu_regs.svh b/rtl/tiny/top/tiny_rcu_regs.svh new file mode 100644 index 00000000..b71d4806 --- /dev/null +++ b/rtl/tiny/top/tiny_rcu_regs.svh @@ -0,0 +1,30 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 +// Handwritten Tiny RCU ABI 1.0; paired with retrosoc/hal/rcu_regs.h. +`ifndef RETROSOC_TINY_RCU__REGS_SVH +`define RETROSOC_TINY_RCU__REGS_SVH +`define APB4_TINY_RCU__ID 32'h100 +`define APB4_TINY_RCU__VERSION 32'h104 +`define APB4_TINY_RCU__CAPABILITY 32'h108 +`define APB4_TINY_RCU__CLOCK_REQUEST 32'h10c +`define APB4_TINY_RCU__CLOCK_CURRENT 32'h110 +`define APB4_TINY_RCU__COMMAND 32'h114 +`define APB4_TINY_RCU__STATUS 32'h118 +`define APB4_TINY_RCU__TIMEOUT 32'h11c +`define APB4_TINY_RCU__GATE_REQUEST 32'h120 +`define APB4_TINY_RCU__GATE_STATUS 32'h124 +`define APB4_TINY_RCU__RESET_REQUEST 32'h128 +`define APB4_TINY_RCU__RESET_DONE 32'h12c +`define APB4_TINY_RCU__RESET_CAUSE 32'h130 +`define APB4_TINY_RCU__FAULT 32'h134 +`define APB4_TINY_RCU__IRQ_STATE 32'h138 +`define APB4_TINY_RCU__IRQ_ENABLE 32'h13c +`define APB4_TINY_RCU__XIN_HZ 32'h140 +`define APB4_TINY_RCU__REF_HZ 32'h144 +`define APB4_TINY_RCU__SYS_HZ 32'h148 +`define APB4_TINY_RCU__MEM_HZ 32'h14c +`define APB4_TINY_RCU__PCLK_HZ 32'h150 +`define APB4_TINY_RCU__CLINT_HZ 32'h154 +`define APB4_TINY_RCU__TARGET_CAP 32'h158 +`define APB4_TINY_RCU__CLKOUT 32'h15c +`endif diff --git a/rtl/tiny/top/tiny_reset_tree.sv b/rtl/tiny/top/tiny_reset_tree.sv index 45c0dac3..1e50da5a 100644 --- a/rtl/tiny/top/tiny_reset_tree.sv +++ b/rtl/tiny/top/tiny_reset_tree.sv @@ -1,12 +1,17 @@ // Copyright (c) 2026 Yuchi Miao // SPDX-License-Identifier: MulanPSL-2.0 -// One SYS clock, separate reset loads. Leaf zero owns the fabric; the remaining -// leaves follow the canonical APB target order. This is not a clock-domain split. +// SYS-clocked reset distribution with an always-on POR synchronizer. The POR +// source remains on the external reference while all functional leaves release +// on the selected SYS clock. module tiny_reset_tree #( - parameter int LeafCount = 17 + parameter int LeafCount = 17, + parameter bit SplitDomains = 1'b0 ) ( input logic clk_i, + input logic por_clk_i, + input logic [LeafCount-1:0] leaf_clk_i, + input logic backend_reset_n_i, input logic rst_n_i, input logic watchdog_reset_req_i, output logic por_rst_n_o, @@ -16,7 +21,8 @@ module tiny_reset_tree #( ); logic s_system_req_n; - assign s_system_req_n = por_rst_n_o && !watchdog_reset_req_i; + assign s_system_req_n = por_rst_n_o && !watchdog_reset_req_i && + (!SplitDomains || backend_reset_n_i); // The core/debug wrapper adds its five local release edges after every // functional endpoint is ready. No external peripheral clock is a prerequisite. assign cpu_ready_rst_n_o = &leaf_rst_n_o; @@ -24,7 +30,7 @@ module tiny_reset_tree #( rst_sync #( .STAGE(5) ) u_por_rst_sync ( - .clk_i (clk_i), + .clk_i (SplitDomains ? por_clk_i : clk_i), .rst_n_i(rst_n_i), .rst_n_o(por_rst_n_o) ); @@ -42,7 +48,7 @@ module tiny_reset_tree #( rst_sync #( .STAGE(5) ) u_leaf_rst_sync ( - .clk_i (clk_i), + .clk_i (SplitDomains ? leaf_clk_i[leaf] : clk_i), .rst_n_i(system_rst_n_o), .rst_n_o(leaf_rst_n_o[leaf]) ); diff --git a/rtl/tiny/top/tiny_sram.sv b/rtl/tiny/top/tiny_sram.sv new file mode 100644 index 00000000..0d814167 --- /dev/null +++ b/rtl/tiny/top/tiny_sram.sv @@ -0,0 +1,107 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +module tiny_sram ( + input logic clk_i, + input logic rst_n_i, + input logic perf_enable_i, + input logic perf_clear_i, + tiny_sram_port_if.slave local_ports [2], + axi4_if.slave external_ports[4], + apb4_if.slave cfg_apb4 +); + logic [3:0] s_local_ready[2], s_local_valid[2]; + logic [31:0] s_local_data[2] [4]; + logic [ 3:0] s_events [6]; + logic [31:0] s_count_d[6], s_count_q[6]; + logic [ 2:0] s_increment[6]; + logic [32:0] s_sum [6]; + + for (genvar group = 0; group < 4; group++) begin : gen_group + tiny_sram_port_if u_ports[3] (); + for (genvar client = 0; client < 2; client++) begin : gen_local + assign u_ports[client].req_valid = local_ports[client].req_valid && + (local_ports[client].addr[16:15] == 2'(group)); + assign u_ports[client].addr = local_ports[client].addr; + assign u_ports[client].write = local_ports[client].write; + assign u_ports[client].wdata = local_ports[client].wdata; + assign u_ports[client].wstrb = local_ports[client].wstrb; + assign u_ports[client].rsp_ready = local_ports[client].rsp_ready && + (local_ports[client].addr[16:15] == 2'(group)); + assign s_local_ready[client][group] = u_ports[client].req_ready; + assign s_local_valid[client][group] = u_ports[client].rsp_valid; + assign s_local_data[client][group] = u_ports[client].rdata; + end + tiny_sram_group u_group ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .ports (u_ports) + ); + tiny_sram_axi4 #( + .GroupBase(32'h3000_0000 + 32'(group) * 32'h8000) + ) u_external ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .axi4 (external_ports[group]), + .memory (u_ports[2]) + ); + assign s_events[0][group] = external_ports[group].arvalid && external_ports[group].arready; + assign s_events[1][group] = external_ports[group].awvalid && external_ports[group].awready; + assign s_events[2][group] = external_ports[group].rvalid && external_ports[group].rready; + assign s_events[3][group] = external_ports[group].wvalid && external_ports[group].wready; + assign s_events[4][group] = + (external_ports[group].arvalid && !external_ports[group].arready) || + (external_ports[group].awvalid && !external_ports[group].awready) || + (external_ports[group].wvalid && !external_ports[group].wready) || + (external_ports[group].rvalid && !external_ports[group].rready) || + (external_ports[group].bvalid && !external_ports[group].bready); + assign s_events[5][group] = + (external_ports[group].bvalid && external_ports[group].bready && + (external_ports[group].bresp != 2'b00)) || + (external_ports[group].rvalid && external_ports[group].rready && + external_ports[group].rlast && (external_ports[group].rresp != 2'b00)); + end + for (genvar client = 0; client < 2; client++) begin : gen_local_response + assign local_ports[client].req_ready = s_local_ready[client][local_ports[client].addr[16:15]]; + assign local_ports[client].rsp_valid = s_local_valid[client][local_ports[client].addr[16:15]]; + assign local_ports[client].rdata = s_local_data[client][local_ports[client].addr[16:15]]; + end + // Preserve the public external-AXI counter meanings. Four simultaneous stalls + // are one stalled cycle; request/beat events are counted individually. + for (genvar counter = 0; counter < 6; counter++) begin : gen_counter + if (counter == 4) begin : gen_cycle_count + assign s_increment[counter] = {2'b0, |s_events[counter]}; + end else begin : gen_event_count + assign s_increment[counter] = {2'b0, s_events[counter][0]} + + {2'b0, s_events[counter][1]} + {2'b0, s_events[counter][2]} + + {2'b0, s_events[counter][3]}; + end + assign s_sum[counter] = {1'b0, s_count_q[counter]} + {30'd0, s_increment[counter]}; + assign s_count_d[counter] = perf_clear_i ? 32'd0 : + perf_enable_i ? (s_sum[counter][32] ? 32'hffff_ffff : s_sum[counter][31:0]) : + s_count_q[counter]; + dffr #( + .DATA_WIDTH(32) + ) u_counter ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_count_d[counter]), + .dat_o (s_count_q[counter]) + ); + end + onchip_ram_reg #( + .Present (1'b1), + .CapacityKiB(128), + .DataBytes (4) + ) u_reg ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .apb4 (cfg_apb4), + .perf_read_requests_i (s_count_q[0]), + .perf_write_requests_i (s_count_q[1]), + .perf_read_beats_i (s_count_q[2]), + .perf_write_beats_i (s_count_q[3]), + .perf_stall_cycles_i (s_count_q[4]), + .perf_error_responses_i(s_count_q[5]) + ); +endmodule diff --git a/rtl/tiny/top/tiny_sram_axi4.sv b/rtl/tiny/top/tiny_sram_axi4.sv new file mode 100644 index 00000000..9c777a70 --- /dev/null +++ b/rtl/tiny/top/tiny_sram_axi4.sv @@ -0,0 +1,232 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// One external transaction per group. Address ownership lasts through the +// terminal response, but the macro is arbitrated separately for every beat. +// W may precede AW on the bus; it is backpressured until its AW is accepted. +module tiny_sram_axi4 #( + parameter logic [31:0] GroupBase = 32'h3000_0000 +) ( + input logic clk_i, + input logic rst_n_i, + axi4_if.slave axi4, + tiny_sram_port_if.master memory +); + typedef enum logic [2:0] { + Idle, + ReadStream, + ReadError, + WriteStream, + WriteResponse, + WriteDrain + } state_e; + typedef struct packed { + state_e state; + logic prefer_write; + logic id; + logic [31:0] addr; + logic [7:0] len, beat; + logic [2:0] size; + logic [1:0] burst, resp; + logic [8:0] issued, completed; + logic last; + } transaction_t; + typedef struct packed { + logic [31:0] addr, data; + logic [3:0] strb; + } write_t; + transaction_t s_transaction_d, s_transaction_q; + write_t s_write_input, s_write_output; + logic [31:0] s_next_addr; + logic [1:0] s_ar_resp, s_aw_resp; + logic [3:0] s_lane_mask; + logic s_expected_last, s_write_legal; + logic s_write_full, s_write_empty, s_write_push, s_write_pop; + logic s_req_accept, s_resp_accept, s_write_accept; + + function automatic logic [1:0] address_response(input logic [31:0] addr_i, + input logic [7:0] len_i, input logic [2:0] size_i, + input logic [1:0] burst_i, input logic lock_i); + logic [32:0] bytes_count, last_addr; + logic wrap_legal; + bytes_count = ({25'd0, len_i} + 33'd1) << size_i; + wrap_legal = (len_i == 8'd1) || (len_i == 8'd3) || (len_i == 8'd7) || (len_i == 8'd15); + last_addr = {1'b0, addr_i} + bytes_count - 33'd1; + if (burst_i == 2'b00) last_addr = {1'b0, addr_i} + (33'd1 << size_i) - 33'd1; + if (burst_i == 2'b10) + last_addr = ({1'b0, addr_i} & ~(bytes_count - 33'd1)) + bytes_count - 33'd1; + if (lock_i || (len_i > 8'd15) || (size_i > 3'd2) || + (burst_i == 2'b11) || ((burst_i == 2'b10) && !wrap_legal) || + ((addr_i & ((32'd1 << size_i) - 32'd1)) != 32'd0) || last_addr[32] || + (addr_i[31:12] != last_addr[31:12])) + return 2'b10; + if ((addr_i < GroupBase) || (last_addr[31:0] > (GroupBase + 32'h7fff))) return 2'b11; + return 2'b00; + endfunction + + assign s_ar_resp = address_response( + axi4.araddr, axi4.arlen, axi4.arsize, axi4.arburst, axi4.arlock + ); + assign s_aw_resp = address_response( + axi4.awaddr, axi4.awlen, axi4.awsize, axi4.awburst, axi4.awlock + ); + assign axi4.arready = (s_transaction_q.state == Idle) && + !(s_transaction_q.prefer_write && axi4.awvalid); + assign axi4.awready = (s_transaction_q.state == Idle) && + (s_transaction_q.prefer_write || !axi4.arvalid); + assign axi4.wready = ((s_transaction_q.state == WriteStream) && !s_transaction_q.last && + (!s_write_full || s_write_pop)) || (s_transaction_q.state == WriteDrain); + assign axi4.rvalid = (s_transaction_q.state == ReadError) || + ((s_transaction_q.state == ReadStream) && memory.rsp_valid); + assign axi4.rid = s_transaction_q.id; + assign axi4.rdata = (s_transaction_q.state == ReadStream) ? memory.rdata : 32'd0; + assign axi4.rresp = s_transaction_q.resp; + assign axi4.rlast = s_transaction_q.beat == s_transaction_q.len; + assign axi4.ruser = '0; + assign axi4.bvalid = s_transaction_q.state == WriteResponse; + assign axi4.bid = s_transaction_q.id; + assign axi4.bresp = s_transaction_q.resp; + assign axi4.buser = '0; + + // Read responses stay in the group's reserved queue until R handshakes. + // Issuance and retirement have independent positions within the same burst. + assign memory.req_valid = ((s_transaction_q.state == ReadStream) && + (s_transaction_q.issued <= {1'b0, s_transaction_q.len})) || + ((s_transaction_q.state == WriteStream) && !s_write_empty); + assign memory.addr = memory.write ? s_write_output.addr : s_transaction_q.addr; + assign memory.write = s_transaction_q.state == WriteStream; + assign memory.wdata = s_write_output.data; + assign memory.wstrb = s_write_output.strb; + assign memory.rsp_ready = ((s_transaction_q.state == ReadStream) && axi4.rready) || + (s_transaction_q.state == WriteStream); + assign s_req_accept = memory.req_valid && memory.req_ready; + assign s_resp_accept = memory.rsp_valid && memory.rsp_ready; + assign s_write_accept = axi4.wvalid && axi4.wready; + assign s_write_push = (s_transaction_q.state == WriteStream) && s_write_accept && s_write_legal; + assign s_write_pop = (s_transaction_q.state == WriteStream) && s_req_accept; + assign s_write_input = {s_transaction_q.addr, axi4.wdata, axi4.wstrb}; + // Capture W before it becomes eligible at the macro. A full queue can + // replace its head on the same edge that the arbiter commits that word. + fifo #( + .DATA_WIDTH ($bits(write_t)), + .BUFFER_DEPTH(2) + ) u_write_fifo ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .flush_i(1'b0), + .push_i (s_write_push), + .full_o (s_write_full), + .dat_i (s_write_input), + .pop_i (s_write_pop), + .empty_o(s_write_empty), + .dat_o (s_write_output), + .cnt_o () // Full/empty and accepted/completed counts cover ownership. + ); + assign s_lane_mask = 4'(((32'd1 << (32'd1 << s_transaction_q.size)) - 32'd1) + << s_transaction_q.addr[1:0]); + assign s_expected_last = s_transaction_q.beat == s_transaction_q.len; + assign s_write_legal = ((axi4.wstrb & ~s_lane_mask) == 4'd0) && (axi4.wlast == s_expected_last); + axi4_addr_gen #( + .ADDR_WIDTH(32) + ) u_address ( + .alen_i (s_transaction_q.len), + .asize_i (s_transaction_q.size), + .aburst_i(s_transaction_q.burst), + .addr_i (s_transaction_q.addr), + .addr_o (s_next_addr) + ); + + always_comb begin + s_transaction_d = s_transaction_q; + unique case (s_transaction_q.state) + Idle: begin + s_transaction_d.beat = '0; + s_transaction_d.issued = '0; + s_transaction_d.completed = '0; + s_transaction_d.last = 1'b0; + if (axi4.arvalid && axi4.arready) begin + s_transaction_d.prefer_write = 1'b1; + s_transaction_d.id = axi4.arid; + s_transaction_d.addr = axi4.araddr; + // Match the shared target's malformed-address error response length. + s_transaction_d.len = (s_ar_resp == 2'b10) ? 8'd0 : axi4.arlen; + s_transaction_d.size = axi4.arsize; + s_transaction_d.burst = axi4.arburst; + s_transaction_d.resp = s_ar_resp; + s_transaction_d.state = (s_ar_resp == 2'b00) ? ReadStream : ReadError; + end else if (axi4.awvalid && axi4.awready) begin + s_transaction_d.prefer_write = 1'b0; + s_transaction_d.id = axi4.awid; + s_transaction_d.addr = axi4.awaddr; + s_transaction_d.len = (s_aw_resp == 2'b10) ? 8'd0 : axi4.awlen; + s_transaction_d.size = axi4.awsize; + s_transaction_d.burst = axi4.awburst; + s_transaction_d.resp = s_aw_resp; + s_transaction_d.state = (s_aw_resp == 2'b00) ? WriteStream : WriteDrain; + end + end + ReadStream: begin + if (s_req_accept) begin + s_transaction_d.issued = s_transaction_q.issued + 9'd1; + s_transaction_d.addr = s_next_addr; + end + if (s_resp_accept) begin + s_transaction_d.completed = s_transaction_q.completed + 9'd1; + if (axi4.rlast) s_transaction_d.state = Idle; + else s_transaction_d.beat = s_transaction_q.beat + 8'd1; + end + end + ReadError: + if (axi4.rready) begin + if (axi4.rlast) s_transaction_d.state = Idle; + else s_transaction_d.beat = s_transaction_q.beat + 8'd1; + end + WriteStream: begin + if (s_write_accept) begin + s_transaction_d.last = s_expected_last || axi4.wlast; + s_transaction_d.beat = s_transaction_q.beat + 8'd1; + s_transaction_d.addr = s_next_addr; + if (!s_write_legal) s_transaction_d.resp = 2'b10; + end + if (s_req_accept) s_transaction_d.issued = s_transaction_q.issued + 9'd1; + if (s_resp_accept) s_transaction_d.completed = s_transaction_q.completed + 9'd1; + // Errors do not discard older queued writes. B acknowledges all + // accepted legal beats only after their macro-commit responses drain. + if (s_transaction_q.last && s_write_empty && + (s_transaction_q.issued == s_transaction_q.completed)) + s_transaction_d.state = WriteResponse; + end + WriteDrain: + if (axi4.wvalid) begin + if (s_expected_last || axi4.wlast) s_transaction_d.state = WriteResponse; + else s_transaction_d.beat = s_transaction_q.beat + 8'd1; + end + WriteResponse: if (axi4.bready) s_transaction_d.state = Idle; + default: s_transaction_d.state = Idle; + endcase + end + dffr #( + .DATA_WIDTH($bits(transaction_t)) + ) u_transaction ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_transaction_d), + .dat_o (s_transaction_q) + ); +`ifdef HAVE_SVA + assert property (@(posedge clk_i) disable iff (!rst_n_i) + s_transaction_q.completed <= s_transaction_q.issued); + assert property (@(posedge clk_i) disable iff (!rst_n_i) + axi4.bvalid |-> s_write_empty && + (s_transaction_q.completed == s_transaction_q.issued)); + assert property (@(posedge clk_i) disable iff (!rst_n_i) + axi4.rvalid && !axi4.rready |=> axi4.rvalid && + $stable( + {axi4.rdata, axi4.rid, axi4.rresp, axi4.rlast} + )); + assert property (@(posedge clk_i) disable iff (!rst_n_i) + axi4.bvalid && !axi4.bready |=> axi4.bvalid && $stable( + {axi4.bid, axi4.bresp} + )); +`endif +endmodule diff --git a/rtl/tiny/top/tiny_sram_group.sv b/rtl/tiny/top/tiny_sram_group.sv new file mode 100644 index 00000000..a1c1c357 --- /dev/null +++ b/rtl/tiny/top/tiny_sram_group.sv @@ -0,0 +1,206 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// One independently arbitrated 32 KiB group. Arbitration advances on the +// physical macro edge, never on an AXI address or burst reservation. Captured +// responses isolate all three clients from subsequent macro output changes. +module tiny_sram_group ( + input logic clk_i, + input logic rst_n_i, + tiny_sram_port_if.slave ports [3] +); + logic [2:0] s_req, s_grant; + logic [1:0] s_selected; + logic s_issue; + logic [1:0] s_rsp_valid_d; + logic [2:0] s_rsp_valid_q; + logic [31:0] s_rsp_data_d[2], s_rsp_data_q[3]; + logic s_read_pending_d, s_read_pending_q; + logic [1:0] s_read_owner_d, s_read_owner_q; + logic [2:0] s_read_bank_d, s_read_bank_q; + logic [2:0] s_write; + logic [31:0] s_addr[3], s_wdata[3]; + logic [ 3:0] s_wstrb [3]; + logic [31:0] s_bank_data [8]; + logic [ 7:0] s_bank_select; + + for (genvar client = 0; client < 3; client++) begin : gen_client + assign s_addr[client] = ports[client].addr; + assign s_write[client] = ports[client].write; + assign s_wdata[client] = ports[client].wdata; + assign s_wstrb[client] = ports[client].wstrb; + assign ports[client].req_ready = s_grant[client]; + assign ports[client].rsp_valid = s_rsp_valid_q[client]; + assign ports[client].rdata = s_rsp_data_q[client]; + + if (client < 2) begin : gen_local_response + assign s_req[client] = ports[client].req_valid && !s_rsp_valid_q[client] && + !(s_read_pending_q && (s_read_owner_q == 2'(client))); + always_comb begin + s_rsp_valid_d[client] = s_rsp_valid_q[client] && !ports[client].rsp_ready; + s_rsp_data_d[client] = s_rsp_data_q[client]; + if (s_read_pending_q && (s_read_owner_q == 2'(client))) begin + s_rsp_valid_d[client] = 1'b1; + s_rsp_data_d[client] = s_bank_data[s_read_bank_q]; + end + if (s_grant[client] && s_write[client]) begin + s_rsp_valid_d[client] = 1'b1; + s_rsp_data_d[client] = '0; + end + end + dffr #( + .DATA_WIDTH(1) + ) u_response_valid ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_rsp_valid_d[client]), + .dat_o (s_rsp_valid_q[client]) + ); + dffr #( + .DATA_WIDTH(32) + ) u_response_data ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_rsp_data_d[client]), + .dat_o (s_rsp_data_q[client]) + ); + end else begin : gen_external_response + logic s_empty, s_full, s_pop, s_push, s_pending; + logic [ 1:0] s_count; + logic [ 2:0] s_reserved; + logic [31:0] s_data; + assign s_pending = s_read_pending_q && (s_read_owner_q == 2'(client)); + assign s_reserved = {1'b0, s_count} + {2'b0, s_pending}; + assign s_pop = ports[client].rsp_ready && !s_empty; + // Include the macro read already in flight. A returning word must fit + // even if the consumer removes READY immediately after this issue. + // A same-client read return and write commit cannot share one FIFO push. + assign s_req[client] = ports[client].req_valid && + ((s_reserved < 3'd2) || ((s_reserved == 3'd2) && s_pop)) && + !(s_pending && s_write[client]); + assign s_push = s_pending || (s_grant[client] && s_write[client]); + assign s_data = s_pending ? s_bank_data[s_read_bank_q] : 32'd0; + assign s_rsp_valid_q[client] = !s_empty; + fifo #( + .DATA_WIDTH (32), + .BUFFER_DEPTH(2) + ) u_response_fifo ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .flush_i(1'b0), + .push_i (s_push), + .full_o (s_full), + .dat_i (s_data), + .pop_i (s_pop), + .empty_o(s_empty), + .dat_o (s_rsp_data_q[client]), + .cnt_o (s_count) + ); +`ifdef FORMAL + always_ff @(posedge clk_i) begin + if (rst_n_i) begin + assert (s_reserved <= 3'd2); + assert (!s_push || !s_full || s_pop); + assert (!(s_pending && s_grant[client] && s_write[client])); + end + end +`endif +`ifdef HAVE_SVA + assert property (@(posedge clk_i) disable iff (!rst_n_i) s_reserved <= 3'd2); + assert property (@(posedge clk_i) disable iff (!rst_n_i) !s_push || !s_full || s_pop); +`endif + end + end + + round_robin_arbiter #( + .CLIENTS(3) + ) u_arbiter ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .advance_i (s_issue), + .request_i (s_req), + .grant_o (s_grant), + .selected_o(s_selected), + .valid_o (s_issue) + ); + assign s_read_pending_d = s_issue && !s_write[s_selected]; + assign s_read_owner_d = s_selected; + assign s_read_bank_d = s_addr[s_selected][14:12]; + dffr #( + .DATA_WIDTH(1) + ) u_read_pending ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .dat_i (s_read_pending_d), + .dat_o (s_read_pending_q) + ); + dffer #( + .DATA_WIDTH(2) + ) u_read_owner ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .en_i (s_read_pending_d), + .dat_i (s_read_owner_d), + .dat_o (s_read_owner_q) + ); + dffer #( + .DATA_WIDTH(3) + ) u_read_bank ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .en_i (s_read_pending_d), + .dat_i (s_read_bank_d), + .dat_o (s_read_bank_q) + ); + + for (genvar bank = 0; bank < 8; bank++) begin : gen_bank + assign s_bank_select[bank] = s_issue && (s_addr[s_selected][14:12] == 3'(bank)); + tc_sram_1024x32 u_ram ( + .clk_i (clk_i), + .cs_i (s_bank_select[bank]), + .addr_i(s_addr[s_selected][11:2]), + .data_i(s_wdata[s_selected]), + .mask_i(s_wstrb[s_selected]), + .wren_i(s_write[s_selected]), + .data_o(s_bank_data[bank]) + ); + end + +`ifdef FORMAL + // Service-count proof: only continuously eligible requests accumulate + // bypasses. No wall-clock liveness is assumed for a withheld response. + for (genvar client = 0; client < 3; client++) begin : gen_formal + logic [1:0] s_bypasses_q; + logic s_past_valid_q = 1'b0; + always_ff @(posedge clk_i) begin + s_past_valid_q <= 1'b1; + if (!rst_n_i || !s_req[client] || s_grant[client]) s_bypasses_q <= '0; + else if (s_issue) s_bypasses_q <= s_bypasses_q + 2'd1; + if (rst_n_i) begin + assert ($onehot0(s_grant)); + assert (s_bypasses_q <= 2'd2); + assert (!(s_req[client] && (s_bypasses_q == 2'd2) && s_issue && !s_grant[client])); + if (client < 2) + assert (!(s_rsp_valid_q[client] && s_read_pending_q && (s_read_owner_q == 2'(client)))); + end + if (s_past_valid_q && rst_n_i && $past( + rst_n_i && ports[client].rsp_valid && !ports[client].rsp_ready + )) begin + assert (ports[client].rsp_valid); + assert (ports[client].rdata == $past(ports[client].rdata)); + end + end + end +`endif +`ifdef HAVE_SVA + assert property (@(posedge clk_i) disable iff (!rst_n_i) $onehot0(s_grant)); + assert property (@(posedge clk_i) disable iff (!rst_n_i) $onehot0(s_bank_select)); + for (genvar client = 0; client < 3; client++) begin : gen_assert + assert property (@(posedge clk_i) disable iff (!rst_n_i) + ports[client].rsp_valid && !ports[client].rsp_ready |=> + ports[client].rsp_valid && $stable( + ports[client].rdata + )); + end +`endif +endmodule diff --git a/rtl/tiny/top/tiny_sram_port_if.sv b/rtl/tiny/top/tiny_sram_port_if.sv new file mode 100644 index 00000000..ff856f59 --- /dev/null +++ b/rtl/tiny/top/tiny_sram_port_if.sv @@ -0,0 +1,25 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +// One word operation, byte strobes, one ordered response per accepted request. +// Local I/D retain their address through the response handshake (bank routing) +// and have one operation outstanding. A fixed-group external client may have +// two operations outstanding, including a reserved macro read return. It may +// advance its request address after acceptance; responses remain in order. +interface tiny_sram_port_if; + logic req_valid, req_ready; + logic [31:0] addr, wdata; + logic write; + logic [3:0] wstrb; + logic rsp_valid, rsp_ready; + logic [31:0] rdata; + + modport master( + output req_valid, addr, wdata, write, wstrb, rsp_ready, + input req_ready, rsp_valid, rdata + ); + modport slave( + input req_valid, addr, wdata, write, wstrb, rsp_ready, + output req_ready, rsp_valid, rdata + ); +endinterface diff --git a/rtl/tiny/top/tiny_sysctrl.sv b/rtl/tiny/top/tiny_sysctrl.sv index 7a0ac57a..fd730151 100644 --- a/rtl/tiny/top/tiny_sysctrl.sv +++ b/rtl/tiny/top/tiny_sysctrl.sv @@ -1,30 +1,95 @@ // Copyright (c) 2026 Yuchi Miao // SPDX-License-Identifier: MulanPSL-2.0 `include "sysctrl_define.svh" +`include "tiny_rcu_regs.svh" -module tiny_sysctrl ( +module tiny_sysctrl #( + parameter bit PllPresent = 1'b0, + parameter bit PllProfiles = 1'b0, + parameter int unsigned XinHzValue = 24_000_000, + parameter logic [13:0] TargetMask = 14'd0 +) ( input logic clk_i, input logic rst_n_i, + input logic clk_ref_i, + input logic rst_ref_n_i, + output logic rcu_link_ready_o, apb4_if.slave apb4, input logic fault_valid_i, + input logic [ 3:0] fault_count_i, input logic [31:0] fault_addr_i, input logic fault_write_i, - input logic fault_master_i, + input logic [ 1:0] fault_master_i, input logic [ 1:0] fault_resp_i, input logic [63:0] cpu_wait_i, input logic [63:0] dma_wait_i, input logic rtc_wake_i, + input logic clock_done_i, + input logic [ 4:0] clock_fault_i, + input logic [ 1:0] clock_current_i, + input logic pll_locked_i, + input logic pll_selected_i, + input logic warm_reset_i, + input logic watchdog_reset_i, + input logic hart_reset_i, + output logic system_reset_o, + output logic rcu_irq_o, + output logic [31:0] clock_timeout_o, + input logic periph_done_i, + input logic [ 4:0] periph_fault_i, + input logic [13:0] gate_actual_i, + input logic [13:0] reset_done_i, + output logic periph_apply_o, + output logic periph_reset_o, + output logic [13:0] periph_request_o, + output logic [31:0] clkout_control_o, + input logic clkout_idle_i, output logic perf_enable_o, output logic perf_clear_o, output logic test_done_o, output logic test_pass_o, - output logic [ 7:0] test_code_o + output logic [ 7:0] test_code_o, + output logic [31:0] xin_hz_o, + output logic [31:0] ref_hz_o, + output logic [31:0] sys_hz_o, + output logic [31:0] mem_hz_o, + output logic [31:0] pclk_hz_o, + output logic [31:0] clint_hz_o, + output logic [ 1:0] rcu_current_o, + output logic rcu_busy_o, + output logic clock_apply_o, + output logic [ 1:0] clock_profile_o ); + localparam logic [31:0] RcuId = `APB4_TINY_RCU__ID; + localparam logic [31:0] RcuVersion = `APB4_TINY_RCU__VERSION; + localparam logic [31:0] RcuCapability = `APB4_TINY_RCU__CAPABILITY; + localparam logic [31:0] ClockRequest = `APB4_TINY_RCU__CLOCK_REQUEST; + localparam logic [31:0] ClockCurrent = `APB4_TINY_RCU__CLOCK_CURRENT; + localparam logic [31:0] RcuCommand = `APB4_TINY_RCU__COMMAND; + localparam logic [31:0] RcuStatus = `APB4_TINY_RCU__STATUS; + localparam logic [31:0] TimeoutRefCycles = `APB4_TINY_RCU__TIMEOUT; + localparam logic [31:0] GateRequest = `APB4_TINY_RCU__GATE_REQUEST; + localparam logic [31:0] GateStatus = `APB4_TINY_RCU__GATE_STATUS; + localparam logic [31:0] ResetRequest = `APB4_TINY_RCU__RESET_REQUEST; + localparam logic [31:0] ResetDone = `APB4_TINY_RCU__RESET_DONE; + localparam logic [31:0] ResetCause = `APB4_TINY_RCU__RESET_CAUSE; + localparam logic [31:0] RcuFault = `APB4_TINY_RCU__FAULT; + localparam logic [31:0] RcuIrqState = `APB4_TINY_RCU__IRQ_STATE; + localparam logic [31:0] RcuIrqEnable = `APB4_TINY_RCU__IRQ_ENABLE; + localparam logic [31:0] XinHz = `APB4_TINY_RCU__XIN_HZ; + localparam logic [31:0] RefHz = `APB4_TINY_RCU__REF_HZ; + localparam logic [31:0] SysHz = `APB4_TINY_RCU__SYS_HZ; + localparam logic [31:0] MemHz = `APB4_TINY_RCU__MEM_HZ; + localparam logic [31:0] PclkHz = `APB4_TINY_RCU__PCLK_HZ; + localparam logic [31:0] ClintHz = `APB4_TINY_RCU__CLINT_HZ; + localparam logic [31:0] TargetCapability = `APB4_TINY_RCU__TARGET_CAP; + localparam logic [31:0] ClkoutControl = `APB4_TINY_RCU__CLKOUT; logic [31:0] s_test_d, s_test_q; logic [31:0] s_fault_addr_d, s_fault_addr_q; logic [31:0] s_fault_count_d, s_fault_count_q; logic [4:0] s_fault_stat_d, s_fault_stat_q; - logic s_fault_master_d, s_fault_master_q; + logic [1:0] s_fault_master_d, s_fault_master_q; + logic [32:0] s_fault_sum; logic [1:0] s_fault_resp_d, s_fault_resp_q; logic s_perf_en_d, s_perf_en_q; logic s_wake_d, s_wake_q; @@ -36,23 +101,137 @@ module tiny_sysctrl ( logic [31:0] s_read_data; logic [31:0] s_offset; logic [31:0] s_rdata_d, s_rdata_q; + logic [1:0] s_clk_req_d, s_clk_req_q; + logic [1:0] s_clk_cur_d, s_clk_cur_q; + logic [1:0] s_clk_latched_d, s_clk_latched_q; + logic s_system_reset_d, s_system_reset_q; + logic s_periph_apply_d, s_periph_apply_q, s_periph_reset_d, s_periph_reset_q; + logic [13:0] s_periph_mask_d, s_periph_mask_q; + logic [3:0] s_active_cmd_d, s_active_cmd_q; + logic s_clock_apply_d, s_clock_apply_q; + logic s_rcu_busy_d, s_rcu_busy_q; + logic [31:0] s_timeout_ref_d, s_timeout_ref_q; + logic [31:0] s_gate_req_d, s_gate_req_q; + logic [31:0] s_gate_stat_d, s_gate_stat_q; + logic [31:0] s_rst_req_d, s_rst_req_q; + logic [31:0] s_reset_done_d, s_reset_done_q; + logic [31:0] s_reset_cause_d, s_reset_cause_q; + logic [31:0] s_rcu_fault_d, s_rcu_fault_q; + logic [31:0] s_rcu_irq_state_d, s_rcu_irq_state_q; + logic [31:0] s_rcu_irq_en_d, s_rcu_irq_en_q; + logic [31:0] s_clkout_control_d, s_clkout_control_q; + logic [31:0] s_sys_hz_d, s_sys_hz_q; + logic [31:0] s_mem_hz_d, s_mem_hz_q; + logic [31:0] s_pclk_hz_d, s_pclk_hz_q; + logic [31:0] s_clkout_hz; + + logic s_rcu_selected, s_rcu_req, s_rcu_write, s_rcu_valid, s_rcu_writable, s_rcu_err; + logic s_rcu_config_err; + logic s_rcu_ready_d, s_rcu_ready_q, s_rcu_err_d, s_rcu_err_q; + logic [31:0] s_rcu_offset, s_rcu_read_data, s_rcu_rdata_d, s_rcu_rdata_q; + apb4_if u_rcu_src ( + .pclk (clk_i), + .presetn(rst_n_i) + ); + apb4_if u_rcu_dst ( + .pclk (clk_ref_i), + .presetn(rst_ref_n_i) + ); + + assign s_rcu_selected = apb4.paddr[11:8] == 4'h1; + assign u_rcu_src.paddr = apb4.paddr; + assign u_rcu_src.pprot = apb4.pprot; + assign u_rcu_src.pwrite = apb4.pwrite; + assign u_rcu_src.pwdata = apb4.pwdata; + assign u_rcu_src.pstrb = apb4.pstrb; + assign u_rcu_src.psel = apb4.psel && s_rcu_selected; + assign u_rcu_src.penable = apb4.penable; + tiny_apb_async_bridge u_rcu_cdc ( + .src_clk_i (clk_i), + .src_rst_n_i(rst_n_i), + .dst_clk_i (clk_ref_i), + .dst_rst_n_i(rst_ref_n_i), + .disabled_i (1'b0), + .idle_o (), + .ready_o (rcu_link_ready_o), + .src_apb4 (u_rcu_src), + .dst_apb4 (u_rcu_dst) + ); + assign s_rcu_offset = {20'd0, u_rcu_dst.paddr[11:0]}; + assign s_rcu_req = u_rcu_dst.psel && u_rcu_dst.penable && !s_rcu_ready_q; + assign s_rcu_err = (u_rcu_dst.paddr[1:0] != 0) || !s_rcu_valid || + (u_rcu_dst.pwrite && (!s_rcu_writable || u_rcu_dst.pstrb != 4'hf)) || + (!u_rcu_dst.pwrite && s_rcu_offset == RcuCommand) || + (u_rcu_dst.pwrite && s_rcu_config_err); + assign s_rcu_write = s_rcu_req && u_rcu_dst.pwrite && !s_rcu_err; + assign s_rcu_ready_d = s_rcu_req; + assign s_rcu_err_d = s_rcu_req && s_rcu_err; + assign s_rcu_rdata_d = s_rcu_req ? s_rcu_read_data : s_rcu_rdata_q; + assign u_rcu_dst.pready = s_rcu_ready_q; + assign u_rcu_dst.pslverr = s_rcu_err_q; + assign u_rcu_dst.prdata = s_rcu_rdata_q; + dffr #( + .DATA_WIDTH(1) + ) u_rcu_ready ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_rcu_ready_d), + .dat_o (s_rcu_ready_q) + ); + dffr #( + .DATA_WIDTH(1) + ) u_rcu_error ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_rcu_err_d), + .dat_o (s_rcu_err_q) + ); + dffr #( + .DATA_WIDTH(32) + ) u_rcu_rdata ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_rcu_rdata_d), + .dat_o (s_rcu_rdata_q) + ); assign s_offset = {20'd0, apb4.paddr[11:0]}; - assign s_req = apb4.psel && apb4.penable && !s_ready_q; + assign s_req = apb4.psel && !s_rcu_selected && apb4.penable && !s_ready_q; assign s_write = s_req && apb4.pwrite && !s_access_err; assign s_access_err = (apb4.paddr[1:0] != 2'd0) || !s_valid || - (apb4.pwrite && (!s_writable || (apb4.pstrb != 4'hf))); + (apb4.pwrite && (!s_writable || (apb4.pstrb != 4'hf))) || + (!apb4.pwrite && (s_offset == RcuCommand)); assign s_ready_d = s_req; assign s_err_d = s_req && s_access_err; assign s_rdata_d = s_req ? s_read_data : s_rdata_q; - assign apb4.pready = s_ready_q; - assign apb4.pslverr = s_err_q; - assign apb4.prdata = s_rdata_q; + assign apb4.pready = s_rcu_selected ? u_rcu_src.pready : s_ready_q; + assign apb4.pslverr = s_rcu_selected ? u_rcu_src.pslverr : s_err_q; + assign apb4.prdata = s_rcu_selected ? u_rcu_src.prdata : s_rdata_q; assign perf_enable_o = s_perf_en_q; assign perf_clear_o = s_write && (s_offset == `APB4_SYSCTRL__PERF_CTRL) && apb4.pwdata[1]; assign test_done_o = s_test_q[31]; assign test_pass_o = s_test_q[0]; assign test_code_o = s_test_q[15:8]; + assign xin_hz_o = XinHzValue; + assign ref_hz_o = 32'd24_000_000; + assign sys_hz_o = s_sys_hz_q; + assign mem_hz_o = s_mem_hz_q; + assign pclk_hz_o = s_pclk_hz_q; + assign clint_hz_o = 32'd1_000_000; + assign rcu_current_o = s_clk_cur_q; + assign rcu_busy_o = s_rcu_busy_q; + assign clock_apply_o = s_clock_apply_q; + assign clock_profile_o = s_clk_latched_q; + assign clock_timeout_o = s_timeout_ref_q; + assign clkout_control_o = s_clkout_control_q; + assign s_clkout_hz = u_rcu_dst.pwdata[2:0] == 2 ? s_sys_hz_q : + u_rcu_dst.pwdata[2:0] == 3 ? s_mem_hz_q : + u_rcu_dst.pwdata[2:0] == 4 ? s_pclk_hz_q : 32'd24_000_000; + assign periph_apply_o = s_periph_apply_q; + assign periph_reset_o = s_periph_reset_q; + assign periph_request_o = s_periph_mask_q; + assign system_reset_o = s_system_reset_q; + assign rcu_irq_o = |(s_rcu_irq_state_q[1:0] & s_rcu_irq_en_q[1:0]); always_comb begin s_valid = 1'b1; @@ -68,7 +247,7 @@ module tiny_sysctrl ( end `APB4_SYSCTRL__FAULT_ADDR: s_read_data = s_fault_addr_q; `APB4_SYSCTRL__FAULT_COUNT: s_read_data = s_fault_count_q; - `APB4_SYSCTRL__FAULT_MASTER: s_read_data = {31'd0, s_fault_master_q}; + `APB4_SYSCTRL__FAULT_MASTER: s_read_data = {30'd0, s_fault_master_q}; `APB4_SYSCTRL__FAULT_DETAIL: s_read_data = {30'd0, s_fault_resp_q}; `APB4_SYSCTRL__PERF_CTRL: begin s_read_data = {31'd0, s_perf_en_q}; @@ -89,6 +268,276 @@ module tiny_sysctrl ( default: s_valid = 1'b0; endcase end + always_comb begin + s_rcu_valid = 1'b1; + s_rcu_writable = 1'b0; + s_rcu_read_data = 32'd0; + unique case (s_rcu_offset) + RcuId: s_rcu_read_data = 32'h5452_4355; + RcuVersion: s_rcu_read_data = 32'h0001_0000; + // Advertise only the instantiated product backend and lifecycle paths. + RcuCapability: + s_rcu_read_data = 32'h0000_0201 | + (PllPresent ? 32'h100 : 32'h2) | (PllProfiles ? 32'hc : 32'd0) | + (TargetMask != 0 ? 32'h1800 : 32'd0); + ClockRequest: begin + s_rcu_read_data = {30'd0, s_clk_req_q}; + s_rcu_writable = 1'b1; + end + ClockCurrent: s_rcu_read_data = {30'd0, s_clk_cur_q}; + RcuCommand: begin + s_rcu_read_data = 32'd0; + s_rcu_writable = 1'b1; + end + RcuStatus: begin + s_rcu_read_data = 32'd0; + s_rcu_read_data[0] = s_rcu_busy_q; + s_rcu_read_data[1] = (s_clk_cur_q == 2'd0); + s_rcu_read_data[2] = pll_locked_i; + s_rcu_read_data[3] = pll_selected_i; + s_rcu_read_data[4] = |s_rcu_fault_q; + s_rcu_read_data[11:8] = s_active_cmd_q; + end + TimeoutRefCycles: begin + s_rcu_read_data = s_timeout_ref_q; + s_rcu_writable = 1'b1; + end + GateRequest: begin + s_rcu_read_data = s_gate_req_q; + s_rcu_writable = 1'b1; + end + GateStatus: s_rcu_read_data = {18'd0, gate_actual_i}; + ResetRequest: begin + s_rcu_read_data = s_rst_req_q; + s_rcu_writable = 1'b1; + end + ResetDone: s_rcu_read_data = s_reset_done_q; + ResetCause: begin + s_rcu_read_data = s_reset_cause_q; + s_rcu_writable = 1'b1; + end + RcuFault: begin + s_rcu_read_data = s_rcu_fault_q; + s_rcu_writable = 1'b1; + end + RcuIrqState: begin + s_rcu_read_data = s_rcu_irq_state_q; + s_rcu_writable = 1'b1; + end + RcuIrqEnable: begin + s_rcu_read_data = s_rcu_irq_en_q; + s_rcu_writable = 1'b1; + end + XinHz: s_rcu_read_data = XinHzValue; + RefHz: s_rcu_read_data = 32'd24_000_000; + SysHz: s_rcu_read_data = s_sys_hz_q; + MemHz: s_rcu_read_data = s_mem_hz_q; + PclkHz: s_rcu_read_data = s_pclk_hz_q; + ClintHz: s_rcu_read_data = 32'd1_000_000; + TargetCapability: s_rcu_read_data = {18'd0, TargetMask}; + ClkoutControl: begin + s_rcu_read_data = s_clkout_control_q; + s_rcu_writable = 1'b1; + end + default: s_rcu_valid = 1'b0; + endcase + end + // Validate before generating a write strobe, so every rejected operation + // returns PSLVERR and preserves staging as well as committed state. + always_comb begin + s_rcu_config_err = 1'b0; + unique case (s_rcu_offset) + ClockRequest: s_rcu_config_err = s_rcu_busy_q || u_rcu_dst.pwdata[31:2] != 0; + TimeoutRefCycles: s_rcu_config_err = s_rcu_busy_q || u_rcu_dst.pwdata == 0; + GateRequest: + s_rcu_config_err = s_rcu_busy_q || (u_rcu_dst.pwdata & ~{18'd0, TargetMask}) != 0; + ResetRequest: + s_rcu_config_err = s_rcu_busy_q || (u_rcu_dst.pwdata & ~{18'd0, TargetMask[13:1], 1'b0}) != 0; + ResetCause: s_rcu_config_err = u_rcu_dst.pwdata[31:6] != 0; + RcuFault: s_rcu_config_err = u_rcu_dst.pwdata[31:5] != 0; + RcuIrqState, RcuIrqEnable: s_rcu_config_err = u_rcu_dst.pwdata[31:2] != 0; + RcuCommand: begin + s_rcu_config_err = s_rcu_busy_q; + unique case (u_rcu_dst.pwdata) + 32'd1: + s_rcu_config_err |= s_clkout_control_q[2:0] != 0 || !clkout_idle_i || + !(s_clk_req_q == 0 || (s_clk_req_q == 1 && !PllPresent) || + (s_clk_req_q >= 2 && PllPresent && PllProfiles)); + 32'd2: s_rcu_config_err |= TargetMask == 0 || (s_gate_req_q & ~{18'd0, TargetMask}) != 0; + 32'd4: + s_rcu_config_err |= s_rst_req_q == 0 || + (s_rst_req_q & ~{18'd0, TargetMask[13:1], 1'b0}) != 0 || + (s_rst_req_q & {18'd0, gate_actual_i}) != 0; + 32'd8: begin + end + default: s_rcu_config_err = 1'b1; + endcase + end + ClkoutControl: + s_rcu_config_err = s_rcu_busy_q || + (u_rcu_dst.pwdata & 32'hff00_00f8) != 0 || u_rcu_dst.pwdata[2:0] > 4 || + (u_rcu_dst.pwdata[2:0] != 0 && (s_clkout_control_q[2:0] != 0 || + !clkout_idle_i || u_rcu_dst.pwdata[23:8] == 0 || + {32'd0, s_clkout_hz} > (64'd48_000_000 * {48'd0, u_rcu_dst.pwdata[23:8]}))); + default: begin + end + endcase + end + always_comb begin + s_clk_req_d = s_clk_req_q; + s_clk_cur_d = s_clk_cur_q; + s_rcu_busy_d = s_rcu_busy_q; + s_clk_latched_d = s_clk_latched_q; + s_system_reset_d = 1'b0; + s_periph_apply_d = 1'b0; + s_periph_reset_d = s_periph_reset_q; + s_periph_mask_d = s_periph_mask_q; + s_active_cmd_d = s_active_cmd_q; + s_clock_apply_d = 1'b0; + s_timeout_ref_d = s_timeout_ref_q; + s_gate_req_d = s_gate_req_q; + s_gate_stat_d = s_gate_stat_q; + s_rst_req_d = s_rst_req_q; + s_reset_done_d = s_reset_done_q; + s_reset_cause_d = s_reset_cause_q; + s_rcu_fault_d = s_rcu_fault_q; + s_rcu_irq_state_d = s_rcu_irq_state_q; + s_rcu_irq_en_d = s_rcu_irq_en_q; + s_clkout_control_d = s_clkout_control_q; + s_sys_hz_d = s_sys_hz_q; + s_mem_hz_d = s_mem_hz_q; + s_pclk_hz_d = s_pclk_hz_q; + if (s_rcu_write) begin + unique case (s_rcu_offset) + RcuCommand: begin + if (s_rcu_busy_q || !(u_rcu_dst.pwdata == 1 || u_rcu_dst.pwdata == 2 || + u_rcu_dst.pwdata == 4 || u_rcu_dst.pwdata == 8)) begin + s_rcu_fault_d[0] = 1'b1; + s_rcu_irq_state_d[1] = 1'b1; + end else if (u_rcu_dst.pwdata == 1) begin + if (s_clkout_control_q[2:0] == 0 && clkout_idle_i && + (s_clk_req_q == 0 || (s_clk_req_q == 1 && !PllPresent) || + (s_clk_req_q >= 2 && PllPresent && PllProfiles))) begin + s_clk_latched_d = s_clk_req_q; + s_clock_apply_d = 1'b1; + s_rcu_busy_d = 1'b1; + s_active_cmd_d = 4'd1; + end else begin + s_rcu_fault_d[0] = 1'b1; + s_rcu_irq_state_d[1] = 1'b1; + end + end else if (u_rcu_dst.pwdata == 8) begin + s_system_reset_d = 1'b1; + s_reset_cause_d[1] = 1'b1; + s_rcu_busy_d = 1'b1; + s_active_cmd_d = 4'd8; + end else if (TargetMask != 0 && u_rcu_dst.pwdata == 2 && + (s_gate_req_q & ~{18'd0, TargetMask}) == 0) begin + s_periph_mask_d = s_gate_req_q[13:0]; + s_periph_reset_d = 1'b0; + s_periph_apply_d = 1'b1; + s_rcu_busy_d = 1'b1; + s_active_cmd_d = 4'd2; + end else if (u_rcu_dst.pwdata == 4 && s_rst_req_q != 0 && + (s_rst_req_q & ~{18'd0, TargetMask[13:1], 1'b0}) == 0 && + (s_rst_req_q & {18'd0, gate_actual_i}) == 0) begin + s_periph_mask_d = s_rst_req_q[13:0]; + s_periph_reset_d = 1'b1; + s_periph_apply_d = 1'b1; + s_reset_done_d = 0; + s_rcu_busy_d = 1'b1; + s_active_cmd_d = 4'd4; + end else begin + s_rcu_fault_d[0] = 1'b1; + s_rcu_irq_state_d[1] = 1'b1; + end + end + ClockRequest: begin + if (u_rcu_dst.pwdata[31:2] == 0) s_clk_req_d = u_rcu_dst.pwdata[1:0]; + else begin + s_rcu_fault_d[0] = 1'b1; + s_rcu_irq_state_d[1] = 1'b1; + end + end + TimeoutRefCycles: begin + if (u_rcu_dst.pwdata != 0) s_timeout_ref_d = u_rcu_dst.pwdata; + else begin + s_rcu_fault_d[0] = 1'b1; + s_rcu_irq_state_d[1] = 1'b1; + end + end + GateRequest: s_gate_req_d = u_rcu_dst.pwdata; + ResetRequest: s_rst_req_d = u_rcu_dst.pwdata; + ResetCause: s_reset_cause_d = s_reset_cause_q & ~u_rcu_dst.pwdata; + RcuFault: s_rcu_fault_d = s_rcu_fault_q & ~u_rcu_dst.pwdata; + RcuIrqState: s_rcu_irq_state_d = s_rcu_irq_state_q & ~u_rcu_dst.pwdata; + RcuIrqEnable: s_rcu_irq_en_d = u_rcu_dst.pwdata & 32'd3; + ClkoutControl: begin + if (s_rcu_busy_q || (u_rcu_dst.pwdata & 32'hff00_00f8) != 0 || + u_rcu_dst.pwdata[2:0] > 4 || + (u_rcu_dst.pwdata[2:0] != 0 && + (s_clkout_control_q[2:0] != 0 || !clkout_idle_i || u_rcu_dst.pwdata[23:8] == 0 || + {32'd0, s_clkout_hz} > (64'd48_000_000 * {48'd0, u_rcu_dst.pwdata[23:8]})))) begin + s_rcu_fault_d[0] = 1'b1; + s_rcu_irq_state_d[1] = 1'b1; + end else s_clkout_control_d = u_rcu_dst.pwdata; + end + default: begin + end + endcase + end + if (clock_done_i) begin + s_rcu_busy_d = 1'b0; + s_active_cmd_d = 0; + s_clk_cur_d = clock_current_i; + s_sys_hz_d = clock_current_i == 2 ? 32'd192_000_000 : + clock_current_i == 3 ? 32'd240_000_000 : + clock_current_i == 1 ? 32'(XinHzValue) : 32'd24_000_000; + s_mem_hz_d = s_sys_hz_d > 120_000_000 ? s_sys_hz_d / 2 : s_sys_hz_d; + s_pclk_hz_d = s_sys_hz_d > 120_000_000 ? s_sys_hz_d / 4 : + s_sys_hz_d > 60_000_000 ? s_sys_hz_d / 2 : s_sys_hz_d; + s_rcu_irq_state_d[0] = 1'b1; + end + if (periph_done_i) begin + s_rcu_busy_d = 1'b0; + s_active_cmd_d = 0; + s_reset_done_d = {18'd0, reset_done_i}; + if (s_periph_reset_q && reset_done_i != 0) s_reset_cause_d[5] = 1'b1; + s_rcu_irq_state_d[0] = 1'b1; + end + if (warm_reset_i) begin + s_rcu_busy_d = 1'b0; + s_active_cmd_d = 0; + s_rcu_irq_en_d = 0; + s_rcu_irq_state_d[0] = 1'b0; + s_clk_cur_d = 0; + s_clk_req_d = 0; + s_gate_req_d = 0; + s_gate_stat_d = 0; + s_rst_req_d = 0; + s_reset_done_d = 0; + s_periph_mask_d = 0; + s_periph_reset_d = 1'b0; + s_clkout_control_d = 0; + s_sys_hz_d = 24_000_000; + s_mem_hz_d = 24_000_000; + s_pclk_hz_d = 24_000_000; + end + // Hardware set wins over W1C, including rejected APB operations. + if (s_rcu_req && s_rcu_err) begin + s_rcu_fault_d[0] = 1'b1; + s_rcu_irq_state_d[1] = 1'b1; + end + if (watchdog_reset_i) s_reset_cause_d[2] = 1'b1; + if (clock_fault_i[3]) s_reset_cause_d[3] = 1'b1; + if (hart_reset_i) s_reset_cause_d[4] = 1'b1; + if ((clock_fault_i | periph_fault_i) != 0) begin + s_rcu_fault_d = s_rcu_fault_d | {27'd0, clock_fault_i | periph_fault_i}; + s_rcu_irq_state_d[1] = 1'b1; + end + end + + assign s_fault_sum = {1'b0, s_fault_count_q} + {29'd0, fault_count_i}; always_comb begin s_test_d = s_test_q; s_fault_addr_d = s_fault_addr_q; @@ -124,7 +573,7 @@ module tiny_sysctrl ( end if (rtc_wake_i) s_wake_d = 1'b1; if (fault_valid_i) begin - if (s_fault_count_q != 32'hffff_ffff) s_fault_count_d = s_fault_count_q + 32'd1; + s_fault_count_d = s_fault_sum[32] ? 32'hffff_ffff : s_fault_sum[31:0]; if (!s_fault_stat_d[0]) begin s_fault_stat_d = {(fault_resp_i == 2'b11) ? 3'd1 : 3'd2, fault_write_i, 1'b1}; s_fault_addr_d = fault_addr_i; @@ -149,6 +598,147 @@ module tiny_sysctrl ( .dat_i (s_fault_addr_d), .dat_o (s_fault_addr_q) ); + dffr #( + .DATA_WIDTH(2) + ) u_clock_latched ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_clk_latched_d), + .dat_o (s_clk_latched_q) + ); + dffr #( + .DATA_WIDTH(20) + ) u_periph_command ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i ({s_active_cmd_d, s_periph_mask_d, s_periph_reset_d, s_periph_apply_d}), + .dat_o ({s_active_cmd_q, s_periph_mask_q, s_periph_reset_q, s_periph_apply_q}) + ); + dffr #( + .DATA_WIDTH(1) + ) u_system_reset ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_system_reset_d), + .dat_o (s_system_reset_q) + ); + dffr #(2) u_rcu_clock_request ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_clk_req_d), + .dat_o (s_clk_req_q) + ); + dffr #(2) u_rcu_clock_current ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_clk_cur_d), + .dat_o (s_clk_cur_q) + ); + dffr #(1) u_rcu_busy ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_rcu_busy_d), + .dat_o (s_rcu_busy_q) + ); + dffr #(1) u_clock_apply ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_clock_apply_d), + .dat_o (s_clock_apply_q) + ); + dffrc #( + .DATA_WIDTH(32), + .RESET_VAL (32'd240000) + ) u_rcu_timeout ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_timeout_ref_d), + .dat_o (s_timeout_ref_q) + ); + dffr #(32) u_rcu_gate_request ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_gate_req_d), + .dat_o (s_gate_req_q) + ); + dffr #(32) u_rcu_gate_status ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_gate_stat_d), + .dat_o (s_gate_stat_q) + ); + dffr #(32) u_rcu_reset_request ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_rst_req_d), + .dat_o (s_rst_req_q) + ); + dffr #(32) u_rcu_reset_done ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_reset_done_d), + .dat_o (s_reset_done_q) + ); + dffrc #( + .DATA_WIDTH(32), + .RESET_VAL (32'd1) + ) u_rcu_reset_cause ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_reset_cause_d), + .dat_o (s_reset_cause_q) + ); + dffr #(32) u_rcu_fault ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_rcu_fault_d), + .dat_o (s_rcu_fault_q) + ); + dffr #(32) u_rcu_irq_state ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_rcu_irq_state_d), + .dat_o (s_rcu_irq_state_q) + ); + dffr #(32) u_rcu_irq_enable ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_rcu_irq_en_d), + .dat_o (s_rcu_irq_en_q) + ); + dffr #(32) u_rcu_clkout ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_clkout_control_d), + .dat_o (s_clkout_control_q) + ); + dffrc #( + .DATA_WIDTH(32), + .RESET_VAL (32'd24000000) + ) u_rcu_sys_hz ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_sys_hz_d), + .dat_o (s_sys_hz_q) + ); + dffrc #( + .DATA_WIDTH(32), + .RESET_VAL (32'd24000000) + ) u_rcu_mem_hz ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_mem_hz_d), + .dat_o (s_mem_hz_q) + ); + dffrc #( + .DATA_WIDTH(32), + .RESET_VAL (32'd24000000) + ) u_rcu_pclk_hz ( + .clk_i (clk_ref_i), + .rst_n_i(rst_ref_n_i), + .dat_i (s_pclk_hz_d), + .dat_o (s_pclk_hz_q) + ); dffr #( .DATA_WIDTH(32) ) u_fault_count_reg ( @@ -166,7 +756,7 @@ module tiny_sysctrl ( .dat_o (s_fault_stat_q) ); dffr #( - .DATA_WIDTH(1) + .DATA_WIDTH(2) ) u_fault_master_reg ( .clk_i (clk_i), .rst_n_i(rst_n_i), diff --git a/scripts/check_clock_reset_domains.py b/scripts/check_clock_reset_domains.py index 6d0acbe7..ed6ca33b 100644 --- a/scripts/check_clock_reset_domains.py +++ b/scripts/check_clock_reset_domains.py @@ -16,6 +16,8 @@ CDC_PRIMITIVES = { "async_fifo", "async_reqack", "axi4_async_bridge", + "tiny_axi4_async_bridge", + "tiny_apb_async_bridge", "cdc_2phase", "cdc_fifo", "cdc_sync", @@ -129,6 +131,21 @@ def validate(document_path: Path, root: Path) -> None: raw_crossings = document.get("crossings") if not isinstance(raw_crossings, list): raise ValueError("crossings must be a list") + generated = document.get("generated_apb_crossings") + if generated is not None: + if (document.get("clock_contract") != "TINY-R2-P7" or + generated.get("generator") != "scripts/generate_tiny.py" or + generated.get("primitive") != "tiny_apb_async_bridge" or + generated.get("source") != "system"): + raise ValueError("unrecognized generated APB crossing contract") + expected = {"aon": ["clint", "archinfo"], "memory": ["xpi"], + "peripheral": ["gpio", "uart0", "timer0", "timer1", "i2c0", "dma", + "pwm", "rtc", "wdg", "rng", "crc", "ws2812", "crypto", "i2s"]} + if (generated.get("destinations") != expected or + generated.get("same_clock_isolation") != ["sdio"] or + generated.get("native_system") != ["sysctrl", "sram"] or + not set(expected).issubset(names)): + raise ValueError("P7 generated APB domain assignment mismatch") crossing_names: set[str] = set() for index, value in enumerate(raw_crossings): field = f"crossings[{index}]" diff --git a/scripts/check_simulation.py b/scripts/check_simulation.py index 2197685e..0a4a5012 100644 --- a/scripts/check_simulation.py +++ b/scripts/check_simulation.py @@ -39,6 +39,19 @@ def main() -> int: if default_failure: rejected.append(default_failure.group(0)) + flow_result_path = args.result.with_name("result-sim.json") + flow_status = None + if flow_result_path.is_file(): + try: + flow_result = json.loads(flow_result_path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError): + flow_result = {} + flow_status = flow_result.get("status", "invalid") + # PIDs are namespace-local and can be reused. A durable terminal + # verdict, not a liveness guess from this process, establishes success. + if flow_status != "passed" or flow_result.get("exit_code") != 0: + missing.append("successful terminal run_flow result") + passed = not missing and not rejected report = { "schema_version": 1, @@ -48,6 +61,8 @@ def main() -> int: "missing_markers": missing, "rejected_matches": sorted(set(rejected)), } + if flow_status is not None: + report["flow_status"] = flow_status atomic_write(args.result, json.dumps(report, indent=2, sort_keys=True) + "\n") print(f"simulation check: {report['status']} ({args.result.resolve()})") return int(not passed) diff --git a/scripts/generate_tiny.py b/scripts/generate_tiny.py index 8c359363..22d04c40 100644 --- a/scripts/generate_tiny.py +++ b/scripts/generate_tiny.py @@ -16,13 +16,36 @@ from scripts.rtl.generate_pin_map import generate as generate_pin_map # noqa: E from scripts.setup_helpers import atomic_write # noqa: E402 TINY = ROOT / "rtl/tiny" +RCU_TARGETS = {"gpio": 1, "uart0": 2, "i2c0": 3, "timer0": 4, "timer1": 5, + "pwm": 6, "i2s": 7, "sdio": 8, "dma": 9, "ws2812": 10, + "rng": 11, "crc": 12, "crypto": 13} def read_topology(path: Path, memory_map: Path) -> dict: document = json.loads(path.read_text()) regions = {r["symbol"] for r in json.loads(memory_map.read_text())["regions"]} - if document.get("schema_version") != 1 or document.get("dma_channels") != 4: - raise ValueError("Tiny requires topology schema 1 and four DMA channels") + if (document.get("schema_version") != 1 or document.get("dma_channels") != 8 + or document.get("dma_request_mask") != "0x31ff"): + raise ValueError("Tiny P6 requires eight DMA channels and request mask 0x31ff") + if document.get("cpu") != {"module": "hazard3_cpu_2port", "local_ports": 2, + "data_outstanding": 1}: + raise ValueError("Tiny P4 requires independent I/D and one ordered data operation") + memory = document.get("main_sram", {}) + if any(memory.get(key) != value for key, value in { + "groups": 4, "group_bytes": 32768, "macros_per_group": 8, + "macro_bytes": 4096, "base": "0x30000000", + "macro_instance": "u_soc.u_sram.gen_group[{group}].u_group.gen_bank[{bank}].u_ram.u_mem", + "external_attachment": "per-target-P5", + }.items()): + raise ValueError("Tiny SRAM geometry/P5 attachment differs from its frozen contract") + if document.get("fabric") != { + "owners": ["cpu_slow", "central_dma", "sdio"], "active_owners": [0, 1, 2], + "cpu_combined_outstanding": 1, "dma_direction_outstanding": 1, + "sdio_direction_outstanding": 1, "target_combined_outstanding": 1, + "sram_targets": [0, 1, 2, 3], "xpi_target": 4, "apb_target_base": 5, + "decode_error_target": 25, "protocol_error_target": 26, "target_count": 27, + }: + raise ValueError("Tiny P6 requires three active owners and 27 targets") if document.get("irq_vector_width") != 32 or document.get("external_irq_count") != 30: raise ValueError("Tiny requires a 32-bit IRQ vector and 30 external inputs") names: set[str] = set() @@ -42,14 +65,24 @@ def read_topology(path: Path, memory_map: Path) -> dict: assigned.add(region) if assigned != {r for r in regions if r.startswith("APB4_")}: raise ValueError("Tiny topology must route every APB region") - if len(document["apb_targets"]) != 16: - raise ValueError("Tiny's fixed APB bridge requires sixteen targets") + if len(document["apb_targets"]) != 20: + raise ValueError("Tiny P6 requires twenty independent APB targets") target_names = [target["name"] for target in document["apb_targets"]] - if len(set(target_names)) != 16 or any(re.fullmatch(r"[a-z][a-z0-9_]*", name) is None + if len(set(target_names)) != 20 or any(re.fullmatch(r"[a-z][a-z0-9_]*", name) is None for name in target_names): raise ValueError("Tiny APB target names must be unique identifiers") if sorted(item["pin"] for item in document["gpio_alt_functions"]) != list(range(32)): raise ValueError("Tiny GPIO map must contain all 32 pins exactly once") + inputs = [signal for pin in document["gpio_alt_functions"] for mode in ("alt0", "alt1") + for signal in pin[mode]["inputs"]] + if len(inputs) != len(set(inputs)): + raise ValueError("Tiny peripheral inputs require one exclusive route") + if document.get("private_crypto") != { + "banks": 6, "bank_bytes": 4096, + "macro_instance": "u_soc.u_crypto.u_storage.gen_bank[{bank}].gen_macro.u_sram.u_mem", + "clock_domain": "peripheral", "logical_domain": "PCLK", + }: + raise ValueError("Tiny requires six private Crypto banks on PCLK") return document @@ -65,7 +98,23 @@ def generate_bindings(document: dict, output: Path, pdk: str = "IHP130", have_pll: bool = False) -> None: platform = platform_values(pdk, have_pll) comment = "// Generated by scripts/generate_tiny.py; do not edit.\n" - irq = [comment, "always_comb begin", " s_irq = '0;"] + constants = [comment, "`ifndef RETROSOC_TINY__TOPOLOGY_SVH", + "`define RETROSOC_TINY__TOPOLOGY_SVH", + f"`define RETROSOC_TINY__TARGET_COUNT {document['fabric']['target_count']}", + f"`define RETROSOC_TINY__LEAF_COUNT {len(document['apb_targets']) + 1}", + f"`define RETROSOC_TINY__DECODE_ERROR {document['fabric']['decode_error_target']}", + f"`define RETROSOC_TINY__PROTOCOL_ERROR {document['fabric']['protocol_error_target']}"] + constants += [f"`define RETROSOC_TINY__TARGET_{target['name'].upper()} {index + 5}" + for index, target in enumerate(document["apb_targets"])] + constants.append("`endif\n") + atomic_write(output / "rtl/tiny_topology.svh", "\n".join(constants)) + # Keep board pulls independently resolved. A packed tri1 with partial + # external drivers loses its weak pulls in the locked Verilator 5.046. + board = [comment, *[f"tri1 s_gpio_{pin};" for pin in range(document["gpio_pins"])]] + board.append("wire [31:0] s_gpio = {" + + ", ".join(f"s_gpio_{pin}" for pin in reversed(range(32))) + "};\n") + atomic_write(output / "rtl/tiny_tb_gpio.svh", "\n".join(board)) + irq = [comment, "always_comb begin", " s_irq_async = '0;"] header = ["#ifndef RETROSOC_GENERATED_IRQ_METADATA_H", "#define RETROSOC_GENERATED_IRQ_METADATA_H", "#include ", "#define RS_SOC_IRQ_VECTOR_WIDTH UINT32_C(32)", @@ -73,7 +122,7 @@ def generate_bindings(document: dict, output: Path, pdk: str = "IHP130", f"#define RS_SOC_ALLOCATED_IRQ_COUNT UINT32_C({len(document['interrupts']) - 2})"] for item in document["interrupts"]: bit, name = item["core_bit"], item["name"].upper() - irq.append(f" s_irq[{bit}] = {item['signal']};") + irq.append(f" s_irq_async[{bit}] = {item['signal']};") header.append(f"#define RS_SOC_IRQ_{name} UINT32_C({bit})") if bit >= 2: header.append(f"#define RS_SOC_EXT_IRQ_{name} UINT32_C({bit - 2})") @@ -90,7 +139,15 @@ def generate_bindings(document: dict, output: Path, pdk: str = "IHP130", "#ifndef RETROSOC_GENERATED_SOC_FEATURES_H\n" "#define RETROSOC_GENERATED_SOC_FEATURES_H\n" "#define RS_SOC_TINY 1\n#define RS_SOC_HAS_PSRAM 0U\n" - "#define RS_SOC_DMA_CHANNELS 4U\n#define RS_SOC_ID 0x54494E59U\n" + "#define RS_SOC_DMA_CHANNELS 8U\n#define RS_SOC_ID 0x54494E59U\n" + "#define RS_SOC_DMA_REQUEST_MASK 0x31FFU\n" + "#define RS_SOC_HAS_I2C1 0U\n#define RS_SOC_HAS_SDIO1 0U\n" + "#define RS_SOC_HAS_UART0 1U\n#define RS_SOC_HAS_UART1 0U\n" + "#define RS_SOC_HAS_I2C0 1U\n" + "#define RS_SOC_HAS_RNG 1U\n#define RS_SOC_HAS_CRC 1U\n" + "#define RS_SOC_HAS_WS2812 1U\n#define RS_SOC_HAS_CRYPTO 1U\n" + "#define RS_SOC_HAS_I2S 1U\n#define RS_SOC_HAS_SDIO0 1U\n" + "#define RS_SOC_HAS_DVP 0U\n#define RS_SOC_RNG_QUALIFIED 0U\n" f"#define RS_SOC_TECHNOLOGY_ID 0x{platform['technology']:08X}U\n" f"#define RS_SOC_PLL_PRESENT {platform['pll_present']}U\n" f"#define RS_SOC_GPIO_HAS_PULLS {platform['gpio_pulls']}U\n#endif\n") @@ -106,33 +163,60 @@ def generate_bindings(document: dict, output: Path, pdk: str = "IHP130", gpio.extend((f"assign u_gpio_if.{mode}_do_i[{pin}] = {value['do']};", f"assign u_gpio_if.{mode}_oe_i[{pin}] = {value['oe']};")) atomic_write(output / "rtl/tiny_gpio.svh", "\n".join(gpio) + "\n") - ports, declarations, routes, decode = [], [comment], [comment], [comment] + declarations, bridges, decode = [comment], [comment], [comment] reset_assignments = [] for index, target in enumerate(document["apb_targets"]): name = target["name"] - ports.append(f"apb4_if.master {name}") + clock = ("s_ref_clk" if name in ("clint", "archinfo") else + "s_backend_mem_clk" if name == "xpi" else + "s_exec_clk" if name in ("sysctrl", "sram", "sdio") else "s_backend_pclk") + target_bit = RCU_TARGETS.get(name) + ip_clock = f"s_{name}_clk" if target_bit is not None else clock + if target_bit is not None: + declarations.append(f"logic {ip_clock};") declarations.append(f"logic s_{name}_rst_n;") - declarations.append(f"apb4_if u_{name}_apb4_if (.pclk(clk_i), .presetn(s_{name}_rst_n));") - reset_assignments.append(f"assign s_{name}_rst_n = s_leaf_rst_n[{index + 1}];") - routes.append(f".{name}(u_{name}_apb4_if)") - decode.append(f"if ({' || '.join(f'`SOC_ADDR_IS_{r}(s_addr_q)' for r in target['regions'])}) " - f"s_select = {len(document['apb_targets'])}'b{1 << index:016b};") - atomic_write(output / "rtl/tiny_apb_ports.svh", ",\n".join(ports) + "\n") + declarations.append(f"apb4_if u_{name}_apb4_if (.pclk({ip_clock}), .presetn(s_{name}_rst_n));") + if target_bit is None: + reset_assignments.append(f"assign s_{name}_rst_n = s_leaf_rst_n[{index + 1}];") + else: + reset_assignments.append(f"(* keep_hierarchy = 1 *) tiny_peripheral_leaf u_{name}_lifecycle (" + f".clk_i({clock}), .rst_n_i(s_leaf_rst_n[{index + 1}]), " + f".gate_i(s_gate_request[{target_bit}]), " + f".reset_i(s_local_reset[{target_bit}]), " + f".clk_o({ip_clock}), .rst_n_o(s_{name}_rst_n), " + f".gated_o(s_actual_gate[{target_bit}]), " + f".held_o(s_reset_held[{target_bit}]));") + extra_ready = " && s_i2s_reset_ready" if name == "i2s" else "" + if name == "dma": + # CDC-ready outputs are observed in SYS, matching APB readiness. + extra_ready = " && s_dma_ready_sys && s_dma_idle_sys" + reset_assignments.append(f"assign s_reset_ready[{target_bit}] = s_{name}_rst_n && " + f"s_apb_cdc_ready[{index}]{extra_ready};") + reset_assignments.append(f"assign s_leaf_clk[{index + 1}] = {clock};") + bridges.append(f"assign s_target_rst_n[{index + 5}] = s_fabric_rst_n;") + crossed = clock != "s_exec_clk" or target_bit is not None + endpoint = f"u_{name}_sys_apb4_if" if crossed else f"u_{name}_apb4_if" + bridges.append(f"tiny_axi42apb4 u_{name}_bridge (" + f".clk_i(s_exec_clk), .rst_n_i(s_fabric_rst_n), " + f".axi4(u_targets_axi4_if[{index + 5}]), .apb4({endpoint}));") + if crossed: + disabled = f"s_disabled_sys[{target_bit}]" if target_bit is not None else "1'b0" + declarations.append(f"apb4_if {endpoint} (.pclk(s_exec_clk), .presetn(s_fabric_rst_n));") + bridges.append(f"tiny_apb_async_bridge u_{name}_cfg_cdc (" + f".src_clk_i(s_exec_clk), .src_rst_n_i(s_fabric_rst_n), " + f".dst_clk_i({ip_clock}), .dst_rst_n_i(s_{name}_rst_n), " + f".disabled_i({disabled}), " + f".idle_o(s_apb_cdc_idle[{index}]), " + f".ready_o(s_apb_cdc_ready[{index}]), " + f".src_apb4({endpoint}), .dst_apb4(u_{name}_apb4_if));") + else: + bridges.append(f"assign s_apb_cdc_idle[{index}] = 1'b1;") + bridges.append(f"assign s_apb_cdc_ready[{index}] = 1'b1;") + decode.append(f"if ({' || '.join(f'`SOC_ADDR_IS_{r}(addr_i)' for r in target['regions'])}) " + f"return 5'd{index + 5};") atomic_write(output / "rtl/tiny_apb_interfaces.svh", "\n".join(declarations + reset_assignments) + "\n") - atomic_write(output / "rtl/tiny_apb_connections.svh", ",\n".join(routes[1:]) + "\n") - atomic_write(output / "rtl/tiny_apb_decode.svh", "\n".join(decode) + "\n") - wiring = [comment] - for index, target in enumerate(document["apb_targets"]): - name = target["name"] - for field, value in {"paddr": "s_addr_q", "pwrite": "s_write_q", - "pwdata": "s_wdata_q", "pstrb": "s_wstrb_q", "pprot": "3'd0", - "psel": f"s_select[{index}] && s_access", - "penable": "s_enable"}.items(): - wiring.append(f"assign {name}.{field} = {value};") - wiring.extend((f"assign s_ready[{index}] = {name}.pready;", - f"assign s_error[{index}] = {name}.pslverr;", - f"assign s_rdata[{index}] = {name}.prdata;")) - atomic_write(output / "rtl/tiny_apb_wiring.svh", "\n".join(wiring) + "\n") + atomic_write(output / "rtl/tiny_apb_bridges.svh", "\n".join(bridges) + "\n") + atomic_write(output / "rtl/tiny_fabric_decode.svh", "\n".join(decode) + "\n") def generate_filelists(output: Path, generated: Path, defines: list[str], @@ -141,7 +225,7 @@ def generate_filelists(output: Path, generated: Path, defines: list[str], prefixes = {"top": TINY / "top", "dv": TINY / "dv", "ip": ROOT / "rtl/ip", "tech": ROOT / "rtl/tech", "clusterip": ROOT / "rtl/managed/clusterip", "hazard3": ROOT / "rtl/managed/hazard3/hdl", "physical": ROOT / "physical", - "cache": ROOT / ".cache"} + "cache": ROOT / ".cache", "tests": ROOT / "tests"} suffix = "_yosys" if synthesis == "YOSYS" else "_verilator" if simulator == "VERILATOR" else "" pdk_name = "pdk_ihp130.fl" if pdk == "IHP130" else f"pdk_ics55{suffix}.fl" templates = [*sorted((TINY / "filelist").glob("*.fl")), diff --git a/scripts/run_c_tests.py b/scripts/run_c_tests.py index 50795283..fe896652 100644 --- a/scripts/run_c_tests.py +++ b/scripts/run_c_tests.py @@ -43,6 +43,7 @@ TEST_SOURCES = ( "crt/src/hal/fabric_monitor.c", "crt/src/hal/sysctrl.c", "crt/src/hal/clock.c", + "crt/src/hal/rcu.c", "crt/src/hal/resource.c", "crt/src/hal/extension.c", "crt/src/hal/user_ip.c", @@ -122,6 +123,7 @@ def main() -> int: "-DRS_NPU_TEST_MMIO", "-DRS_FABRIC_MONITOR_TEST_MMIO", "-DRS_RESOURCE_TEST_MMIO", + "-DRS_TINY_RCU_TEST_MMIO", "-I", str(memory_map_root / "include"), "-I", diff --git a/scripts/run_flow.py b/scripts/run_flow.py index 26dcddc5..91ba1867 100644 --- a/scripts/run_flow.py +++ b/scripts/run_flow.py @@ -124,24 +124,22 @@ def main() -> int: args.result.parent.mkdir(parents=True, exist_ok=True) started = datetime.now(timezone.utc) start_clock = time.monotonic() - atomic_write( - args.result, - json.dumps( - { - "schema_version": 1, - "tool": args.tool, - "command": command, - "started_at": started.isoformat(), - "status": "running", - "log": str(args.log.resolve()), - }, - indent=2, - sort_keys=True, - ) - + "\n", - ) + running_result = { + "schema_version": 1, + "tool": args.tool, + "command": command, + "started_at": started.isoformat(), + "status": "running", + "parent_pid": os.getpid(), + "log": str(args.log.resolve()), + } + atomic_write(args.result, json.dumps(running_result, indent=2, sort_keys=True) + "\n") def interrupt_flow(_signum: int, _frame: object) -> None: + # A launcher can deliver SIGINT followed by SIGTERM. Do not let a + # second signal interrupt process-group cleanup or result persistence. + signal.signal(signal.SIGINT, signal.SIG_IGN) + signal.signal(signal.SIGTERM, signal.SIG_IGN) raise KeyboardInterrupt signal.signal(signal.SIGINT, interrupt_flow) @@ -169,6 +167,8 @@ def main() -> int: pass_fds=jobserver_fds(), start_new_session=True, ) + running_result["child_pid"] = process.pid + atomic_write(args.result, json.dumps(running_result, indent=2, sort_keys=True) + "\n") assert process.stdout is not None if args.stream_bytes: while chunk := os.read(process.stdout.fileno(), 4096): @@ -193,11 +193,25 @@ def main() -> int: returncode = process.wait() except KeyboardInterrupt: error_message = "interrupted" + # Persist cancellation before the bounded cleanup wait. If the outer + # executor subsequently kills this wrapper, the file must not imply + # that the job is still running or that its child finished normally. + cancelled = dict(running_result, status="interrupted", exit_code=130, + interrupted_at=datetime.now(timezone.utc).isoformat(), + child_cleanup_pending=process is not None) + atomic_write(args.result, json.dumps(cancelled, indent=2, sort_keys=True) + "\n") if process is not None: terminate_process_group(process) returncode = 130 except (OSError, subprocess.SubprocessError) as error: error_message = str(error) + if process is not None: + terminate_process_group(process) + except BaseException as error: + error_message = f"unexpected {type(error).__name__}: {error}" + if process is not None: + terminate_process_group(process) + returncode = 1 finished = datetime.now(timezone.utc) result = { @@ -211,8 +225,13 @@ def main() -> int: "peak_rss_kib": resource.getrusage(resource.RUSAGE_CHILDREN).ru_maxrss, "exit_code": returncode, "status": "interrupted" if returncode == 130 else ("passed" if returncode == 0 else "failed"), + "parent_pid": os.getpid(), "log": str(args.log.resolve()), } + if process is not None: + result["child_pid"] = process.pid + result["child_exit_code"] = process.returncode + result["child_cleanup_pending"] = process.returncode is None if error_message: result["error"] = error_message if args.success_marker: diff --git a/scripts/setup_tiny.py b/scripts/setup_tiny.py index 826c9697..1af49294 100644 --- a/scripts/setup_tiny.py +++ b/scripts/setup_tiny.py @@ -15,7 +15,7 @@ from scripts.setup_helpers import ensure_git_repo # noqa: E402 TINY_SOURCES = ( "hazard3", "cluster_common", "cluster_archinfo", "cluster_pwm", - "cluster_rtc", "cluster_wdg", "pdk_ihp130", + "cluster_rtc", "cluster_wdg", "cluster_rng", "cluster_crc", "pdk_ihp130", ) diff --git a/scripts/tiny_ics55_platform.py b/scripts/tiny_ics55_platform.py index 0ddba621..281765c6 100644 --- a/scripts/tiny_ics55_platform.py +++ b/scripts/tiny_ics55_platform.py @@ -114,10 +114,12 @@ def verify_locked_views(root: Path) -> dict: return verified -def inspect_netlist(document: dict) -> dict: +def expand_netlist(document: dict) -> tuple: + """Resolve hierarchy aliases without assigning a product clock contract.""" modules = document["modules"] top = "retrosoc_tiny_asic" leaves = [] + boundaries = {} aliases = {} def canonical(bit): @@ -159,6 +161,7 @@ def inspect_netlist(document: dict) -> dict: path = f"{scope}/{name}" attrs = modules.get(kind, {}).get("attributes", {}) if kind in modules and not int(str(attrs.get("blackbox", "0")), 2): + boundaries[path] = {"path": path, "type": kind, "ports": ports} visit(kind, path, ports) else: directions = cell.get("port_directions", { @@ -168,21 +171,44 @@ def inspect_netlist(document: dict) -> dict: "directions": directions}) visit(top, top, {}) - for c in leaves: + for c in [*leaves, *boundaries.values()]: for port, bits in c["ports"].items(): c["ports"][port] = [canonical(bit) for bit in bits] + return modules, top, leaves, canonical, boundaries + + +def inspect_netlist(document: dict) -> dict: + # Keep the original P1 parked-PLL/direct-SYS contract fail-closed. + modules, top, leaves, canonical, _ = expand_netlist(document) clock = modules[top]["cells"]["u_clock_buffer"]["connections"]["clk_o"] if len(clock) != 1 or not isinstance(clock[0], int): raise ValueError("missing live SYS clock") system = canonical(f"{top}:{clock[0]}") if system in ("0", "1", "x", "z"): raise ValueError("SYS is not a live clock net") - ram = [c for c in leaves if c["type"] == RAM] + all_ram = [c for c in leaves if c["type"] == RAM] + crypto_names = {f"u_soc.u_crypto.u_storage.gen_bank[{n}].gen_macro.u_sram.u_mem" + for n in range(6)} + crypto = [c for c in all_ram if any(c["path"].replace("/", ".").endswith("." + name) + for name in crypto_names)] + observed_crypto = {name for name in crypto_names for c in crypto + if c["path"].replace("/", ".").endswith("." + name)} + if len(all_ram) != 38 or len(crypto) != 6 or observed_crypto != crypto_names: + raise ValueError("Tiny P6 requires exactly 32 main SRAM and six private Crypto macros") + if any(c["ports"].get("CLK") != [system] for c in crypto): + raise ValueError("private Crypto SRAM does not use SAFE24 PCLK") + ram = [c for c in all_ram if c not in crypto] pll = [c for c in leaves if c["type"] == "PLL_TOP"] if any(c["type"] == "ics55_ecos_sram_4096x32_m8" for c in leaves): raise ValueError("Tiny instantiated the unsupported 16 KiB SRAM interface stub") if len(ram) != 32 or len(pll) != 1: raise ValueError("expected 32 main SRAM macros and one PLL") + expected_ram = {f"u_soc.u_sram.gen_group[{g}].u_group.gen_bank[{n}].u_ram.u_mem" + for g in range(4) for n in range(8)} + observed_ram = {next((name for name in expected_ram + if c["path"].replace("/", ".").endswith("." + name)), "") for c in ram} + if observed_ram != expected_ram: + raise ValueError("Tiny P4 SRAM group/macro hierarchy is missing or stale") if any(c["ports"].get("CLK") != [system] for c in ram): raise ValueError("main SRAM does not share direct SYS") constants = {"0": 0, "1": 1} @@ -202,7 +228,7 @@ def inspect_netlist(document: dict) -> dict: enable = pll[0]["ports"].get("EN", []) if len(enable) != 1 or constants.get(enable[0]) != 0: raise ValueError("PLL enable is not proven constant zero") - cpu = [c for c in leaves if "u_hazard3_cpu_1port" in c["path"] + cpu = [c for c in leaves if "u_hazard3_cpu_2port" in c["path"] and ("CK" in c["ports"] or "CKN" in c["ports"])] if not cpu or any(c["ports"].get("CK", c["ports"].get("CKN")) != [system] for c in cpu): raise ValueError("CPU sequential clocks do not share direct SYS") @@ -213,6 +239,7 @@ def inspect_netlist(document: dict) -> dict: "system_clock_source": {"port": "extclk_i_pad", "net": reference, "path": clock_path}, "cpu_clocked_cells": len(cpu), "sram": [{"path": c["path"], "clock": c["ports"]["CLK"]} for c in ram], + "crypto_sram": [{"path": c["path"], "clock": c["ports"]["CLK"]} for c in crypto], "pll": pll[0], "pll_enable": 0, "qualification": "SAFE24 digital connectivity only"} diff --git a/scripts/tiny_r2_p4.py b/scripts/tiny_r2_p4.py new file mode 100644 index 00000000..455645e0 --- /dev/null +++ b/scripts/tiny_r2_p4.py @@ -0,0 +1,255 @@ +#!/usr/bin/env python3 +"""Replay immutable P3 images on P4 RTL without relabeling image provenance.""" +from __future__ import annotations + +import argparse +from pathlib import Path +import sys +import subprocess + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT)) +from scripts import tiny_r2_baseline as b # noqa: E402 +from scripts import tiny_r2_p3 as p3 # noqa: E402 + +PHASE = "TINY-R2-P4" +HARDWARE_KEYS = ("SOC", "PDK", "HAVE_PLL", "HAVE_SRAM_IF", "HAVE_SRAM_MACRO", + "SRAM_SIZE_KIB", "PDK_BEHAV", "HAVE_HP", "EXT_CLK_HZ", "AUD_CLK_HZ", + "CLINT_TIMEBASE_HZ", "MGMT_CPU_CLK_HZ", "JTAG_IDCODE") +LOCAL_FIELDS = {"window", "port", "accepted", "completed", "latency_max", "admission_wait"} +BANK_FIELDS = {"window", "group", "issues_i", "issues_d", "issues_external", "conflicts", + "wait_i", "wait_d", "wait_external"} + + +def inputs(root: Path, variant: Path) -> dict: + result = p3.inputs(root, variant) + # Publication bindings are tested source consumers, not generated PDFs. + names = subprocess.check_output( + ["git", "ls-files", "--cached", "--others", "--exclude-standard", "-z", "--", "publications"], + cwd=root, text=True).split("\0") + for name in names: + path = root / name + if name and path.is_file() and path.suffix in (".py", ".json", ".typ"): + result[name] = p3.sha256(path) + return result + + +def compatible_hardware(image: dict, model: dict) -> None: + a, z = (c["configuration"] for c in (image, model)) + if any(key not in a or key not in z or a[key] != z[key] for key in HARDWARE_KEYS): + raise ValueError("retained image and candidate hardware configuration differ") + + +def parse_log(log: str, flow: dict) -> dict: + samples = p3.parse_log(log, flow) + rows = [b.parse_fields(line, LOCAL_FIELDS) for line in log.splitlines() + if line.startswith("R2_P4_LOCAL ")] + rows.sort(key=lambda row: (row["window"], row["port"])) + if [(r["window"], r["port"]) for r in rows] != [(w, p) for w in range(1, 9) for p in range(2)]: + raise ValueError("missing or duplicate P4 local observations") + for row in rows: + if row["completed"] > row["accepted"] or row["accepted"] - row["completed"] > 1: + raise ValueError("P4 local request/response accounting mismatch") + samples["local"] = rows + banks = [b.parse_fields(line, BANK_FIELDS) for line in log.splitlines() + if line.startswith("R2_P4_BANK ")] + banks.sort(key=lambda row: (row["window"], row["group"])) + if [(r["window"], r["group"]) for r in banks] != [(w, g) for w in range(1, 9) for g in range(4)]: + raise ValueError("missing or duplicate P4 bank observations") + samples["banks"] = banks + return samples + + +def reference_cases(path: Path, names: list[str]) -> list[dict]: + report = b.read_json(path) + if report.get("phase") != p3.PHASE or report.get("status") != "measurement_complete": + raise ValueError("reference is not a completed P3 measurement report") + matrix = b.read_json(b.check_artifact(report["matrix"])) + if matrix.get("status") != "passed": + raise ValueError("reference matrix did not pass") + if not names or len(set(names)) != len(names): + raise ValueError("select distinct retained cases") + result = [] + for name in names: + matching = [c for c in report["cases"] if c["name"] == name] + original = [c for c in matrix["cases"] if c["name"] == name] + if len(matching) != 1 or len(original) != 1 or matching[0]["image"] != original[0]["image"]: + raise ValueError("unknown or mismatched retained case") + case = matching[0] + p3.validate_image(b.check_artifact(case["image"])) + # Recheck the original logs, not just a report's claimed numeric values. + samples = p3.validate_simulation(original[0]["simulations"]["verilator"], + case["image"], "verilator") + if samples != case["samples"]: + raise ValueError("reference report differs from its retained execution") + result.append(case) + return result + + +def replay(args) -> None: + root, variant = args.root, args.variant_root + parent = variant / "meta/tiny-r2-p4/replay" / args.simulator + directory = b.new_attempt(parent, "attempt-") + record = {"phase": PHASE, "status": "running", "repetitions": 1, "cases": []} + b.dump(parent / "latest.json", {"path": str(directory / "record.json")}) + try: + reference = b.artifact(args.reference) + cases = reference_cases(args.reference, args.cases.split(",")) + configuration = p3.configuration(variant) + for case in cases: + compatible_hardware(b.read_json(b.check_artifact(case["image"]))["configuration"], + configuration) + before = inputs(root, variant) + tools = b.tools_identity(root) + command = args.command[1:] if args.command[:1] == ["--"] else args.command + if not command: + raise ValueError("fresh model compilation command required") + p3.execute_make(root, directory, "compile-model", command) + if before != inputs(root, variant): + raise ValueError("source changed while compiling candidate model") + runtime = b.runtime_inputs(root, variant, args.simulator) + model = variant / "sim" / args.simulator / ("emu" if args.simulator == "verilator" else "simv") + record.update(reference=reference, configuration=configuration, tools=tools, + model=b.artifact(model), source_digest=b.digest_map(before), + source=b.snapshot(root, directory / "inputs", before | runtime), + compilation={s: b.artifact(directory / f"compile-model.{s}") for s in ("json", "log")}) + launch = [str(model)] + if args.simulator == "iverilog": + record["runtime_tool"] = b.vvp_identity(args.vvp, tools["iverilog"]) + launch.insert(0, record["runtime_tool"]["executable"]["path"]) + for case in cases: + image = p3.validate_image(b.check_artifact(case["image"])) + begin, end = image["layout"]["descriptor_range"] + cmd = [*launch, "+firmware=" + image["artifacts"]["tiny_p3.hex"]["path"], + "+tiny_r2_p3", f"+baseline_tcd_base={begin:x}", f"+baseline_tcd_end={end:x}", + "+max_cycles=100000000"] + label = case["name"] + flow = b.execute(root, directory, label, + ["timeout", "--foreground", f"{args.timeout}s", *cmd], quiet=True) + samples = parse_log((directory / f"{label}.log").read_text(), flow) + p3.validate_image(b.check_artifact(case["image"])) + record["cases"].append({"name": label, "image": case["image"], "samples": samples, + "baseline_samples": case["samples"], + "command": cmd, "timeout_seconds": args.timeout, + "flow": b.artifact(directory / f"{label}.json"), + "log": b.artifact(directory / f"{label}.log")}) + b.dump(directory / "record.json", record) + if before != inputs(root, variant) or runtime != b.runtime_inputs(root, variant, args.simulator): + raise ValueError("candidate inputs changed during execution") + b.check_artifact(record["model"]) + b.check_artifact(reference) + for case in cases: + p3.validate_image(b.check_artifact(case["image"])) + for tool in tools.values(): + b.check_artifact(tool["executable"]) + record["status"] = "passed" + except (OSError, ValueError) as error: + record.update(status="failed", error=str(error)) + raise + finally: + b.dump(directory / "record.json", record) + print(f"P4 {args.simulator}: one execution per image; {directory}") + + +def validate_replay(path: Path, simulator: str) -> dict: + record = b.read_json(path) + if record.get("phase") != PHASE or record.get("status") != "passed" or record.get("repetitions") != 1: + raise ValueError("not a completed single-execution P4 replay") + for field in ("model", "reference"): + b.check_artifact(record[field]) + for value in record["compilation"].values(): + b.check_artifact(value) + for field in ("inputs", "snapshot", "patch"): + b.check_artifact(record["source"][field]) + captured = b.read_json(Path(record["source"]["inputs"]["path"])) + if b.digest_map(captured) != record["source"]["input_digest"]: + raise ValueError("candidate source digest mismatch") + for tool in record["tools"].values(): + b.check_artifact(tool["executable"]) + b.check_artifact(tool["archive"]) + prefix = [record["model"]["path"]] + if simulator == "iverilog": + runtime = b.check_artifact(record["runtime_tool"]["executable"]) + compiler = Path(record["tools"]["iverilog"]["executable"]["path"]) + if runtime.resolve().parent != compiler.resolve().parent: + raise ValueError("unmatched Icarus runtime") + prefix.insert(0, str(runtime)) + reference = reference_cases(Path(record["reference"]["path"]), [c["name"] for c in record["cases"]]) + for case, original in zip(record["cases"], reference, strict=True): + if case["image"] != original["image"] or case["baseline_samples"] != original["samples"]: + raise ValueError("comparison changed the retained image or reference measurements") + image = p3.validate_image(b.check_artifact(case["image"])) + compatible_hardware(image["configuration"], record["configuration"]) + first, end = image["layout"]["descriptor_range"] + expected = [*prefix, "+firmware=" + image["artifacts"]["tiny_p3.hex"]["path"], + "+tiny_r2_p3", f"+baseline_tcd_base={first:x}", f"+baseline_tcd_end={end:x}", + "+max_cycles=100000000"] + flow = b.read_json(b.check_artifact(case["flow"])) + if case["command"] != expected or case["timeout_seconds"] <= 0 or flow["command"] != [ + "timeout", "--foreground", f"{case['timeout_seconds']}s", *expected]: + raise ValueError("model/image execution provenance mismatch") + if parse_log(b.check_artifact(case["log"]).read_text(), flow) != case["samples"]: + raise ValueError("reported candidate samples differ from the log") + return record + + +def report(args) -> None: + base = args.variant_root / "meta/tiny-r2-p4" + runs = {} + for simulator in ("verilator", "iverilog"): + pointer = b.read_json(base / "replay" / simulator / "latest.json") + path = Path(pointer["path"]) + runs[simulator] = (validate_replay(path, simulator), b.artifact(path)) + before = b.digest_map(inputs(args.root, args.variant_root)) + if any(run[0]["source_digest"] != before for run in runs.values()): + raise ValueError("candidate source changed after measurement") + cases = {c["name"]: c for c in runs["verilator"][0]["cases"]} + if set(cases) != {"perf-o2-plain-flat", "perf-o2-plain-banked"}: + raise ValueError("P4 comparison requires both approved placement images") + cross = runs["iverilog"][0]["cases"] + if len(cross) != 1 or cross[0]["name"] != "perf-o2-plain-banked": + raise ValueError("P4 requires one banked Icarus execution") + if any(cross[0][key] != cases["perf-o2-plain-banked"][key] for key in ("image", "samples")): + raise ValueError("cross-simulator image/results differ") + comparisons = [] + for name, case in cases.items(): + rows = [] + for old, new in zip(case["baseline_samples"]["cases"], case["samples"]["cases"], strict=True): + rows.append({"name": new["name"], "baseline_cycles": old["cycles"], + "candidate_cycles": new["cycles"], "baseline_instructions": old["instructions"], + "candidate_instructions": new["instructions"], + "cycle_ratio": old["cycles"] / new["cycles"]}) + comparisons.append({"name": name, "image": case["image"], "workloads": rows, + "local": case["samples"]["local"], "banks": case["samples"]["banks"]}) + b.dump(base / "report.json", {"phase": PHASE, "status": "measurement_complete", + "source_digest": before, "comparisons": comparisons, + "executions": {key: value[1] for key, value in runs.items()}, + "qualification": "matched-binary SAFE24 simulation; not phase/physical acceptance"}) + print(f"P4 matched-image report: {base / 'report.json'}") + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--root", type=Path, required=True) + parser.add_argument("--variant-root", type=Path, required=True) + parser.add_argument("--report", action="store_true") + parser.add_argument("--reference", type=Path) + parser.add_argument("--cases") + parser.add_argument("--simulator", choices=("verilator", "iverilog")) + parser.add_argument("--vvp", default="vvp") + parser.add_argument("--timeout", type=int, default=21600) + parser.add_argument("command", nargs=argparse.REMAINDER) + args = parser.parse_args() + if args.timeout <= 0: + parser.error("timeout must be positive") + if args.report: + report(args) + else: + if args.reference is None or not args.cases or not args.simulator: + parser.error("replay requires reference, cases and simulator") + replay(args) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/tiny_r2_p5.py b/scripts/tiny_r2_p5.py new file mode 100644 index 00000000..1dbf93b0 --- /dev/null +++ b/scripts/tiny_r2_p5.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python3 +"""Replay immutable P3 images on P5 RTL without relabeling image provenance.""" +from __future__ import annotations + +import argparse +from pathlib import Path +import sys + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT)) +from scripts import tiny_r2_baseline as b # noqa: E402 +from scripts import tiny_r2_p3 as p3 # noqa: E402 +from scripts import tiny_r2_p4 as previous # noqa: E402 + +PHASE = "TINY-R2-P5" +HARDWARE_KEYS = ("SOC", "PDK", "HAVE_PLL", "HAVE_SRAM_IF", "HAVE_SRAM_MACRO", + "SRAM_SIZE_KIB", "PDK_BEHAV", "HAVE_HP", "EXT_CLK_HZ", "AUD_CLK_HZ", + "CLINT_TIMEBASE_HZ", "MGMT_CPU_CLK_HZ", "JTAG_IDCODE") +FABRIC_FIELDS = {"window", "active_targets_max", "dma_rw_overlap_cycles", "dma_rw_beat_overlap_cycles"} + + +def inputs(root: Path, variant: Path) -> dict: + result = previous.inputs(root, variant) + for name in ("AGENTS.md", "docs/engineering.md", "docs/rtl-coding-style.md", + "docs/rtl-coding-style-compliance.md", "rtl/rtl_style_manifest.json", + "rtl/rtl_style_audit.json", "rtl/rtl_readiness.json", ".verible-format", + ".github/workflows/_regression.yml"): + result[name] = b.sha256(root / name) + for directory in (root / "quality", root / "rtl/tiny/mk"): + for path in directory.rglob("*"): + if path.is_file() and path.suffix in (".json", ".mk"): + result[str(path.relative_to(root))] = b.sha256(path) + return result + + +def compatible_hardware(image: dict, model: dict) -> None: + a, z = (c["configuration"] for c in (image, model)) + if any(key not in a or key not in z or a[key] != z[key] for key in HARDWARE_KEYS): + raise ValueError("retained image and candidate hardware configuration differ") + + +def parse_log(log: str, flow: dict) -> dict: + samples = previous.parse_log(log, flow) + rows = [b.parse_fields(line, FABRIC_FIELDS) for line in log.splitlines() + if line.startswith("R2_P5_FABRIC ")] + rows.sort(key=lambda row: row["window"]) + if [row["window"] for row in rows] != list(range(1, 9)): + raise ValueError("missing or duplicate P5 fabric observations") + for row in rows: + if any(value < 0 for value in row.values()) or row["active_targets_max"] > 23: + raise ValueError("invalid P5 fabric observation") + if row["dma_rw_beat_overlap_cycles"] > row["dma_rw_overlap_cycles"]: + raise ValueError("P5 beat overlap exceeds transaction overlap") + if row["dma_rw_overlap_cycles"] and row["active_targets_max"] < 2: + raise ValueError("P5 overlap without independent targets") + samples["fabric"] = rows + return samples + + +def reference_cases(path: Path, names: list[str]) -> list[dict]: + report = b.read_json(path) + if report.get("phase") != previous.PHASE or report.get("status") != "measurement_complete": + raise ValueError("reference is not a completed P4 measurement report") + if not names or len(set(names)) != len(names): + raise ValueError("select distinct retained cases") + original = previous.validate_replay(b.check_artifact(report["executions"]["verilator"]), "verilator") + if original["source_digest"] != report["source_digest"]: + raise ValueError("P4 report source differs from retained execution") + result = [] + for name in names: + matches = [c for c in original["cases"] if c["name"] == name] + comparisons = [c for c in report["comparisons"] if c["name"] == name] + if len(matches) != 1 or len(comparisons) != 1: + raise ValueError("unknown retained P4 case") + case, comparison = matches[0], comparisons[0] + if comparison["image"] != case["image"]: + raise ValueError("P4 comparison changed the retained image") + rows = comparison["workloads"] + if len(rows) != len(case["samples"]["cases"]): + raise ValueError("P4 comparison workload count differs") + for row, sample in zip(rows, case["samples"]["cases"], strict=True): + if (row["name"], row["candidate_cycles"], row["candidate_instructions"]) != ( + sample["name"], sample["cycles"], sample["instructions"]): + raise ValueError("P4 comparison differs from retained samples") + if comparison["local"] != case["samples"]["local"] or comparison["banks"] != case["samples"]["banks"]: + raise ValueError("P4 comparison altered local observations") + result.append({"name": name, "image": case["image"], "samples": case["samples"]}) + return result + + +def replay(args) -> None: + root, variant = args.root, args.variant_root + parent = variant / "meta/tiny-r2-p5/replay" / args.simulator + directory = b.new_attempt(parent, "attempt-") + record = {"phase": PHASE, "status": "running", "repetitions": 1, "cases": []} + b.dump(directory / "record.json", record) + b.dump(parent / "latest.json", {"path": str(directory / "record.json")}) + try: + reference = b.artifact(args.reference) + cases = reference_cases(args.reference, args.cases.split(",")) + configuration = p3.configuration(variant) + for case in cases: + compatible_hardware(b.read_json(b.check_artifact(case["image"]))["configuration"], + configuration) + before = inputs(root, variant) + tools = b.tools_identity(root) + command = args.command[1:] if args.command[:1] == ["--"] else args.command + if not command: + raise ValueError("fresh model compilation command required") + p3.execute_make(root, directory, "compile-model", command) + if before != inputs(root, variant): + raise ValueError("source changed while compiling candidate model") + runtime = b.runtime_inputs(root, variant, args.simulator) + model = variant / "sim" / args.simulator / ("emu" if args.simulator == "verilator" else "simv") + record.update(reference=reference, configuration=configuration, tools=tools, + model=b.artifact(model), source_digest=b.digest_map(before), + source=b.snapshot(root, directory / "inputs", before | runtime), + compilation={s: b.artifact(directory / f"compile-model.{s}") for s in ("json", "log")}) + launch = [str(model)] + if args.simulator == "iverilog": + record["runtime_tool"] = b.vvp_identity(args.vvp, tools["iverilog"]) + launch.insert(0, record["runtime_tool"]["executable"]["path"]) + for case in cases: + image = p3.validate_image(b.check_artifact(case["image"])) + begin, end = image["layout"]["descriptor_range"] + cmd = [*launch, "+firmware=" + image["artifacts"]["tiny_p3.hex"]["path"], + "+tiny_r2_p3", "+tiny_r2_p5", f"+baseline_tcd_base={begin:x}", f"+baseline_tcd_end={end:x}", + "+max_cycles=100000000"] + label = case["name"] + flow = b.execute(root, directory, label, + ["timeout", "--foreground", f"{args.timeout}s", *cmd], quiet=True) + samples = parse_log((directory / f"{label}.log").read_text(), flow) + p3.validate_image(b.check_artifact(case["image"])) + record["cases"].append({"name": label, "image": case["image"], "samples": samples, + "baseline_samples": case["samples"], + "command": cmd, "timeout_seconds": args.timeout, + "flow": b.artifact(directory / f"{label}.json"), + "log": b.artifact(directory / f"{label}.log")}) + b.dump(directory / "record.json", record) + if before != inputs(root, variant) or runtime != b.runtime_inputs(root, variant, args.simulator): + raise ValueError("candidate inputs changed during execution") + b.check_artifact(record["model"]) + b.check_artifact(reference) + for case in cases: + p3.validate_image(b.check_artifact(case["image"])) + for tool in tools.values(): + b.check_artifact(tool["executable"]) + record["status"] = "passed" + except (OSError, ValueError) as error: + record.update(status="failed", error=str(error)) + raise + finally: + b.dump(directory / "record.json", record) + print(f"P5 {args.simulator}: one execution per image; {directory}") + + +def validate_replay(path: Path, simulator: str) -> dict: + record = b.read_json(path) + if record.get("phase") != PHASE or record.get("status") != "passed" or record.get("repetitions") != 1: + raise ValueError("not a completed single-execution P5 replay") + for field in ("model", "reference"): + b.check_artifact(record[field]) + for value in record["compilation"].values(): + b.check_artifact(value) + for field in ("inputs", "snapshot", "patch"): + b.check_artifact(record["source"][field]) + captured = b.read_json(Path(record["source"]["inputs"]["path"])) + if b.digest_map(captured) != record["source"]["input_digest"]: + raise ValueError("candidate source digest mismatch") + for tool in record["tools"].values(): + b.check_artifact(tool["executable"]) + b.check_artifact(tool["archive"]) + prefix = [record["model"]["path"]] + if simulator == "iverilog": + runtime = b.check_artifact(record["runtime_tool"]["executable"]) + compiler = Path(record["tools"]["iverilog"]["executable"]["path"]) + if runtime.resolve().parent != compiler.resolve().parent: + raise ValueError("unmatched Icarus runtime") + prefix.insert(0, str(runtime)) + reference = reference_cases(Path(record["reference"]["path"]), [c["name"] for c in record["cases"]]) + for case, original in zip(record["cases"], reference, strict=True): + if case["image"] != original["image"] or case["baseline_samples"] != original["samples"]: + raise ValueError("comparison changed the retained image or reference measurements") + image = p3.validate_image(b.check_artifact(case["image"])) + compatible_hardware(image["configuration"], record["configuration"]) + first, end = image["layout"]["descriptor_range"] + expected = [*prefix, "+firmware=" + image["artifacts"]["tiny_p3.hex"]["path"], + "+tiny_r2_p3", "+tiny_r2_p5", f"+baseline_tcd_base={first:x}", f"+baseline_tcd_end={end:x}", + "+max_cycles=100000000"] + flow = b.read_json(b.check_artifact(case["flow"])) + if case["command"] != expected or case["timeout_seconds"] <= 0 or flow["command"] != [ + "timeout", "--foreground", f"{case['timeout_seconds']}s", *expected]: + raise ValueError("model/image execution provenance mismatch") + if parse_log(b.check_artifact(case["log"]).read_text(), flow) != case["samples"]: + raise ValueError("reported candidate samples differ from the log") + return record + + +def report(args) -> None: + base = args.variant_root / "meta/tiny-r2-p5" + runs = {} + for simulator in ("verilator", "iverilog"): + pointer = b.read_json(base / "replay" / simulator / "latest.json") + path = Path(pointer["path"]) + runs[simulator] = (validate_replay(path, simulator), b.artifact(path)) + before = b.digest_map(inputs(args.root, args.variant_root)) + if any(run[0]["source_digest"] != before for run in runs.values()): + raise ValueError("candidate source changed after measurement") + cases = {c["name"]: c for c in runs["verilator"][0]["cases"]} + if set(cases) != {"perf-o2-plain-flat", "perf-o2-plain-banked"}: + raise ValueError("P5 comparison requires both approved placement images") + cross = runs["iverilog"][0]["cases"] + if len(cross) != 1 or cross[0]["name"] != "perf-o2-plain-banked": + raise ValueError("P5 requires one banked Icarus execution") + if any(cross[0][key] != cases["perf-o2-plain-banked"][key] for key in ("image", "samples")): + raise ValueError("cross-simulator image/results differ") + comparisons = [] + for name, case in cases.items(): + rows = [] + for old, new in zip(case["baseline_samples"]["cases"], case["samples"]["cases"], strict=True): + rows.append({"name": new["name"], "baseline_cycles": old["cycles"], + "candidate_cycles": new["cycles"], "baseline_instructions": old["instructions"], + "candidate_instructions": new["instructions"], + "cycle_ratio": old["cycles"] / new["cycles"]}) + comparisons.append({"name": name, "image": case["image"], "workloads": rows, + "local": case["samples"]["local"], "banks": case["samples"]["banks"], + "fabric": case["samples"]["fabric"]}) + b.dump(base / "report.json", {"phase": PHASE, "status": "measurement_complete", + "reference_phase": previous.PHASE, + "source_digest": before, "comparisons": comparisons, + "executions": {key: value[1] for key, value in runs.items()}, + "qualification": "matched-binary SAFE24 simulation; not phase/physical acceptance"}) + print(f"P5 matched-image report: {base / 'report.json'}") + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--root", type=Path, required=True) + parser.add_argument("--variant-root", type=Path, required=True) + parser.add_argument("--report", action="store_true") + parser.add_argument("--reference", type=Path) + parser.add_argument("--cases") + parser.add_argument("--simulator", choices=("verilator", "iverilog")) + parser.add_argument("--vvp", default="vvp") + parser.add_argument("--timeout", type=int, default=21600) + parser.add_argument("command", nargs=argparse.REMAINDER) + args = parser.parse_args() + if args.timeout <= 0: + parser.error("timeout must be positive") + if args.report: + report(args) + else: + if args.reference is None or not args.cases or not args.simulator: + parser.error("replay requires reference, cases and simulator") + replay(args) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/tiny_r2_p6.py b/scripts/tiny_r2_p6.py new file mode 100644 index 00000000..1cda97cb --- /dev/null +++ b/scripts/tiny_r2_p6.py @@ -0,0 +1,386 @@ +#!/usr/bin/env python3 +"""Capture and check source-bound Tiny P6 acceptance without promoting a phase.""" +from __future__ import annotations + +import argparse +from datetime import datetime, timezone +from pathlib import Path +import re +import sys +import tarfile + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT)) +from scripts import tiny_r2_baseline as b # noqa: E402 +from scripts import tiny_r2_p5 as previous # noqa: E402 +from scripts.check_simulation import DEFAULT_FAILURE # noqa: E402 + +PHASE = "TINY-R2-P6" +MODEL = "tests/rtl/sdio_native_model.sv" +VERIFICATION_UPDATE_PATHS = {MODEL, "scripts/tiny_r2_p6.py", "tests/test_tiny_r2_p6.py"} +CONSUMER_UPDATE_PATHS = { + "publications/programming_reference.py", "tests/test_publication_programming.py", + "physical/commercial/config/products/tiny.mk", "physical/commercial/tcl/common/products/tiny.tcl", + "tests/test_commercial_flow.py", "scripts/tiny_r2_p6.py", "tests/test_tiny_r2_p6.py", +} +COLLECTION_UPDATE_PATHS = {"scripts/tiny_r2_p6.py", "tests/test_tiny_r2_p6.py"} +RECORDS = { + "UART": {"pads": 1, "echo_bytes": 4, "flow_rejected": 1}, + "DMA": {"channels": 8, "rejected_requests": 5}, + "RNG": {"unqualified": 1, "diagnostic": 1, "irq": 1}, + "CRC": {"dma": 1, "tails": 4, "empty": 1}, + "CRYPTO": {"init": 1, "aes_sha": 1, "dma45": 1, "rsa_public": 1, "zeroize": 1}, + "RECOVERY": {"audio_stops": 2, "ws_underflow": 1, "ws_abort": 1}, + "SDIO": {"dma_bytes": 2048, "pio_bytes": 512, "width1": 1, "width4": 1, "irq": 1, "errors": 2}, +} + + +def parse_log(content: str, flow: dict) -> dict: + if (flow.get("status") != "passed" or flow.get("exit_code") != 0 + or not re.search(r"SIM_TEST_PASS Tiny cycles=[0-9]+(?:\r?\n|$)", content) + or DEFAULT_FAILURE.search(content)): + raise ValueError("P6 requires successful command, terminal marker and no forbidden errors") + result = {} + for name, expected in RECORDS.items(): + rows = [b.parse_fields(line, set(expected)) for line in content.splitlines() + if line.startswith("R2_P6_" + name + " ")] + if rows != [expected]: + raise ValueError(f"missing, duplicate or invalid P6 {name} record") + result[name.lower()] = rows[0] + fields = {"bits", "frames", "ws_frames", "background_jobs", "tcd", "elapsed_us", "cycles", "instructions"} + audio = [b.parse_fields(line, fields) for line in content.splitlines() + if line.startswith("R2_P6_AUDIO ")] + if [row["bits"] for row in audio] != [16, 24]: + raise ValueError("P6 needs exactly both audio formats") + for row in audio: + if (row["frames"] != 256 or row["ws_frames"] != 2 or + not 1 <= row["background_jobs"] <= 64 or + row["tcd"] != int(row["bits"] == 24) or not 0 < row["elapsed_us"] < 50000 or + not 0 < row["instructions"] <= row["cycles"]): + raise ValueError("invalid P6 audio/contention observation") + result["audio"] = audio + wires = [b.parse_fields(line, {"bits", "samples"}) for line in content.splitlines() + if line.startswith("R2_P6_I2S_WIRE ")] + if wires != [{"bits": 16, "samples": 512}, {"bits": 24, "samples": 512}]: + raise ValueError("missing or incorrect independent audio wire observations") + result["audio_wires"] = wires + fields = {"owner", "read_bytes", "write_bytes", "descriptor_bytes", "ar_stall_max", + "aw_stall_max", "w_stall_max", "r_stall_max", "b_stall_max"} + bus = sorted([b.parse_fields(line, fields) for line in content.splitlines() + if line.startswith("R2_P6_AXI ")], key=lambda row: row["owner"]) + if [row["owner"] for row in bus] != [0, 1, 2]: + raise ValueError("P6 needs each actual AXI owner observation") + for row in bus: + if (any(v < 0 for v in row.values()) or row["read_bytes"] == 0 or row["write_bytes"] == 0 + or row["descriptor_bytes"] > row["read_bytes"]): + raise ValueError("invalid P6 AXI payload/backpressure observation") + if bus[1]["descriptor_bytes"] == 0: + raise ValueError("no actual background descriptor fetch observed") + result["bus"] = bus + contention = [b.parse_fields(line, {"sdio_dma_cycles", "cpu_fetches"}) + for line in content.splitlines() if line.startswith("R2_P6_CONTENTION ")] + if len(contention) != 1 or any(value <= 0 for value in contention[0].values()): + raise ValueError("missing actual SDIO/DMA overlap and CPU progress") + result["contention"] = contention[0] + fields = {"ws_frames", "ws_wait_max", "fifo_max", "underflows", "aborts", "clock_stops"} + devices = [b.parse_fields(line, fields) for line in content.splitlines() + if line.startswith("R2_P6_DEVICES ")] + if len(devices) != 1: + raise ValueError("missing or duplicate independent device observations") + d = devices[0] + if (d["ws_frames"] != 4 or not 0 <= d["ws_wait_max"] <= 4800 or + not 1 <= d["fifo_max"] <= 16 or d["underflows"] != 1 or + d["aborts"] != 1 or d["clock_stops"] != 2): + raise ValueError("P6 device coverage/service budget failed") + result["devices"] = d + return result + + +def inputs(variants: list[Path]) -> dict: + result = {} + for variant in variants: + config = b.configuration(variant)["configuration"] + if config.get("APP") != "ci_smoke": + raise ValueError("P6 qualification requires the ci_smoke composition") + result.update(previous.inputs(ROOT, variant)) + for name in (".bake.toml", ".clang-format", "docs/misra-c-2012.md", + "quality/misra/deviations.md", "docs/ip/dma.md", "docs/ip/i2s.md", + "docs/ip/sdio.md", "docs/ip/crypto.md", "docs/ip/ws2812.md", + "physical/commercial/config/products/tiny.mk", + "physical/commercial/tcl/common/products/tiny.tcl"): + result[name] = b.sha256(ROOT / name) + return result + + +def current_tools() -> dict: + tools = b.tools_identity(ROOT) + tools["vvp"] = b.vvp_identity("vvp", tools["iverilog"]) + return tools + + +def checked_flow(path: Path, captured_at: str) -> dict: + flow = b.read_json(path) + if flow.get("status") != "passed" or flow.get("exit_code") != 0: + raise ValueError(f"unsuccessful gate: {path}") + if (not flow.get("started_at") or not flow.get("finished_at") + or flow["started_at"] < captured_at or flow["finished_at"] < flow["started_at"]): + raise ValueError(f"gate lacks successful post-capture chronology: {path}") + return flow + + +def check_model_declaration_move(before: str, after: str) -> None: + """Permit only relocation of one unchanged continuous net declaration. + + Verilator/sv2v accept its forward reference; native Icarus needs the integer + first. Every other source byte, including all procedural behavior, must match. + """ + pattern = re.compile(r"^ wire \[3:0\] s_token_lanes = [^;]+;\n", re.M) + old, new = pattern.findall(before), pattern.findall(after) + if (len(old) != 1 or old != new or pattern.sub("", before) != pattern.sub("", after) + or after.index(" integer s_data_bytes;") > after.index(new[0])): + raise ValueError("not the permitted SDIO model declaration-only relocation") + + +def snapshot_model(source: dict) -> str: + with tarfile.open(b.check_artifact(source["snapshot"])) as archive: + return archive.extractfile(MODEL).read().decode() + + +def check_update(directory: Path, saved: dict, update: dict, current: dict) -> dict: + if (update.get("phase") != PHASE or update.get("kind") != "native_model_declaration_order" + or update["original_source"] != b.artifact(directory / "source.json")): + raise ValueError("verification update does not bind the original campaign") + old = b.read_json(b.check_artifact(saved["source"]["inputs"])) + new = b.read_json(b.check_artifact(update["source"]["inputs"])) + changed = {name for name in old.keys() | new.keys() if old.get(name) != new.get(name)} + if (MODEL not in changed or not changed <= VERIFICATION_UPDATE_PATHS + or sorted(changed) != update["changed_inputs"] or new != current): + raise ValueError("verification update changed DUT/firmware/constraints or unapproved inputs") + check_model_declaration_move(snapshot_model(saved["source"]), snapshot_model(update["source"])) + for source in (saved["source"], update["source"]): + for name in ("inputs", "snapshot", "patch"): + b.check_artifact(source[name]) + if update["tools"] != saved["tools"] or update["captured_at"] < saved["captured_at"]: + raise ValueError("verification update changed tools or chronology") + return old + + +def capture_verification_update(directory: Path, variants: list[Path]) -> None: + path = directory / "verification-update.json" + if path.exists(): + raise ValueError("never overwrite a verification update") + saved = b.read_json(directory / "source.json") + current = inputs(variants) + old = b.read_json(b.check_artifact(saved["source"]["inputs"])) + changed = sorted(name for name in old.keys() | current.keys() if old.get(name) != current.get(name)) + if MODEL not in changed or not set(changed) <= VERIFICATION_UPDATE_PATHS: + raise ValueError("only the native model and its collector tests may change") + check_model_declaration_move(snapshot_model(saved["source"]), (ROOT / MODEL).read_text()) + update = {"phase": PHASE, "kind": "native_model_declaration_order", + "original_source": b.artifact(directory / "source.json"), "changed_inputs": changed, + "source": b.snapshot(ROOT, directory / "inputs-native-model", current), + "tools": current_tools(), "captured_at": datetime.now(timezone.utc).isoformat()} + check_update(directory, saved, update, current) + b.dump(path, update) + + +def check_consumer_update(directory: Path, previous_update: dict, update: dict, current: dict) -> None: + """Permit only consumers not used by the running RTL/smoke flows.""" + if (update.get("phase") != PHASE or update.get("kind") != "publication_and_commercial_consumers" + or update["previous_update"] != b.artifact(directory / "verification-update.json")): + raise ValueError("consumer update does not bind the verified model snapshot") + old = b.read_json(b.check_artifact(previous_update["source"]["inputs"])) + new = b.read_json(b.check_artifact(update["source"]["inputs"])) + changed = {name for name in old.keys() | new.keys() if old.get(name) != new.get(name)} + if (not changed or not changed <= CONSUMER_UPDATE_PATHS or + sorted(changed) != update["changed_inputs"] or new != current): + raise ValueError("consumer update changed simulation, firmware, smoke constraints or unrelated inputs") + for field in ("inputs", "snapshot", "patch"): + b.check_artifact(update["source"][field]) + if (update["tools"] != previous_update["tools"] or + update["captured_at"] < previous_update["captured_at"]): + raise ValueError("consumer update changed tools or chronology") + + +def capture_consumer_update(directory: Path, variants: list[Path]) -> None: + path = directory / "consumer-update.json" + if path.exists(): + raise ValueError("never overwrite a consumer update") + previous_update = b.read_json(directory / "verification-update.json") + current = inputs(variants) + old = b.read_json(b.check_artifact(previous_update["source"]["inputs"])) + changed = sorted(name for name in old.keys() | current.keys() if old.get(name) != current.get(name)) + if not changed or not set(changed) <= CONSUMER_UPDATE_PATHS: + raise ValueError("unexpected non-consumer source changes") + update = {"phase": PHASE, "kind": "publication_and_commercial_consumers", + "previous_update": b.artifact(directory / "verification-update.json"), + "changed_inputs": changed, + "source": b.snapshot(ROOT, directory / "inputs-consumers", current), + "tools": current_tools(), "captured_at": datetime.now(timezone.utc).isoformat()} + check_consumer_update(directory, previous_update, update, current) + b.dump(path, update) + + +def check_collection_update(directory: Path, previous_update: dict, update: dict, current: dict) -> None: + if (update.get("phase") != PHASE or update.get("kind") != "terminal_log_collection" + or update["previous_update"] != b.artifact(directory / "consumer-update.json")): + raise ValueError("collection update does not bind the consumer snapshot") + old = b.read_json(b.check_artifact(previous_update["source"]["inputs"])) + new = b.read_json(b.check_artifact(update["source"]["inputs"])) + changed = {name for name in old.keys() | new.keys() if old.get(name) != new.get(name)} + if (not changed or not changed <= COLLECTION_UPDATE_PATHS or + sorted(changed) != update["changed_inputs"] or new != current): + raise ValueError("collection update changed inputs outside the collector/tests") + for field in ("inputs", "snapshot", "patch"): + b.check_artifact(update["source"][field]) + if (update["tools"] != previous_update["tools"] or + update["captured_at"] < previous_update["captured_at"]): + raise ValueError("collection update changed tools or chronology") + + +def capture_collection_update(directory: Path, variants: list[Path]) -> None: + path = directory / "collection-update.json" + if path.exists(): + raise ValueError("never overwrite a collection update") + previous_update = b.read_json(directory / "consumer-update.json") + current = inputs(variants) + old = b.read_json(b.check_artifact(previous_update["source"]["inputs"])) + changed = sorted(name for name in old.keys() | current.keys() if old.get(name) != current.get(name)) + if not changed or not set(changed) <= COLLECTION_UPDATE_PATHS: + raise ValueError("unexpected non-collector source changes") + update = {"phase": PHASE, "kind": "terminal_log_collection", + "previous_update": b.artifact(directory / "consumer-update.json"), + "changed_inputs": changed, + "source": b.snapshot(ROOT, directory / "inputs-collection", current), + "tools": current_tools(), "captured_at": datetime.now(timezone.utc).isoformat()} + check_collection_update(directory, previous_update, update, current) + b.dump(path, update) + + +def capture(directory: Path, variants: list[Path]) -> None: + if (directory / "source.json").exists(): + raise ValueError("capture needs a fresh evidence directory; never overwrite provenance") + if {b.configuration(v)["configuration"]["PDK"] for v in variants} != {"ICS55", "IHP130"}: + raise ValueError("capture both Tiny PDK input sets before validation") + source = b.snapshot(ROOT, directory / "inputs", inputs(variants)) + tools = current_tools() + b.dump(directory / "source.json", {"phase": PHASE, "source": source, "tools": tools, + "captured_at": datetime.now(timezone.utc).isoformat(), + "variants": [str(p.resolve()) for p in variants], + "configurations": [b.configuration(p) for p in variants]}) + + +def report(directory: Path, gates: list[Path], variants: list[Path]) -> None: + saved = b.read_json(directory / "source.json") + if saved.get("phase") != PHASE: + raise ValueError("wrong evidence phase") + if saved["tools"] != current_tools(): + raise ValueError("locked tool identity changed after capture") + selected = [Path(p) for p in saved["variants"]] + current = inputs(selected) + original = b.read_json(b.check_artifact(saved["source"]["inputs"])) + update_path = directory / "verification-update.json" + update = b.read_json(update_path) if update_path.exists() else None + consumer_path = directory / "consumer-update.json" + consumer = b.read_json(consumer_path) if consumer_path.exists() else None + collection_path = directory / "collection-update.json" + collection = b.read_json(collection_path) if collection_path.exists() else None + consumer_inputs = current + if collection: + if not consumer: + raise ValueError("collection update requires the consumer snapshot") + check_collection_update(directory, consumer, collection, current) + consumer_inputs = b.read_json(b.check_artifact(consumer["source"]["inputs"])) + verified_inputs = current + if consumer: + if not update: + raise ValueError("consumer update requires the model snapshot") + check_consumer_update(directory, update, consumer, consumer_inputs) + verified_inputs = b.read_json(b.check_artifact(update["source"]["inputs"])) + if update: + original = check_update(directory, saved, update, verified_inputs) + elif current != original: + raise ValueError("P6 technical inputs changed after capture") + for name in ("snapshot", "patch"): + b.check_artifact(saved["source"][name]) + checked = {} + for path in gates: + gate = checked_flow(path, saved["captured_at"]) + checked[str(path)] = {"result": b.artifact(path), "log": b.artifact(Path(gate["log"]))} + simulations = {} + selections = [(variant, simulator) for variant in variants for simulator in ("iverilog", "verilator") + if (variant / "sim" / simulator / "result-sim.json").is_file()] + for variant, simulator in selections: + config = b.configuration(variant) + if config["configuration"].get("APP") != "ci_smoke": + raise ValueError("simulation must use the complete ci_smoke composition") + pdk = config["configuration"]["PDK"] + key = pdk + "/" + simulator + if key in simulations or simulator not in ("iverilog", "verilator"): + raise ValueError("duplicate or unsupported simulation variant") + folder = variant / "sim" / simulator + model_source = update if update and simulator == "iverilog" else saved + flow = checked_flow(folder / "result-sim.json", model_source["captured_at"]) + compilation = checked_flow(folder / "result-compile.json", model_source["captured_at"]) + if flow["started_at"] < compilation["finished_at"]: + raise ValueError("simulation predates completed model compilation") + check = b.read_json(folder / "result-sim-check.json") + if check.get("status") != "passed": + raise ValueError("missing strict simulation verdict") + samples = parse_log((folder / "sim.log").read_text(), flow) + paths = [folder / name for name in ("result-sim.json", "result-sim-check.json", "sim.log", + "result-compile.json", "compile.log")] + paths += [folder / ("emu" if simulator == "verilator" else "simv"), + variant / "sw/firmware", variant / "sw/retrosoc_fw.hex", variant / "sw/retrosoc_fw.map", + variant / "sw/retrosoc_fw.bin", variant / "sw/ld2_all_sram.lds", + variant / "sw/memory_regions.ld", + variant / "meta/manifest.json"] + paths += sorted(path for path in (variant / "sw/include").rglob("*") if path.is_file()) + runtime = b.runtime_inputs(ROOT, variant, simulator) + if update and simulator == "verilator": + if runtime.get(MODEL) != current[MODEL]: + raise ValueError("missing native card model runtime input") + runtime[MODEL] = original[MODEL] + simulations[key] = {"configuration": config, "simulator": simulator, "samples": samples, + "source": model_source["source"], "runtime_inputs": runtime, + "artifacts": [b.artifact(path) for path in paths]} + if set(simulations) != {p + "/" + s for p in ("ICS55", "IHP130") for s in ("iverilog", "verilator")}: + raise ValueError("P6 needs both simulators on both Tiny PDKs") + # Physical/quality reports remain independent gates; this status never + # promotes functional collection into phase or physical qualification. + b.dump(directory / "report.json", {"phase": PHASE, "status": "functional_evidence_collected", + "tools": saved["tools"], + "verification_update": b.artifact(update_path) if update else None, + "consumer_update": b.artifact(consumer_path) if consumer else None, + "collection_update": b.artifact(collection_path) if collection else None, + "source": saved["source"], "source_inputs_rechecked": len(current), "gates": checked, + "simulations": simulations, "limitations": ["human review pending", "timing observational", + "no QFN64/package, routed/PVT/CDC/RDC or publication qualification"]}) + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("action", choices=("capture", "capture-verification-update", "capture-consumer-update", + "capture-collection-update", "report")) + parser.add_argument("--evidence-root", type=Path, required=True) + parser.add_argument("--variant-root", action="append", type=Path, required=True) + parser.add_argument("--gate", action="append", type=Path, default=[]) + args = parser.parse_args() + directory = args.evidence_root.resolve() + if not directory.is_relative_to(ROOT / "build"): + parser.error("evidence must stay under the repository build root") + if args.action == "capture": + capture(directory, args.variant_root) + elif args.action == "capture-verification-update": + capture_verification_update(directory, args.variant_root) + elif args.action == "capture-consumer-update": + capture_consumer_update(directory, args.variant_root) + elif args.action == "capture-collection-update": + capture_collection_update(directory, args.variant_root) + else: + report(directory, args.gate, args.variant_root) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/tiny_r2_p7.py b/scripts/tiny_r2_p7.py new file mode 100644 index 00000000..082c9f41 --- /dev/null +++ b/scripts/tiny_r2_p7.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +"""P7 structural clock/macro evidence; separate from the frozen P1 audit.""" +from __future__ import annotations + +import argparse +import json +from pathlib import Path +import sys + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT)) +from scripts import tiny_r2_baseline as b # noqa: E402 +from scripts.config_key import variant_id # noqa: E402 +from scripts.tiny_ics55_platform import expand_netlist, inspect_clock_source # noqa: E402 + +PHASE = "TINY-R2-P7" +RAM_TYPES = {"ICS55": ("ics55_ecos_sram_1024x32_m8", "CLK"), + "IHP130": ("RM_IHPSG13_1P_1024x32_c2_bm_bist", "A_CLK")} + + +def inspect_netlist(document: dict, pdk: str) -> dict: + modules, top, leaves, canonical, boundaries = expand_netlist(document) + + def boundary(suffix: str, kind: str) -> dict: + found = [cell for name, cell in boundaries.items() + if name.replace("/", ".").endswith("." + suffix) + and cell["type"].split("$")[0] == kind] + if len(found) != 1: + raise ValueError(f"missing or ambiguous P7 clock boundary: {suffix}") + return found[0] + + def pin(cell: dict, name: str) -> str: + bits = cell["ports"].get(name, []) + if len(bits) != 1 or bits[0] in ("0", "1", "x", "z"): + raise ValueError(f"missing/constant P7 clock: {cell['path']}/{name}") + return bits[0] + + root = boundary("u_soc.u_clock_backend.u_sys_mux", "safe_clock_mux") + memory = boundary("u_soc.u_clock_backend.u_mem_mux", "safe_clock_mux") + peripheral = boundary("u_soc.u_clock_backend.u_pclk_mux", "safe_clock_mux") + cpu_gate = boundary("u_soc.u_cpu.u_cpu_gate", "tiny_clock_gate") + crypto_gate = boundary("u_soc.u_crypto_lifecycle", "tiny_peripheral_leaf") + sys_clock = pin(root, "clk_o") + pclk = pin(peripheral, "clk_o") + if pin(cpu_gate, "clk_i") != sys_clock or pin(memory, "clk0_i") != sys_clock: + raise ValueError("CPU gate/MEM source is not common SYS") + if pin(crypto_gate, "clk_i") != pclk: + raise ValueError("private Crypto storage gate is not sourced from PCLK") + cpu_clock = pin(cpu_gate, "clk_o") + crypto_clock = pin(crypto_gate, "clk_o") + ram_type, ram_pin = RAM_TYPES[pdk] + macros = [cell for cell in leaves if cell["type"] == ram_type] + main_names = {f"u_soc.u_sram.gen_group[{g}].u_group.gen_bank[{n}].u_ram.u_mem" + for g in range(4) for n in range(8)} + crypto_names = {f"u_soc.u_crypto.u_storage.gen_bank[{n}].gen_macro.u_sram.u_mem" + for n in range(6)} + observed = set() + for cell in macros: + name = next((name for name in main_names | crypto_names + if cell["path"].replace("/", ".").endswith("." + name)), None) + if name is None or name in observed: + raise ValueError("extra, duplicated or renamed physical SRAM macro") + observed.add(name) + expected_clock = sys_clock if name in main_names else crypto_clock + if pin(cell, ram_pin) != expected_clock: + raise ValueError(f"physical SRAM clock mismatch: {name}") + if len(macros) != 38 or observed != main_names | crypto_names: + raise ValueError("P7 requires 32 main SRAM plus six private Crypto macros") + cpu = [cell for cell in leaves if "u_hazard3_cpu_2port" in cell["path"] + and ("CK" in cell["ports"] or "CKN" in cell["ports"] or "CLK" in cell["ports"])] + if not cpu: + raise ValueError("missing CPU clock endpoints") + for cell in cpu: + for name in ("CK", "CKN", "CLK"): + if name in cell["ports"] and pin(cell, name) not in (sys_clock, cpu_clock): + raise ValueError("CPU has an unapproved clock or a hidden divider") + pll = [cell for cell in leaves if cell["type"] == "PLL_TOP"] + if pdk == "ICS55": + if len(pll) != 1 or pin(pll[0], "CKOUT1") != pin(root, "clk1_i"): + raise ValueError("P7 requires one PLL CKOUT1 connected to the SYS mux") + if pin(pll[0], "REFCLK") != pin(root, "clk0_i"): + raise ValueError("PLL reference and safe SYS source differ") + constants = {"0": 0, "1": 1} + for cell in leaves: + if cell["type"] in ("TIELOH7R", "TIEHIH7R"): + for bit in cell["ports"]["Z"]: + constants[bit] = int(cell["type"] == "TIEHIH7R") + if pll[0]["ports"].get("EN", ["x"])[0] in constants: + raise ValueError("P7 PLL enable must be controlled, not tied on/off") + external = modules[top]["ports"]["extclk_i_pad"]["bits"] + if len(external) != 1: + raise ValueError("missing external reference input") + inspect_clock_source(leaves, pin(root, "clk0_i"), + canonical(f"{top}:{external[0]}"), constants) + elif pll: + raise ValueError("no-PLL compatibility instantiated a PLL") + return {"system_clock": sys_clock, "memory_clock": pin(memory, "clk_o"), + "peripheral_clock": pclk, "cpu_clock": cpu_clock, + "main_macros": 32, "private_crypto_macros": 6, "pll_macros": len(pll), + "cpu_clocked_cells": len(cpu), "macro_bindings": macros, + "clock_boundaries": [root, memory, peripheral, cpu_gate, crypto_gate], + "qualification": "structural domain binding only; no physical operating-point qualification"} + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("action", choices=("audit-netlist", "verify-netlist", "capture-source", "verify-source")) + parser.add_argument("--variant-root", type=Path, required=True) + parser.add_argument("--netlist", type=Path) + parser.add_argument("--config", type=Path) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + try: + if args.action in ("capture-source", "verify-source"): + inputs = b.source_inputs(ROOT, "ICS55") | b.source_inputs(ROOT, "IHP130") + for folder in ("tests", "configs/ci"): + for path in (ROOT / folder).rglob("*"): + if path.is_file() and path.suffix in (".py", ".sv", ".svh", ".c", ".S", ".ld", ".mk"): + inputs[str(path.relative_to(ROOT))] = b.sha256(path) + contract = b.artifact(ROOT / "docs/ip/tiny-soc.md") + if args.action == "capture-source": + if args.output.exists(): + raise ValueError("source evidence already exists; use a new attempt instead of overwriting") + b.dump(args.output, {"phase": PHASE, "repository": b.git_info(ROOT), + "specification": contract, "source_inputs": inputs, + "source_digest": b.digest_map(inputs), "tools": b.tools_identity(ROOT), + "snapshot": b.snapshot(ROOT, args.output.parent / "source", inputs)}) + else: + recorded = b.read_json(args.output) + if (recorded.get("phase") != PHASE or recorded.get("source_inputs") != inputs or + recorded.get("specification") != contract): + raise ValueError("P7 sources changed after the retained source snapshot") + print(f"{PHASE} {args.action}: PASS") + return 0 + if args.netlist is None or args.config is None: + raise ValueError("netlist actions require --netlist and --config") + manifest = b.configuration(args.variant_root) + pdk = manifest["configuration"]["PDK"] + config = dict(line.split("=", 1) for line in args.config.read_text().splitlines() if "=" in line) + for key, value in {"SOC": "TINY", "PDK": pdk, + "HAVE_PLL": "YES" if pdk == "ICS55" else "NO", + "HAVE_SRAM_MACRO": "YES", "SRAM_SIZE_KIB": "128", + "TOP_DESIGN": "retrosoc_tiny_asic", "PERIOD_PS": "41667"}.items(): + if config.get(key) != value: + raise ValueError(f"P7 consumed configuration mismatch: {key}") + values = [f"{k}={v}" for k, v in manifest["configuration"].items() + if k not in ("SIMU", "SYNTH", "SYNTH_RECIPE", "STA")] + expected = variant_id(manifest["profile"], values, + ROOT / "dependencies/dependencies.lock.json", + "2000-01-01-00-00").rsplit("-", 1)[1] + if config.get("CONFIG_DIGEST") != expected or args.variant_root.name.rsplit("-", 1)[1] != expected: + raise ValueError("P7 consumed configuration digest mismatch") + paths = (args.netlist, Path(str(args.netlist) + ".json"), args.config) + if args.action == "audit-netlist" and not paths[1].is_file(): + # A mapped Verilog export is also a valid audit input. Serialize it + # without optimization or remapping; retain the conversion result. + # This supports the pre-P7 IHP130 recipe which did not emit JSON. + flow = b.read_json(args.variant_root / "syn/yosys/result-synth.json") + if flow.get("status") != "passed" or flow.get("exit_code") != 0: + raise ValueError("cannot derive audit JSON from an unsuccessful synthesis") + b.execute(ROOT, args.output.parent, "p7-netlist-json", + ["timeout", "--foreground", "120s", "yosys", "-Q", "-T", "-p", + f'read_verilog "{paths[0]}"; write_json "{paths[1]}"'], quiet=True) + inputs = [b.artifact(path) for path in paths] + auditors = [b.artifact(ROOT / "scripts" / name) for name in + ("tiny_r2_p7.py", "tiny_ics55_platform.py", "tiny_r2_baseline.py", "config_key.py")] + if args.action == "audit-netlist": + result = {"phase": PHASE, "status": "passed", "pdk": pdk, + "audit_scripts": auditors, "inputs": inputs, + "configuration": manifest["configuration"], "config_digest": expected, + "binding": inspect_netlist(json.loads(paths[1].read_text()), pdk)} + b.dump(args.output, result) + else: + result = b.read_json(args.output) + if (result.get("status") != "passed" or result.get("phase") != PHASE or + result.get("audit_scripts") != auditors or result.get("config_digest") != expected or + result.get("inputs") != inputs): + raise ValueError("P7 structural evidence is missing, stale, or not passed") + except (OSError, ValueError, KeyError) as error: + print(f"{PHASE}: {error}", file=sys.stderr) + return 1 + print(f"{PHASE} {args.action}: PASS (structural evidence only)") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/c/test_runtime.c b/tests/c/test_runtime.c index a1cf4bea..664778e6 100644 --- a/tests/c/test_runtime.c +++ b/tests/c/test_runtime.c @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include #include @@ -65,13 +67,56 @@ uint32_t rs_ga2d_test_mem_pad_mode; volatile uint32_t rs_npu_test_mmio[1024]; volatile uint32_t rs_fabric_monitor_test_mmio[1024]; volatile uint32_t rs_resource_test_mmio[1024]; +volatile uint32_t rs_tiny_rcu_test_mmio[1024]; volatile uint32_t rs_dma_test_mmio[1024]; static uint32_t ws_registers[16]; static uint32_t ws_pushed; static uint32_t ws_pixels[33]; +static uint32_t ws_idle_on_interrupt_read; +static uint32_t dma_irq_enabled = 8U; +static bool dma_irq_pending; +static rs_dma_session_t dma_irq_owner; +static rs_status_t dma_irq_result; + +uint32_t rs_dma_test_irq_save(void) { + const uint32_t saved = dma_irq_enabled; + dma_irq_enabled = 0U; + return saved; +} + +void rs_dma_test_irq_restore(uint32_t saved) { + dma_irq_enabled = saved; + if ((saved != 0U) && dma_irq_pending) { + dma_irq_pending = false; + /* Model START taking effect before an interrupt can execute. */ + if (RS_DMA_CH_REG(3U, RS_DMA_CH_REG_CTRL) == RS_DMA_CH_CTRL_START) { + RS_DMA_CH_REG(3U, RS_DMA_CH_REG_STATUS) = RS_DMA_STATUS_BUSY; + } + dma_irq_result = rs_dma_session_acquire(&dma_irq_owner, 3U); + if (dma_irq_result == RS_OK) { + const rs_dma_config_t config = {.kind = RS_DMA_KIND_MM_TO_MM, + .source = 0x3000U, + .destination = 0x4000U, + .byte_count = 16U, + .width = RS_DMA_WIDTH_32, + .source_increment = true, + .destination_increment = true, + .burst_beats = 1U}; + dma_irq_result = rs_dma_session_configure(&dma_irq_owner, &config); + } + } +} uint32_t rs_ws2812_test_read(uint32_t offset) { - return ws_registers[offset / 4U]; + const uint32_t value = ws_registers[offset / 4U]; + if ((offset == 0x30U) && (ws_idle_on_interrupt_read != 0U)) { + ws_registers[0x18U / 4U] = 8U; + if (ws_idle_on_interrupt_read == 1U) { + ws_registers[0x30U / 4U] |= RS_WS2812_INTR_DONE; + } + ws_idle_on_interrupt_read = 0U; + } + return value; } void rs_ws2812_test_write(uint32_t offset, uint32_t value) { @@ -105,13 +150,121 @@ static void ws_test_reset(void) { ws_registers[0x18U / 4U] = 8U; ws_registers[0x24U / 4U] = 3U; ws_pushed = 0U; + ws_idle_on_interrupt_read = 0U; +} + +static int test_dma_tcd_preemption(void) { + static _Alignas(64) + rs_dma_tcd_t tcd = {.source = 0x1000U, + .destination = 0x2000U, + .byte_count = 32U, + .y_count = 1U, + .control = RS_DMA_TCD_VALID | RS_DMA_TCD_SRC_INC | RS_DMA_TCD_DST_INC | + (UINT32_C(1) << RS_DMA_TCD_BURST_SHIFT)}; + rs_status_t result; + bool valid; + ws_test_reset(); + dma_irq_pending = true; + dma_irq_result = RS_OK; + result = rs_dma_submit_tcd(3U, &tcd, 1U); + valid = (result == RS_ETIMEOUT) && (dma_irq_result == RS_EIO) && !dma_irq_pending && + (dma_irq_enabled == 8U) && + (RS_DMA_CH_REG(3U, RS_DMA_CH_REG_TCD_HEAD) == (uint32_t)(uintptr_t)&tcd) && + (RS_DMA_CH_REG(3U, RS_DMA_CH_REG_TCD_COUNT) == 1U) && + (RS_DMA_CH_REG(3U, RS_DMA_CH_REG_SRC_ADDR) == tcd.source); + RS_DMA_CH_REG(3U, RS_DMA_CH_REG_STATUS) = 0U; + (void)rs_dma_session_release(&dma_irq_owner); + if (!valid) { + return 1; + } + /* An already leased direct channel must remain untouched on rejection. */ + if (rs_dma_session_acquire(&dma_irq_owner, 3U) != RS_OK) { + return 2; + } + RS_DMA_CH_REG(3U, RS_DMA_CH_REG_SRC_ADDR) = 0x3000U; + result = rs_dma_submit_tcd(3U, &tcd, 1U); + valid = (result == RS_EIO) && (dma_irq_enabled == 8U) && + (RS_DMA_CH_REG(3U, RS_DMA_CH_REG_TCD_HEAD) == 0U) && + (RS_DMA_CH_REG(3U, RS_DMA_CH_REG_TCD_COUNT) == 0U) && + (RS_DMA_CH_REG(3U, RS_DMA_CH_REG_SRC_ADDR) == 0x3000U); + (void)rs_dma_session_release(&dma_irq_owner); + if (!valid) { + return 3; + } + /* Calling from an already masked context must not enable interrupts. */ + dma_irq_enabled = 0U; + result = rs_dma_submit_tcd(3U, &tcd, 1U); + valid = (result == RS_ETIMEOUT) && (dma_irq_enabled == 0U); + dma_irq_enabled = 8U; + return valid ? 0 : 4; +} + +static int test_ws2812_completion_race(void) { + rs_ws2812_dma_status_t transfer; + for (uint32_t delay = 1U; delay <= 2U; ++delay) { + ws_test_reset(); + if (rs_ws2812_dma_begin(ws_pixels, 4U, 0U, 100U) != RS_OK) { + return 1; + } + /* Complete after INTR_STATE was sampled, before STATUS is sampled. + * The second case also delays the sticky DONE latch until next service. */ + ws_idle_on_interrupt_read = delay; + if (rs_ws2812_dma_service(1U) != RS_OK) { + return 2; + } + (void)rs_ws2812_dma_status(&transfer); + if (!transfer.active || transfer.draining || transfer.dma_pending) { + return 3; + } + ws_registers[0x30U / 4U] |= RS_WS2812_INTR_DONE; + if (rs_ws2812_dma_service(2U) != RS_OK) { + return 4; + } + (void)rs_ws2812_dma_status(&transfer); + if (transfer.active || (transfer.result != RS_OK) || (ws_registers[0x24U / 4U] != 3U)) { + return 5; + } + } + /* An idle core without DONE must still terminate on error, abort or the + * original deadline; waiting for the sticky latch cannot hide a failure. */ + for (uint32_t failure = 0U; failure < 3U; ++failure) { + const rs_status_t expected = (failure == 2U) ? RS_ETIMEOUT : RS_EIO; + ws_test_reset(); + if (rs_ws2812_dma_begin(ws_pixels, 4U, 0U, 3U) != RS_OK) { + return 6; + } + ws_idle_on_interrupt_read = 2U; + if (rs_ws2812_dma_service(1U) != RS_OK) { + return 7; + } + if (failure == 0U) { + ws_registers[0x2cU / 4U] = 1U; + } else if (failure == 1U) { + ws_registers[0x30U / 4U] = RS_WS2812_INTR_ABORTED; + } + if (rs_ws2812_dma_service(3U) != expected) { + return 8; + } + (void)rs_ws2812_dma_service(4U); + (void)rs_ws2812_dma_service(5U); + (void)rs_ws2812_dma_status(&transfer); + if (transfer.active || (transfer.result != expected)) { + return 9; + } + } + return 0; } static int test_dma_sessions(void) { rs_dma_session_t first = {0}, second = {0}; - rs_dma_config_t config = {.kind = RS_DMA_KIND_MM_TO_MM, .source = 0x1000U, - .destination = 0x2000U, .byte_count = 32U, .width = RS_DMA_WIDTH_32, - .source_increment = true, .destination_increment = true, .burst_beats = 1U}; + rs_dma_config_t config = {.kind = RS_DMA_KIND_MM_TO_MM, + .source = 0x1000U, + .destination = 0x2000U, + .byte_count = 32U, + .width = RS_DMA_WIDTH_32, + .source_increment = true, + .destination_increment = true, + .burst_beats = 1U}; ws_test_reset(); if ((rs_dma_session_acquire(&first, 3U) != RS_OK) || (rs_dma_session_acquire(&first, 2U) != RS_EIO) || @@ -123,13 +276,11 @@ static int test_dma_sessions(void) { return 1; } RS_DMA_CH_REG(3U, RS_DMA_CH_REG_STATUS) = RS_DMA_STATUS_BUSY; - if ((rs_dma_session_release(&first) != RS_EIO) || - (rs_dma_session_abort(&first) != RS_OK)) { + if ((rs_dma_session_release(&first) != RS_EIO) || (rs_dma_session_abort(&first) != RS_OK)) { return 2; } RS_DMA_CH_REG(3U, RS_DMA_CH_REG_STATUS) = RS_DMA_STATUS_ABORTED; - if ((rs_dma_session_release(&first) != RS_OK) || - (rs_dma_session_release(&first) != RS_EIO) || + if ((rs_dma_session_release(&first) != RS_OK) || (rs_dma_session_release(&first) != RS_EIO) || (rs_dma_session_acquire(&second, 3U) != RS_OK) || (rs_dma_session_release(&second) != RS_OK)) { return 3; @@ -162,15 +313,13 @@ static int test_ws2812_refill(void) { ws_pixels[32] = 0U; if ((rs_ws2812_dma_begin(ws_pixels, 33U, 0U, 100U) != RS_OK) || (ws_pushed != 16U) || (ws_registers[0x24U / 4U] != 8U) || (rs_ws2812_push(0U, 1U) != RS_EIO) || - (rs_ws2812_start(1U) != RS_EIO) || - (rs_dma_session_acquire(&competitor, 3U) != RS_EIO)) { + (rs_ws2812_start(1U) != RS_EIO) || (rs_dma_session_acquire(&competitor, 3U) != RS_EIO)) { return 4; } ws_registers[0x30U / 4U] = RS_WS2812_INTR_FIFO_LOW; (void)rs_ws2812_dma_service(1U); (void)rs_ws2812_dma_status(&transfer); - if ((transfer.batches != 0U) || - ((ws_registers[0x30U / 4U] & RS_WS2812_INTR_FIFO_LOW) != 0U)) { + if ((transfer.batches != 0U) || ((ws_registers[0x30U / 4U] & RS_WS2812_INTR_FIFO_LOW) != 0U)) { return 5; } /* Hardware REMAINING includes queued words: it must never size the batch. */ @@ -206,8 +355,7 @@ static int test_ws2812_refill(void) { ws_registers[0x30U / 4U] = RS_WS2812_INTR_DONE; if ((rs_ws2812_dma_service(15U) != RS_OK) || (rs_dma_session_acquire(&competitor, 3U) != RS_OK) || - (rs_dma_session_release(&competitor) != RS_OK) || - (ws_registers[0x24U / 4U] != 3U)) { + (rs_dma_session_release(&competitor) != RS_OK) || (ws_registers[0x24U / 4U] != 3U)) { return 11; } /* Wire DONE may precede a delayed final B. Retain the lease until both @@ -768,7 +916,11 @@ static int test_sdio_helpers(void) { rs_sd_memory_info_t info = {0}; uint32_t value; - if ((rs_sdio_clock_calculate(72000000U, 400000U, &clock) != RS_OK) || + if ((rs_sdio_clock_calculate(24000000U, 400000U, &clock) != RS_OK) || + (clock.half_period != 30U) || (clock.actual_hz != 400000U) || + (rs_sdio_clock_calculate(24000000U, 12000000U, &clock) != RS_OK) || + (clock.half_period != 1U) || (clock.actual_hz != 12000000U) || + (rs_sdio_clock_calculate(72000000U, 400000U, &clock) != RS_OK) || (clock.half_period != 90U) || (clock.actual_hz != 400000U) || (rs_sdio_clock_calculate(100000000U, 50000000U, &clock) != RS_OK) || (clock.half_period != 1U) || (clock.actual_hz != 50000000U)) { @@ -936,32 +1088,48 @@ rs_status_t rs_tiny_dma_tcd_validate(uint32_t channel, const rs_dma_tcd_t *tcd); static int test_tiny_dma_config_validation(void) { rs_dma_config_t config = { - .kind = RS_DMA_KIND_MM_TO_MM, .request = RS_DMA_REQUEST_SOFTWARE, + .kind = RS_DMA_KIND_MM_TO_MM, + .request = RS_DMA_REQUEST_SOFTWARE, .source = (uintptr_t)UINT32_C(0x30000000), - .destination = (uintptr_t)UINT32_C(0x30001000), .byte_count = 259U, - .width = RS_DMA_WIDTH_32, .source_increment = true, .destination_increment = true, + .destination = (uintptr_t)UINT32_C(0x30001000), + .byte_count = 259U, + .width = RS_DMA_WIDTH_32, + .source_increment = true, + .destination_increment = true, .burst_beats = 16U, }; static _Alignas(64) rs_dma_tcd_t descriptor; if ((rs_tiny_dma_config_validate(3U, &config) != RS_OK) || - (rs_tiny_dma_config_validate(4U, &config) != RS_EINVAL)) { + (rs_tiny_dma_config_validate(7U, &config) != RS_OK) || + (rs_tiny_dma_config_validate(8U, &config) != RS_EINVAL)) { return 1; } config.kind = RS_DMA_KIND_MM_TO_STREAM; config.request = RS_DMA_REQUEST_I2S_TX; config.byte_count = 256U; - if (rs_tiny_dma_config_validate(0U, &config) != RS_EINVAL) { + if (rs_tiny_dma_config_validate(6U, &config) != RS_OK) { return 2; } descriptor.source = UINT32_C(0x30000000); descriptor.byte_count = 256U; descriptor.y_count = 1U; descriptor.control = RS_DMA_TCD_VALID | RS_DMA_TCD_SRC_INC | - ((uint32_t)RS_DMA_KIND_MM_TO_STREAM << RS_DMA_TCD_KIND_SHIFT) | - ((uint32_t)RS_DMA_REQUEST_I2S_TX << RS_DMA_TCD_REQUEST_SHIFT); - if (rs_tiny_dma_tcd_validate(0U, &descriptor) != RS_ENOTSUP) { + ((uint32_t)RS_DMA_KIND_MM_TO_STREAM << RS_DMA_TCD_KIND_SHIFT) | + ((uint32_t)RS_DMA_REQUEST_I2S_TX << RS_DMA_TCD_REQUEST_SHIFT); + if (rs_tiny_dma_tcd_validate(6U, &descriptor) != RS_OK) { return 3; } + config.kind = RS_DMA_KIND_STREAM_TO_MM; + config.request = RS_DMA_REQUEST_DVP_RX; + if (rs_tiny_dma_config_validate(2U, &config) != RS_EINVAL) { + return 4; + } + descriptor.control = RS_DMA_TCD_VALID | RS_DMA_TCD_DST_INC | + ((uint32_t)RS_DMA_KIND_STREAM_TO_MM << RS_DMA_TCD_KIND_SHIFT) | + ((uint32_t)RS_DMA_REQUEST_DVP_RX << RS_DMA_TCD_REQUEST_SHIFT); + if (rs_tiny_dma_tcd_validate(2U, &descriptor) != RS_ENOTSUP) { + return 5; + } return 0; } @@ -2853,9 +3021,12 @@ static int test_npu_p6_reference_edge_ops(void) { uint8_t depthwise_weights[72] = {0U}; int32_t depthwise_parameters[4] = {1, INT32_C(1073741824), 1, 0}; rs_npu_p6_memory_t depthwise_memory = { - .arena = {.base = UINT32_C(0x1000), .bytes = sizeof(depthwise_arena), .data = depthwise_arena}, - .weights = - {.base = UINT32_C(0x3000), .bytes = sizeof(depthwise_weights), .data = depthwise_weights}, + .arena = {.base = UINT32_C(0x1000), + .bytes = sizeof(depthwise_arena), + .data = depthwise_arena}, + .weights = {.base = UINT32_C(0x3000), + .bytes = sizeof(depthwise_weights), + .data = depthwise_weights}, .params = { .base = UINT32_C(0x2000), @@ -2891,7 +3062,9 @@ static int test_npu_p6_reference_edge_ops(void) { { uint8_t average_arena[16] = {1U, 2U, 3U, 4U}; rs_npu_p6_memory_t average_memory = { - .arena = {.base = UINT32_C(0x4000), .bytes = sizeof(average_arena), .data = average_arena}, + .arena = {.base = UINT32_C(0x4000), + .bytes = sizeof(average_arena), + .data = average_arena}, .weights = {.base = UINT32_C(0x5000), .bytes = 1U, .data = average_arena}, .params = {.base = UINT32_C(0x6000), .bytes = 1U, .data = average_arena}, }; @@ -2978,6 +3151,61 @@ static int test_crypto_lifecycle_contract(void) { return 0; } +static int test_tiny_rcu_boundaries(void) { + rs_tiny_rcu_snapshot_t snapshot; + uint32_t hz = 0U; + rs_tiny_rcu_test_mmio[RS_TINY_RCU_CAPABILITY_OFFSET / 4U] = 3U; + rs_tiny_rcu_test_mmio[RS_TINY_RCU_TARGET_CAP_OFFSET / 4U] = 0x3FFFU; + rs_tiny_rcu_test_mmio[RS_TINY_RCU_XIN_HZ_OFFSET / 4U] = 96000000U; + rs_tiny_rcu_test_mmio[RS_TINY_RCU_REF_HZ_OFFSET / 4U] = 24000000U; + rs_tiny_rcu_test_mmio[RS_TINY_RCU_COMMAND_OFFSET / 4U] = 0xDEADU; + if (rs_tiny_rcu_request_profile(RS_TINY_RCU_PROFILE_PLL192, 10U) != RS_ENOTSUP || + rs_tiny_rcu_request_profile((rs_tiny_rcu_profile_t)4, 10U) != RS_EINVAL || + rs_tiny_rcu_apply_gate(0U, 0U) != RS_EINVAL || + rs_tiny_rcu_apply_reset(2U, 0U) != RS_EINVAL || + rs_tiny_rcu_apply_reset(1U, 10U) != RS_EINVAL || + rs_tiny_rcu_apply_gate(1U << 14U, 10U) != RS_ENOTSUP || + rs_tiny_rcu_apply_reset(1U << 17U, 10U) != RS_ENOTSUP) { + return 1; + } + rs_tiny_rcu_test_mmio[RS_TINY_RCU_STATUS_OFFSET / 4U] = RS_TINY_RCU_STATUS_BUSY; + if (rs_tiny_rcu_get_snapshot(&snapshot) != RS_EBUSY || + rs_tiny_rcu_apply_gate(2U, 10U) != RS_EBUSY || + rs_tiny_rcu_request_profile(RS_TINY_RCU_PROFILE_SAFE24, 10U) != RS_EBUSY) { + return 2; + } + rs_tiny_rcu_test_mmio[RS_TINY_RCU_STATUS_OFFSET / 4U] = 0U; + rs_tiny_rcu_test_mmio[RS_TINY_RCU_GATE_STATUS_OFFSET / 4U] = 2U; + if (rs_tiny_rcu_apply_reset(2U, 10U) != RS_EBUSY) { + return 3; + } + rs_tiny_rcu_test_mmio[RS_TINY_RCU_FAULT_OFFSET / 4U] = 4U; + if (rs_tiny_rcu_apply_gate(2U, 10U) != RS_ETIMEOUT) { + return 4; + } + rs_tiny_rcu_test_mmio[RS_TINY_RCU_FAULT_OFFSET / 4U] = 8U; + if (rs_tiny_rcu_apply_gate(2U, 10U) != RS_EIO || + rs_tiny_rcu_test_mmio[RS_TINY_RCU_COMMAND_OFFSET / 4U] != 0xDEADU) { + return 5; + } + rs_tiny_rcu_test_mmio[RS_TINY_RCU_FAULT_OFFSET / 4U] = 0U; + if (rs_tiny_rcu_profile_hz(RS_TINY_RCU_PROFILE_XIN, &hz) != RS_OK || hz != 96000000U || + rs_tiny_rcu_get_snapshot(NULL) != RS_EINVAL || + rs_tiny_rcu_get_snapshot(&snapshot) != RS_OK || snapshot.xin_hz != hz || + rs_tiny_rcu_set_clkout(5U, 1U) != RS_EINVAL || + rs_tiny_rcu_set_clkout(1U, 0U) != RS_EINVAL || + rs_tiny_rcu_clear_fault(32U) != RS_EINVAL || + rs_tiny_rcu_clear_reset_cause(64U) != RS_EINVAL) { + return 6; + } + if (rs_tiny_rcu_set_clkout(1U, 1U) != RS_OK || + rs_tiny_rcu_set_clkout(1U, 2U) != RS_EBUSY || + rs_tiny_rcu_set_clkout(0U, 0U) != RS_OK) { + return 7; + } + return 0; +} + int main(void) { const int results[] = { test_string_helpers(), @@ -2986,9 +3214,12 @@ int main(void) { test_wait_helper(), test_crypto_lifecycle_contract(), test_clock_frequency_contract(), + test_tiny_rcu_boundaries(), test_ws2812_helpers(), test_dma_sessions(), test_ws2812_refill(), + test_dma_tcd_preemption(), + test_ws2812_completion_race(), test_timer_helpers(), test_psram_helpers(), test_sdram_helpers(), diff --git a/tests/publication_history.py b/tests/publication_history.py new file mode 100644 index 00000000..fd45f9ed --- /dev/null +++ b/tests/publication_history.py @@ -0,0 +1,40 @@ +"""Materialize immutable, locally available publication test inputs. + +This is a test fixture, never a publication promotion or a current-RTL verdict. +Each exported tree keeps its own adapter, technical contract, tests and lock. +""" +from __future__ import annotations + +import io +import json +import subprocess +import tarfile +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +ORIGINAL_BOOK = "2805ea91833a6bede32f3a82df3d17d20eb5ec77" +P5_CONTRACT = "b3b30a72cd6ef5340c28e5044c96cd13258ec5a6" + + +def export(repository: Path, revision: str, destination: Path, paths: list[str] = ()) -> None: + data = subprocess.check_output(["git", "-C", str(repository), "archive", revision, *paths]) + destination.mkdir(parents=True, exist_ok=True) + with tarfile.open(fileobj=io.BytesIO(data)) as archive: + for member in archive.getmembers(): + target = (destination / member.name).resolve() + if not target.is_relative_to(destination.resolve()) or member.issym() or member.islnk(): + raise ValueError("unsafe publication fixture archive member") + archive.extractall(destination) + + +def snapshot(destination: Path, revision: str) -> Path: + export(ROOT, revision, destination, ["publications", "scripts", "rtl", "crt", "app", + "configs", "docs", "dependencies", "LICENSE", + "Makefile", "README.md", "tests"]) + lock = json.loads((destination / "dependencies/dependencies.lock.json").read_text()) + for name in ("cluster_common", "cluster_archinfo", "cluster_pwm", "cluster_rtc", + "cluster_wdg", "hazard3"): + source = lock["sources"][name] + export(ROOT / source["destination"], source["revision"], destination / source["destination"], + ["hdl"] if name == "hazard3" else []) + return destination diff --git a/tests/rtl/i2s_tb.sv b/tests/rtl/i2s_tb.sv index 80a2cb20..4ac05c0c 100644 --- a/tests/rtl/i2s_tb.sv +++ b/tests/rtl/i2s_tb.sv @@ -98,6 +98,28 @@ module i2s_tb; end endtask + task automatic zero_strobe_rx_write; + logic [31:0] before_status, after_status; + read(32'h8, before_status); + if (before_status[2]) $fatal(1, "missing RX data for direction test"); + @(negedge clk_i); + apb4.paddr = 32'h20; + apb4.pwdata = 0; + apb4.pstrb = 0; + apb4.pwrite = 1; + apb4.psel = 1; + apb4.penable = 0; + @(negedge clk_i); + apb4.penable = 1; + while (!apb4.pready) @(negedge clk_i); + if (!apb4.pslverr) $fatal(1, "zero-strobe write became an RX read"); + apb4.psel = 0; + apb4.penable = 0; + apb4.pwrite = 0; + read(32'h8, after_status); + if (before_status[23:16] != after_status[23:16]) $fatal(1, "invalid write popped RX FIFO"); + endtask + always @(posedge i2s.sclk_o) sclk_edges = sclk_edges + 1; initial begin @@ -132,6 +154,9 @@ module i2s_tb; repeat (4000) @(posedge clk_aud_i); if (sclk_edges == 0) $fatal(1, "I2S SCLK did not toggle"); read(32'h0000_0008, value); + write(32'h0000_0000, 32'h0000_0000); + repeat (64) @(negedge clk_i); + zero_strobe_rx_write(); write(32'h0000_0000, 32'h0000_0008); write(32'h0000_0000, 32'h0000_0009); repeat (2000) @(posedge clk_aud_i); diff --git a/tests/rtl/sdio_axi_memory_responder.sv b/tests/rtl/sdio_axi_memory_responder.sv index 89e9dbe3..8cb9c6da 100644 --- a/tests/rtl/sdio_axi_memory_responder.sv +++ b/tests/rtl/sdio_axi_memory_responder.sv @@ -141,7 +141,17 @@ module sdio_axi_memory_responder #( assign axi4.rresp = (s_read_error_q) ? `AXI4_RESP_SLAVE_ERROR : `AXI4_RESP_OKAY; assign axi4.rlast = s_read_bad_last_q || ((s_read_index_q + 1'b1) >= (s_read_len_q + 1'b1)); assign axi4.ruser = '0; - assign axi4.rvalid = s_read_active_q; + integer s_read_gap = 0; + integer s_read_gap_count = 0; + task automatic set_read_gap(input integer cycles_i); + s_read_gap = cycles_i < 0 ? 0 : cycles_i; + endtask + always @(posedge clk_i or negedge rst_n_i) begin + if (!rst_n_i) s_read_gap_count <= 0; + else if (axi4.rvalid && axi4.rready) s_read_gap_count <= s_read_gap; + else if (s_read_gap_count != 0) s_read_gap_count <= s_read_gap_count - 1; + end + assign axi4.rvalid = s_read_active_q && (s_read_gap_count == 0); assign axi4.awready = !s_write_active_q && !axi4.bvalid && !s_aw_stall_q; assign axi4.wready = s_write_active_q && !s_w_stall_q; diff --git a/tests/rtl/sdio_command_tb.sv b/tests/rtl/sdio_command_tb.sv index f8bb2f1d..ddc0115a 100644 --- a/tests/rtl/sdio_command_tb.sv +++ b/tests/rtl/sdio_command_tb.sv @@ -32,6 +32,8 @@ module sdio_command_tb; integer launch_count; integer captured_bits; logic [ 47:0] captured_command; + tri1 command_pad; + assign command_pad = cmd_oe_o ? cmd_do_o : 1'bz; always #1 clk_i = ~clk_i; @@ -77,8 +79,8 @@ module sdio_command_tb; end always @(posedge sck) begin - if (cmd_oe_o) begin - captured_command = {captured_command[46:0], cmd_do_o}; + if (captured_bits < 48 && (captured_bits != 0 || command_pad == 1'b0)) begin + captured_command = {captured_command[46:0], command_pad}; captured_bits = captured_bits + 1; end end @@ -95,6 +97,7 @@ module sdio_command_tb; start_i = 1'b0; clock_enable_i = 1'b1; wait (done_o); + @(negedge clk_i); if (error_o || (launch_count != 48) || (captured_bits != 48) || (captured_command[47:46] != 2'b01) || (captured_command[0] != 1'b1) || (captured_command[7:1] != sdio_pkg::sdio_crc7_calc( @@ -104,6 +107,33 @@ module sdio_command_tb; error_o, launch_count, captured_bits, captured_command, cmd_oe_o); end + // A running SDCLK can accept a command on either half-cycle. The + // stopped-clock first command above does not exercise this boundary. + for (integer phase = 0; phase < 16; phase++) begin + repeat (phase % 3 + 3) @(negedge clk_i); + captured_bits = 0; + captured_command = '0; + cmd_index_i = 6'd8; + cmd_arg_i = 32'h1aa ^ 32'(phase); + start_i = 1'b1; + @(negedge clk_i); + start_i = 1'b0; + wait (done_o); + @(negedge clk_i); + if (error_o || captured_bits != 48 || captured_command[47:40] != 8'h48 || + captured_command[39:8] != cmd_arg_i || captured_command[0] != 1'b1 || + captured_command[7:1] != sdio_pkg::sdio_crc7_calc( + captured_command[47:8] + )) + $fatal( + 1, + "SDIO running-clock frame phase=%0d bits=%0d frame=%h", + phase, + captured_bits, + captured_command + ); + end + @(posedge clk_i); resp_type_i = sdio_pkg::SdioRespR1; timeout_cycles_i = 32'd4; diff --git a/tests/rtl/sdio_native_model.sv b/tests/rtl/sdio_native_model.sv index 58ffad58..fe68aa2a 100644 --- a/tests/rtl/sdio_native_model.sv +++ b/tests/rtl/sdio_native_model.sv @@ -14,13 +14,14 @@ // implements only public command/data behavior exercised by this repository; // it is not a normative-specification replacement. module sdio_native_card_model #( - parameter bit SdioOnly = 1'b0, - parameter bit HighCapacity = 1'b1, - parameter int BlockBytes = 512, - parameter int BlockCount = 128, - parameter int FunctionCount = 1, - parameter int FunctionBytes = 4096, - parameter int BusyCycles = 2 + parameter bit MemoryInitialization = 1'b0, + parameter bit SdioOnly = 1'b0, + parameter bit HighCapacity = 1'b1, + parameter int BlockBytes = 512, + parameter int BlockCount = 128, + parameter int FunctionCount = 1, + parameter int FunctionBytes = 4096, + parameter int BusyCycles = 2 ) ( input logic rst_n_i, input logic sck_i, @@ -48,60 +49,63 @@ module sdio_native_card_model #( WriteBusy } data_state_e; - logic [ 7:0] s_memory [ 0:MemoryBytes-1]; - logic [ 7:0] s_function_memory [0:FunctionStorageBytes-1]; + logic [7:0] s_memory[0:MemoryBytes-1]; + logic [7:0] s_function_memory[0:FunctionStorageBytes-1]; - logic [ 47:0] s_cmd_shift; - integer s_cmd_bits; - logic s_cmd_response_active; - logic [135:0] s_cmd_response; - integer s_cmd_response_length; - integer s_cmd_response_index; - integer s_cmd_response_delay; - integer s_response_delay; - integer s_data_delay_cfg; - logic s_inject_crc_error; - logic s_inject_timeout; - logic s_inject_write_error; - logic s_next_cmd_crc_error; + logic [47:0] s_cmd_shift; + integer s_cmd_bits; + logic s_cmd_response_active; + logic [135:0] s_cmd_response; + integer s_cmd_response_length; + integer s_cmd_response_index; + integer s_cmd_response_delay; + integer s_response_delay; + integer s_data_delay_cfg; + logic s_inject_crc_error; + logic s_inject_timeout; + logic s_inject_write_error; + logic s_next_cmd_crc_error; - logic [ 5:0] s_last_cmd; - logic [ 31:0] s_last_arg; - logic s_last_cmd_write; - logic s_last_cmd_data; - logic s_pending_busy; - integer s_busy_count; + logic [5:0] s_last_cmd; + logic [31:0] s_last_arg; + logic s_last_cmd_write; + logic s_last_cmd_data; + logic s_memory_width4; + logic s_pending_busy; + integer s_busy_count; - data_state_e s_data_state; - logic s_data_width4; - logic s_data_fixed; - integer s_data_address; - integer s_data_bytes; - integer s_data_byte_index; - integer s_data_bit_index; - integer s_data_nibble_index; - integer s_data_crc_index; - integer s_data_delay; - logic [ 15:0] s_read_crc; - logic [ 15:0] s_read_crc_lane [ 0:3]; - logic [ 15:0] s_write_crc; - logic [ 15:0] s_write_crc_lane [ 0:3]; - logic [ 15:0] s_received_crc; - logic [ 15:0] s_received_crc_lane [ 0:3]; - logic [ 7:0] s_write_byte; - logic [ 7:0] s_write_byte_lane [ 0:3]; - integer s_write_bit_count; - integer s_write_nibble_count; - logic [ 4:0] s_write_response_token; - integer s_write_response_index; - logic s_write_response_started; - logic s_data_crc_error; - logic s_data_timeout; - logic s_irq_pending; - logic [ 2:0] s_cmd_function; - logic [ 31:0] s_cmd_address; - logic s_cmd_fixed_address; - integer s_cmd_count; + data_state_e s_data_state; + logic s_data_width4; + logic s_data_fixed; + integer s_data_address; + integer s_data_bytes; + wire [3:0] s_token_lanes = !s_data_width4 ? 4'b0001 : + (s_data_bytes >= 4 ? 4'b1111 : (4'b0001 << s_data_bytes) - 1'b1); + integer s_data_byte_index; + integer s_data_bit_index; + integer s_data_nibble_index; + integer s_data_crc_index; + integer s_data_delay; + logic [15:0] s_read_crc; + logic [15:0] s_read_crc_lane[0:3]; + logic [15:0] s_write_crc; + logic [15:0] s_write_crc_lane[0:3]; + logic [15:0] s_received_crc; + logic [15:0] s_received_crc_lane[0:3]; + logic [7:0] s_write_byte; + logic [7:0] s_write_byte_lane[0:3]; + integer s_write_bit_count; + integer s_write_nibble_count; + logic [4:0] s_write_response_token; + integer s_write_response_index; + logic s_write_response_started; + logic s_data_crc_error; + logic s_data_timeout; + logic s_irq_pending; + logic [2:0] s_cmd_function; + logic [31:0] s_cmd_address; + logic s_cmd_fixed_address; + integer s_cmd_count; function automatic logic [6:0] crc7_40(input logic [39:0] data_i); logic [6:0] crc; @@ -404,17 +408,20 @@ module sdio_native_card_model #( endtask task automatic command_received(input logic [47:0] frame_i); - logic [ 5:0] index; - logic [31:0] argument; - logic [ 6:0] expected_crc; - logic [39:0] body; - logic [31:0] response_payload; - integer address; + logic [ 5:0] index; + logic [ 31:0] argument; + logic [ 6:0] expected_crc; + logic [ 39:0] body; + logic [ 31:0] response_payload; + logic [127:0] memory_csd_body; + logic application_command; + integer address; begin index = frame_i[45:40]; argument = frame_i[39:8]; body = frame_i[47:8]; expected_crc = crc7_40(body); + application_command = s_last_cmd == 6'd55; s_last_cmd = index; s_last_arg = argument; s_last_cmd_data = 1'b0; @@ -471,7 +478,15 @@ module sdio_native_card_model #( cmd_do_o = 1'b1; end 6'd2, 6'd9: begin - make_r2_response(index == 6'd9 ? {HighCapacity ? 1'b0 : 1'b0, 127'd0} : 128'd0); + memory_csd_body = '0; + if (MemoryInitialization && index == 6'd9) begin + // The 136-bit wire frame holds the CSD at [127:0], including + // its CRC/end byte. make_r2_response appends that final byte. + memory_csd_body[119:118] = 2'd1; + memory_csd_body[75:72] = 4'd9; + memory_csd_body[61:40] = 22'(BlockCount / 1024 - 1); + end + make_r2_response(memory_csd_body); s_cmd_response_active = 1'b1; s_cmd_response_index = 0; s_cmd_response_delay = s_response_delay; @@ -492,6 +507,15 @@ module sdio_native_card_model #( s_busy_count = BusyCycles; end 6'd16, 6'd55, 6'd41, 6'd6, 6'd13: begin + if (MemoryInitialization) begin + if (index == 6'd41 && application_command) response_payload = 32'hc0ff8000; + if (index == 6'd6) begin + if (application_command) s_memory_width4 = argument == 2; + // This deterministic card supports default speed only. + else + response_payload = 32'h00400000; + end + end queue_response(index, response_payload, 1'b0, s_inject_crc_error); s_inject_crc_error = 1'b0; end @@ -654,9 +678,10 @@ module sdio_native_card_model #( s_last_arg = 32'd0; s_last_cmd_write = 1'b0; s_last_cmd_data = 1'b0; + s_memory_width4 = 1'b0; s_pending_busy = 1'b0; s_busy_count = 0; - end else if (cmd_oe_i) begin + end else if ((s_cmd_bits != 0) || (cmd_oe_i && cmd_do_i == 1'b0)) begin if (s_cmd_bits == 0) begin s_cmd_shift = {{47{1'b0}}, cmd_do_i}; s_cmd_bits = 1; @@ -717,6 +742,7 @@ module sdio_native_card_model #( s_last_arg = 32'd0; s_last_cmd_write = 1'b0; s_last_cmd_data = 1'b0; + s_memory_width4 = 1'b0; s_pending_busy = 1'b0; s_busy_count = 0; s_data_state = DataIdle; @@ -828,7 +854,7 @@ module sdio_native_card_model #( dat_do_o = 4'b1111; for (integer lane = 0; lane < 4; lane++) begin if (lane < s_data_bytes) begin - dat_do_o[lane] = s_read_crc_lane[lane][15-(s_data_crc_index+1)]; + dat_do_o[lane] = s_read_crc_lane[lane][15-s_data_crc_index]; end end end @@ -838,7 +864,7 @@ module sdio_native_card_model #( end else begin s_data_crc_index = s_data_crc_index + 1; dat_do_o = 4'b1111; - dat_do_o[0] = s_read_crc[15-(s_data_crc_index+1)]; + dat_do_o[0] = s_read_crc[15-s_data_crc_index]; end end WriteResponse: begin @@ -850,7 +876,9 @@ module sdio_native_card_model #( s_write_response_index = s_write_response_index + 1; dat_do_o[0] = s_write_response_token[4-s_write_response_index]; end else begin - s_data_state = (s_write_response_token == 5'b00101) ? WriteBusy : DataIdle; + // Keep the enum assignment portable to Icarus's native SV parser. + if (s_write_response_token == 5'b00101) s_data_state = WriteBusy; + else s_data_state = DataIdle; s_busy_count = BusyCycles; dat_do_o[0] = (s_write_response_token == 5'b00101) ? 1'b0 : 1'b1; end @@ -879,7 +907,7 @@ module sdio_native_card_model #( end else begin case (s_data_state) WriteWait: begin - if ((dat_oe_i != 4'b0000) && (dat_do_i == 4'b0000)) begin + if ((dat_oe_i != 4'b0000) && ((dat_do_i & s_token_lanes) == 4'b0000)) begin s_data_state = WritePayload; s_write_bit_count = 0; s_write_nibble_count = 0; @@ -903,7 +931,8 @@ module sdio_native_card_model #( s_data_crc_error = 1'b0; if (s_data_width4) begin for (integer lane = 0; lane < 4; lane++) begin - if (s_received_crc_lane[lane] != s_write_crc_lane[lane]) begin + if ((s_data_bytes % 4 == 0 || lane < s_data_bytes % 4) && + s_received_crc_lane[lane] != s_write_crc_lane[lane]) begin s_data_crc_error = 1'b1; end end @@ -938,6 +967,9 @@ module sdio_native_card_model #( end initial begin + if (MemoryInitialization && (!HighCapacity || SdioOnly || BlockBytes != 512 || + BlockCount < 1024 || BlockCount % 1024 != 0)) + $fatal(1, "SDIO enumeration profile requires an exactly representable SDHC capacity"); if ((BlockBytes < 1) || (BlockCount < 1) || (FunctionCount < 1) || (FunctionBytes < 256)) begin $fatal(1, "sdio_native_card_model: invalid storage geometry"); end @@ -955,6 +987,7 @@ module sdio_native_card_model #( s_last_arg = 32'd0; s_last_cmd_write = 1'b0; s_last_cmd_data = 1'b0; + s_memory_width4 = 1'b0; s_pending_busy = 1'b0; s_busy_count = 0; s_data_state = DataIdle; diff --git a/tests/rtl/sdio_standalone_tb.sv b/tests/rtl/sdio_standalone_tb.sv index 6d452204..1fe49df2 100644 --- a/tests/rtl/sdio_standalone_tb.sv +++ b/tests/rtl/sdio_standalone_tb.sv @@ -12,7 +12,9 @@ module sdio_standalone_tb #( parameter bit CardHighCapacity = 1'b1, - parameter bit RunFourBit = 1'b1 + parameter bit RunFourBit = 1'b1, + parameter bit PadLoopback = 1'b0, + parameter int CardBusyCycles = 8 ); localparam logic [31:0] HostCtrl = 32'h00C; localparam logic [31:0] ClockCtrl = 32'h010; @@ -65,8 +67,21 @@ module sdio_standalone_tb #( integer timeout_count_before; integer crc_count_before; - assign sdio.cmd_di_i = sdio.cmd_oe_o ? sdio.cmd_do_o : card_cmd_do; - assign sdio.dat_di_i = (sdio.dat_oe_o & sdio.dat_do_o) | (~sdio.dat_oe_o & card_dat_do); + tri1 command_pad, data0_pad, data1_pad, data2_pad, data3_pad; + wire [3:0] data_pads = {data3_pad, data2_pad, data1_pad, data0_pad}; + assign command_pad = sdio.cmd_oe_o ? sdio.cmd_do_o : 1'bz; + assign command_pad = sdio.cmd_oe_o ? 1'bz : card_cmd_do; + assign data0_pad = sdio.dat_oe_o[0] ? sdio.dat_do_o[0] : 1'bz; + assign data1_pad = sdio.dat_oe_o[1] ? sdio.dat_do_o[1] : 1'bz; + assign data2_pad = sdio.dat_oe_o[2] ? sdio.dat_do_o[2] : 1'bz; + assign data3_pad = sdio.dat_oe_o[3] ? sdio.dat_do_o[3] : 1'bz; + assign data0_pad = sdio.dat_oe_o[0] ? 1'bz : card_dat_do[0]; + assign data1_pad = sdio.dat_oe_o[1] ? 1'bz : card_dat_do[1]; + assign data2_pad = sdio.dat_oe_o[2] ? 1'bz : card_dat_do[2]; + assign data3_pad = sdio.dat_oe_o[3] ? 1'bz : card_dat_do[3]; + assign sdio.cmd_di_i = PadLoopback ? command_pad : (sdio.cmd_oe_o ? sdio.cmd_do_o : card_cmd_do); + assign sdio.dat_di_i = PadLoopback ? data_pads : + ((sdio.dat_oe_o & sdio.dat_do_o) | (~sdio.dat_oe_o & card_dat_do)); always #5 clk_i = ~clk_i; @@ -87,15 +102,15 @@ module sdio_standalone_tb #( .HighCapacity(CardHighCapacity), .BlockBytes (512), .BlockCount (128), - .BusyCycles (8) + .BusyCycles (CardBusyCycles) ) u_card ( .rst_n_i (rst_n_i), .sck_i (sdio.sck_o), .cmd_oe_i(sdio.cmd_oe_o), - .cmd_do_i(sdio.cmd_do_o), + .cmd_do_i(PadLoopback ? command_pad : sdio.cmd_do_o), .cmd_do_o(card_cmd_do), .dat_oe_i(sdio.dat_oe_o), - .dat_do_i(sdio.dat_do_o), + .dat_do_i(PadLoopback ? data_pads : sdio.dat_do_o), .dat_do_o(card_dat_do), .irq_o (card_irq) ); @@ -204,7 +219,13 @@ module sdio_standalone_tb #( end end if (timeout_guard >= 200000) begin - $fatal(1, "data path did not drain"); + $fatal( + 1, + "data path did not drain: data_state=%0d dma_state=%0d rx=%b%b dma_bytes=%0d card_state=%0d card_bytes=%0d clk=%b", + u_apb4_sdio.u_sdio_core.u_sdio_data.s_state_q, + u_apb4_sdio.u_sdio_core.u_sdio_dma.s_state_q, u_apb4_sdio.u_sdio_core.s_data_rx_valid, + u_apb4_sdio.u_sdio_core.s_data_rx_ready, u_apb4_sdio.s_bytes_done, + u_card.u_model.s_data_state, u_card.u_model.s_data_byte_index, sdio.sck_o); end end endtask @@ -552,6 +573,84 @@ module sdio_standalone_tb #( $fatal(1, "memory-to-card DMA end-to-end direction failed"); end + // P6 exercises a full four-lane DMA payload at divider 1, with memory + // arbitration delays. Short one-lane tails do not cover its word pauses. + if (RunFourBit) begin + u_axi_memory.clear_memory(); + u_axi_memory.set_read_gap(24); + u_axi_memory.write_word(32'h0, 32'h200); + u_axi_memory.write_word(32'h4, 32'd1024); + u_axi_memory.write_word(32'h8, 32'd0); + u_axi_memory.write_word(32'hc, 32'h5); + for (integer word_index = 0; word_index < 256; word_index++) + u_axi_memory.write_word(32'h200 + 32'(word_index * 4), 32'h13579bdf ^ 32'(word_index)); + configure_dma_data(1'b1, 1'b1, 16'd512); + apb_write(BlockCount, 32'd2, 4'hf); + u_card.arm_write(24000, 1024, 1'b1, 1'b0); + apb_write(DataStart, 32'd1, 4'h1); + wait_data(); + apb_read(DataStatus, apb_value); + for (integer byte_index = 0; byte_index < 1024; byte_index++) begin + if (u_card.read_backing( + 24000 + byte_index + ) != 8'((32'h13579bdf ^ 32'(byte_index / 4)) >> ((byte_index % 4) * 8))) + $fatal( + 1, + "SDIO four-lane DMA payload byte=%0d got=%h expected=%h", + byte_index, + u_card.read_backing( + 24000 + byte_index + ), + 8'((32'h13579bdf ^ 32'(byte_index / 4)) >> ((byte_index % 4) * 8)) + ); + end + if ((apb_value & 32'h1e) != 0) + $fatal( + 1, + "SDIO four-lane DMA block status=%h card_crc=%b", + apb_value, + u_card.u_model.s_data_crc_error + ); + if (u_axi_memory.read_word(32'hc) != 32'h00010004) + $fatal(1, "SDIO four-lane DMA descriptor did not retire"); + // Delay descriptor reads while the card sends its first receive word. + u_axi_memory.clear_memory(); + u_axi_memory.write_word(32'h0, 32'h800); + u_axi_memory.write_word(32'h4, 32'd1024); + u_axi_memory.write_word(32'h8, 32'd0); + u_axi_memory.write_word(32'hc, 32'h5); + configure_dma_data(1'b1, 1'b0, 16'd512); + apb_write(BlockCount, 32'd2, 4'hf); + u_card.arm_read(24000, 1024, 1'b1, 1'b0); + apb_write(DataStart, 32'd1, 4'h1); + wait_data(); + apb_read(DataStatus, apb_value); + for (integer word_index = 0; word_index < 256; word_index++) begin + if (u_axi_memory.read_word( + 32'h800 + 32'(word_index * 4) + ) != (32'h13579bdf ^ 32'(word_index))) + $fatal( + 1, + "SDIO delayed-descriptor receive word=%0d got=%h expected=%h", + word_index, + u_axi_memory.read_word( + 32'h800 + 32'(word_index * 4) + ), + 32'h13579bdf ^ 32'(word_index) + ); + end + if ((apb_value & 32'h1e) != 0 || u_axi_memory.read_word(32'hc) != 32'h00010004) + $fatal( + 1, + "SDIO delayed-descriptor receive status=%h descriptor=%h", + apb_value, + u_axi_memory.read_word( + 32'hc + ) + ); + u_axi_memory.set_read_gap(0); + end + apb_read(AxiCount, apb_value); timeout_count_before = apb_value; u_axi_memory.clear_memory(); @@ -675,3 +774,10 @@ endmodule module sdio_onebit_tb; sdio_standalone_tb #(.RunFourBit(1'b0)) u_tb (); endmodule + +module tiny_p6_sdio_pads_tb; + sdio_standalone_tb #( + .PadLoopback (1'b1), + .CardBusyCycles(2) + ) u_tb (); +endmodule diff --git a/tests/rtl/tiny_apb_async_bridge_tb.sv b/tests/rtl/tiny_apb_async_bridge_tb.sv new file mode 100644 index 00000000..0c3790c1 --- /dev/null +++ b/tests/rtl/tiny_apb_async_bridge_tb.sv @@ -0,0 +1,87 @@ +`timescale 1ns / 1ps +module tiny_apb_async_bridge_tb; + logic clk_i = 0, dst_clk = 0, rst_n_i = 0, dst_rst_n = 0; + logic run_dst = 1, disabled = 0, idle, ready; + logic respond = 1; + integer writes = 0; + logic [31:0] saved; + logic [31:0] value; + always #5 clk_i = !clk_i; + always #17 if (run_dst) dst_clk = !dst_clk; + apb4_if apb4 ( + .pclk (clk_i), + .presetn(rst_n_i) + ); + apb4_if endpoint ( + .pclk (dst_clk), + .presetn(dst_rst_n) + ); + tiny_apb_async_bridge u_dut ( + .src_clk_i (clk_i), + .src_rst_n_i(rst_n_i), + .dst_clk_i (dst_clk), + .dst_rst_n_i(dst_rst_n), + .disabled_i (disabled), + .idle_o (idle), + .ready_o (ready), + .src_apb4 (apb4), + .dst_apb4 (endpoint) + ); + assign endpoint.pready = endpoint.psel && endpoint.penable && respond; + assign endpoint.pslverr = endpoint.paddr[8]; + assign endpoint.prdata = saved ^ endpoint.paddr; + always @(posedge dst_clk) begin + if (dst_rst_n && endpoint.psel && endpoint.penable && endpoint.pready && endpoint.pwrite) begin + writes = writes + 1; + if (endpoint.pstrb != 4'h5 || endpoint.pprot != 0) $fatal(1, "APB payload changed"); + saved = endpoint.pwdata; + end + end + `include "apb4_bfm.svh" + initial begin + apb4_idle(); + #2; + rst_n_i = 0; + dst_rst_n = 0; + repeat (6) @(negedge clk_i); + rst_n_i = 1; + dst_rst_n = 1; + wait (ready); + repeat (5) @(negedge clk_i); + apb4_write(32'h20, 32'h87654321, 4'h5, 0); + apb4_read(32'h20, 0, value); + if (value != 32'h87654301 || writes != 1) $fatal(1, "APB CDC data/duplicate"); + apb4_read(32'h100, 1, value); + respond = 0; + fork + apb4_read(32'h20, 1, value); + begin + wait (endpoint.psel && endpoint.penable); + #3 dst_rst_n = 0; + repeat (15) @(negedge clk_i); + dst_rst_n = 1; + end + join + respond = 1; + wait (ready); + repeat (30) @(negedge clk_i); + if (endpoint.psel) $fatal(1, "reset replayed an old APB request"); + disabled = 1; + @(negedge dst_clk); + run_dst = 0; + apb4_read(32'h20, 1, value); + apb4_write(32'h20, 32'hbad, 4'h5, 1); + if (writes != 1) $fatal(1, "disabled APB side effect"); + run_dst = 1; + disabled = 0; + repeat (30) @(negedge clk_i); + apb4_read(32'h20, 0, value); + if (value != 32'h87654301) $fatal(1, "CDC restart data"); + $display("SIM_TEST_PASS Tiny APB CDC"); + $finish; + end + initial begin + #100000; + $fatal(1, "SIM_TEST_TIMEOUT Tiny APB CDC"); + end +endmodule diff --git a/tests/rtl/tiny_axi4_admission_tb.sv b/tests/rtl/tiny_axi4_admission_tb.sv new file mode 100644 index 00000000..0f151659 --- /dev/null +++ b/tests/rtl/tiny_axi4_admission_tb.sv @@ -0,0 +1,135 @@ +`timescale 1ns / 1ps +module tiny_axi4_admission_tb; + logic clk = 0, rst_n = 0, block_new = 0, idle; + always #5 clk = !clk; + axi4_if source ( + .aclk (clk), + .aresetn(rst_n) + ); + axi4_if sink ( + .aclk (clk), + .aresetn(rst_n) + ); + tiny_axi4_admission u_dut ( + .clk_i (clk), + .rst_n_i(rst_n), + .block_i(block_new), + .idle_o (idle), + .source (source), + .sink (sink) + ); + task automatic tick; + @(posedge clk); + #1; + @(negedge clk); + endtask + initial begin + source.awvalid = 0; + source.arvalid = 0; + source.wvalid = 0; + source.awid = 1; + source.awaddr = 'h30001234; + source.awlen = 1; + source.awsize = 2; + source.awburst = 1; + source.awlock = 0; + source.awcache = 0; + source.awprot = 0; + source.awqos = 0; + source.awregion = 0; + source.awuser = 0; + source.arid = 0; + source.araddr = 'h30005678; + source.arlen = 1; + source.arsize = 2; + source.arburst = 1; + source.arlock = 0; + source.arcache = 0; + source.arprot = 0; + source.arqos = 0; + source.arregion = 0; + source.aruser = 0; + source.wdata = 'h12345678; + source.wstrb = 15; + source.wlast = 0; + source.wuser = 0; + source.bready = 0; + source.rready = 0; + sink.awready = 0; + sink.arready = 0; + sink.wready = 1; + sink.bid = 1; + sink.bresp = 0; + sink.buser = 0; + sink.bvalid = 0; + sink.rid = 0; + sink.rdata = 'hdeadbeef; + sink.rresp = 0; + sink.rlast = 0; + sink.ruser = 0; + sink.rvalid = 0; + repeat (3) tick(); + rst_n = 1; + tick(); + source.awvalid = 1; + source.arvalid = 1; + source.wvalid = 1; + tick(); + block_new = 1; + source.awvalid = 0; + source.arvalid = 0; + source.awaddr = 0; + source.araddr = 0; + repeat (5) begin + tick(); + if (idle || !sink.awvalid || !sink.arvalid || source.wready || + sink.awaddr != 'h30001234 || sink.araddr != 'h30005678) + $fatal(1, "accepted stalled address lost when admission blocked"); + end + sink.awready = 1; + sink.arready = 1; + tick(); + sink.awready = 0; + sink.arready = 0; + source.wlast = 1; + tick(); + source.wvalid = 0; + sink.bvalid = 1; + sink.rvalid = 1; + repeat (3) begin + tick(); + if (idle || !source.bvalid || !source.rvalid || source.rdata != 'hdeadbeef) + $fatal(1, "response backpressure lost outstanding work"); + end + source.bready = 1; + source.rready = 1; + tick(); + sink.bvalid = 0; + sink.rlast = 1; + if (idle) $fatal(1, "read retired before RLAST"); + tick(); + sink.rvalid = 0; + source.awvalid = 1; + source.arvalid = 1; + repeat (3) begin + tick(); + if (!idle || source.awready || source.arready || sink.awvalid || sink.arvalid) + $fatal(1, "unaccepted blocked request counted as outstanding"); + end + block_new = 0; + tick(); + source.awvalid = 0; + source.arvalid = 0; + if (idle || !sink.awvalid || !sink.arvalid) + $fatal(1, "retained upstream requests did not resume"); + rst_n = 0; + tick(); + if (!idle || sink.awvalid || sink.arvalid) $fatal(1, "reset replayed old session"); + $display("SIM_TEST_PASS Tiny AXI admission drain/backpressure/resume"); + $finish; + end + initial begin + #10000; + $fatal(1, "SIM_TEST_TIMEOUT admission"); + end +endmodule diff --git a/tests/rtl/tiny_axi4_async_bridge_tb.sv b/tests/rtl/tiny_axi4_async_bridge_tb.sv new file mode 100644 index 00000000..577698e0 --- /dev/null +++ b/tests/rtl/tiny_axi4_async_bridge_tb.sv @@ -0,0 +1,432 @@ +`timescale 1ns / 1ps + +module tiny_axi4_async_bridge_tb; + logic src_clk = 1'b0; + logic dst_clk = 1'b0; + logic src_rst_n = 1'b0; + logic dst_rst_n = 1'b0; + logic clear = 1'b0; + logic clear_busy; + logic ready; + integer sink_ar_count = 0; + integer sink_aw_count = 0; + integer source_r_count = 0; + integer source_b_count = 0; + logic test_reset_response; + logic test_write_response; + + always @(posedge src_clk) begin + if (src_rst_n && source.rvalid && source.rready) source_r_count++; + if (src_rst_n && source.bvalid && source.bready) source_b_count++; + end + + always #5 src_clk = !src_clk; + always #7 dst_clk = !dst_clk; + + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) source ( + .aclk (src_clk), + .aresetn(src_rst_n) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) sink ( + .aclk (dst_clk), + .aresetn(dst_rst_n) + ); + + tiny_axi4_async_bridge u_dut ( + .src_clk_i (src_clk), + .src_rst_n_i (src_rst_n), + .dst_clk_i (dst_clk), + .dst_rst_n_i (dst_rst_n), + .clear_i (clear), + .clear_busy_o(clear_busy), + .ready_o (ready), + .epoch_o (), + .src_axi4 (source), + .dst_axi4 (sink) + ); + + always @(posedge dst_clk) begin + if (dst_rst_n && sink.arvalid && sink.arready) sink_ar_count = sink_ar_count + 1; + if (dst_rst_n && sink.awvalid && sink.awready) sink_aw_count = sink_aw_count + 1; + end + + task automatic source_defaults; + begin + source.awid = 0; + source.awaddr = '0; + source.awlen = 0; + source.awsize = 2; + source.awburst = 1; + source.awlock = 0; + source.awcache = 0; + source.awprot = 0; + source.awqos = 0; + source.awregion = 0; + source.awuser = 0; + source.awvalid = 0; + source.wdata = 32'h1234_5678; + source.wstrb = 4'hf; + source.wlast = 1; + source.wuser = 0; + source.wvalid = 0; + source.bready = 1; + source.arid = 0; + source.araddr = '0; + source.arlen = 0; + source.arsize = 2; + source.arburst = 1; + source.arlock = 0; + source.arcache = 0; + source.arprot = 0; + source.arqos = 0; + source.arregion = 0; + source.aruser = 0; + source.arvalid = 0; + source.rready = 1; + end + endtask + + task automatic sink_defaults; + begin + sink.awready = 1; + sink.wready = 1; + sink.bid = 0; + sink.bresp = 0; + sink.buser = 0; + sink.bvalid = 0; + sink.arready = 1; + sink.rid = 0; + sink.rdata = 32'hcafe_babe; + sink.rresp = 0; + sink.rlast = 1; + sink.ruser = 0; + sink.rvalid = 0; + end + endtask + + task automatic wait_ready; + begin + wait (ready === 1'b1); + repeat (3) @(posedge src_clk); + end + endtask + + task automatic issue_read(input logic [31:0] address); + begin + @(negedge src_clk); + source.araddr = address; + source.arvalid = 1; + do @(posedge src_clk); while (!source.arready); + @(negedge src_clk); + source.arvalid = 0; + end + endtask + + task automatic issue_write(input logic [31:0] address); + begin + @(negedge src_clk); + source.awaddr = address; + source.awvalid = 1; + do @(posedge src_clk); while (!source.awready); + @(negedge src_clk); + source.awvalid = 0; + source.wvalid = 1; + do @(posedge src_clk); while (!source.wready); + @(negedge src_clk); + source.wvalid = 0; + end + endtask + + initial begin + source_defaults(); + sink_defaults(); + test_reset_response = $test$plusargs("same_cycle_reset_read") || + $test$plusargs("same_cycle_reset_write"); + test_write_response = $test$plusargs("same_cycle_write") || + $test$plusargs("held_write_stability") || $test$plusargs("same_cycle_reset_write"); + repeat (5) @(posedge src_clk); + src_rst_n = 1; + dst_rst_n = 1; + wait_ready(); + + if ($test$plusargs("clear_pending")) begin + source.rready = 0; + issue_read(32'h5400_0010); + repeat (12) @(negedge dst_clk); + @(negedge src_clk); + clear = 1; + wait (source.rvalid === 1'b1); + @(negedge src_clk); + if (source.rresp !== 2'b11) $fatal(1, "SIM_TEST_FAIL clear must return error"); + clear = 0; + source.rready = 1; + @(posedge src_clk); + @(negedge src_clk); + repeat (200) @(negedge src_clk); + if (source_r_count != 1 || ready || source.arready) + $fatal(1, "SIM_TEST_FAIL pending clear lost isolation"); + // A delayed pre-clear response must never complete a new request. + @(negedge dst_clk); + sink.rvalid = 1; + do @(posedge dst_clk); while (!sink.rready); + @(negedge dst_clk); + sink.rvalid = 0; + repeat (20) @(negedge src_clk); + if (source.rvalid || source_r_count != 1) + $fatal(1, "SIM_TEST_FAIL late response escaped clear isolation"); + dst_rst_n = 0; + repeat (8) @(negedge src_clk); + dst_rst_n = 1; + wait_ready(); + repeat (20) @(negedge src_clk); + if (source.rvalid || source_r_count != 1) + $fatal(1, "SIM_TEST_FAIL late response replay after reset"); + $display("SIM_TEST_PASS Tiny AXI pending-clear quarantine/recovery"); + $finish; + end + + if ($test$plusargs("burst_abort")) begin + source.arlen = 3; + source.arid = 1; + source.rready = 0; + issue_read(32'h5400_0100); + wait (sink.arvalid === 1'b1); + @(negedge dst_clk); + sink.rvalid = 1; + sink.rlast = 0; + sink.rid = 1; + do @(posedge dst_clk); while (!sink.rready); + @(negedge dst_clk); + sink.rvalid = 0; + wait (source.rvalid === 1'b1); + @(negedge src_clk); + dst_rst_n = 0; + repeat (8) @(negedge src_clk); + // Preserve the already exposed first beat; synthesize exactly the three + // missing error beats, with RLAST only on the original fourth beat. + for (integer beat = 0; beat < 4; beat++) begin + wait (source.rvalid === 1'b1); + @(negedge src_clk); + if (source.rid !== 1'b1 || source.rlast !== (beat == 3) || + source.rresp !== (beat == 0 ? 2'b00 : 2'b11)) + $fatal(1, "SIM_TEST_FAIL reset read burst beat=%0d", beat); + source.rready = 1; + @(posedge src_clk); + @(negedge src_clk); + source.rready = 0; + end + dst_rst_n = 1; + wait_ready(); + if (source_r_count != 4 || source.rvalid) $fatal(1, "SIM_TEST_FAIL read burst count"); + + source.awlen = 3; + source.awid = 1; + source.bready = 0; + @(negedge src_clk); + source.awvalid = 1; + do @(posedge src_clk); while (!source.awready); + @(negedge src_clk); + source.awvalid = 0; + dst_rst_n = 0; + repeat (8) @(negedge src_clk); + for (integer beat = 0; beat < 4; beat++) begin + if (source.bvalid) $fatal(1, "SIM_TEST_FAIL reset B before write data drain"); + source.wvalid = 1; + source.wlast = beat == 3; + do @(posedge src_clk); while (!source.wready); + @(negedge src_clk); + source.wvalid = 0; + end + wait (source.bvalid === 1'b1); + @(negedge src_clk); + if (source.bid !== 1'b1 || source.bresp !== 2'b11) + $fatal(1, "SIM_TEST_FAIL reset burst write response"); + source.bready = 1; + @(posedge src_clk); + @(negedge src_clk); + dst_rst_n = 1; + wait_ready(); + repeat (20) @(negedge src_clk); + if (source_b_count != 1 || source.bvalid) $fatal(1, "SIM_TEST_FAIL write burst count"); + $display("SIM_TEST_PASS Tiny AXI burst abort conservation"); + $finish; + end + + if (test_reset_response || $test$plusargs( + "same_cycle_clear" + ) || $test$plusargs( + "same_cycle_write" + ) || $test$plusargs( + "held_read_stability" + ) || $test$plusargs( + "held_write_stability" + )) begin + if (test_write_response) begin + source.bready = 0; + issue_write(32'h5400_0020); + repeat (12) @(negedge dst_clk); + sink.bvalid = 1; + do @(posedge dst_clk); while (!sink.bready); + @(negedge dst_clk); + sink.bvalid = 0; + wait (source.bvalid === 1'b1); + end else begin + source.rready = 0; + issue_read(32'h5400_0020); + wait (sink.arvalid === 1'b1); + @(negedge dst_clk); + sink.rvalid = 1; + do @(posedge dst_clk); while (!sink.rready); + @(negedge dst_clk); + sink.rvalid = 0; + wait (source.rvalid === 1'b1); + end + @(negedge src_clk); + if ($test$plusargs("held_read_stability") || $test$plusargs("held_write_stability")) begin + // Source reset remains high. A stalled response is still owned by + // the source interface even when the remote endpoint resets. + dst_rst_n = 0; + repeat (8) begin + @(negedge src_clk); + if ($test$plusargs( + "held_read_stability" + ) && (source.rvalid !== 1'b1 || source.rresp !== 2'b00 || + source.rdata !== 32'hcafe_babe || source.rlast !== 1'b1)) + $fatal(1, "SIM_TEST_FAIL stalled source R response changed on remote reset"); + if ($test$plusargs( + "held_write_stability" + ) && (source.bvalid !== 1'b1 || source.bresp !== 2'b00)) + $fatal(1, "SIM_TEST_FAIL stalled source B response changed on remote reset"); + end + $display("SIM_TEST_PASS Tiny AXI held response stability"); + $finish; + end + if (test_reset_response) begin + dst_rst_n = 0; + end else begin + clear = 1; + wait (u_dut.s_clear_src_pulse === 1'b1); + @(negedge src_clk); + end + source.rready = 1; + source.bready = 1; + @(posedge src_clk); + @(negedge src_clk); + clear = 0; + repeat (200) @(negedge src_clk); + if (test_write_response && source_b_count != 1) + $fatal(1, "SIM_TEST_FAIL one AW produced %0d B handshakes during clear", source_b_count); + if (!test_write_response && source_r_count != 1) + $fatal(1, "SIM_TEST_FAIL one AR produced %0d R handshakes during clear", source_r_count); + $display("SIM_TEST_PASS Tiny AXI same-cycle clear conservation"); + $finish; + end + + issue_read(32'h5400_0040); + wait (sink.arvalid === 1'b1); + @(negedge dst_clk); + sink.rvalid = 1; + do @(posedge dst_clk); while (!sink.rready); + @(negedge dst_clk); + sink.rvalid = 0; + wait (source.rvalid === 1'b1); + if (source.rresp != 2'b00 || source.rdata != 32'hcafe_babe || !source.rlast) + $fatal(1, "normal Tiny AXI read response mismatch"); + @(posedge src_clk); + @(negedge src_clk); + + source.rready = 0; + issue_read(32'h5400_0060); + wait (sink.arvalid === 1'b1); + @(negedge dst_clk); + sink.rvalid = 1; + do @(posedge dst_clk); while (!sink.rready); + @(negedge dst_clk); + sink.rvalid = 0; + wait (source.rvalid === 1'b1); + dst_rst_n = 0; + wait (u_dut.s_abort_read_q === 1'b1); + #1; + if (!source.rvalid || source.rresp != 2'b00 || !source.rlast || source.rdata != 32'hcafe_babe) + $fatal(1, "remote reset changed held read response"); + source.rready = 1; + do @(posedge src_clk); while (u_dut.s_abort_read_q); + source.rready = 0; + @(negedge dst_clk); + dst_rst_n = 1; + wait_ready(); + source.rready = 1; + + issue_read(32'h5400_0080); + wait (sink.arvalid === 1'b1); + sink.arready = 0; + @(negedge dst_clk); + dst_rst_n = 0; + wait (source.rvalid === 1'b1); + if (source.rresp != 2'b11 || !source.rlast || source.rdata != 0) + $fatal(1, "destination reset did not return read DECERR"); + if (sink.arvalid || sink.awvalid) $fatal(1, "destination reset retained stale request"); + @(negedge dst_clk); + dst_rst_n = 1; + sink.arready = 1; + wait_ready(); + if (sink_ar_count != 2) $fatal(1, "reset replayed the read request"); + + sink.awready = 0; + sink.wready = 0; + issue_write(32'h5400_00c0); + wait (sink.awvalid === 1'b1); + @(negedge dst_clk); + dst_rst_n = 0; + wait (source.bvalid === 1'b1); + if (source.bresp != 2'b11) $fatal(1, "destination reset did not return write DECERR"); + if (sink.arvalid || sink.awvalid || sink.wvalid) + $fatal(1, "destination reset retained stale write request"); + @(negedge dst_clk); + dst_rst_n = 1; + sink.awready = 1; + sink.wready = 1; + wait_ready(); + if (sink_aw_count != 0) $fatal(1, "reset replayed the write request"); + + source.bready = 0; + issue_write(32'h5400_00e0); + repeat (12) @(negedge dst_clk); + @(negedge dst_clk); + sink.bvalid = 1; + do @(posedge dst_clk); while (!sink.bready); + @(negedge dst_clk); + sink.bvalid = 0; + wait (source.bvalid === 1'b1); + dst_rst_n = 0; + wait (u_dut.s_abort_write_q === 1'b1); + #1; + if (!source.bvalid || source.bresp != 2'b00) + $fatal(1, "remote reset changed held write response"); + source.bready = 1; + do @(posedge src_clk); while (u_dut.s_abort_write_q); + source.bready = 0; + @(negedge dst_clk); + dst_rst_n = 1; + wait_ready(); + source.bready = 1; + + $display("SIM_TEST_PASS Tiny AXI CDC reset abort/error/no-replay"); + $finish; + end + + initial begin + #100000; + $fatal(1, "SIM_TEST_TIMEOUT Tiny AXI CDC"); + end +endmodule diff --git a/tests/rtl/tiny_axi4_fabric_tb.sv b/tests/rtl/tiny_axi4_fabric_tb.sv index 58f0cff4..85c499b4 100644 --- a/tests/rtl/tiny_axi4_fabric_tb.sv +++ b/tests/rtl/tiny_axi4_fabric_tb.sv @@ -1,14 +1,16 @@ +`include "tiny_topology.svh" `timescale 1ns / 1ps module tiny_axi4_fabric_tb; logic clk_i = 0; logic rst_n_i = 0; + logic target_rst_n[`RETROSOC_TINY__TARGET_COUNT]; always #5 clk_i = !clk_i; axi4_if #( .ADDR_WIDTH(32), .DATA_WIDTH(32), .ID_WIDTH (1), .USER_WIDTH(1) - ) masters[2] ( + ) masters[3] ( .aclk (clk_i), .aresetn(rst_n_i) ); @@ -17,65 +19,71 @@ module tiny_axi4_fabric_tb; .DATA_WIDTH(32), .ID_WIDTH (1), .USER_WIDTH(1) - ) targets[5] ( + ) targets[`RETROSOC_TINY__TARGET_COUNT] ( .aclk (clk_i), .aresetn(rst_n_i) ); - logic fault_valid, fault_master, fault_write; - logic [31:0] fault_addr; - logic [ 1:0] fault_resp; + logic [5:0] fault_valid, fault_instruction; + logic [5:0][31:0] fault_addr; + logic [5:0][ 1:0] fault_resp; logic [63:0] cpu_wait, dma_wait; integer faults = 0; - integer completions [2]; - integer target_requests[5]; - logic [1:0][31:0] awaddr; - logic [1:0][ 7:0] awlen; - logic [1:0][ 2:0] awsize; - logic [1:0][ 1:0] awburst; - logic [1:0][ 0:0] awlock; - logic [1:0][ 0:0] awid; - logic [1:0][ 0:0] awvalid; - logic [1:0][31:0] wdata; - logic [1:0][ 3:0] wstrb; - logic [1:0][ 0:0] wlast; - logic [1:0][ 0:0] wvalid; - logic [1:0][ 0:0] bready; - logic [1:0][31:0] araddr; - logic [1:0][ 7:0] arlen; - logic [1:0][ 2:0] arsize; - logic [1:0][ 1:0] arburst; - logic [1:0][ 0:0] arlock; - logic [1:0][ 0:0] arid; - logic [1:0][ 0:0] arvalid; - logic [1:0][ 0:0] rready; - logic [1:0][ 0:0] awready; - logic [1:0][ 0:0] arready; - logic [1:0][ 0:0] wready; - logic [1:0][ 0:0] bvalid; - logic [1:0][ 0:0] bid; - logic [1:0][ 1:0] bresp; - logic [1:0][ 0:0] rvalid; - logic [1:0][ 0:0] rid; - logic [1:0][ 0:0] rlast; - logic [1:0][31:0] rdata; - logic [1:0][ 1:0] rresp; + integer completions [ 3]; + integer target_requests[`RETROSOC_TINY__TARGET_COUNT]; + logic [2:0][31:0] awaddr; + logic [2:0][ 7:0] awlen; + logic [2:0][ 2:0] awsize; + logic [2:0][ 1:0] awburst; + logic [2:0][ 0:0] awlock; + logic [2:0][ 0:0] awid; + logic [2:0][ 0:0] awvalid; + logic [2:0][31:0] wdata; + logic [2:0][ 3:0] wstrb; + logic [2:0][ 0:0] wlast; + logic [2:0][ 0:0] wvalid; + logic [2:0][ 0:0] bready; + logic [2:0][31:0] araddr; + logic [2:0][ 7:0] arlen; + logic [2:0][ 2:0] arsize; + logic [2:0][ 1:0] arburst; + logic [2:0][ 0:0] arlock; + logic [2:0][ 0:0] arid; + logic [2:0][ 0:0] arvalid; + logic [2:0][ 2:0] arprot; + logic [2:0][ 0:0] rready; + logic [2:0][ 0:0] awready; + logic [2:0][ 0:0] arready; + logic [2:0][ 0:0] wready; + logic [2:0][ 0:0] bvalid; + logic [2:0][ 0:0] bid; + logic [2:0][ 1:0] bresp; + logic [2:0][ 0:0] rvalid; + logic [2:0][ 0:0] rid; + logic [2:0][ 0:0] rlast; + logic [2:0][31:0] rdata; + logic [2:0][ 1:0] rresp; tiny_axi4_fabric u_dut ( - .clk_i (clk_i), - .rst_n_i (rst_n_i), - .perf_enable_i (1'b1), - .perf_clear_i (1'b0), - .masters (masters), - .targets (targets), - .fault_valid_o (fault_valid), - .fault_addr_o (fault_addr), - .fault_master_o(fault_master), - .fault_write_o (fault_write), - .fault_resp_o (fault_resp), - .cpu_wait_o (cpu_wait), - .dma_wait_o (dma_wait) + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .target_rst_n_i (target_rst_n), + .perf_enable_i (1'b1), + .perf_clear_i (1'b0), + .masters (masters), + .targets (targets), + .fault_valid_o (fault_valid), + .fault_addr_o (fault_addr), + .fault_instruction_o(fault_instruction), + .owner_idle_o (), + .target_idle_o (), + .fault_resp_o (fault_resp), + .cpu_wait_o (cpu_wait), + .dma_wait_o (dma_wait) ); - always @(posedge clk_i) if (fault_valid) faults <= faults + 1; - for (genvar m = 0; m < 2; m++) begin : gen_master + for (genvar target = 0; target < `RETROSOC_TINY__TARGET_COUNT; target++) begin : gen_target_reset + assign target_rst_n[target] = rst_n_i; + end + always @(posedge clk_i) faults <= faults + $countones(fault_valid); + for (genvar m = 0; m < 3; m++) begin : gen_master assign masters[m].awaddr = awaddr[m]; assign masters[m].awlen = awlen[m]; assign masters[m].awsize = awsize[m]; @@ -113,17 +121,17 @@ module tiny_axi4_fabric_tb; assign masters[m].awregion = '0; assign masters[m].awuser = '0; assign masters[m].arcache = '0; - assign masters[m].arprot = '0; + assign masters[m].arprot = arprot[m]; assign masters[m].arqos = '0; assign masters[m].arregion = '0; assign masters[m].aruser = '0; assign masters[m].wuser = '0; end - for (genvar t = 0; t < 5; t++) begin : gen_target + for (genvar t = 0; t < `RETROSOC_TINY__TARGET_COUNT; t++) begin : gen_target // Deterministic terminal responder allows independent checks of routing, // burst length and ID preservation without sharing the fabric decoder. axi4_error_slave #( - .Response(t == 3 ? 2'b11 : (t == 4 ? 2'b10 : 2'b00)) + .Response(t == `RETROSOC_TINY__DECODE_ERROR ? 2'b11 : (t == `RETROSOC_TINY__PROTOCOL_ERROR ? 2'b10 : 2'b00)) ) u_response ( .clk_i (clk_i), .rst_n_i(rst_n_i), @@ -238,9 +246,11 @@ module tiny_axi4_fabric_tb; arlock = '0; arid = '0; arvalid = '0; + arprot = '0; rready = '0; completions[0] = 0; completions[1] = 0; + completions[2] = 0; repeat (5) @(negedge clk_i); rst_n_i = 1; fork @@ -263,10 +273,18 @@ module tiny_axi4_fabric_tb; end join if (completions[0]!=7 || completions[1]!=6 || faults!=7 || - target_requests[0]!=3 || target_requests[1]!=1 || target_requests[2]!=2 || - target_requests[3]!=3 || target_requests[4]!=4 || cpu_wait==0 || dma_wait==0) + target_requests[0]!=2 || target_requests[3]!=1 || target_requests[4]!=1 || + target_requests[6]!=2 || target_requests[`RETROSOC_TINY__DECODE_ERROR]!=3 || target_requests[`RETROSOC_TINY__PROTOCOL_ERROR]!=4) $fatal(1, "accounting/fairness mismatch faults=%0d", faults); read_request(0, 32'h30000000, 1, 2, 1, 0); + arprot[0] = 3'b100; + read_request(0, 32'h10001000, 1, 2, 1, 2); + read_request(0, 32'h54000000, 1, 2, 1, 3); + if (target_requests[6] != 2 || target_requests[4] != 1) + $fatal(1, "instruction request reached a peripheral or non-NSS0 window"); + read_request(0, 32'h00000000, 1, 2, 1, 0); + read_request(0, 32'h50000000, 1, 2, 1, 0); + if (target_requests[4] != 3) $fatal(1, "NOR instruction windows not reachable"); $display("SIM_TEST_PASS Tiny fabric"); $finish; end diff --git a/tests/rtl/tiny_clock_external_tb.sv b/tests/rtl/tiny_clock_external_tb.sv new file mode 100644 index 00000000..2c8a1fe0 --- /dev/null +++ b/tests/rtl/tiny_clock_external_tb.sv @@ -0,0 +1,159 @@ +`timescale 1ns / 1ps +module tiny_clock_external_tb; + localparam integer XinHz = `TINY_TEST_XIN_HZ; + logic xin = 0, run_xin = 1, rst_n = 0, apply = 0; + logic [1:0] profile = 0, current_profile; + logic ref_clk, sys_clk, mem_clk, pclk, done, busy, fault; + logic [31:0] xin_hz, ref_hz, sys_hz, mem_hz, pclk_hz; + logic por_reset_n, old_por_reset_n; + logic [2:0] reset_outputs, old_reset_outputs; + integer ref_edges = 0; + always #(500000000.0 / XinHz) if (run_xin) xin = !xin; + tiny_reset_tree #( + .LeafCount (1), + .SplitDomains(1) + ) u_reset_tree ( + .clk_i (sys_clk), + .por_clk_i (ref_clk), + .leaf_clk_i (sys_clk), + .backend_reset_n_i (1'b1), + .rst_n_i (rst_n), + .watchdog_reset_req_i(1'b0), + .por_rst_n_o (por_reset_n), + .system_rst_n_o (reset_outputs[0]), + .leaf_rst_n_o (reset_outputs[1]), + .cpu_ready_rst_n_o (reset_outputs[2]) + ); + tiny_reset_tree #( + .LeafCount (1), + .SplitDomains(1) + ) u_old_reset_tree ( + .clk_i (sys_clk), + .por_clk_i (xin), + .leaf_clk_i (sys_clk), + .backend_reset_n_i (1'b1), + .rst_n_i (rst_n), + .watchdog_reset_req_i(1'b0), + .por_rst_n_o (old_por_reset_n), + .system_rst_n_o (old_reset_outputs[0]), + .leaf_rst_n_o (old_reset_outputs[1]), + .cpu_ready_rst_n_o (old_reset_outputs[2]) + ); + always @(posedge ref_clk or negedge rst_n) begin + if (!rst_n) ref_edges = 0; + else ref_edges++; + end + always @(posedge por_reset_n) begin + if (ref_edges < 5) $fatal(1, "AON reset released before five REF24 edges"); + end + always @(negedge xin) begin + if (XinHz == 24000000 && rst_n && + ({por_reset_n, reset_outputs} !== {old_por_reset_n, old_reset_outputs})) + $fatal(1, "24 MHz reset connection correction changed behavior"); + end + tiny_clock_reset_subsystem #( + .RefClkHz(XinHz), + .HavePll (1'b0) + ) u_dut ( + .ref_clk_i (xin), + .rst_n_i (rst_n), + .profile_i (profile), + .apply_i (apply), + .idle_i (1'b1), + .clients_idle_i (1'b1), + .warm_reset_i (1'b0), + .system_request_i (1'b0), + .command_ack_i (1'b1), + .timeout_i (32'd512), + .current_profile_o(current_profile), + .busy_o (busy), + .fault_o (fault), + .fault_code_o (), + .done_o (done), + .block_o (), + .ref_clk_o (ref_clk), + .ref_rst_n_o (), + .system_reset_n_o (), + .pll_locked_o (), + .pll_selected_o (), + .sys_clk_o (sys_clk), + .mem_clk_o (mem_clk), + .pclk_o (pclk), + .sys_rst_n_o (), + .mem_rst_n_o (), + .pclk_rst_n_o (), + .xin_hz_o (xin_hz), + .ref_hz_o (ref_hz), + .sys_hz_o (sys_hz), + .mem_hz_o (mem_hz), + .pclk_hz_o (pclk_hz) + ); + task automatic command(input logic [1:0] requested); + @(negedge ref_clk); + profile = requested; + apply = 1; + @(negedge ref_clk); + apply = 0; + repeat (600) @(negedge ref_clk); + if (busy) $fatal(1, "external clock command stuck"); + endtask + task automatic measure(input real expected_sys, input real expected_pclk); + realtime start_time, period; + @(posedge sys_clk); + start_time = $realtime; + repeat (100) @(posedge sys_clk); + period = ($realtime - start_time) / 100.0; + if (period < expected_sys - 0.01 || period > expected_sys + 0.01) + $fatal(1, "external SYS frequency mismatch"); + @(posedge mem_clk); + start_time = $realtime; + repeat (100) @(posedge mem_clk); + period = ($realtime - start_time) / 100.0; + if (period < expected_sys - 0.01 || period > expected_sys + 0.01) + $fatal(1, "external MEM frequency mismatch"); + @(posedge pclk); + start_time = $realtime; + repeat (100) @(posedge pclk); + period = ($realtime - start_time) / 100.0; + if (period < expected_pclk - 0.02 || period > expected_pclk + 0.02) + $fatal(1, "external PCLK frequency mismatch"); + endtask + initial begin + #1 rst_n = 1; + #1 rst_n = 0; + repeat (20) @(negedge xin); + rst_n = 1; + repeat (200) @(negedge xin); + if (xin_hz != XinHz || ref_hz != 24000000 || sys_hz != 24000000) + $fatal(1, "external SAFE24 normalization failed"); + measure(1000.0 / 24.0, 1000.0 / 24.0); + command(1); + if (fault || current_profile != 1 || sys_hz != XinHz || mem_hz != XinHz || + pclk_hz != (XinHz > 60000000 ? XinHz / 2 : XinHz)) + $fatal(1, "external profile commit failed"); + measure(1000000000.0 / XinHz, (XinHz > 60000000 ? 2000000000.0 : 1000000000.0) / XinHz); + command(0); + measure(1000.0 / 24.0, 1000.0 / 24.0); + command(2); + if (!fault || current_profile != 0) $fatal(1, "no-PLL profile accepted a PLL request"); + @(negedge xin); + run_xin = 0; + #1000; + // XIN loss has no independent safety clock. Recovery is external reset. + rst_n = 0; + #100; + if (por_reset_n || reset_outputs != 0) $fatal(1, "stopped-clock reset did not assert"); + run_xin = 1; + repeat (20) @(negedge xin); + rst_n = 1; + repeat (200) @(negedge xin); + if (fault || current_profile != 0) $fatal(1, "external reset recovery failed"); + measure(1000.0 / 24.0, 1000.0 / 24.0); + $display("SIM_TEST_PASS Tiny external XIN=%0d", XinHz); + $finish; + end + initial begin + #1000000; + $fatal(1, "SIM_TEST_TIMEOUT Tiny external clock"); + end +endmodule diff --git a/tests/rtl/tiny_clock_output_tb.sv b/tests/rtl/tiny_clock_output_tb.sv new file mode 100644 index 00000000..9b6ea09c --- /dev/null +++ b/tests/rtl/tiny_clock_output_tb.sv @@ -0,0 +1,56 @@ +`timescale 1ns / 1ps +module tiny_clock_output_tb; + logic ref_clk = 0, sys_clk = 0, mem_clk = 0, pclk = 0, rst_n = 0; + logic [31:0] control = 0; + wire clk_out, enabled; + always #20 ref_clk = !ref_clk; + always #2.5 sys_clk = !sys_clk; + always #5 mem_clk = !mem_clk; + always #10 pclk = !pclk; + tiny_clock_output u_dut ( + .clk_ref_i (ref_clk), + .clk_sys_i (sys_clk), + .clk_mem_i (mem_clk), + .clk_periph_i(pclk), + .rst_n_i (rst_n), + .control_i (control), + .clk_o (clk_out), + .enable_o (enabled) + ); + task automatic measure(input integer source, divisor, input realtime expected); + realtime previous_edge; + @(negedge ref_clk); + control = (divisor << 8) | source; + repeat (12) @(negedge ref_clk); + if (!enabled) $fatal(1, "CLKOUT enable missing"); + @(posedge clk_out); + previous_edge = $realtime; + repeat (8) begin + @(posedge clk_out); + if ($realtime - previous_edge < expected - 0.001 || + $realtime - previous_edge > expected + 0.001) + $fatal(1, "CLKOUT source/divisor period mismatch source=%0d", source); + previous_edge = $realtime; + end + @(negedge ref_clk); + control = 0; + repeat (12) @(negedge ref_clk); + if (enabled || clk_out) $fatal(1, "CLKOUT disable not acknowledged low"); + endtask + initial begin + repeat (4) @(negedge ref_clk); + rst_n = 1; + repeat (4) @(negedge ref_clk); + if (enabled || clk_out) $fatal(1, "CLKOUT active at cold reset"); + measure(1, 1, 80.0); + measure(2, 5, 50.0); + measure(3, 3, 60.0); + measure(4, 2, 80.0); + $display("SIM_TEST_PASS Tiny CLKOUT measured sources/divisors/off"); + $finish; + end + initial begin + #100000; + $fatal(1, "SIM_TEST_TIMEOUT CLKOUT"); + end +endmodule diff --git a/tests/rtl/tiny_clock_reset_pll_tb.sv b/tests/rtl/tiny_clock_reset_pll_tb.sv new file mode 100644 index 00000000..7a3f9ddc --- /dev/null +++ b/tests/rtl/tiny_clock_reset_pll_tb.sv @@ -0,0 +1,175 @@ +`timescale 1ns / 1ps + +module tiny_clock_reset_pll_tb; + logic ref_clk_i = 1'b0; + logic rst_n_i = 1'b0; + logic [1:0] profile = 2'd0; + logic apply = 1'b0; + logic [1:0] current_profile; + logic busy, fault; + logic idle = 1, clients_idle = 1; + logic reset_session_n, done, pll_selected; + logic [4:0] fault_code; + logic [4:0] observed_faults = 0; + always @(posedge ref_clk_i) observed_faults <= observed_faults | fault_code; + logic sys_clk, mem_clk, pclk; + logic sys_rst_n, mem_rst_n, pclk_rst_n; + logic [31:0] xin_hz, ref_hz, sys_hz, mem_hz, pclk_hz; + + // Match the selected 24 MHz reference expected by the ICS55 PLL model. + always #20.833333 ref_clk_i = !ref_clk_i; + + tiny_clock_reset_subsystem #( + .RefClkHz(24_000_000), + .HavePll (1'b1) + ) u_dut ( + .ref_clk_i (ref_clk_i), + .rst_n_i (rst_n_i), + .profile_i (profile), + .apply_i (apply), + .idle_i (idle), + .clients_idle_i (clients_idle), + .warm_reset_i (1'b0), + .system_request_i (1'b0), + .command_ack_i (1'b1), +`ifdef TINY_PLL_SUCCESS + .timeout_i (32'd10000), +`else + .timeout_i (32'd512), +`endif + .fault_code_o (fault_code), + .done_o (done), + .block_o (), + .ref_clk_o (), + .ref_rst_n_o (), + .system_reset_n_o (reset_session_n), + .pll_locked_o (), + .pll_selected_o (pll_selected), + .current_profile_o(current_profile), + .busy_o (busy), + .fault_o (fault), + .sys_clk_o (sys_clk), + .mem_clk_o (mem_clk), + .pclk_o (pclk), + .sys_rst_n_o (sys_rst_n), + .mem_rst_n_o (mem_rst_n), + .pclk_rst_n_o (pclk_rst_n), + .xin_hz_o (xin_hz), + .ref_hz_o (ref_hz), + .sys_hz_o (sys_hz), + .mem_hz_o (mem_hz), + .pclk_hz_o (pclk_hz) + ); + + task automatic apply_profile(input logic [1:0] requested); + begin + @(negedge ref_clk_i); + profile = requested; + apply = 1'b1; + @(negedge ref_clk_i); + apply = 1'b0; + end + endtask + + task automatic wait_done; + integer cycles; + begin + cycles = 0; + while (!done && cycles < 12000) begin + @(negedge ref_clk_i); + cycles++; + end + if (!done || busy) $fatal(1, "backend did not finish with bounded handshake"); + end + endtask + + task automatic check_periods(input real sys_period, input real mem_period, + input real pclk_period); + realtime started, measured; + begin + @(posedge sys_clk); + started = $realtime; + repeat (100) @(posedge sys_clk); + measured = ($realtime - started) / 100.0; + if (measured < sys_period - 0.01 || measured > sys_period + 0.01) + $fatal(1, "SYS measured period %f expected %f", measured, sys_period); + @(posedge mem_clk); + started = $realtime; + repeat (100) @(posedge mem_clk); + measured = ($realtime - started) / 100.0; + if (measured < mem_period - 0.02 || measured > mem_period + 0.02) + $fatal(1, "MEM measured period %f expected %f", measured, mem_period); + @(posedge pclk); + started = $realtime; + repeat (100) @(posedge pclk); + measured = ($realtime - started) / 100.0; + if (measured < pclk_period - 0.04 || measured > pclk_period + 0.04) + $fatal(1, "PCLK measured period %f expected %f", measured, pclk_period); + end + endtask + + initial begin + #1 rst_n_i = 1; + #1 rst_n_i = 0; + repeat (5) @(negedge ref_clk_i); + rst_n_i = 1'b1; + repeat (32) @(negedge ref_clk_i); + idle = 0; + clients_idle = 0; + apply_profile(2'd2); + repeat (30) @(negedge ref_clk_i); + if (!busy || current_profile != 0 || pll_selected) + $fatal(1, "profile changed before accepted work drained"); + idle = 1; + clients_idle = 1; + wait_done(); +`ifdef TINY_PLL_SUCCESS + if (fault || current_profile != 2 || sys_hz != 192_000_000 || + mem_hz != 96_000_000 || pclk_hz != 48_000_000) + $fatal(1, "PLL192 commit mismatch"); + check_periods(1000.0 / 192.0, 1000.0 / 96.0, 1000.0 / 48.0); + apply_profile(2'd3); + wait_done(); + if (fault || current_profile != 3 || sys_hz != 240_000_000 || + mem_hz != 120_000_000 || pclk_hz != 60_000_000) + $fatal(1, "PLL240 commit mismatch"); + check_periods(1000.0 / 240.0, 1000.0 / 120.0, 1000.0 / 60.0); + apply_profile(2'd0); + wait_done(); + if (current_profile != 0 || sys_hz != 24_000_000) $fatal(1, "SAFE24 return failed"); + check_periods(1000.0 / 24.0, 1000.0 / 24.0, 1000.0 / 24.0); +`ifdef TINY_PLL_NATIVE + // Real locked ICS55 monitor must detect either stopped polarity, reset + // the affected session, and restart from REF24 without a PLL edge. + apply_profile(2'd2); + wait_done(); + force u_dut.u_pll.gen_ecos.u_backend.pll_clk_o = 1'b0; + repeat (400) @(negedge ref_clk_i); + if (!observed_faults[3] || current_profile != 0 || !reset_session_n) + $fatal(1, "PLL stopped-low recovery failed"); + check_periods(1000.0 / 24.0, 1000.0 / 24.0, 1000.0 / 24.0); + release u_dut.u_pll.gen_ecos.u_backend.pll_clk_o; + apply_profile(2'd3); + wait_done(); + force u_dut.u_pll.gen_ecos.u_backend.pll_clk_o = 1'b1; + repeat (400) @(negedge ref_clk_i); + if (current_profile != 0 || !reset_session_n) $fatal(1, "PLL stopped-high recovery failed"); + check_periods(1000.0 / 24.0, 1000.0 / 24.0, 1000.0 / 24.0); + release u_dut.u_pll.gen_ecos.u_backend.pll_clk_o; +`endif + $display("SIM_TEST_PASS Tiny PLL transitions and measured clocks"); +`else + if (!fault || current_profile != 0 || observed_faults != 5'b00100 || + sys_hz != 24_000_000 || mem_hz != 24_000_000 || pclk_hz != 24_000_000) + $fatal(1, "PLL lock timeout was not fail-closed"); + check_periods(1000.0 / 24.0, 1000.0 / 24.0, 1000.0 / 24.0); + $display("SIM_TEST_PASS Tiny PLL lock-timeout backend"); +`endif + $finish; + end + + initial begin + #3000000; + $fatal(1, "SIM_TEST_TIMEOUT Tiny PLL clock/reset backend"); + end +endmodule diff --git a/tests/rtl/tiny_clock_reset_subsystem_tb.sv b/tests/rtl/tiny_clock_reset_subsystem_tb.sv new file mode 100644 index 00000000..f8ec9b40 --- /dev/null +++ b/tests/rtl/tiny_clock_reset_subsystem_tb.sv @@ -0,0 +1,87 @@ +`timescale 1ns / 1ps + +module tiny_clock_reset_subsystem_tb; + logic ref_clk_i = 1'b0; + logic rst_n_i = 1'b0; + logic [1:0] profile = 2'd0; + logic apply = 1'b0; + logic [1:0] current_profile; + logic busy, fault; + logic sys_clk, mem_clk, pclk; + logic sys_rst_n, mem_rst_n, pclk_rst_n; + logic [31:0] xin_hz, ref_hz, sys_hz, mem_hz, pclk_hz; + + always #5 ref_clk_i = !ref_clk_i; + + tiny_clock_reset_subsystem #( + .RefClkHz(24_000_000), + .HavePll (1'b0) + ) u_dut ( + .ref_clk_i (ref_clk_i), + .rst_n_i (rst_n_i), + .profile_i (profile), + .apply_i (apply), + .idle_i (1'b1), + .clients_idle_i (1'b1), + .warm_reset_i (1'b0), + .timeout_i (32'd512), + .system_request_i (1'b0), + .command_ack_i (1'b1), + .fault_code_o (), + .done_o (), + .block_o (), + .ref_clk_o (), + .ref_rst_n_o (), + .system_reset_n_o (), + .pll_locked_o (), + .pll_selected_o (), + .current_profile_o(current_profile), + .busy_o (busy), + .fault_o (fault), + .sys_clk_o (sys_clk), + .mem_clk_o (mem_clk), + .pclk_o (pclk), + .sys_rst_n_o (sys_rst_n), + .mem_rst_n_o (mem_rst_n), + .pclk_rst_n_o (pclk_rst_n), + .xin_hz_o (xin_hz), + .ref_hz_o (ref_hz), + .sys_hz_o (sys_hz), + .mem_hz_o (mem_hz), + .pclk_hz_o (pclk_hz) + ); + + task automatic apply_profile(input logic [1:0] requested); + begin + @(negedge ref_clk_i); + profile = requested; + apply = 1'b1; + @(negedge ref_clk_i); + apply = 1'b0; + repeat (600) @(negedge ref_clk_i); + end + endtask + + initial begin + repeat (5) @(negedge ref_clk_i); + rst_n_i = 1'b1; + repeat (32) @(negedge ref_clk_i); + if (current_profile != 0 || sys_hz != 24_000_000 || mem_hz != 24_000_000 || + pclk_hz != 24_000_000 || !sys_rst_n || !mem_rst_n || !pclk_rst_n) begin + $fatal(1, "SAFE24 backend state mismatch"); + end + apply_profile(2'd1); + if (current_profile != 1 || fault || sys_hz != 24_000_000) begin + $fatal(1, "external profile handling mismatch"); + end + apply_profile(2'd2); + if (!fault || current_profile != 1) $fatal(1, "unsupported PLL was accepted"); + $display("SIM_TEST_PASS Tiny clock/reset backend"); + $finish; + end + + initial begin + #100000; + $fatal(1, "SIM_TEST_TIMEOUT Tiny clock/reset backend"); + end +endmodule diff --git a/tests/rtl/tiny_p7_source_reset_tb.sv b/tests/rtl/tiny_p7_source_reset_tb.sv new file mode 100644 index 00000000..18969d98 --- /dev/null +++ b/tests/rtl/tiny_p7_source_reset_tb.sv @@ -0,0 +1,415 @@ +`timescale 1ns / 1ps +`include "tiny_topology.svh" + +// Production DMA master, admission, CDC and lifecycle/reset components. The +// include is extracted from the current top and its generated bindings by the +// Python test. Unrelated CPU/APB/SDIO clients are idle; the AXI target is a +// backpressured behavioral slave. No reset command bypasses the controller. +module tiny_p7_source_reset_tb; + logic s_ref_clk = 0, s_exec_clk = 0, s_backend_pclk = 0, s_backend_mem_clk = 0; + realtime sys_half = 20.833333, pclk_half = 20.833333, mem_half = 20.833333; + always #20.833333 s_ref_clk = !s_ref_clk; + always #(sys_half) s_exec_clk = !s_exec_clk; + always #(pclk_half) s_backend_pclk = !s_backend_pclk; + always #(mem_half) s_backend_mem_clk = !s_backend_mem_clk; + logic rst_n_i = 0, s_backend_reset_n = 1; + logic s_por_rst_n, s_rst_n, s_cpu_ready_rst_n, s_fabric_rst_n, s_xpi_rst_n; + logic [`RETROSOC_TINY__LEAF_COUNT-1:0] s_leaf_clk, s_leaf_rst_n; + struct packed {logic reset_req_o;} u_wdg_if; + assign u_wdg_if.reset_req_o = 0; + logic s_dma_clk, s_dma_rst_n; + logic s_periph_apply = 0, s_periph_reset_command = 1; + logic [13:0] s_periph_request = 14'h200; + logic [13:0] s_periph_idle, s_periph_idle_ref; + logic [13:0] s_gate_request, s_local_reset, s_actual_gate, s_reset_held, s_reset_ready; + logic [13:0] s_gate_ref, s_held_ref, s_ready_ref, s_periph_reset_done; + logic s_bus_idle_ref, s_periph_block, s_periph_done, s_block_pclk; + logic [ 4:0] s_periph_fault; + logic [31:0] s_clock_timeout = 256; + logic s_clock_block = 0, s_clock_safe, s_dma_admission_idle, s_dma_idle_sys; + logic s_dma_cdc_ready, s_dma_cdc_busy, s_dma_ready_sys; + // Completion-mailbox and unrelated clients have no work in this fixture. + logic s_dma_done_pending_q = 0, s_dma_done_ready = 1; + logic s_cpu_clock_idle = 1, s_sdio_admission_idle = 1, s_xpi_cdc_busy = 0; + logic [ 2:0] s_owner_idle; + logic [`RETROSOC_TINY__TARGET_COUNT-1:0] s_target_idle; + logic [`RETROSOC_TINY__LEAF_COUNT-2:0] s_apb_cdc_idle = '1, s_apb_cdc_ready = '1; + assign s_actual_gate[13:10] = 0; + assign s_actual_gate[8:0] = 0; + assign s_reset_held[13:10] = 0; + assign s_reset_held[8:0] = 0; + assign s_reset_ready[13:10] = 0; + assign s_reset_ready[8:0] = 0; + + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) u_dma_pclk_axi4_if ( + .aclk (s_dma_clk), + .aresetn(s_dma_rst_n) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) u_dma_admitted_axi4_if ( + .aclk (s_dma_clk), + .aresetn(s_dma_rst_n) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) u_masters_axi4_if[3] ( + .aclk (s_exec_clk), + .aresetn(s_fabric_rst_n) + ); + + `include "source_reset_bindings.svh" + + logic rd_start = 0, wr_start = 0, rd_start_ready, wr_start_ready; + logic rd_busy, wr_busy, rd_valid, rd_ready = 1, rd_last, rd_done, wr_done; + logic wr_valid = 0, wr_ready; + logic [31:0] rd_addr = 32'h30000000, wr_addr = 32'h30000100; + logic [4:0] rd_beats = 4, wr_beats = 4; + logic [31:0] rd_data, wr_data = 32'hcafef00d; + logic [1:0] rd_resp, wr_resp; + logic rd_id_error, wr_id_error; + dma_axi4_master u_dma_master ( + .clk_i (s_dma_clk), + .rst_n_i (s_dma_rst_n), + .clear_i (1'b0), + .read_start_valid_i (rd_start), + .read_start_ready_o (rd_start_ready), + .read_addr_i (rd_addr), + .read_beats_i (rd_beats), + .read_fixed_i (1'b0), + .read_busy_o (rd_busy), + .read_beat_valid_o (rd_valid), + .read_beat_ready_i (rd_ready), + .read_data_o (rd_data), + .read_resp_o (rd_resp), + .read_last_o (rd_last), + .read_expected_last_o(), + .read_id_error_o (rd_id_error), + .read_done_o (rd_done), + .write_start_valid_i (wr_start), + .write_start_ready_o (wr_start_ready), + .write_addr_i (wr_addr), + .write_beats_i (wr_beats), + .write_fixed_i (1'b0), + .write_busy_o (wr_busy), + .write_data_valid_i (wr_valid), + .write_data_ready_o (wr_ready), + .write_data_i (wr_data), + .write_strb_i (4'hf), + .write_done_o (wr_done), + .write_resp_o (wr_resp), + .write_id_error_o (wr_id_error), + .axi4 (u_dma_pclk_axi4_if) + ); + assign s_periph_idle = {4'hf, !(rd_busy || wr_busy || rd_start || wr_start), 9'h1ff}; + + logic hold_ar = 0, hold_aw = 0, hold_w = 0, hold_r = 0, hold_b = 0; + logic target_rd = 0, target_wr = 0; + logic [31:0] target_addr; + integer target_rleft, target_wleft; + wire target_idle = !target_rd && !target_wr; + assign s_owner_idle = {1'b1, target_idle, 1'b1}; + assign s_target_idle = {`RETROSOC_TINY__TARGET_COUNT{target_idle}}; + assign u_masters_axi4_if[1].arready = !target_rd && !hold_ar; + assign u_masters_axi4_if[1].awready = !target_wr && !hold_aw; + assign u_masters_axi4_if[1].wready = target_wr && target_wleft != 0 && !hold_w; + assign u_masters_axi4_if[1].rid = 0; + assign u_masters_axi4_if[1].rresp = 0; + assign u_masters_axi4_if[1].ruser = 0; + assign u_masters_axi4_if[1].bid = 0; + assign u_masters_axi4_if[1].bresp = 0; + assign u_masters_axi4_if[1].buser = 0; + logic ar_stalled = 0, aw_stalled = 0; + logic [39:0] held_ar, held_aw; + always @(posedge s_exec_clk or negedge s_fabric_rst_n) begin + if (!s_fabric_rst_n) begin + ar_stalled <= 0; + aw_stalled <= 0; + end else begin + if (ar_stalled && (!u_masters_axi4_if[1].arvalid || + {u_masters_axi4_if[1].araddr, u_masters_axi4_if[1].arlen} !== held_ar)) + $fatal(1, "SIM_TEST_FAIL stalled AR revoked or changed"); + if (aw_stalled && (!u_masters_axi4_if[1].awvalid || + {u_masters_axi4_if[1].awaddr, u_masters_axi4_if[1].awlen} !== held_aw)) + $fatal(1, "SIM_TEST_FAIL stalled AW revoked or changed"); + ar_stalled <= u_masters_axi4_if[1].arvalid && !u_masters_axi4_if[1].arready; + aw_stalled <= u_masters_axi4_if[1].awvalid && !u_masters_axi4_if[1].awready; + held_ar <= {u_masters_axi4_if[1].araddr, u_masters_axi4_if[1].arlen}; + held_aw <= {u_masters_axi4_if[1].awaddr, u_masters_axi4_if[1].awlen}; + end + end + always @(posedge s_exec_clk or negedge s_fabric_rst_n) begin + if (!s_fabric_rst_n) begin + target_rd <= 0; + target_wr <= 0; + target_rleft <= 0; + target_wleft <= 0; + target_addr <= 0; + u_masters_axi4_if[1].rvalid <= 0; + u_masters_axi4_if[1].rdata <= 0; + u_masters_axi4_if[1].rlast <= 0; + u_masters_axi4_if[1].bvalid <= 0; + end else begin + if (u_masters_axi4_if[1].arvalid && u_masters_axi4_if[1].arready) begin + target_rd <= 1; + target_addr <= u_masters_axi4_if[1].araddr; + target_rleft <= int'(u_masters_axi4_if[1].arlen) + 1; + end + if (target_rd && !hold_r && !u_masters_axi4_if[1].rvalid) begin + u_masters_axi4_if[1].rvalid <= 1; + u_masters_axi4_if[1].rdata <= target_addr ^ 32'h12345678; + u_masters_axi4_if[1].rlast <= target_rleft == 1; + end + if (u_masters_axi4_if[1].rvalid && u_masters_axi4_if[1].rready) begin + u_masters_axi4_if[1].rvalid <= 0; + target_rleft <= target_rleft - 1; + target_addr <= target_addr + 4; + if (u_masters_axi4_if[1].rlast) target_rd <= 0; + end + if (u_masters_axi4_if[1].awvalid && u_masters_axi4_if[1].awready) begin + target_wr <= 1; + target_wleft <= int'(u_masters_axi4_if[1].awlen) + 1; + end + if (u_masters_axi4_if[1].wvalid && u_masters_axi4_if[1].wready) begin + if (u_masters_axi4_if[1].wlast !== (target_wleft == 1)) + $fatal(1, "SIM_TEST_FAIL write burst boundary"); + target_wleft <= target_wleft - 1; + end + if (target_wr && target_wleft == 0 && !hold_b) u_masters_axi4_if[1].bvalid <= 1; + if (u_masters_axi4_if[1].bvalid && u_masters_axi4_if[1].bready) begin + u_masters_axi4_if[1].bvalid <= 0; + target_wr <= 0; + end + end + end + + // Independent scoreboard survives local source reset. Looking only at the + // bridge's reset-cleared pending bits would conceal an unsafe reset. + integer reads = 0, read_completions = 0, writes = 0, write_completions = 0; + integer read_beat = 0, local_resets = 0; + always @(posedge s_dma_clk or negedge rst_n_i or negedge s_backend_reset_n) begin + if (!rst_n_i || !s_backend_reset_n) begin + reads = 0; + read_completions = 0; + writes = 0; + write_completions = 0; + read_beat = 0; + end else if (s_dma_rst_n) begin + if (u_dma_pclk_axi4_if.arvalid && u_dma_pclk_axi4_if.arready) reads++; + if (u_dma_pclk_axi4_if.awvalid && u_dma_pclk_axi4_if.awready) writes++; + if (rd_valid && rd_ready) begin + if (rd_resp != 0 || rd_id_error || rd_data !== ((rd_addr + 4 * read_beat) ^ 32'h12345678) || + rd_last !== (read_beat == int'(rd_beats) - 1)) + $fatal(1, "SIM_TEST_FAIL stale or malformed read response"); + read_beat++; + if (rd_last) begin + read_completions++; + read_beat = 0; + end + end + if (wr_done) begin + if (wr_resp != 0 || wr_id_error) $fatal(1, "SIM_TEST_FAIL write response"); + write_completions++; + end + if (read_completions > reads || write_completions > writes) + $fatal(1, "SIM_TEST_FAIL duplicate terminal response"); + end + end + always @(posedge s_local_reset[9]) begin + if (reads != read_completions || writes != write_completions || !target_idle || + rd_busy || wr_busy || !s_dma_admission_idle) + $fatal(1, "SIM_TEST_FAIL P7_SOURCE_RESET_UNDRAINED"); + local_resets++; + end + // SYS-source reset of XPI is never an independent reset event. Allow delta + // propagation of the asynchronously asserted production reset tree. + always @(negedge s_fabric_rst_n) begin + #0.001; + if (s_xpi_rst_n !== 0 || s_dma_rst_n !== 0) + $fatal(1, "SIM_TEST_FAIL common system reset left an endpoint alive"); + end + + task automatic ready; + wait (s_dma_rst_n && s_dma_cdc_ready && s_dma_ready_sys && s_ready_ref[9]); + repeat (24) @(negedge s_ref_clk); + endtask + task automatic start_read; + @(negedge s_dma_clk); + rd_addr += 32'h100; + rd_start = 1; + do @(posedge s_dma_clk); while (!rd_start_ready); + @(negedge s_dma_clk); + rd_start = 0; + endtask + task automatic start_write; + @(negedge s_dma_clk); + wr_start = 1; + do @(posedge s_dma_clk); while (!wr_start_ready); + @(negedge s_dma_clk); + wr_start = 0; + endtask + task automatic send_words(input integer words); + @(negedge s_dma_clk); + wr_valid = 1; + repeat (words) begin + do @(posedge s_dma_clk); while (!wr_ready); + @(negedge s_dma_clk); + end + wr_valid = 0; + endtask + task automatic reset_command; + @(negedge s_ref_clk); + s_periph_apply = 1; + @(negedge s_ref_clk); + s_periph_apply = 0; + endtask + task automatic completion(input logic [4:0] expected_fault); + while (!s_periph_done) @(negedge s_ref_clk); + if (s_periph_fault !== expected_fault || + (expected_fault == 0 && s_periph_reset_done !== 14'h200) || + (expected_fault != 0 && s_local_reset != 0)) + $fatal(1, "SIM_TEST_FAIL reset command result fault=%h", s_periph_fault); + @(negedge s_ref_clk); + if (expected_fault == 0) ready(); + endtask + task automatic require_draining; + repeat (48) @(negedge s_ref_clk); + if (!s_periph_block || s_local_reset != 0 || !s_dma_rst_n || s_periph_done) + $fatal(1, "SIM_TEST_FAIL reset did not preserve outstanding work"); + endtask + task automatic recovery_read; + start_read(); + wait (!rd_busy); + repeat (24) @(negedge s_ref_clk); + if (reads != read_completions || writes != write_completions) + $fatal(1, "SIM_TEST_FAIL recovery completion accounting"); + endtask + + initial begin + if ($test$plusargs("xin48")) begin + sys_half = 10.416667; + mem_half = 10.416667; + pclk_half = 10.416667; + end + if ($test$plusargs("xin96")) begin + sys_half = 5.208333; + mem_half = 5.208333; + pclk_half = 10.416667; + end + if ($test$plusargs("pll192")) begin + sys_half = 2.604167; + mem_half = 5.208333; + pclk_half = 10.416667; + end + if ($test$plusargs("pll240")) begin + sys_half = 2.083333; + mem_half = 4.166667; + pclk_half = 8.333333; + end + // Deliberately falsified idle observations must trip the independent + // scoreboard. This is a negative test, never a qualifying DUT mode. + if ($test$plusargs("break_drain")) begin + force s_bus_idle_ref = 1'b1; + force s_periph_idle_ref = 14'h3fff; + end + #1 rst_n_i = 1; + #1 rst_n_i = 0; + repeat (8) @(negedge s_ref_clk); + rst_n_i = 1; + ready(); + recovery_read(); + + // An accepted but downstream-stalled AR survives until normal completion. + hold_ar = 1; + start_read(); + wait (u_masters_axi4_if[1].arvalid); + reset_command(); + require_draining(); + hold_ar = 0; + completion(0); + recovery_read(); + + // A late R prevents local reset, even after a bounded command timeout. + hold_r = 1; + start_read(); + wait (target_rd); + reset_command(); + require_draining(); + completion(5'b00010); + if (!rd_busy) $fatal(1, "SIM_TEST_FAIL timeout discarded read"); + hold_r = 0; + wait (!rd_busy); + reset_command(); + completion(0); + recovery_read(); + + // AW backpressure, an incomplete W burst, and a delayed B all prohibit + // source reset. The real DMA master owns BREADY and the original WLAST. + hold_aw = 1; + hold_b = 1; + start_write(); + wait (u_masters_axi4_if[1].awvalid); + reset_command(); + require_draining(); + hold_aw = 0; + send_words(1); + require_draining(); + hold_w = 1; + send_words(3); + require_draining(); + hold_w = 0; + wait (target_wleft == 0); + completion(5'b00010); + if (!wr_busy) $fatal(1, "SIM_TEST_FAIL timeout discarded write"); + hold_b = 0; + wait (!wr_busy); + reset_command(); + completion(0); + // The first post-reset write must also receive its own response exactly once. + start_write(); + send_words(4); + wait (!wr_busy); + recovery_read(); + + // A response already in the source holder is still outstanding when the + // consumer applies backpressure, even if the target is otherwise idle. + rd_ready = 0; + start_read(); + wait (rd_valid); + reset_command(); + require_draining(); + rd_ready = 1; + completion(0); + recovery_read(); + if (local_resets != 4) $fatal(1, "SIM_TEST_FAIL reset scenario coverage"); + + // The other legal reset route cancels the complete system session. + hold_r = 1; + start_read(); + wait (target_rd); + @(negedge s_ref_clk); + s_backend_reset_n = 0; + repeat (8) @(negedge s_ref_clk); + s_backend_reset_n = 1; + hold_r = 0; + ready(); + recovery_read(); + $display("SIM_TEST_PASS Tiny P7 production source reset local=4 timeout=2 system=1"); + $finish; + end + initial begin + #1000000; + $fatal(1, "SIM_TEST_TIMEOUT Tiny P7 production source reset"); + end +endmodule diff --git a/tests/rtl/tiny_peripheral_control_tb.sv b/tests/rtl/tiny_peripheral_control_tb.sv new file mode 100644 index 00000000..99457750 --- /dev/null +++ b/tests/rtl/tiny_peripheral_control_tb.sv @@ -0,0 +1,122 @@ +`timescale 1ns / 1ps +module tiny_peripheral_control_tb; + logic clk = 0, leaf_clk = 0, rst_n = 0, run_leaf = 1; + logic apply = 0, reset_cmd = 0, bus_idle = 1; + logic [13:0] request_mask = 0, idle_mask = '1; + logic [13:0] gates, resets, actual, held, ready, completed; + logic block_new, done; + logic [4:0] fault; + logic ip_clk, ip_rst_n; + integer hold_edges = 0, release_edges = 0; + always #5 clk = !clk; + always #7 if (run_leaf) leaf_clk = !leaf_clk; + tiny_peripheral_control u_control ( + .clk_i (clk), + .rst_n_i (rst_n), + .warm_reset_i (1'b0), + .apply_i (apply), + .reset_command_i(reset_cmd), + .request_i (request_mask), + .idle_i (idle_mask), + .bus_idle_i (bus_idle), + .gate_actual_i (actual), + .reset_held_i (held), + .reset_ready_i (ready), + .timeout_i (32'd128), + .gate_o (gates), + .reset_o (resets), + .block_o (block_new), + .done_o (done), + .fault_o (fault), + .reset_done_o (completed) + ); + tiny_peripheral_leaf u_leaf ( + .clk_i (leaf_clk), + .rst_n_i(rst_n), + .gate_i (gates[2]), + .reset_i(resets[2]), + .clk_o (ip_clk), + .rst_n_o(ip_rst_n), + .gated_o(actual[2]), + .held_o (held[2]) + ); + assign actual[13:3] = 0; + assign actual[1:0] = 0; + assign held[13:3] = 0; + assign held[1:0] = 0; + assign ready = {11'd0, ip_rst_n, 2'd0}; + always @(posedge ip_clk) begin + if (resets[2]) hold_edges++; + if (!resets[2] && !ip_rst_n && hold_edges != 0) release_edges++; + end + task automatic command(input logic resetting, input logic [13:0] mask); + @(negedge clk); + reset_cmd = resetting; + request_mask = mask; + apply = 1; + @(negedge clk); + apply = 0; + endtask + task automatic completion(input logic [4:0] expected_fault); + integer count; + count = 0; + while (!done && count < 200) begin + @(negedge clk); + count++; + end + if (!done || block_new || fault != expected_fault) + $fatal(1, "lifecycle completion fault=%h expected=%h", fault, expected_fault); + endtask + initial begin + #1 rst_n = 1; + #1 rst_n = 0; + repeat (8) @(negedge clk); + rst_n = 1; + repeat (20) @(negedge clk); + // CPU WFI permission must complete without pausing an unrelated DMA job. + idle_mask[9] = 0; + bus_idle = 0; + command(0, 14'h1); + while (!done) begin + @(negedge clk); + if (block_new) $fatal(1, "CPU-only gate arm blocked independent traffic"); + end + completion(0); + if (gates != 1) $fatal(1, "CPU WFI arm waited for unrelated DMA"); + command(0, 0); + completion(0); + idle_mask[9] = 1; + bus_idle = 1; + idle_mask[2] = 0; + command(0, 14'h4); + completion(5'b00010); + if (gates != 0 || actual != 0) $fatal(1, "busy timeout changed gates"); + idle_mask[2] = 1; + command(0, 14'h4); + completion(0); + if (!actual[2]) $fatal(1, "gate completed before leaf stopped"); + repeat (10) @(negedge clk); + if (ip_clk) $fatal(1, "gated clock high"); + command(1, 14'h4); + completion(5'b00001); + if (resets != 0 || completed != 0) $fatal(1, "reset accepted for gated IP"); + command(0, 0); + completion(0); + command(1, 14'h4); + completion(0); + if (completed != 14'h4 || hold_edges < 5 || release_edges < 5 || !ip_rst_n) + $fatal(1, "reset acknowledged before five hold/release edges"); + @(negedge leaf_clk); + run_leaf = 0; + command(1, 14'h4); + completion(5'b10000); + if (completed != 0 || !resets[2] || ip_rst_n) + $fatal(1, "missing clock acknowledged reset or removed isolation"); + $display("SIM_TEST_PASS Tiny peripheral lifecycle"); + $finish; + end + initial begin + #100000; + $fatal(1, "SIM_TEST_TIMEOUT Tiny peripheral lifecycle"); + end +endmodule diff --git a/tests/rtl/tiny_r2_p4_cpu.S b/tests/rtl/tiny_r2_p4_cpu.S new file mode 100644 index 00000000..ce5ae705 --- /dev/null +++ b/tests/rtl/tiny_r2_p4_cpu.S @@ -0,0 +1,77 @@ +.section .text +.global _start +.option norvc +_start: + li sp, 0x3001f000 + la t0, trap + csrw mtvec, t0 + li s0, 0 + # A C.NOP, followed by a 32-bit ADDI crossing the group boundary, C.RET. + li s1, 0x30007ffc + li s2, 3 +1: li t0, 0x05130001 + sw t0, 0(s1) + li t0, 0x80820090 + sw t0, 4(s1) + fence.i + jalr ra, s1, 0 + li t0, 9 + bne a0, t0, fail + li t0, 0x8000 + add s1, s1, t0 + addi s2, s2, -1 + bnez s2, 1b + # Instruction-side failure returns to RA via the trap handler. + li t0, 0x10001000 + jalr ra, t0, 0 + li t0, 1 + bne s0, t0, fail + # A debugger will patch this code and release the SRAM flag while halted. + li t0, 0x30000080 + li t1, 0x00100513 + sw t1, 0(t0) + li t1, 0x00008067 + sw t1, 4(t0) + li t1, 0x3001ffe0 + sw zero, 0(t1) + fence.i + jalr ra, t0, 0 + li t0, 1 + bne a0, t0, fail + li t0, 0x1000b084 + li t1, 1 + sw t1, 0(t0) +#ifdef TINY_TEST_P7 + csrsi 0xbf0, 1 + li t0, 128 + csrs mie, t0 + wfi +5: csrr t1, mip + andi t1, t1, 128 + bnez t1, 5b + wfi +#endif + li t0, 0x3001ffe0 +2: lw t1, 0(t0) + beqz t1, 2b + fence.i + li t0, 0x30000080 + jalr ra, t0, 0 + li t0, 2 + bne a0, t0, fail + li t0, 0x1000b084 + li t1, 0x80000001 + sw t1, 0(t0) +3: j 3b +trap: + csrr t2, mcause + li t3, 1 + bne t2, t3, fail + addi s0, s0, 1 + csrw mepc, ra + mret +fail: + li t0, 0x1000b084 + li t1, 0x80000000 + sw t1, 0(t0) +4: j 4b diff --git a/tests/rtl/tiny_r2_p4_cpu.ld b/tests/rtl/tiny_r2_p4_cpu.ld new file mode 100644 index 00000000..51fe0f35 --- /dev/null +++ b/tests/rtl/tiny_r2_p4_cpu.ld @@ -0,0 +1,6 @@ +ENTRY(_start) +SECTIONS { + . = 0; + .text : { *(.text*) } + /DISCARD/ : { *(.riscv.attributes) *(.comment) } +} diff --git a/tests/rtl/tiny_r2_p4_cpu_tb.sv b/tests/rtl/tiny_r2_p4_cpu_tb.sv new file mode 100644 index 00000000..54b00783 --- /dev/null +++ b/tests/rtl/tiny_r2_p4_cpu_tb.sv @@ -0,0 +1,347 @@ +`timescale 1ns / 1ps +module tiny_r2_p4_cpu_tb; + logic clk_i = 0, rst_n_i = 0; +`ifdef TINY_TEST_P7 + localparam bit P7Lifecycle = 1; +`else + localparam bit P7Lifecycle = 0; +`endif + logic cold_rst_n = 0, clock_pause = 0, clock_idle, gated; + logic [31:0] irq = 0; + always #5 clk_i = ~clk_i; + wire s_rst_n = rst_n_i; + logic s_jtag_tck_driver = 0, s_jtag_tms_driver = 1, s_jtag_tdi_driver = 0; + wire s_jtag_tdo; + logic core_rst_n, quiesce, idle, halted; + ahbl_if cpu[2] ( + .hclk (clk_i), + .hresetn(core_rst_n) + ); + tiny_sram_port_if local_ports[2] (); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) slow ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) ext[4] ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + apb4_if cfg ( + .pclk (clk_i), + .presetn(rst_n_i) + ); + logic [7:0] rom [0:4095]; + string firmware; + logic read_pending = 0, aw_pending = 0, write_pending = 0, hold_read = 0; + logic [31:0] read_data = 0, write_addr = 0; + logic [ 1:0] read_resp = 0; + integer starts = 0; + logic completed = 0; + logic [31:0] status; + bit ignored; + `include "tiny_jtag_smoke.svh" + +tiny_cpu_wrapper #( + .EnableAtomics (0), + .EnableWfiGate (P7Lifecycle), + .PreserveDebug (P7Lifecycle), + .ResetSyncStages(5) + ) u_cpu ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .debug_rst_n_i (cold_rst_n), + .wfi_gate_i (P7Lifecycle), + .gated_o (gated), + .hart_reset_o (), + .irq_i (irq), + .jtag_tck_i (s_jtag_tck_driver), + .jtag_tms_i (s_jtag_tms_driver), + .jtag_tdi_i (s_jtag_tdi_driver), + .jtag_trst_n_i (cold_rst_n), + .jtag_tdo_o (s_jtag_tdo), + .debug_halted_o(halted), + .bus_idle_i (idle), + .core_rst_n_o (core_rst_n), + .quiesce_o (quiesce), + .cpu (cpu) + ); + tiny_cpu_mem #( + .ClockPause(P7Lifecycle) + ) u_cpu_mem ( + .clk_i (clk_i), + .rst_n_i (core_rst_n), + .quiesce_i (quiesce), + .clock_pause_i (clock_pause), + .clock_idle_o (clock_idle), + .idle_o (idle), + .local_fault_valid_o(), // P5 fault aggregation has its own fixture. + .local_fault_addr_o (), + .local_fault_write_o(), + .cpu (cpu), + .local_ports (local_ports), + .slow_axi4 (slow) + ); + tiny_sram u_sram ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .perf_enable_i (1'b0), + .perf_clear_i (1'b0), + .local_ports (local_ports), + .external_ports(ext), + .cfg_apb4 (cfg) + ); + // Verification-only slow memory, not a product Boot ROM. It also supplies + // the terminal mailbox and instruction fault used by this CPU fixture. + assign slow.arready = !read_pending; + assign slow.rvalid = read_pending && !hold_read; + assign slow.rdata = read_data; + assign slow.rresp = read_resp; + assign slow.rid = 0; + assign slow.rlast = 1; + assign slow.ruser = 0; + assign slow.awready = !aw_pending && !write_pending; + assign slow.wready = aw_pending; + assign slow.bvalid = write_pending; + assign slow.bresp = 0; + assign slow.bid = 0; + assign slow.buser = 0; + always @(posedge clk_i) begin + if (!rst_n_i) begin + read_pending <= 0; + aw_pending <= 0; + write_pending <= 0; + end else begin + if (slow.rvalid && slow.rready) read_pending <= 0; + if (slow.arvalid && slow.arready) begin + read_pending <= 1; + if (slow.araddr < 4096) begin + read_resp <= 0; + read_data <= { + rom[slow.araddr+3], rom[slow.araddr+2], rom[slow.araddr+1], rom[slow.araddr] + }; + end else begin + read_resp <= 3; + read_data <= 0; + if (!slow.arprot[2]) $fatal(1, "unexpected slow data read %h", slow.araddr); + end + end + if (slow.awvalid && slow.awready) begin + aw_pending <= 1; + write_addr <= slow.awaddr; + end + if (slow.wvalid && slow.wready) begin + aw_pending <= 0; + write_pending <= 1; + if (write_addr != 32'h1000b084) $fatal(1, "unexpected slow write %h", write_addr); + if (slow.wdata == 1) starts <= starts + 1; + else if (slow.wdata == 32'h80000001) completed <= 1; + else $fatal(1, "SIM_TEST_FAIL CPU fixture code=%h", slow.wdata); + end + if (slow.bvalid && slow.bready) write_pending <= 0; + end + end + for (genvar g = 0; g < 4; g++) begin : gen_init + initial begin + ext[g].awid = 0; + ext[g].awaddr = 0; + ext[g].awlen = 0; + ext[g].awsize = 2; + ext[g].awburst = 1; + ext[g].awlock = 0; + ext[g].awcache = 0; + ext[g].awprot = 0; + ext[g].awqos = 0; + ext[g].awregion = 0; + ext[g].awuser = 0; + ext[g].awvalid = 0; + ext[g].wdata = 0; + ext[g].wstrb = 15; + ext[g].wlast = 1; + ext[g].wuser = 0; + ext[g].wvalid = 0; + ext[g].bready = 0; + ext[g].arid = 0; + ext[g].araddr = 0; + ext[g].arlen = 0; + ext[g].arsize = 2; + ext[g].arburst = 1; + ext[g].arlock = 0; + ext[g].arcache = 0; + ext[g].arprot = 0; + ext[g].arqos = 0; + ext[g].arregion = 0; + ext[g].aruser = 0; + ext[g].arvalid = 0; + ext[g].rready = 0; + end + end + task automatic abstract_wait; + integer polls; + logic [31:0] value; + polls = 0; + do begin + jtag_dmi(0, 7'h16, 0, value); + polls++; + end while (value[12] && polls < 128); + if (value[12] || value[10:8] != 0) $fatal(1, "abstract command status %h", value); + endtask + task automatic set_gpr(input logic [4:0] regnum, input logic [31:0] value); + logic [31:0] unused; + jtag_dmi(1, 7'h04, value, unused); + jtag_dmi(1, 7'h17, 32'h00231000 | {27'd0, regnum}, unused); + abstract_wait(); + endtask + task automatic execute_debug(input logic [31:0] instruction); + logic [31:0] unused; + jtag_dmi(1, 7'h20, instruction, unused); + jtag_dmi(1, 7'h21, 32'h00100073, unused); + jtag_dmi(1, 7'h17, 32'h00240000, unused); + abstract_wait(); + endtask + task automatic debug_store(input logic [31:0] address, input logic [31:0] value); + set_gpr(28, address); + set_gpr(29, value); + execute_debug(32'h01de2023); // sw t4,0(t3) + endtask + initial begin + #10000000; + $display( + "CPU_DIAG starts=%0d completed=%b core_reset=%b halted=%b pending=%b idle=%b quiesce=%b hold=%b read=%b", + starts, completed, core_rst_n, halted, u_cpu.s_reset_pending, idle, quiesce, hold_read, + read_pending); + $display("CPU_DIAG I addr=%h trans=%b ready=%b D addr=%h trans=%b ready=%b slowstate=%0d", + cpu[0].haddr, cpu[0].htrans, cpu[0].hready, cpu[1].haddr, cpu[1].htrans, + cpu[1].hready, u_cpu_mem.s_slow_q.state); + $fatal(1, "SIM_TEST_TIMEOUT Tiny P4 CPU"); + end + initial begin + if (!$value$plusargs("firmware=%s", firmware)) $fatal(1, "missing CPU fixture image"); + $readmemh(firmware, rom); + cfg.paddr = 0; + cfg.pprot = 0; + cfg.psel = 0; + cfg.penable = 0; + cfg.pwrite = 0; + cfg.pwdata = 0; + cfg.pstrb = 15; + repeat (6) jtag_tick(1, 0, ignored); + @(negedge clk_i); + rst_n_i = 1; + cold_rst_n = 1; + repeat (12) jtag_tick(1, 0, ignored); + jtag_idle(1); + jtag_ir(5'h11); + wait (starts == 1); + $display("P4_CPU_STAGE boot-and-boundaries"); + if (P7Lifecycle) begin + wait (gated); + repeat (10) @(negedge clk_i); + irq = 2; // Tiny wrapper timer IRQ is input bit 1. + wait (!gated); + repeat (10) @(negedge clk_i); + irq = 0; + wait (gated); + end + jtag_dmi(1, 7'h10, 1, status); + jtag_dmi(1, 7'h10, 32'h80000001, status); + wait (halted); + $display("P4_CPU_STAGE halted"); + debug_store(32'h30000080, 32'h00200513); + debug_store(32'h3001ffe0, 1); + execute_debug(32'h0000100f); // FENCE.I before debugger resume + jtag_dmi(1, 7'h10, 32'h40000001, status); + jtag_dmi(1, 7'h10, 1, status); + wait (completed); + $display("P4_CPU_STAGE patched-code-executed"); + // A different external owner retains a B response across hart reset. + @(negedge clk_i); + ext[2].awaddr = 32'h30010100; + ext[2].awvalid = 1; + do @(posedge clk_i); while (!ext[2].awready); + @(negedge clk_i); + ext[2].awvalid = 0; + ext[2].wdata = 32'hdecafbad; + ext[2].wvalid = 1; + do @(posedge clk_i); while (!ext[2].wready); + @(negedge clk_i); + ext[2].wvalid = 0; + wait (ext[2].bvalid); + @(negedge clk_i); + hold_read = 1; + wait (read_pending); + jtag_dmi(1, 7'h10, 3, status); + repeat (32) begin + @(posedge clk_i); + if (!core_rst_n || !ext[2].bvalid) $fatal(1, "hart reset discarded accepted work"); + end + @(negedge clk_i); + hold_read = 0; + wait (!core_rst_n); + $display("P4_CPU_STAGE hart-reset-drained"); + if (!ext[2].bvalid) $fatal(1, "hart reset reset external SRAM frontend"); + jtag_dmi(1, 7'h10, 1, status); + wait (core_rst_n); + wait (starts == 2); + @(negedge clk_i); + ext[2].bready = 1; + @(posedge clk_i); + @(negedge clk_i); + ext[2].bready = 0; + ext[2].araddr = 32'h30010100; + ext[2].arvalid = 1; + do @(posedge clk_i); while (!ext[2].arready); + @(negedge clk_i); + ext[2].arvalid = 0; + ext[2].rready = 1; + do @(posedge clk_i); while (!ext[2].rvalid); + if (ext[2].rdata !== 32'hdecafbad || ext[2].rresp != 0) + $fatal(1, "SRAM payload lost on hart reset"); + if (P7Lifecycle) begin + @(negedge clk_i); + ext[2].rready = 0; + // A clock pause is different from a hart reset: unaccepted requests + // may remain parked, and resume without being dropped or replayed. + clock_pause = 1; + wait (clock_idle); + repeat (20) begin + @(negedge clk_i); + if (!clock_idle || local_ports[0].req_valid || local_ports[1].req_valid) + $fatal(1, "CPU local admission continued while clock-paused"); + end + clock_pause = 0; + jtag_dmi(1, 7'h10, 32'h80000001, status); + wait (halted); + set_gpr(28, 0); + jtag_dmi(1, 7'h04, 32'h5a1234a5, status); + jtag_dmi(1, 7'h20, 32'h000e2e83, status); // lw t4,0(t3) + jtag_dmi(1, 7'h21, 32'h00100073, status); + hold_read = 1; + jtag_dmi(1, 7'h17, 32'h00240000, status); + wait (read_pending); + @(negedge clk_i); + rst_n_i = 0; + repeat (10) @(negedge clk_i); + hold_read = 0; + rst_n_i = 1; + repeat (20) @(negedge clk_i); + jtag_dmi(0, 7'h04, 0, status); + if (status != 32'h5a1234a5) $fatal(1, "warm reset lost retained debug data"); + jtag_dmi(0, 7'h16, 0, status); + if (status[12] || status[10:8] == 0) + $fatal(1, "warm reset did not terminate pending debug command with error"); + $display("P7_CPU_LIFECYCLE_PASS irq-wake debug-wake clock-pause warm-debug-abort"); + end + $display("SIM_TEST_PASS Tiny P4 CPU compressed-boundaries fence-i debug-patch hart-drain"); + $finish; + end +endmodule diff --git a/tests/rtl/tiny_r2_p4_formal.sv b/tests/rtl/tiny_r2_p4_formal.sv new file mode 100644 index 00000000..515907c1 --- /dev/null +++ b/tests/rtl/tiny_r2_p4_formal.sv @@ -0,0 +1,58 @@ +module tiny_r2_p4_formal ( + input logic clk_i, + input logic rst_n_i +); + logic s_started_q = 1'b0; + (* anyseq *) logic [2:0] s_valid, s_ready, s_write; + tiny_sram_port_if u_ports[3] (); + always_ff @(posedge clk_i) begin + s_started_q <= 1'b1; + assume (rst_n_i == s_started_q); + end + for (genvar client = 0; client < 3; client++) begin : gen_client + logic [2:0] s_outstanding_q = '0; + wire s_accept = u_ports[client].req_valid && u_ports[client].req_ready; + wire s_complete = u_ports[client].rsp_valid && u_ports[client].rsp_ready; + always_ff @(posedge clk_i) begin + if (!rst_n_i) s_outstanding_q <= '0; + else begin + case ({ + s_accept, s_complete + }) + 2'b10: s_outstanding_q <= s_outstanding_q + 3'd1; + 2'b01: s_outstanding_q <= s_outstanding_q - 3'd1; + default: begin + end + endcase + assert (s_outstanding_q <= ((client == 2) ? 3'd2 : 3'd1)); + assert (!u_ports[client].rsp_valid || (s_outstanding_q != 3'd0)); + end + end + assign u_ports[client].req_valid = s_valid[client]; + assign u_ports[client].rsp_ready = s_ready[client]; + assign u_ports[client].write = s_write[client]; + assign u_ports[client].addr = 32'h30000000 + 32'(client) * 32'h1000; + assign u_ports[client].wdata = 32'(client); + assign u_ports[client].wstrb = 4'hf; + end + tiny_sram_group u_dut ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .ports (u_ports) + ); +endmodule + +// Abstract only the macro payload: arbitration, storage reservation, response +// capture and all control logic are the production group implementation. +module tc_sram_1024x32 ( + input logic clk_i, + cs_i, + input logic [ 9:0] addr_i, + input logic [31:0] data_i, + input logic [ 3:0] mask_i, + input logic wren_i, + output logic [31:0] data_o +); + (* anyseq *) logic [31:0] s_value; + assign data_o = s_value; +endmodule diff --git a/tests/rtl/tiny_r2_p4_memory_tb.sv b/tests/rtl/tiny_r2_p4_memory_tb.sv new file mode 100644 index 00000000..5c5c44ed --- /dev/null +++ b/tests/rtl/tiny_r2_p4_memory_tb.sv @@ -0,0 +1,675 @@ +`timescale 1ns / 1ps +module tiny_r2_p4_memory_tb; + logic clk_i = 0, rst_n_i = 0, quiesce = 0, idle; + logic perf_enable = 1, perf_clear = 0; + always #5 clk_i = ~clk_i; + ahbl_if cpu[2] ( + .hclk (clk_i), + .hresetn(rst_n_i) + ); + tiny_sram_port_if local_ports[2] (); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) ext[4] ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) slow ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + apb4_if cfg ( + .pclk (clk_i), + .presetn(rst_n_i) + ); + logic [31:0] addr[2], data[2], rdata[2]; + logic [2:0] size [2]; + logic [1:0] trans[2]; + logic [1:0] wr, ready, resp; + logic [31:0] awaddr[4], wdata[4], araddr[4], erdata[4]; + logic [3:0] wstrb[4]; + logic [7:0] awlen[4], arlen[4]; + logic [2:0] awsize[4], arsize[4]; + logic [1:0] awburst[4], arburst[4], bresp[4], rresp[4]; + logic [3:0] awvalid, wvalid, wlast, bready, arvalid, rready; + logic [3:0] awready, wready, bvalid, arready, rvalid, rlast; + integer parallel_issues = 0; + integer local_latency_max = 0; + logic [31:0] observed; + logic [31:0] seed = 32'h12345678; + logic [1:0] local_fault_valid, local_fault_write; + logic [1:0][31:0] local_fault_addr; + integer local_fault_count[2] = '{0, 0}; + always @(posedge clk_i) begin + for (integer p = 0; p < 2; p++) if (rst_n_i && local_fault_valid[p]) local_fault_count[p]++; + end + + tiny_cpu_mem u_cpu_mem ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .quiesce_i (quiesce), + .idle_o (idle), + .local_fault_valid_o(local_fault_valid), + .local_fault_addr_o (local_fault_addr), + .local_fault_write_o(local_fault_write), + .cpu (cpu), + .local_ports (local_ports), + .slow_axi4 (slow) + ); + tiny_sram u_sram ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .perf_enable_i (perf_enable), + .perf_clear_i (perf_clear), + .local_ports (local_ports), + .external_ports(ext), + .cfg_apb4 (cfg) + ); + axi4_error_slave #( + .Response(2'b11) + ) u_slow_error ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .axi4 (slow) + ); + for (genvar p = 0; p < 2; p++) begin : gen_cpu + assign cpu[p].haddr = addr[p]; + assign cpu[p].hwdata = data[p]; + assign cpu[p].hsize = size[p]; + assign cpu[p].htrans = trans[p]; + assign cpu[p].hwrite = wr[p]; + assign cpu[p].hburst = 0; + assign cpu[p].hprot = 4'(p); + assign cpu[p].hmastlock = 0; + assign ready[p] = cpu[p].hready; + assign resp[p] = cpu[p].hresp; + assign rdata[p] = cpu[p].hrdata; + end + for (genvar g = 0; g < 4; g++) begin : gen_ext + assign ext[g].awaddr = awaddr[g]; + assign ext[g].awlen = awlen[g]; + assign ext[g].awsize = awsize[g]; + assign ext[g].awburst = awburst[g]; + assign ext[g].awvalid = awvalid[g]; + assign ext[g].wdata = wdata[g]; + assign ext[g].wstrb = wstrb[g]; + assign ext[g].wlast = wlast[g]; + assign ext[g].wvalid = wvalid[g]; + assign ext[g].bready = bready[g]; + assign ext[g].araddr = araddr[g]; + assign ext[g].arlen = arlen[g]; + assign ext[g].arsize = arsize[g]; + assign ext[g].arburst = arburst[g]; + assign ext[g].arvalid = arvalid[g]; + assign ext[g].rready = rready[g]; + assign ext[g].awid = '0; + assign ext[g].awlock = '0; + assign ext[g].awcache = '0; + assign ext[g].awprot = '0; + assign ext[g].awqos = '0; + assign ext[g].awregion = '0; + assign ext[g].awuser = '0; + assign ext[g].wuser = '0; + assign ext[g].arid = '0; + assign ext[g].arlock = '0; + assign ext[g].arcache = '0; + assign ext[g].arprot = '0; + assign ext[g].arqos = '0; + assign ext[g].arregion = '0; + assign ext[g].aruser = '0; + assign awready[g] = ext[g].awready; + assign wready[g] = ext[g].wready; + assign bvalid[g] = ext[g].bvalid; + assign bresp[g] = ext[g].bresp; + assign arready[g] = ext[g].arready; + assign rvalid[g] = ext[g].rvalid; + assign rresp[g] = ext[g].rresp; + assign rlast[g] = ext[g].rlast; + assign erdata[g] = ext[g].rdata; + end + always @(posedge clk_i) begin + if ((int'(u_sram.gen_group[0].u_group.s_issue) + + int'(u_sram.gen_group[1].u_group.s_issue) + + int'(u_sram.gen_group[2].u_group.s_issue) + + int'(u_sram.gen_group[3].u_group.s_issue)) > 1) + parallel_issues++; + end + task automatic ahb(input integer p, input bit write_access, input logic [31:0] address, + input logic [2:0] width, input logic [31:0] value, input bit error_expected, + input integer max_latency, output logic [31:0] result); + time accepted; + integer error_cycles; + @(negedge clk_i); + addr[p] = address; + size[p] = width; + wr[p] = write_access; + trans[p] = 2; + do @(posedge clk_i); while (!ready[p]); + accepted = $time; + @(negedge clk_i); + trans[p] = 0; + data[p] = value; + error_cycles = 0; + do begin + @(posedge clk_i); + if (resp[p]) error_cycles++; + end while (!ready[p]); + result = rdata[p]; + if (resp[p] != error_expected || (error_expected && error_cycles != 2)) + $fatal(1, "AHB error protocol p=%0d addr=%h errors=%0d", p, address, error_cycles); + if (max_latency != 0 && (($time - accepted) / 10) > max_latency) + $fatal(1, "local latency p=%0d addr=%h cycles=%0d", p, address, ($time - accepted) / 10); + if (max_latency != 0 && (($time - accepted) / 10) > local_latency_max) + local_latency_max = integer'(($time - accepted) / 10); + @(negedge clk_i); + endtask + task automatic store(input logic [31:0] address, input logic [31:0] value, + input logic [2:0] width); + logic [31:0] unused; + ahb(1, 1, address, width, value, 0, 3, unused); + endtask + task automatic load(input integer p, input logic [31:0] address, input logic [31:0] value, + input integer max_latency); + logic [31:0] got; + ahb(p, 0, address, 2, 0, 0, max_latency, got); + if (got !== value) + $fatal(1, "local data p=%0d addr=%h got=%h expected=%h", p, address, got, value); + endtask + task automatic read_external(input integer g, input logic [31:0] address, + input logic [31:0] value, input integer hold_cycles, + input logic [1:0] expected_resp); + @(negedge clk_i); + araddr[g] = address; + arvalid[g] = 1; + do @(posedge clk_i); while (!arready[g]); + @(negedge clk_i); + arvalid[g] = 0; + while (!rvalid[g]) @(negedge clk_i); + repeat (hold_cycles) begin + @(posedge clk_i); + if (!rvalid[g] || erdata[g] !== value || rresp[g] != expected_resp || !rlast[g]) + $fatal(1, "external response changed under backpressure"); + @(negedge clk_i); + end + if (erdata[g] !== value || rresp[g] != expected_resp || !rlast[g]) + $fatal(1, "external read g=%0d got=%h expected=%h resp=%b", g, erdata[g], value, rresp[g]); + rready[g] = 1; + @(posedge clk_i); + @(negedge clk_i); + rready[g] = 0; + endtask + task automatic write_external(input integer g, input logic [31:0] address, + input logic [31:0] value, input logic [3:0] mask, + input integer delay_data); + @(negedge clk_i); + awaddr[g] = address; + awvalid[g] = 1; + do @(posedge clk_i); while (!awready[g]); + @(negedge clk_i); + awvalid[g] = 0; + repeat (delay_data) @(negedge clk_i); + wdata[g] = value; + wstrb[g] = mask; + wvalid[g] = 1; + wlast[g] = 1; + do @(posedge clk_i); while (!wready[g]); + @(negedge clk_i); + wvalid[g] = 0; + bready[g] = 1; + do @(posedge clk_i); while (!bvalid[g]); + if (bresp[g] != 0) $fatal(1, "external write"); + @(negedge clk_i); + bready[g] = 0; + endtask + task automatic config_read(input logic [11:0] offset, input logic [31:0] expected); + @(negedge clk_i); + cfg.paddr = {20'h10017, offset}; + cfg.psel = 1; + cfg.penable = 0; + @(negedge clk_i); + cfg.penable = 1; + do @(posedge clk_i); while (!cfg.pready); + if (cfg.pslverr || cfg.prdata !== expected) + $fatal(1, "SRAM ABI/perf offset=%h got=%h expected=%h", offset, cfg.prdata, expected); + @(negedge clk_i); + cfg.psel = 0; + cfg.penable = 0; + endtask + task automatic burst_and_errors; + // Sixteen captured write beats, with W asserted before AW. + @(negedge clk_i); + wdata[0] = 32'hcafe0000; + wstrb[0] = 15; + wvalid[0] = 1; + wlast[0] = 0; + repeat (3) begin + @(posedge clk_i); + if (wready[0]) $fatal(1, "W accepted without AW ownership"); + end + @(negedge clk_i); + awaddr[0] = 32'h30000fc0; + awlen[0] = 15; + awvalid[0] = 1; + do @(posedge clk_i); while (!awready[0]); + @(negedge clk_i); + awvalid[0] = 0; + for (integer beat = 0; beat < 16; beat++) begin + wdata[0] = 32'hcafe0000 + beat; + wlast[0] = (beat == 15); + do @(posedge clk_i); while (!wready[0]); + @(negedge clk_i); + end + wvalid[0] = 0; + wait (bvalid[0]); + load(0, 32'h30000ffc, 32'hcafe000f, 3); // B held while local read completes. + if (!bvalid[0] || bresp[0] != 0) $fatal(1, "B not retained"); + @(negedge clk_i); + bready[0] = 1; + @(posedge clk_i); + @(negedge clk_i); + bready[0] = 0; + awlen[0] = 0; + araddr[0] = 32'h30000fc0; + arlen[0] = 15; + arvalid[0] = 1; + do @(posedge clk_i); while (!arready[0]); + @(negedge clk_i); + arvalid[0] = 0; + for (integer beat = 0; beat < 16; beat++) begin + wait (rvalid[0]); + repeat (beat % 4) @(negedge clk_i); + if (erdata[0] !== (32'hcafe0000 + beat) || rresp[0] != 0 || rlast[0] != (beat == 15)) + $fatal(1, "burst read ordering/last at beat %0d", beat); + rready[0] = 1; + @(posedge clk_i); + @(negedge clk_i); + rready[0] = 0; + end + arlen[0] = 0; + // A malformed write cannot commit bytes. Test strobes and early/missing LAST. + for (integer bad = 0; bad < 3; bad++) begin + @(negedge clk_i); + awaddr[0] = 32'h30000fc0; + awvalid[0] = 1; + awsize[0] = (bad == 0) ? 1 : 2; + awlen[0] = (bad == 1) ? 3 : 0; + do @(posedge clk_i); while (!awready[0]); + @(negedge clk_i); + awvalid[0] = 0; + wvalid[0] = 1; + wdata[0] = 0; + wstrb[0] = (bad == 0) ? 4'h8 : 4'hf; + wlast[0] = (bad != 2); + do @(posedge clk_i); while (!wready[0]); + @(negedge clk_i); + wvalid[0] = 0; + bready[0] = 1; + do @(posedge clk_i); while (!bvalid[0]); + if (bresp[0] != 2) $fatal(1, "malformed write accepted"); + @(negedge clk_i); + bready[0] = 0; + load(0, 32'h30000fc0, 32'hcafe0000, 3); + end + awsize[0] = 2; + awlen[0] = 0; + wlast[0] = 1; + wstrb[0] = 15; + endtask + task automatic back_to_back; + // The next address is already present when the preceding write completes. + @(negedge clk_i); + addr[1] = 32'h30000100; + size[1] = 2; + wr[1] = 1; + trans[1] = 2; + do @(posedge clk_i); while (!ready[1]); + for (integer word_index = 0; word_index < 8; word_index++) begin + @(negedge clk_i); + data[1] = 32'hf00d0000 + word_index; + addr[1] = 32'h30000104 + word_index * 4; + trans[1] = (word_index == 7) ? 0 : 2; + do @(posedge clk_i); while (!ready[1]); + if (resp[1]) $fatal(1, "pipelined write error"); + end + @(negedge clk_i); + for (integer word_index = 0; word_index < 8; word_index++) + load(0, 32'h30000100 + word_index * 4, 32'hf00d0000 + word_index, 3); + endtask + task automatic streaming_burst; + time started, first_beat, previous_beat, final_write; + integer read_gap_max, write_gap_max; + read_gap_max = 0; + write_gap_max = 0; + @(negedge clk_i); + awaddr[0] = 32'h30000400; + awlen[0] = 15; + awvalid[0] = 1; + do @(posedge clk_i); while (!awready[0]); + started = $time; + @(negedge clk_i); + awvalid[0] = 0; + wvalid[0] = 1; + for (integer beat = 0; beat < 16; beat++) begin + wdata[0] = 32'hface0000 + beat; + wlast[0] = (beat == 15); + do @(posedge clk_i); while (!wready[0]); + if (beat == 0) first_beat = $time; + else begin + if (($time - previous_beat) != 10) $fatal(1, "write streaming bubble beat=%0d", beat); + write_gap_max = 1; + end + previous_beat = $time; + @(negedge clk_i); + end + final_write = previous_beat; + wvalid[0] = 0; + bready[0] = 1; + do @(posedge clk_i); while (!bvalid[0]); + if (bresp[0] != 0) $fatal(1, "streaming write response"); + $display("P4_STREAM_WRITE first=%0d total=%0d b_after_last=%0d", (first_beat - started) / 10, + ($time - started) / 10, ($time - final_write) / 10); + @(negedge clk_i); + bready[0] = 0; + awlen[0] = 0; + araddr[0] = 32'h30000400; + arlen[0] = 15; + arvalid[0] = 1; + rready[0] = 1; + do @(posedge clk_i); while (!arready[0]); + started = $time; + @(negedge clk_i); + arvalid[0] = 0; + for (integer beat = 0; beat < 16; beat++) begin + do @(posedge clk_i); while (!rvalid[0]); + if (erdata[0] !== (32'hface0000 + beat) || rresp[0] != 0 || rlast[0] != (beat == 15)) + $fatal(1, "streaming read ordering beat=%0d", beat); + if (beat == 0) first_beat = $time; + else begin + if (($time - previous_beat) != 10) $fatal(1, "read streaming bubble beat=%0d", beat); + read_gap_max = 1; + end + previous_beat = $time; + @(negedge clk_i); + end + rready[0] = 0; + arlen[0] = 0; + $display("P4_STREAM_READ first=%0d total=%0d", (first_beat - started) / 10, + (previous_beat - started) / 10); + $display("P4_STREAM read_gap_max=%0d write_gap_max=%0d beats=16", read_gap_max, write_gap_max); + endtask + + task automatic buffered_read; + // Two reserved words must survive a full queue, local writes to their + // addresses, and READY dropping again immediately after the first pop. + @(negedge clk_i); + araddr[0] = 32'h30000400; + arlen[0] = 15; + arvalid[0] = 1; + do @(posedge clk_i); while (!arready[0]); + @(negedge clk_i); + arvalid[0] = 0; + repeat (8) @(negedge clk_i); + store(32'h30000400, 32'habcd0000, 2); + store(32'h30000404, 32'habcd0001, 2); + store(32'h30000408, 32'habcd0002, 2); + for (integer beat = 0; beat < 16; beat++) begin + rready[0] = 1; + do @(posedge clk_i); while (!rvalid[0]); + if (erdata[0] !== ((beat == 2) ? 32'habcd0002 : (32'hface0000 + beat)) || + rresp[0] != 0 || rlast[0] != (beat == 15)) + $fatal(1, "reserved response lost/reordered beat=%0d", beat); + @(negedge clk_i); + rready[0] = 0; + repeat (beat % 5 + 1) @(negedge clk_i); + end + arlen[0] = 0; + endtask + + task automatic buffered_write_errors; + integer beats, stalls; + // Earlier queued beats must commit before an error B. Later legal beats + // after a bad strobe keep the existing target policy; early LAST ends W. + for (integer bad = 0; bad < 3; bad++) begin + for (integer n = 0; n < 8; n++) store(32'h30000600 + n * 4, 32'h12340000 + n, 2); + beats = (bad == 1) ? 4 : 8; + stalls = 0; + @(negedge clk_i); + awaddr[0] = 32'h30000600; + awlen[0] = 7; + awsize[0] = (bad == 0) ? 1 : 2; + awvalid[0] = 1; + do @(posedge clk_i); while (!awready[0]); + @(negedge clk_i); + awvalid[0] = 0; + fork + begin + for (integer beat = 0; beat < beats; beat++) begin + wvalid[0] = 1; + wdata[0] = 32'hbeefbeef; + wstrb[0] = (bad == 0) ? ((beat == 3) ? 4'hf : ((beat % 2) == 0 ? 4'h3 : 4'hc)) : 4'hf; + wlast[0] = (bad == 1) ? (beat == 3) : ((bad == 0) && (beat == 7)); + do begin + @(posedge clk_i); + if (!wready[0]) stalls++; + end while (!wready[0]); + @(negedge clk_i); + wvalid[0] = 0; + if (beat == 4) repeat (3) @(negedge clk_i); + end + end + begin + repeat (6) load(0, 32'h30000020, 32'h98765432, 0); + end + begin + repeat (6) load(1, 32'h30000020, 32'h98765432, 0); + end + join + wait (bvalid[0]); + if (bresp[0] != 2) $fatal(1, "buffered malformed write response"); + if (bad != 0 && stalls == 0) $fatal(1, "write buffer full path not exercised"); + for (integer n = 0; n < 8; n++) begin + if (bad == 0) begin + load(0, 32'h30000600 + n * 4, + (n == 1) ? 32'h1234beef : ((n < 4) ? 32'hbeefbeef : (32'h12340000 + n)), 3); + end else begin + load(0, 32'h30000600 + n * 4, (n < (beats - 1)) ? 32'hbeefbeef : (32'h12340000 + n), 3); + end + end + @(negedge clk_i); + bready[0] = 1; + @(posedge clk_i); + @(negedge clk_i); + bready[0] = 0; + end + awlen[0] = 0; + awsize[0] = 2; + wlast[0] = 1; + wstrb[0] = 15; + endtask + initial begin + #20000000; + $fatal(1, "SIM_TEST_TIMEOUT Tiny P4 memory"); + end + initial begin + wr = 0; + trans[0] = 0; + trans[1] = 0; + addr[0] = 0; + addr[1] = 0; + data[0] = 0; + data[1] = 0; + size[0] = 2; + size[1] = 2; + awvalid = 0; + wvalid = 0; + wlast = 0; + bready = 0; + arvalid = 0; + rready = 0; + for (integer g = 0; g < 4; g++) begin + awaddr[g] = 0; + awlen[g] = 0; + awsize[g] = 2; + awburst[g] = 1; + araddr[g] = 0; + arlen[g] = 0; + arsize[g] = 2; + arburst[g] = 1; + wdata[g] = 0; + wstrb[g] = 15; + end + cfg.paddr = 0; + cfg.pprot = 0; + cfg.psel = 0; + cfg.penable = 0; + cfg.pwrite = 0; + cfg.pwdata = 0; + cfg.pstrb = 15; + repeat (5) @(negedge clk_i); + rst_n_i = 1; + config_read(12'h00c, 131072); + config_read(12'h010, 32); + config_read(12'h014, 4096); + // First/last words of all 32 physical macros and every 32 KiB boundary. + for (integer bank = 0; bank < 32; bank++) begin + store(32'h30000000 + bank * 4096, 32'ha5000000 + bank, 2); + store(32'h30000ffc + bank * 4096, 32'h5a000000 + bank, 2); + load(0, 32'h30000000 + bank * 4096, 32'ha5000000 + bank, 3); + load(1, 32'h30000ffc + bank * 4096, 32'h5a000000 + bank, 3); + end + store(32'h30000020, 32'h11223344, 2); + store(32'h30000021, 32'haa, 0); + store(32'h30000022, 32'hbbcc, 1); + load(0, 32'h30000020, 32'hbbccaa44, 3); + for (integer lane = 0; lane < 4; lane++) begin + store(32'h30000028, 32'h11223344, 2); + store(32'h30000028 + lane, 32'haa, 0); + load(0, 32'h30000028, (32'h11223344 & ~(32'hff << (lane * 8))) | (32'haa << (lane * 8)), 3); + end + store(32'h30000028, 32'h11223344, 2); + store(32'h30000028, 32'haabb, 1); + load(0, 32'h30000028, 32'h1122aabb, 3); + back_to_back(); + fork + load(0, 32'h30000000, 32'ha5000000, 3); + load(1, 32'h30008000, 32'ha5000008, 3); + join + if (parallel_issues == 0) $fatal(1, "different groups serialized"); + fork + load(0, 32'h30000000, 32'ha5000000, 0); + load(1, 32'h30000000, 32'ha5000000, 0); + read_external(0, 32'h30000000, 32'ha5000000, 0, 0); + join + // A held R must retain old data while local writes use that same macro. + fork + read_external(0, 32'h30000020, 32'hbbccaa44, 31, 0); + begin + wait (rvalid[0]); + store(32'h30000020, 32'h98765432, 2); + load(0, 32'h30000020, 32'h98765432, 3); + end + join + // Waiting for external W does not lock the macro against CPU accesses. + fork + write_external(0, 32'h30000024, 32'hdeadbeef, 15, 31); + begin + wait (wready[0]); + load(0, 32'h30000020, 32'h98765432, 3); + end + join + load(1, 32'h30000024, 32'hdeadbeef, 3); + @(negedge clk_i); + perf_clear = 1; + @(negedge clk_i); + perf_clear = 0; + fork + read_external(0, 32'h30000000, 32'ha5000000, 7, 0); + read_external(1, 32'h30008000, 32'ha5000008, 7, 0); + read_external(2, 32'h30010000, 32'ha5000010, 7, 0); + read_external(3, 32'h30018000, 32'ha5000018, 7, 0); + join + config_read(12'h020, 4); + config_read(12'h024, 0); + config_read(12'h028, 4); + config_read(12'h030, 7); // Four parallel stalls count once per clock. + burst_and_errors(); + streaming_burst(); + buffered_read(); + buffered_write_errors(); + read_external(0, 32'h30008000, 0, 0, 3); + ahb(1, 0, 32'h70000000, 2, 0, 1, 0, observed); + if (local_fault_count[1] != 0) $fatal(1, "external CPU error double-counted locally"); + ahb(1, 0, 32'h30000001, 2, 0, 1, 0, observed); + if (local_fault_count[1] != 1 || local_fault_addr[1] != 32'h30000001 || local_fault_write[1]) + $fatal(1, "local data fault metadata missing"); + ahb(0, 1, 32'h30000000, 2, 0, 1, 0, observed); + if (local_fault_count[0] != 1 || !local_fault_write[0]) + $fatal(1, "local instruction fault metadata missing"); + load(1, 32'h30000000, 32'ha5000000, 3); + // Deterministic word/byte readback across all groups without touching a + // simulator memory array: every operation travels through the real ports. + for (integer g = 0; g < 4; g++) begin + for (integer n = 0; n < 4096; n++) begin + seed = seed ^ (seed << 13); + seed = seed ^ (seed >> 17); + seed = seed ^ (seed << 5); + store(32'h30000000 + g * 32768 + (n % 8192) * 4, seed, 2); + load(0, 32'h30000000 + g * 32768 + (n % 8192) * 4, seed, 3); + end + end + store(32'h3001fff0, 32'h76543210, 2); + @(negedge clk_i); + araddr[3] = 32'h3001fff0; + arlen[3] = 3; + arvalid[3] = 1; + do @(posedge clk_i); while (!arready[3]); + @(negedge clk_i); + arvalid[3] = 0; + wait (rvalid[3]); + repeat (4) @(negedge clk_i); + @(negedge clk_i); + rst_n_i = 0; + repeat (3) @(negedge clk_i); + if (rvalid != 0 || bvalid != 0) $fatal(1, "system reset retained a stale response"); + rst_n_i = 1; + arlen[3] = 0; + load(0, 32'h3001fff0, 32'h76543210, 3); + read_external(3, 32'h3001fff0, 32'h76543210, 0, 0); + // Reset after W acceptance, before the captured word reaches a macro. + // The cancelled word and its completion must not replay into a new session. + @(negedge clk_i); + awaddr[3] = 32'h3001fff0; + awlen[3] = 3; + awvalid[3] = 1; + do @(posedge clk_i); while (!awready[3]); + @(negedge clk_i); + awvalid[3] = 0; + wdata[3] = 32'hbad0bad0; + wlast[3] = 0; + wvalid[3] = 1; + do @(posedge clk_i); while (!wready[3]); + @(negedge clk_i); + wvalid[3] = 0; + rst_n_i = 0; + repeat (3) @(negedge clk_i); + rst_n_i = 1; + awlen[3] = 0; + wlast[3] = 1; + repeat (5) @(negedge clk_i); + if (bvalid != 0 || rvalid != 0) $fatal(1, "cancelled W response replayed"); + read_external(3, 32'h3001fff0, 32'h76543210, 0, 0); + $display("P4_BUFFER backpressure errors reset conservation PASS"); + $display("P4_MEMORY local_latency_max=%0d parallel_issue_cycles=%0d", local_latency_max, + parallel_issues); + $display("SIM_TEST_PASS Tiny P4 memory seed=12345678 random_per_group=4096"); + $finish; + end +endmodule diff --git a/tests/rtl/tiny_r2_p5_cpu.S b/tests/rtl/tiny_r2_p5_cpu.S new file mode 100644 index 00000000..8e03bacc --- /dev/null +++ b/tests/rtl/tiny_r2_p5_cpu.S @@ -0,0 +1,144 @@ +.section .text +.global _start +.option norvc +_start: + # Verification-only slow ROM. Copy this position-independent image to SRAM. + la t0, _start + la t1, image_end + li t2, 0x30000000 +1: lw t3, 0(t0) + sw t3, 0(t2) + addi t0, t0, 4 + addi t2, t2, 4 + bltu t0, t1, 1b + fence.i + la t0, sram_main + li t1, 0x30000000 + add t0, t0, t1 + jr t0 +sram_main: + li sp, 0x3001f000 + li s0, 0x1000a100 + li s1, 0x1000b000 + li s2, 0x3001ffe0 + lw t0, 0x40(s1) + bnez t0, mapped_done + li t0, 0x30010000 + li t1, 256 + li t2, 0x12345678 +2: sw t2, 0(t0) + addi t0, t0, 4 + addi t2, t2, 1 + addi t1, t1, -1 + bnez t1, 2b + li t0, 0xe0 + sw t0, 4(s0) + li t0, 0x30010000 + sw t0, 8(s0) + li t0, 0x30018000 + sw t0, 12(s0) + li t0, 1024 + sw t0, 16(s0) + sw zero, 20(s0) + li t0, 16 + sw t0, 24(s0) + li t0, 1 + sw t0, 0(s0) + jal ra, wait_dma + li t0, 0x30018000 + li t1, 256 + li t2, 0x12345678 +3: lw t3, 0(t0) + bne t3, t2, fail + addi t0, t0, 4 + addi t2, t2, 1 + addi t1, t1, -1 + bnez t1, 3b + # Start an XPI read whose response is withheld by the boundary model. + li t0, 0x50000000 + sw t0, 8(s0) + li t0, 0x10009040 + sw t0, 12(s0) + li t0, 0x60 + sw t0, 4(s0) + li t0, 4 + sw t0, 16(s0) + li t0, 1 + sw t0, 0(s0) + li t0, 0x30008000 + li t1, 64 +4: sw t1, 0(t0) + addi t0, t0, 4 + addi t1, t1, -1 + bnez t1, 4b + li t0, 1 + sw t0, 0x40(s1) + lw t1, 0x40(s1) + bne t0, t1, fail + sw t0, 0(s2) + # The debugger resets this hart while DMA/XPI stays owned. PERF_CTRL is + # retained by SYSCTRL and selects the resume path after the ROM copy. +5: lw t1, 0x40(s1) + li t0, 1 + bne t0, t1, fail + j 5b +mapped_done: + jal ra, wait_dma + li t0, 0x10009044 + lw t1, 0(t0) + li t0, 0x40000000 + bne t0, t1, fail + li t0, 0xe0 + sw t0, 4(s0) + li t0, 0x30018000 + sw t0, 12(s0) + li t0, 64 + sw t0, 16(s0) + sw zero, 0x40(s1) + li t0, 2 + sw t0, 0(s2) + li t0, 0x50000100 + sw t0, 8(s0) + li t0, 1 + sw t0, 0(s0) + li t1, 128 +7: addi t1, t1, -1 + bnez t1, 7b + li t0, 8 + sw t0, 0(s0) + lw t1, 32(s0) + andi t1, t1, 1 + beqz t1, fail + li t0, 3 + sw t0, 0(s2) + li t2, 100000 +8: lw t1, 32(s0) + andi t0, t1, 1 + beqz t0, 9f + addi t2, t2, -1 + bnez t2, 8b + j fail +9: andi t0, t1, 8 + beqz t0, fail + li t0, 0x80000001 + sw t0, 0x84(s1) +10: j 10b +wait_dma: + li t2, 100000 +11: lw t1, 32(s0) + andi t0, t1, 1 + beqz t0, 12f + addi t2, t2, -1 + bnez t2, 11b + j fail +12: andi t0, t1, 0x14 + li t1, 4 + bne t0, t1, fail + ret +fail: + li t0, 0x1000b084 + li t1, 0x80000000 + sw t1, 0(t0) +13: j 13b +.balign 4 +image_end: diff --git a/tests/rtl/tiny_r2_p5_fabric_formal.sv b/tests/rtl/tiny_r2_p5_fabric_formal.sv new file mode 100644 index 00000000..998294ab --- /dev/null +++ b/tests/rtl/tiny_r2_p5_fabric_formal.sv @@ -0,0 +1,129 @@ +module tiny_r2_p5_fabric_formal ( + input logic clk_i, + rst_n_i +); +`ifdef TINY_P5_RESET_FORMAL + localparam bit TargetReset = 1'b1; +`else + localparam bit TargetReset = 1'b0; +`endif + logic started = 0; + (* anyseq *) logic [2:0] arvalid, awvalid, wvalid, rready, bready; + (* anyseq *)logic [2:0][1:0] length; + (* anyseq *)logic target_reset_n; + wire target_enable = !TargetReset || target_reset_n; + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) masters[3] ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) target ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + logic idle, fault, fault_write, fault_instruction; + logic [31:0] fault_addr; + logic [1:0] fault_owner, fault_resp; + always_ff @(posedge clk_i) begin + started <= 1; + assume (rst_n_i == started); + end + tiny_axi4_target u_target ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .enable_i (target_enable), + .masters (masters), + .target (target), + .idle_o (idle), + .fault_valid_o (fault), + .fault_addr_o (fault_addr), + .fault_master_o (fault_owner), + .fault_write_o (fault_write), + .fault_instruction_o(fault_instruction), + .fault_resp_o (fault_resp) + ); + axi4_error_slave #( + .Response(2'b10) + ) u_endpoint ( + .clk_i (clk_i), + .rst_n_i(rst_n_i && target_enable), + .axi4 (target) + ); + for (genvar m = 0; m < 3; m++) begin : gen_master + logic outstanding = 0; + logic [2:0] remaining = 0; + logic write_data_pending = 0; + wire accept=(masters[m].arvalid && masters[m].arready)||(masters[m].awvalid && masters[m].awready); + wire done=(masters[m].rvalid && masters[m].rready && masters[m].rlast)|| + (masters[m].bvalid && masters[m].bready); + assign masters[m].araddr = 32'h30000000 + 32'(m) * 32'h100; + assign masters[m].awaddr = 32'h30000000 + 32'(m) * 32'h100; + assign masters[m].arlen = {6'd0, length[m]}; + assign masters[m].awlen = 0; + assign masters[m].arsize = 2; + assign masters[m].awsize = 2; + assign masters[m].arburst = 1; + assign masters[m].awburst = 1; + assign masters[m].arlock = 0; + assign masters[m].awlock = 0; + assign masters[m].arid = 1'(m); + assign masters[m].awid = 1'(m); + assign masters[m].arcache = 0; + assign masters[m].awcache = 0; + assign masters[m].arprot = 0; + assign masters[m].awprot = 0; + assign masters[m].arqos = 0; + assign masters[m].awqos = 0; + assign masters[m].arregion = 0; + assign masters[m].awregion = 0; + assign masters[m].aruser = 0; + assign masters[m].awuser = 0; + assign masters[m].arvalid = arvalid[m] && !outstanding; + assign masters[m].awvalid = awvalid[m] && !outstanding; + assign masters[m].wvalid = wvalid[m]; + assign masters[m].wlast = 1; + assign masters[m].wdata = 32'(m); + assign masters[m].wstrb = 15; + assign masters[m].wuser = 0; + assign masters[m].rready = rready[m]; + assign masters[m].bready = bready[m]; + always_ff @(posedge clk_i) begin + if (!rst_n_i) begin + outstanding <= 0; + remaining <= 0; + write_data_pending <= 0; + end else begin + if (masters[m].arvalid && masters[m].arready) remaining <= {1'b0, length[m]} + 3'd1; + if (masters[m].rvalid) begin + assert (remaining != 0); + assert (masters[m].rlast == (remaining == 1)); + if (masters[m].rready) remaining <= remaining - 3'd1; + end + if (masters[m].awvalid && masters[m].awready) write_data_pending <= 1; + if (masters[m].wvalid && masters[m].wready) write_data_pending <= 0; + if (masters[m].bvalid) assert (!write_data_pending); + if (accept) outstanding <= 1; + if (done) outstanding <= 0; + assert (!(accept && outstanding)); + assert (!done || outstanding); + assert (!(masters[m].arready && masters[m].awready)); + if (done) begin + assert (fault && fault_owner == 2'(m)); + if (TargetReset) + assert (fault_resp == 2 || fault_resp == 3); + else assert (fault_resp == 2); + assert (fault_addr == 32'h30000000 + 32'(m) * 32'h100); + end + end + end + end +endmodule diff --git a/tests/rtl/tiny_r2_p5_fabric_tb.sv b/tests/rtl/tiny_r2_p5_fabric_tb.sv new file mode 100644 index 00000000..f8ac5184 --- /dev/null +++ b/tests/rtl/tiny_r2_p5_fabric_tb.sv @@ -0,0 +1,525 @@ +`include "tiny_topology.svh" +`timescale 1ns / 1ps +module tiny_r2_p5_fabric_tb; + logic clk_i = 0; + logic rst_n_i = 0; + logic target_rst_n[`RETROSOC_TINY__TARGET_COUNT]; + always #5 clk_i = !clk_i; + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) masters[3] ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) targets[`RETROSOC_TINY__TARGET_COUNT] ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + logic [5:0] fault_valid, fault_instruction; + logic [5:0][31:0] fault_addr; + logic [5:0][ 1:0] fault_resp; + logic [63:0] cpu_wait, dma_wait; + integer faults = 0; + logic [`RETROSOC_TINY__TARGET_COUNT-1:0] + hold_address = '0, hold_read = '0, hold_write = '0, hold_b = '0; + integer error_beat [`RETROSOC_TINY__TARGET_COUNT]; + logic [ 2:0] error_last_wait = '0; + logic release_error = 0; + integer simultaneous_faults = 0; + logic [31:0] seed = 32'h12345678; + integer completions [ 3]; + integer target_requests [`RETROSOC_TINY__TARGET_COUNT]; + logic [ 2:0][31:0] awaddr; + logic [ 2:0][ 7:0] awlen; + logic [ 2:0][ 2:0] awsize; + logic [ 2:0][ 1:0] awburst; + logic [ 2:0][ 0:0] awlock; + logic [ 2:0][ 0:0] awid; + logic [ 2:0][ 0:0] awvalid; + logic [ 2:0][31:0] wdata; + logic [ 2:0][ 3:0] wstrb; + logic [ 2:0][ 0:0] wlast; + logic [ 2:0][ 0:0] wvalid; + logic [ 2:0][ 0:0] bready; + logic [ 2:0][31:0] araddr; + logic [ 2:0][ 7:0] arlen; + logic [ 2:0][ 2:0] arsize; + logic [ 2:0][ 1:0] arburst; + logic [ 2:0][ 0:0] arlock; + logic [ 2:0][ 0:0] arid; + logic [ 2:0][ 0:0] arvalid; + logic [ 2:0][ 2:0] arprot; + logic [ 2:0][ 0:0] rready; + logic [ 2:0][ 0:0] awready; + logic [ 2:0][ 0:0] arready; + logic [ 2:0][ 0:0] wready; + logic [ 2:0][ 0:0] bvalid; + logic [ 2:0][ 0:0] bid; + logic [ 2:0][ 1:0] bresp; + logic [ 2:0][ 0:0] rvalid; + logic [ 2:0][ 0:0] rid; + logic [ 2:0][ 0:0] rlast; + logic [ 2:0][31:0] rdata; + logic [ 2:0][ 1:0] rresp; + tiny_axi4_fabric u_dut ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .target_rst_n_i (target_rst_n), + .perf_enable_i (1'b1), + .perf_clear_i (1'b0), + .masters (masters), + .targets (targets), + .fault_valid_o (fault_valid), + .fault_addr_o (fault_addr), + .fault_instruction_o(fault_instruction), + .owner_idle_o (), + .target_idle_o (), + .fault_resp_o (fault_resp), + .cpu_wait_o (cpu_wait), + .dma_wait_o (dma_wait) + ); + for (genvar target = 0; target < `RETROSOC_TINY__TARGET_COUNT; target++) begin : gen_target_reset + assign target_rst_n[target] = rst_n_i; + end + always @(posedge clk_i) begin + faults <= faults + $countones(fault_valid); + if (fault_valid[2] && fault_valid[4]) begin + simultaneous_faults <= simultaneous_faults + 1; + if (fault_addr[2] != 32'h30000004 || fault_addr[4] != 32'h30008004 || + fault_resp[2] != 2 || fault_resp[4] != 2) + $fatal(1, "first nonfinal error metadata lost"); + end + end + for (genvar m = 0; m < 3; m++) begin : gen_master + assign masters[m].awaddr = awaddr[m]; + assign masters[m].awlen = awlen[m]; + assign masters[m].awsize = awsize[m]; + assign masters[m].awburst = awburst[m]; + assign masters[m].awlock = awlock[m]; + assign masters[m].awid = awid[m]; + assign masters[m].awvalid = awvalid[m]; + assign masters[m].wdata = wdata[m]; + assign masters[m].wstrb = wstrb[m]; + assign masters[m].wlast = wlast[m]; + assign masters[m].wvalid = wvalid[m]; + assign masters[m].bready = bready[m]; + assign masters[m].araddr = araddr[m]; + assign masters[m].arlen = arlen[m]; + assign masters[m].arsize = arsize[m]; + assign masters[m].arburst = arburst[m]; + assign masters[m].arlock = arlock[m]; + assign masters[m].arid = arid[m]; + assign masters[m].arvalid = arvalid[m]; + assign masters[m].rready = rready[m]; + assign awready[m] = masters[m].awready; + assign arready[m] = masters[m].arready; + assign wready[m] = masters[m].wready; + assign bvalid[m] = masters[m].bvalid; + assign bid[m] = masters[m].bid; + assign bresp[m] = masters[m].bresp; + assign rvalid[m] = masters[m].rvalid; + assign rid[m] = masters[m].rid; + assign rlast[m] = masters[m].rlast; + assign rdata[m] = masters[m].rdata; + assign rresp[m] = masters[m].rresp; + assign masters[m].awcache = '0; + assign masters[m].awprot = '0; + assign masters[m].awqos = '0; + assign masters[m].awregion = '0; + assign masters[m].awuser = '0; + assign masters[m].arcache = '0; + assign masters[m].arprot = arprot[m]; + assign masters[m].arqos = '0; + assign masters[m].arregion = '0; + assign masters[m].aruser = '0; + assign masters[m].wuser = '0; + end + for (genvar t = 0; t < `RETROSOC_TINY__TARGET_COUNT; t++) begin : gen_target + tiny_p5_target_model #( + .ErrorResponse(t == `RETROSOC_TINY__DECODE_ERROR ? 3 : (t == `RETROSOC_TINY__PROTOCOL_ERROR ? 2 : 0)) + ) u_response ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .hold_address_i(hold_address[t]), + .hold_read_i (hold_read[t]), + .hold_write_i (hold_write[t]), + .hold_b_i (hold_b[t]), + .error_beat_i (error_beat[t]), + .axi4 (targets[t]) + ); + initial begin + target_requests[t] = 0; + error_beat[t] = -1; + end + always @(posedge clk_i) begin + if ((targets[t].arvalid && targets[t].arready) || (targets[t].awvalid && targets[t].awready)) + target_requests[t] <= target_requests[t] + 1; + end + end + task automatic read_request(input int m, input logic [31:0] addr, input int beats, + input logic [2:0] size, input logic [1:0] burst, + input logic [1:0] expected); + logic [35:0] held; + @(negedge clk_i); + araddr[m] = addr; + arlen[m] = 8'(beats - 1); + arsize[m] = size; + arburst[m] = burst; + arid[m] = 1'(m); + arvalid[m] = 1; + do @(posedge clk_i); while (!arready[m]); + @(negedge clk_i); + arvalid[m] = 0; + araddr[m] = 32'hbad00000; // The accepted address, not live pins, owns responses. + for (int beat = 0; beat < beats; beat++) begin + do @(posedge clk_i); while (!rvalid[m]); + held = {rid[m], rlast[m], rresp[m], rdata[m]}; + repeat (3) begin + @(negedge clk_i); + if (!rvalid[m] || held != {rid[m], rlast[m], rresp[m], rdata[m]}) + $fatal(1, "read changed under backpressure"); + end + if (rresp[m] != expected || rid[m] != 1'(m) || rlast[m] != (beat == beats - 1)) + $fatal( + 1, + "read response/ID/length mismatch m=%0d addr=%x beat=%0d resp=%0d", + m, + addr, + beat, + rresp[m] + ); + if (expected == 0 && rdata[m] !== (32'h10000000 ^ (addr + (32'(beat) << size)))) + $fatal(1, "read data routed from wrong target/beat"); + rready[m] = 1; + @(posedge clk_i); + @(negedge clk_i); + rready[m] = 0; + end + completions[m]++; + endtask + task automatic write_request(input int m, input logic [31:0] addr, input int beats, + input logic [1:0] expected, input bit early_w); + @(negedge clk_i); + awaddr[m] = addr; + awlen[m] = 8'(beats - 1); + awsize[m] = 2; + awburst[m] = 1; + awid[m] = 1'(m); + wdata[m] = 32'hb0000000 ^ addr; + wstrb[m] = 4'b0101; + wlast[m] = (beats == 1); + if (early_w) begin + wvalid[m] = 1; + repeat (4) begin + @(posedge clk_i); + if (wready[m]) $fatal(1, "W accepted before AW"); + end + @(negedge clk_i); + end + awvalid[m] = 1; + do @(posedge clk_i); while (!awready[m]); + @(negedge clk_i); + awvalid[m] = 0; + awaddr[m] = 32'hbad08000; + if (!early_w) repeat (5) @(negedge clk_i); + for (int beat = 0; beat < beats; beat++) begin + wvalid[m] = 1; + wlast[m] = (beat == beats - 1); + wdata[m] = 32'hb0000000 ^ addr ^ 32'(beat); + do @(posedge clk_i); while (!wready[m]); + @(negedge clk_i); + wvalid[m] = 0; + repeat (2) @(negedge clk_i); + end + do @(posedge clk_i); while (!bvalid[m]); + repeat (3) begin + @(negedge clk_i); + if (!bvalid[m] || bresp[m] != expected || bid[m] != 1'(m)) + $fatal(1, "write response mismatch"); + end + bready[m] = 1; + @(posedge clk_i); + @(negedge clk_i); + bready[m] = 0; + completions[m]++; + endtask + + task automatic mixed_error_read(input int m, input logic [31:0] address); + @(negedge clk_i); + araddr[m] = address; + arlen[m] = 3; + arsize[m] = 2; + arburst[m] = 1; + arvalid[m] = 1; + arid[m] = 1'(m); + do @(posedge clk_i); while (!arready[m]); + @(negedge clk_i); + arvalid[m] = 0; + for (integer beat = 0; beat < 4; beat++) begin + wait (rvalid[m]); + if (beat == 3) begin + error_last_wait[m] = 1; + wait (release_error); + end + @(negedge clk_i); + if (rresp[m] != (beat == 1 ? 2 : 0) || rlast[m] != (beat == 3)) + $fatal(1, "mixed read response was altered"); + rready[m] = 1; + @(posedge clk_i); + @(negedge clk_i); + rready[m] = 0; + end + endtask + task automatic random_reads(input int m, input logic [31:0] initial_seed); + logic [31:0] state; + logic [31:0] address; + integer beats; + state = initial_seed; + for (integer n = 0; n < 32; n++) begin + state = state ^ (state << 13); + state = state ^ (state >> 17); + state = state ^ (state << 5); + address = 32'h30000000 + (state % 4) * 32768 + ((state >> 4) % 16) * 64; + beats = 1 + integer'((state >> 12) % 16); + read_request(m, address, beats, 2, 1, 0); + end + endtask + initial begin + awaddr = '0; + awlen = '0; + awsize = '0; + awburst = '0; + awlock = '0; + awid = '0; + awvalid = '0; + wdata = '0; + wstrb = '0; + wlast = '0; + wvalid = '0; + bready = '0; + araddr = '0; + arlen = '0; + arsize = '0; + arburst = '0; + arlock = '0; + arid = '0; + arvalid = '0; + arprot = '0; + rready = '0; + for (integer m = 0; m < 3; m++) completions[m] = 0; + repeat (5) @(negedge clk_i); + rst_n_i = 1; + // XPI can withhold its first response indefinitely without owning APB/SRAM. + hold_read[4] = 1; + fork + read_request(1, 32'h50000000, 16, 2, 1, 0); + begin + wait (target_requests[4] == 1); + read_request(0, 32'h1000b000, 1, 2, 1, 0); + read_request(0, 32'h30000000, 4, 2, 1, 0); + if (!u_dut.s_busy_q[2] || u_dut.target_idle_o[4]) + $fatal(1, "XPI test did not retain ownership"); + hold_read[4] = 0; + end + join + // One owner's read and write can own different targets simultaneously. + hold_read[0] = 1; + hold_b[1] = 1; + hold_write[1] = 1; + fork + read_request(1, 32'h30000000, 16, 2, 1, 0); + write_request(1, 32'h30008000, 16, 0, 1); + begin + wait (u_dut.s_busy_q[2] && u_dut.s_busy_q[3]); + read_request(2, 32'h30010000, 4, 2, 1, 0); + if (u_dut.target_idle_o[0] || u_dut.target_idle_o[1]) + $fatal(1, "different targets serialized"); + hold_read[0] = 0; + hold_b[1] = 0; + hold_write[1] = 0; + end + join + // A waiting APB FIFO branch cannot block independent control admission. + hold_read[`RETROSOC_TINY__TARGET_XPI] = 1; + fork + read_request(1, 32'h10009000, 1, 2, 1, 0); + begin + wait (!u_dut.target_idle_o[`RETROSOC_TINY__TARGET_XPI]); + read_request(0, 32'h1000a000, 1, 2, 1, 0); + hold_read[`RETROSOC_TINY__TARGET_XPI] = 0; + end + join + fork + begin + repeat (8) read_request(0, 32'h30000000, 4, 2, 1, 0); + end + begin + repeat (8) write_request(1, 32'h30000000, 4, 0, 0); + end + begin + repeat (8) read_request(2, 32'h30000000, 4, 2, 1, 0); + end + join + if (cpu_wait == 0 || dma_wait == 0) $fatal(1, "same-target wait counters never advanced"); + // Even when both CPU directions request different targets together, + // the fabric must retain the CPU's one-combined-operation contract. + fork + read_request(0, 32'h30000000, 4, 2, 1, 0); + write_request(0, 32'h30008000, 4, 0, 0); + join + error_beat[0] = 1; + error_beat[1] = 1; + fork + mixed_error_read(1, 32'h30000000); + mixed_error_read(2, 32'h30008000); + begin + wait (error_last_wait[1] && error_last_wait[2]); + @(negedge clk_i); + release_error = 1; + end + join + if (simultaneous_faults != 1) $fatal(1, "simultaneous terminal faults lost"); + error_beat[0] = -1; + error_beat[1] = -1; + fork + random_reads(0, 32'h12345678); + random_reads(1, 32'h87654321); + random_reads(2, 32'hc0ffee01); + join + // An accepted address buffered ahead of a stopped target remains owned. + @(negedge clk_i); + hold_address[0] = 1; + araddr[1] = 32'h30000000; + arlen[1] = 0; + arsize[1] = 2; + arburst[1] = 1; + arvalid[1] = 1; + do @(posedge clk_i); while (!arready[1]); + @(negedge clk_i); + arvalid[1] = 0; + repeat (8) @(negedge clk_i); + if (u_dut.owner_idle_o[1] || u_dut.target_idle_o[0]) $fatal(1, "buffered AR not outstanding"); + arvalid = '0; + awvalid = '0; + wvalid = '0; + rready = '0; + bready = '0; + rst_n_i = 0; + repeat (5) @(negedge clk_i); + hold_address = '0; + rst_n_i = 1; + repeat (4) @(negedge clk_i); + if (rvalid != 0 || bvalid != 0 || u_dut.owner_idle_o != 7) $fatal(1, "old session replayed"); + read_request(2, 32'h30000000, 1, 2, 1, 0); + $display("SIM_TEST_PASS Tiny P5 fabric seed=12345678 random_reads=96 concurrent_faults=2"); + $finish; + end + initial begin + repeat (100000) @(posedge clk_i); + $fatal(1, "SIM_TEST_TIMEOUT Tiny P5 fabric"); + end +endmodule + +// Verification endpoint: delays only the creation of VALID, never withdraws a +// presented response. Per-address data and write checks detect crossed routes. +module tiny_p5_target_model #( + parameter logic [1:0] ErrorResponse = 0 +) ( + input logic clk_i, + rst_n_i, + hold_address_i, + hold_read_i, + hold_write_i, + hold_b_i, + input integer error_beat_i, + axi4_if.slave axi4 +); + logic active = 0, writing = 0, write_done = 0, id = 0; + logic [31:0] address = 0, start_address = 0; + logic [7:0] length = 0, beat = 0; + logic [2:0] size = 0; + logic [1:0] burst = 0; + assign axi4.arready = !active && !hold_address_i; + assign axi4.awready = !active && !hold_address_i && !axi4.arvalid; + assign axi4.wready = active && writing && !write_done && !hold_write_i; + assign axi4.ruser = 0; + assign axi4.buser = 0; + always @(posedge clk_i) begin + if (!rst_n_i) begin + active <= 0; + writing <= 0; + write_done <= 0; + axi4.rvalid <= 0; + axi4.bvalid <= 0; + axi4.rdata <= 0; + axi4.rresp <= 0; + axi4.rlast <= 0; + axi4.rid <= 0; + axi4.bid <= 0; + axi4.bresp <= 0; + end else begin + if (axi4.arvalid && axi4.arready) begin + active <= 1; + writing <= 0; + address <= axi4.araddr; + start_address <= axi4.araddr; + length <= axi4.arlen; + beat <= 0; + id <= axi4.arid; + size <= axi4.arsize; + burst <= axi4.arburst; + end + if (axi4.awvalid && axi4.awready) begin + active <= 1; + writing <= 1; + write_done <= 0; + address <= axi4.awaddr; + start_address <= axi4.awaddr; + length <= axi4.awlen; + beat <= 0; + id <= axi4.awid; + size <= axi4.awsize; + burst <= axi4.awburst; + end + if (active && !writing && !axi4.rvalid && !hold_read_i) begin + axi4.rvalid <= 1; + axi4.rid <= id; + axi4.rdata <= 32'h10000000 ^ address; + axi4.rresp<=(ErrorResponse != 0) ? ErrorResponse : ((integer'(beat)==error_beat_i) ? 2 : 0); + axi4.rlast <= beat == length; + end + if (axi4.rvalid && axi4.rready) begin + axi4.rvalid <= 0; + if (axi4.rlast) active <= 0; + else begin + beat <= beat + 1; + if (burst == 1) address <= address + (32'd1 << size); + end + end + if (axi4.wvalid && axi4.wready) begin + if (axi4.wdata !== (32'hb0000000 ^ start_address ^ {24'd0, beat})) + $fatal(1, "write data crossed AW ownership"); + if (axi4.wlast || beat == length) write_done <= 1; + else beat <= beat + 1; + end + if (active && writing && write_done && !hold_b_i) begin + axi4.bvalid <= 1; + axi4.bid <= id; + axi4.bresp <= ErrorResponse; + end + if (axi4.bvalid && axi4.bready) begin + axi4.bvalid <= 0; + active <= 0; + write_done <= 0; + end + end + end +endmodule diff --git a/tests/rtl/tiny_r2_p5_fault_formal.sv b/tests/rtl/tiny_r2_p5_fault_formal.sv new file mode 100644 index 00000000..8d07699b --- /dev/null +++ b/tests/rtl/tiny_r2_p5_fault_formal.sv @@ -0,0 +1,55 @@ +module tiny_r2_p5_fault_formal; + (* anyseq *)logic [ 7:0] valid; + (* anyseq *)logic [ 7:0][31:0] addresses; + (* anyseq *)logic [ 1:0] local_write; + (* anyseq *)logic [ 5:0] instruction; + (* anyseq *)logic [ 5:0][ 1:0] responses; + logic [ 3:0] count; + logic [31:0] address; + logic [1:0] owner, response; + logic write_access; + logic found; + integer expected_owner, expected_write; + tiny_fault_collector u_dut ( + .local_valid_i (valid[1:0]), + .local_addr_i (addresses[1:0]), + .local_write_i (local_write), + .external_valid_i (valid[7:2]), + .external_addr_i (addresses[7:2]), + .external_resp_i (responses), + .external_instruction_i(instruction), + .count_o (count), + .addr_o (address), + .master_o (owner), + .write_o (write_access), + .resp_o (response) + ); + always_comb begin + assert (count == $countones(valid)); + found = 0; + expected_owner = 0; + expected_write = 0; + // Enumerate the specified lexicographic order, independently of the + // collector's packed rank and minimum selection implementation. + for (integer m = 0; m < 3; m++) begin + for (integer path = 0; path < 2; path++) begin + for (integer direction = 0; direction < 2; direction++) begin + for (integer event_index = 0; event_index < 8; event_index++) begin + expected_owner = (event_index < 2) ? 0 : ((event_index - 2) / 2); + expected_write=(event_index<2)?integer'(local_write[event_index]):((event_index-2)%2); + if(!found && valid[event_index] && expected_owner==m && expected_write==direction && + ((event_index<2)?(event_index==path): + (((event_index<4)&&instruction[event_index-2])?(path==0):(path==1)))) begin + found = 1; + assert (owner == 2'(m)); + assert (write_access == 1'(direction)); + assert (address == addresses[event_index]); + assert (response == ((event_index < 2) ? 2'b10 : responses[event_index-2])); + end + end + end + end + end + if (valid == 0) assert ({address, owner, write_access, response} == 0); + end +endmodule diff --git a/tests/rtl/tiny_r2_p5_reset_tb.sv b/tests/rtl/tiny_r2_p5_reset_tb.sv new file mode 100644 index 00000000..5290e2ff --- /dev/null +++ b/tests/rtl/tiny_r2_p5_reset_tb.sv @@ -0,0 +1,545 @@ +`include "tiny_topology.svh" +`timescale 1ns / 1ps +module tiny_r2_p5_reset_tb; + logic clk_i = 0, rst_n_i = 0; + always #5 clk_i = !clk_i; + logic target_rst_n[`RETROSOC_TINY__TARGET_COUNT]; + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) masters[3] ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) targets[`RETROSOC_TINY__TARGET_COUNT] ( + .aclk (clk_i), + .aresetn(target_rst_n) + ); + apb4_if apb ( + .pclk (clk_i), + .presetn(target_rst_n[`RETROSOC_TINY__TARGET_XPI]) + ); + logic [`RETROSOC_TINY__TARGET_COUNT-1:0] + hold_address = '0, hold_read = '0, hold_write = '0, hold_b = '0; + integer error_beat[`RETROSOC_TINY__TARGET_COUNT]; + integer + captures[`RETROSOC_TINY__TARGET_COUNT], + addresses[`RETROSOC_TINY__TARGET_COUNT], + writes[`RETROSOC_TINY__TARGET_COUNT]; + logic [5:0] fault_valid, fault_instruction; + logic [ 5:0][31:0] fault_addr; + logic [ 5:0][ 1:0] fault_resp; + logic [ 2:0] owner_idle; + logic [`RETROSOC_TINY__TARGET_COUNT-1:0] target_idle; + logic [63:0] cpu_wait, dma_wait; + logic [2:0] arvalid = 0, awvalid = 0, wvalid = 0, rready = 0, bready = 0; + logic [2:0][31:0] araddr = 0, awaddr = 0, wdata = 0; + logic [2:0][7:0] arlen = 0, awlen = 0; + logic [2:0] wlast = 0; + wire [2:0] arready, awready, wready, rvalid, bvalid, rlast, rid, bid; + wire [2:0][1:0] rresp, bresp; + wire [2:0][31:0] rdata; + integer faults [6]; + logic [5:0][31:0] last_fault_addr; + logic [5:0][ 1:0] last_fault_resp; + integer cases = 0; + logic dma_start = 0, dma_valid = 0, dma_start_ready, dma_ready, dma_busy, dma_done; + logic [31:0] dma_addr = 0, dma_data = 0; + logic [4:0] dma_beats = 1; + logic [1:0] dma_resp; + logic dma_id_error; + + tiny_axi4_fabric u_dut ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .target_rst_n_i (target_rst_n), + .perf_enable_i (1'b0), + .perf_clear_i (1'b0), + .masters (masters), + .targets (targets), + .fault_valid_o (fault_valid), + .fault_addr_o (fault_addr), + .fault_resp_o (fault_resp), + .fault_instruction_o(fault_instruction), + .owner_idle_o (owner_idle), + .target_idle_o (target_idle), + .cpu_wait_o (cpu_wait), + .dma_wait_o (dma_wait) + ); + + // Production master: BREADY cannot rise until its last W handshake. + dma_axi4_master u_dma ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .clear_i (1'b0), + .read_start_valid_i (1'b0), + .read_start_ready_o (), + .read_addr_i (32'd0), + .read_beats_i (5'd1), + .read_fixed_i (1'b0), + .read_busy_o (), + .read_beat_valid_o (), + .read_beat_ready_i (1'b0), + .read_data_o (), + .read_resp_o (), + .read_last_o (), + .read_expected_last_o(), + .read_id_error_o (), + .read_done_o (), + .write_start_valid_i (dma_start), + .write_start_ready_o (dma_start_ready), + .write_addr_i (dma_addr), + .write_beats_i (dma_beats), + .write_fixed_i (1'b0), + .write_busy_o (dma_busy), + .write_data_valid_i (dma_valid), + .write_data_ready_o (dma_ready), + .write_data_i (dma_data), + .write_strb_i (4'hf), + .write_done_o (dma_done), + .write_resp_o (dma_resp), + .write_id_error_o (dma_id_error), + .axi4 (masters[1]) + ); + + for (genvar m = 0; m < 3; m++) begin : gen_master + if (m != 1) begin : gen_manual + assign masters[m].araddr = araddr[m]; + assign masters[m].arlen = arlen[m]; + assign masters[m].arsize = 2; + assign masters[m].arburst = 1; + assign masters[m].arid = 1; + assign masters[m].arvalid = arvalid[m]; + assign masters[m].arlock = 0; + assign masters[m].arprot = 0; + assign masters[m].arcache = 0; + assign masters[m].arqos = 0; + assign masters[m].arregion = 0; + assign masters[m].aruser = 0; + assign masters[m].awaddr = awaddr[m]; + assign masters[m].awlen = awlen[m]; + assign masters[m].awsize = 2; + assign masters[m].awburst = 1; + assign masters[m].awid = 1; + assign masters[m].awvalid = awvalid[m]; + assign masters[m].awlock = 0; + assign masters[m].awprot = 0; + assign masters[m].awcache = 0; + assign masters[m].awqos = 0; + assign masters[m].awregion = 0; + assign masters[m].awuser = 0; + assign masters[m].wdata = wdata[m]; + assign masters[m].wstrb = 15; + assign masters[m].wvalid = wvalid[m]; + assign masters[m].wlast = wlast[m]; + assign masters[m].wuser = 0; + assign masters[m].rready = rready[m]; + assign masters[m].bready = bready[m]; + end + assign arready[m] = masters[m].arready; + assign awready[m] = masters[m].awready; + assign wready[m] = masters[m].wready; + assign rvalid[m] = masters[m].rvalid; + assign bvalid[m] = masters[m].bvalid; + assign rlast[m] = masters[m].rlast; + assign rid[m] = masters[m].rid; + assign bid[m] = masters[m].bid; + assign rresp[m] = masters[m].rresp; + assign bresp[m] = masters[m].bresp; + assign rdata[m] = masters[m].rdata; + + // Independent upstream scoreboards also run in Icarus without SVA. + integer rleft = 0, wleft = 0, bleft = 0; + logic rheld = 0, bheld = 0; + logic [36:0] saved_r; + logic [ 3:0] saved_b; + always @(posedge clk_i) begin + if (!rst_n_i) begin + rleft = 0; + wleft = 0; + bleft = 0; + rheld = 0; + bheld = 0; + end else begin + if (rheld && (!masters[m].rvalid || + {masters[m].rid, masters[m].rdata, masters[m].rresp, + masters[m].rlast, masters[m].ruser} !== saved_r)) + $fatal(1, "held R response changed"); + if (bheld && (!masters[m].bvalid || + {masters[m].bid, masters[m].bresp, masters[m].buser} !== saved_b)) + $fatal(1, "held B response changed"); + if (masters[m].arvalid && masters[m].arready) begin + if (rleft != 0) $fatal(1, "duplicate read admission"); + rleft = integer'(masters[m].arlen) + 1; + end + if (masters[m].awvalid && masters[m].awready) begin + if (bleft != 0) $fatal(1, "duplicate write admission"); + wleft = integer'(masters[m].awlen) + 1; + bleft = 1; + end + if (masters[m].bvalid && (bleft != 1 || wleft != 0)) + $fatal(1, "B before complete W drain or duplicate B"); + if (masters[m].wvalid && masters[m].wready) begin + if (wleft <= 0 || masters[m].wlast != (wleft == 1)) $fatal(1, "W count/last mismatch"); + wleft--; + end + if (masters[m].rvalid) begin + if (rleft <= 0 || masters[m].rlast != (rleft == 1)) $fatal(1, "R count/last mismatch"); + if (masters[m].rready) rleft--; + end + if (masters[m].bvalid && masters[m].bready) bleft--; + rheld = masters[m].rvalid && !masters[m].rready; + bheld = masters[m].bvalid && !masters[m].bready; + saved_r = { + masters[m].rid, masters[m].rdata, masters[m].rresp, masters[m].rlast, masters[m].ruser + }; + saved_b = {masters[m].bid, masters[m].bresp, masters[m].buser}; + end + end + end + + for (genvar t = 0; t < `RETROSOC_TINY__TARGET_COUNT; t++) begin : gen_target + if (t == `RETROSOC_TINY__TARGET_XPI) begin : gen_apb + tiny_axi42apb4 u_bridge ( + .clk_i (clk_i), + .rst_n_i(target_rst_n[t]), + .axi4 (targets[t]), + .apb4 (apb) + ); + end else begin : gen_memory + tiny_r2_p5_reset_target_model u_model ( + .clk_i (clk_i), + .rst_n_i (target_rst_n[t]), + .hold_address_i(hold_address[t]), + .hold_read_i (hold_read[t]), + .hold_write_i (hold_write[t]), + .hold_b_i (hold_b[t]), + .error_beat_i (error_beat[t]), + .axi4 (targets[t]) + ); + end + always @(posedge clk_i) begin + if (rst_n_i) begin + if (targets[t].arvalid && targets[t].arready || targets[t].awvalid && targets[t].awready) + addresses[t]++; + if (targets[t].wvalid && targets[t].wready) writes[t]++; + if (targets[t].rvalid && targets[t].rready) captures[t]++; + if (!target_rst_n[t] && (targets[t].arvalid || targets[t].awvalid || + targets[t].wvalid || targets[t].rready || targets[t].bready)) + $fatal(1, "downstream handshake during target reset"); + end + end + end + assign apb.pready = apb.psel && apb.penable && !hold_read[`RETROSOC_TINY__TARGET_XPI] && !hold_b[`RETROSOC_TINY__TARGET_XPI]; + assign apb.prdata = 32'hcafe_f00d; + assign apb.pslverr = 0; + always @(posedge clk_i) begin + if (rst_n_i) begin + for (integer e = 0; e < 6; e++) begin + if (fault_valid[e]) begin + faults[e]++; + last_fault_addr[e] = fault_addr[e]; + last_fault_resp[e] = fault_resp[e]; + end + end + end + end + + task automatic quiet; + repeat (4) @(negedge clk_i); + if (owner_idle != 7 || target_idle != {`RETROSOC_TINY__TARGET_COUNT{1'b1}} || rvalid != 0 || bvalid != 0) + $fatal(1, "credit/response not retired"); + endtask + task automatic receive_read(input int index, input int beats, input logic [1:0] expected, + input logic [31:0] data); + do @(posedge clk_i); while (!rvalid[0]); + @(negedge clk_i); + repeat (3) @(negedge clk_i); + if (rid[0] !== 1'b1 || rresp[0] !== expected || rdata[0] !== data || + rlast[0] !== (index == beats - 1)) + $fatal( + 1, + "read data/ID/response mismatch index=%0d expected=%0d got=%0d", + index, + expected, + rresp[0] + ); + rready[0] = 1; + @(posedge clk_i); + @(negedge clk_i); + rready[0] = 0; + endtask + task automatic read_case(input logic [31:0] address, input int t, input int beats, + input bit buffered, input bit before_address, input bit inject_reset); + integer first_capture, prefix, before_faults, before_addresses; + logic [ 1:0] expected; + logic [31:0] data; + @(negedge clk_i); + first_capture = captures[t]; + before_faults = faults[0]; + before_addresses = addresses[t]; + hold_address[t] = before_address; + hold_read[t] = !buffered && inject_reset; + araddr[0] = address; + arlen[0] = 8'(beats - 1); + arvalid[0] = 1; + do @(posedge clk_i); while (!arready[0]); + @(negedge clk_i); + arvalid[0] = 0; + if (buffered) begin + wait (rvalid[0]); + repeat (5) @(negedge clk_i); + end else if (!before_address) begin + wait (addresses[t] > before_addresses); + @(negedge clk_i); + end + prefix = captures[t] - first_capture; + if (inject_reset) begin + target_rst_n[t] = 0; + before_addresses = addresses[t]; + repeat (3) @(negedge clk_i); + // Release the downstream reset while the upstream is still stalled. + // The cancelled session must never be issued again after release. + if (t != 0) target_rst_n[t] = 1; + hold_address[t] = 0; + hold_read[t] = 0; + repeat (4) @(negedge clk_i); + if (addresses[t] != before_addresses) $fatal(1, "cancelled AR replayed"); + end + for (integer n = 0; n < beats; n++) begin + expected = inject_reset && n >= prefix ? 3 : (n == error_beat[t] ? 2 : 0); + data = expected == 3 ? 0 : (t == `RETROSOC_TINY__TARGET_XPI ? 32'hcafe_f00d : (address + 32'(4 * n)) ^ 32'h10000000); + receive_read(n, beats, expected, data); + end + target_rst_n[t] = 1; + quiet(); + if (inject_reset && (prefix < beats || (error_beat[t] >= 0 && error_beat[t] < prefix))) begin + expected = (error_beat[t] >= 0 && error_beat[t] < prefix) ? 2 : 3; + if (faults[0] != before_faults + 1 || last_fault_resp[0] != expected || + last_fault_addr[0] != address + 32'(4 * (expected == 2 ? error_beat[t] : prefix))) + $fatal(1, "first failing read beat metadata/count lost"); + end else if (faults[0] != before_faults) $fatal(1, "spurious read fault"); + cases++; + endtask + task automatic send_word(input int n, input int beats); + @(negedge clk_i); + wdata[2] = 32'ha5000000 + 32'(n); + wlast[2] = n == beats - 1; + wvalid[2] = 1; + do @(posedge clk_i); while (!wready[2]); + @(negedge clk_i); + wvalid[2] = 0; + endtask + task automatic write_case(input logic [31:0] address, input int t, input int beats, + input int prefix, input bit buffered, input bit before_address); + integer before_faults, after_writes; + @(negedge clk_i); + before_faults = faults[5]; + hold_address[t] = before_address; + hold_b[t] = !buffered; + awaddr[2] = address; + awlen[2] = 8'(beats - 1); + awvalid[2] = 1; + do @(posedge clk_i); while (!awready[2]); + @(negedge clk_i); + awvalid[2] = 0; + for (integer n = 0; n < prefix; n++) send_word(n, beats); + if (buffered) begin + wait (bvalid[2]); + repeat (3) @(negedge clk_i); + end + target_rst_n[t] = 0; + after_writes = writes[t]; + repeat (3) @(negedge clk_i); + if (t != 0) target_rst_n[t] = 1; + hold_address[t] = 0; + hold_b[t] = 0; + for (integer n = prefix; n < beats; n++) begin + if (bvalid[2]) $fatal(1, "write response before remaining data"); + send_word(n, beats); + end + do @(posedge clk_i); while (!bvalid[2]); + @(negedge clk_i); + repeat (5) @(negedge clk_i); + if (bresp[2] != (buffered ? 0 : 3) || bid[2] !== 1'b1 || writes[t] != after_writes) + $fatal(1, "write response/ID or downstream session replay"); + bready[2] = 1; + @(posedge clk_i); + @(negedge clk_i); + bready[2] = 0; + target_rst_n[t] = 1; + quiet(); + if (faults[5] != before_faults + (buffered ? 0 : 1)) $fatal(1, "write fault count mismatch"); + cases++; + endtask + task automatic dma_case(input logic [31:0] address, input int t, input int beats, + input int prefix); + integer before_faults, after_writes; + @(negedge clk_i); + before_faults = faults[3]; + hold_b[t] = 1; + dma_addr = address; + dma_beats = 5'(beats); + dma_start = 1; + do @(posedge clk_i); while (!dma_start_ready); + @(negedge clk_i); + dma_start = 0; + wait (!owner_idle[1]); + @(negedge clk_i); + for (integer n = 0; n < beats; n++) begin + if (n == prefix) begin + target_rst_n[t] = 0; + after_writes = writes[t]; + repeat (3) @(negedge clk_i); + target_rst_n[t] = 1; + hold_b[t] = 0; + end + dma_data = 32'h5a000000 + 32'(n); + dma_valid = 1; + do @(posedge clk_i); while (!dma_ready); + @(negedge clk_i); + dma_valid = 0; + end + do @(posedge clk_i); while (!dma_done); + if (dma_resp != 3 || dma_id_error) $fatal(1, "production DMA did not terminate with error"); + @(negedge clk_i); + quiet(); + if (dma_busy || writes[t] != after_writes || faults[3] != before_faults + 1) + $fatal(1, "production DMA write drain/credit/count failure"); + cases++; + endtask + + initial begin + for (integer t = 0; t < `RETROSOC_TINY__TARGET_COUNT; t++) begin + target_rst_n[t] = 0; + captures[t] = 0; + addresses[t] = 0; + writes[t] = 0; + error_beat[t] = -1; + end + for (integer e = 0; e < 6; e++) faults[e] = 0; + repeat (4) @(negedge clk_i); + rst_n_i = 1; + for (integer t = 0; t < `RETROSOC_TINY__TARGET_COUNT; t++) target_rst_n[t] = 1; + // SRAM and XPI: address cancellation, missing 16 beats, two captured + // beats under backpressure, and already captured successful completion. + for (integer t = 0; t < 5; t += 4) begin + read_case(t == 0 ? 32'h30000000 : 32'h50000000, t, 16, 0, 1, 1); + read_case(t == 0 ? 32'h30000000 : 32'h50000000, t, 16, 0, 0, 1); + read_case(t == 0 ? 32'h30000000 : 32'h50000000, t, 16, 1, 0, 1); + read_case(t == 0 ? 32'h30000000 : 32'h50000000, t, 1, 1, 0, 1); + read_case(t == 0 ? 32'h30000000 : 32'h50000000, t, 1, 1, 0, 0); + write_case(t == 0 ? 32'h30000000 : 32'h50000000, t, 16, 0, 0, 1); + write_case(t == 0 ? 32'h30000000 : 32'h50000000, t, 16, 2, 0, 0); + write_case(t == 0 ? 32'h30000000 : 32'h50000000, t, 16, 16, 0, 0); + write_case(t == 0 ? 32'h30000000 : 32'h50000000, t, 1, 1, 1, 0); + dma_case(t == 0 ? 32'h30000000 : 32'h50000000, t, 16, 2); + end + error_beat[0] = 1; + read_case(32'h30000000, 0, 16, 1, 0, 1); + error_beat[0] = -1; + // Actual APB bridge, with a withheld peripheral completion. + read_case(32'h10009000, `RETROSOC_TINY__TARGET_XPI, 1, 0, 1, 1); + read_case(32'h10009000, `RETROSOC_TINY__TARGET_XPI, 1, 0, 0, 1); + read_case(32'h10009000, `RETROSOC_TINY__TARGET_XPI, 1, 1, 0, 1); + write_case(32'h10009000, `RETROSOC_TINY__TARGET_XPI, 1, 0, 0, 1); + write_case(32'h10009000, `RETROSOC_TINY__TARGET_XPI, 1, 1, 0, 0); + write_case(32'h10009000, `RETROSOC_TINY__TARGET_XPI, 1, 1, 1, 0); + dma_case(32'h10009000, `RETROSOC_TINY__TARGET_XPI, 1, 0); + $display("P5_RESET cases=%0d production_dma=3 burst_beats=16 held_RB=1 first_fault=1", cases); + $display("SIM_TEST_PASS Tiny P5 target reset isolation"); + $finish; + end + initial begin + repeat (20000) @(posedge clk_i); + $fatal(1, "SIM_TEST_TIMEOUT Tiny P5 target reset isolation cases=%0d", cases); + end +endmodule + +module tiny_r2_p5_reset_target_model ( + input logic clk_i, + rst_n_i, + hold_address_i, + hold_read_i, + hold_write_i, + hold_b_i, + input integer error_beat_i, + axi4_if.slave axi4 +); + logic active = 0, writing = 0, write_done = 0, id = 0; + logic [31:0] address = 0; + logic [7:0] length = 0, beat = 0; + assign axi4.arready = !active && !hold_address_i && rst_n_i; + assign axi4.awready = !active && !hold_address_i && !axi4.arvalid && rst_n_i; + assign axi4.wready = active && writing && !write_done && !hold_write_i && rst_n_i; + assign axi4.ruser = 0; + assign axi4.buser = 0; + always @(posedge clk_i or negedge rst_n_i) begin + if (!rst_n_i) begin + active <= 0; + writing <= 0; + write_done <= 0; + axi4.rvalid <= 0; + axi4.bvalid <= 0; + axi4.rdata <= 0; + axi4.rresp <= 0; + axi4.rlast <= 0; + axi4.rid <= 0; + axi4.bid <= 0; + axi4.bresp <= 0; + end else begin + if (axi4.arvalid && axi4.arready) begin + active <= 1; + writing <= 0; + address <= axi4.araddr; + length <= axi4.arlen; + beat <= 0; + id <= axi4.arid; + end + if (axi4.awvalid && axi4.awready) begin + active <= 1; + writing <= 1; + write_done <= 0; + length <= axi4.awlen; + beat <= 0; + id <= axi4.awid; + end + if (active && !writing && !axi4.rvalid && !hold_read_i) begin + axi4.rvalid <= 1; + axi4.rid <= id; + axi4.rdata <= 32'h10000000 ^ address; + axi4.rresp <= integer'(beat) == error_beat_i ? 2 : 0; + axi4.rlast <= beat == length; + end + if (axi4.rvalid && axi4.rready) begin + axi4.rvalid <= 0; + if (axi4.rlast) active <= 0; + else begin + beat <= beat + 1; + address <= address + 4; + end + end + if (axi4.wvalid && axi4.wready) begin + if (beat == length) write_done <= 1; + else beat <= beat + 1; + end + if (active && writing && write_done && !axi4.bvalid && !hold_b_i) begin + axi4.bvalid <= 1; + axi4.bid <= id; + axi4.bresp <= 0; + end + if (axi4.bvalid && axi4.bready) begin + axi4.bvalid <= 0; + active <= 0; + write_done <= 0; + end + end + end +endmodule diff --git a/tests/rtl/tiny_r2_p5_system_tb.sv b/tests/rtl/tiny_r2_p5_system_tb.sv new file mode 100644 index 00000000..76fb24ad --- /dev/null +++ b/tests/rtl/tiny_r2_p5_system_tb.sv @@ -0,0 +1,192 @@ +`include "tiny_topology.svh" +`timescale 1ns / 1ps +module tiny_r2_p5_system_tb; + logic clk_i = 0, rst_n_i = 0; + always #20.833 clk_i = ~clk_i; + wire s_rst_n = rst_n_i; + logic s_jtag_tck_driver = 0, s_jtag_tms_driver = 1, s_jtag_tdi_driver = 0; + wire s_jtag_tdo; + logic done, pass; + logic [7:0] code; + gpio_if gpio (); + xpi_if xpi (); + integer overlap_cycles = 0, progress = 0, controls = 0, controls_before = 0; + logic hart_reset_observed = 0; + logic [31:0] status; + bit ignored; + `include "tiny_jtag_smoke.svh" +retrosoc_tiny u_dut ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .jtag_tck_i (s_jtag_tck_driver), + .jtag_tms_i (s_jtag_tms_driver), + .jtag_tdi_i (s_jtag_tdi_driver), + .jtag_trst_n_i(rst_n_i), + .jtag_tdo_o (s_jtag_tdo), + .uart0_rx_i (1'b1), + .uart1_rx_i (1'b1), + .gpio (gpio), + .xpi (xpi), + .test_done_o (done), + .test_pass_o (pass), + .test_code_o (code) + ); + assign gpio.di_i = 0; + assign xpi.io_di_i = 0; + always @(posedge clk_i) begin + if (rst_n_i) begin + if(u_dut.u_masters_axi4_if[1].rvalid && u_dut.u_masters_axi4_if[1].rready && + u_dut.u_masters_axi4_if[1].wvalid && u_dut.u_masters_axi4_if[1].wready) + overlap_cycles++; + if((u_dut.u_fabric.s_busy_q[2] || u_dut.u_fabric.s_busy_q[3]) && + (!u_dut.u_fabric.target_idle_o[4] || !u_dut.u_fabric.target_idle_o[`RETROSOC_TINY__TARGET_XPI]) && + u_dut.u_sysctrl_apb4_if.psel && u_dut.u_sysctrl_apb4_if.penable && u_dut.u_sysctrl_apb4_if.pready) + controls++; + if(u_dut.u_cpu_local_if[1].req_valid && u_dut.u_cpu_local_if[1].req_ready && + u_dut.u_cpu_local_if[1].write && u_dut.u_cpu_local_if[1].addr==32'h3001ffe0) begin + progress = integer'(u_dut.u_cpu_local_if[1].wdata); + if (progress == 2) u_dut.u_xpi.hold_mapped = 1; + if (progress == 3) begin + if (!u_dut.u_fabric.s_busy_q[2]) $fatal(1, "abort discarded accepted read"); + u_dut.u_xpi.hold_mapped = 0; + end + end + end + end + initial begin + repeat (6) jtag_tick(1, 0, ignored); + @(negedge clk_i); + rst_n_i = 1; + repeat (12) jtag_tick(1, 0, ignored); + jtag_idle(1); + jtag_ir(5'h11); + wait (progress == 1); + $display("P5_SYSTEM_STAGE independent-progress"); + if (controls == 0 || overlap_cycles == 0 || !u_dut.u_fabric.s_busy_q[2]) + $fatal(1, "CPU/control progress or real DMA overlap absent"); + // Move the held DMA work to an independent APB branch before resetting + // the hart. CPU debug entry may fetch the reset/exception ROM aperture; + // accepted CPU work must be allowed to drain rather than discarded. + @(negedge clk_i); + u_dut.u_xpi.hold_mapped = 0; + wait (u_dut.u_xpi_apb4_if.psel && u_dut.u_xpi_apb4_if.penable && !u_dut.u_xpi_apb4_if.pready); + controls_before = controls; + repeat (64) @(posedge clk_i); + if (controls == controls_before) $fatal(1, "APB FIFO wait blocked independent control"); + jtag_dmi(1, 7'h10, 1, status); + jtag_dmi(1, 7'h10, 3, status); + $display("P5_SYSTEM_STAGE reset-request pending=%b", u_dut.u_cpu.s_reset_pending); + wait (!u_dut.s_core_rst_n); + if (!u_dut.u_fabric.s_busy_q[3] || u_dut.u_fabric.target_idle_o[`RETROSOC_TINY__TARGET_XPI]) + $fatal(1, "hart reset cancelled unrelated DMA"); + hart_reset_observed = 1; + $display("P5_SYSTEM_STAGE hart-reset"); + jtag_dmi(1, 7'h10, 1, status); + wait (u_dut.s_core_rst_n); + @(negedge clk_i); + u_dut.u_xpi.hold_apb = 0; + $display("P5_SYSTEM_STAGE released"); + wait (done); + if (!pass || !hart_reset_observed || progress != 3) + $fatal(1, "SIM_TEST_FAIL P5 CPU/DMA code=%0d", code); + repeat (20) @(posedge clk_i); + $display( + "P5_SYSTEM dma_overlap_cycles=%0d independent_controls=%0d hart_reset=1 abort_drained=1", + overlap_cycles, controls); + $display("SIM_TEST_PASS Tiny P5 system"); + $finish; + end + initial begin + repeat (500000) @(posedge clk_i); + $display( + "P5_DIAG core_rst=%b pending=%b halted=%b idle=%b quiesce=%b hold=%b busy=%b I=%h D=%h", + u_dut.s_core_rst_n, u_dut.u_cpu.s_reset_pending, u_dut.s_debug_halted, + u_dut.s_cpu_bus_idle, u_dut.s_cpu_quiesce, u_dut.u_xpi.hold_mapped, + u_dut.u_fabric.s_busy_q, u_dut.u_cpu_ahbl_if[0].haddr, u_dut.u_cpu_ahbl_if[1].haddr); + $fatal(1, "SIM_TEST_TIMEOUT Tiny P5 system progress=%0d", progress); + end +endmodule + +// Verification-only XPI boundary substitute. The production Tiny integration, +// CPU, DMA, SRAM, APB branches and SYSCTRL remain real. This does not qualify an +// XPI device or replace the pin-level NOR regression. The small reset ROM is +// only a fixture boot source, never a product Boot ROM. +module apb4_xpi ( + output logic lifecycle_idle_o, + input logic clk_i, + rst_n_i, + dma_xfer_done_i, + output logic dma_tx_stall_o, + dma_rx_stall_o, + irq_o, + apb4_if.slave apb4, + axi4_if.slave mem_axi4, + xpi_if.dut xpi +); + logic [7:0] rom [0:4095]; + string firmware; + logic active = 0, id = 0, hold_mapped = 1, hold_apb = 1; + logic [31:0] captured_write = 0; + logic [31:0] address = 0; + logic [7:0] beat = 0, length = 0; + assign lifecycle_idle_o = !active && !(apb4.psel && apb4.penable); + initial begin + if (!$value$plusargs("firmware=%s", firmware)) $fatal(1, "firmware required"); + $readmemh(firmware, rom); + end + assign dma_tx_stall_o = 0; + assign dma_rx_stall_o = 0; + assign irq_o = 0; + assign xpi.sck_o = 0; + assign xpi.nss_o = '1; + assign xpi.io_oe_o = 0; + assign xpi.io_do_o = 0; + assign apb4.pready=apb4.psel && apb4.penable && + !(apb4.pwrite && apb4.paddr[11:0]==12'h040 && hold_apb); + assign apb4.pslverr = 0; + assign apb4.prdata = (apb4.paddr[11:0] == 12'h044) ? captured_write : 32'd0; + assign mem_axi4.arready = !active; + assign mem_axi4.awready = 0; + assign mem_axi4.wready = 0; + assign mem_axi4.bvalid = 0; + assign mem_axi4.bid = 0; + assign mem_axi4.bresp = 0; + assign mem_axi4.buser = 0; + assign mem_axi4.rresp = 0; + assign mem_axi4.ruser = 0; + always @(posedge clk_i) begin + if (rst_n_i && apb4.psel && apb4.penable && apb4.pready && apb4.pwrite) + captured_write <= apb4.pwdata; + if (!rst_n_i) begin + active <= 0; + mem_axi4.rvalid <= 0; + mem_axi4.rdata <= 0; + mem_axi4.rid <= 0; + mem_axi4.rlast <= 0; + end else begin + if (mem_axi4.arvalid && mem_axi4.arready) begin + active <= 1; + address <= mem_axi4.araddr; + id <= mem_axi4.arid; + beat <= 0; + length <= mem_axi4.arlen; + end + if (active && !mem_axi4.rvalid && !(hold_mapped && address >= 32'h50000000)) begin + mem_axi4.rvalid <= 1; + mem_axi4.rid <= id; + mem_axi4.rlast <= beat == length; + if (address < 4096) + mem_axi4.rdata <= {rom[address+3], rom[address+2], rom[address+1], rom[address]}; + else mem_axi4.rdata <= 32'h10000000 ^ address; + end + if (mem_axi4.rvalid && mem_axi4.rready) begin + mem_axi4.rvalid <= 0; + if (mem_axi4.rlast) active <= 0; + else begin + address <= address + 4; + beat <= beat + 1; + end + end + end + end +endmodule diff --git a/tests/rtl/tiny_r2_p6_bridge_tb.sv b/tests/rtl/tiny_r2_p6_bridge_tb.sv new file mode 100644 index 00000000..8797d53a --- /dev/null +++ b/tests/rtl/tiny_r2_p6_bridge_tb.sv @@ -0,0 +1,87 @@ +`timescale 1ns / 1ps +module tiny_r2_p6_bridge_tb; + logic clk_i = 0, rst_n_i = 0; + always #5 clk_i = !clk_i; + axi4_if #( + .ADDR_WIDTH(32), + .DATA_WIDTH(32), + .ID_WIDTH (1), + .USER_WIDTH(1) + ) axi ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + apb4_if apb ( + .pclk (clk_i), + .presetn(rst_n_i) + ); + tiny_axi42apb4 u_dut ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .axi4 (axi), + .apb4 (apb) + ); + assign apb.pready = apb.psel && apb.penable; + assign apb.prdata = 32'h13579bdf; + // A strict APB4 receiver rejects nonzero read strobes independently of PWRITE. + assign apb.pslverr = apb.psel && apb.penable && !apb.pwrite && apb.pstrb != 0; + task automatic write_read(input logic [3:0] mask); + @(negedge clk_i); + axi.awvalid = 1; + do @(posedge clk_i); while (!axi.awready); + @(negedge clk_i); + axi.awvalid = 0; + axi.wvalid = 1; + axi.wstrb = mask; + do @(posedge clk_i); while (!axi.wready); + @(negedge clk_i); + axi.wvalid = 0; + do @(posedge clk_i); while (!apb.penable); + if (!apb.pwrite || apb.pstrb !== mask) $fatal(1, "SIM_TEST_FAIL lost write byte lanes"); + do @(posedge clk_i); while (!axi.bvalid); + if (axi.bresp != 0) $fatal(1, "SIM_TEST_FAIL APB write response"); + @(negedge clk_i); + axi.arvalid = 1; + do @(posedge clk_i); while (!axi.arready); + @(negedge clk_i); + axi.arvalid = 0; + do @(posedge clk_i); while (!axi.rvalid); + if (axi.rresp != 0 || axi.rdata !== 32'h13579bdf || !axi.rlast) + $fatal(1, "SIM_TEST_FAIL stale write strobes corrupted APB read"); + @(negedge clk_i); + endtask + initial begin + axi.awvalid = 0; + axi.awaddr = 'h10007000; + axi.awlen = 0; + axi.awsize = 2; + axi.awburst = 1; + axi.awlock = 0; + axi.awid = 0; + axi.wvalid = 0; + axi.wdata = 'h12345678; + axi.wstrb = 0; + axi.wlast = 1; + axi.bready = 1; + axi.arvalid = 0; + axi.araddr = 'h10007008; + axi.arlen = 0; + axi.arsize = 2; + axi.arburst = 1; + axi.arlock = 0; + axi.arid = 0; + axi.rready = 1; + repeat (4) @(negedge clk_i); + rst_n_i = 1; + write_read(15); + write_read(1); + write_read(3); + write_read(10); + $display("SIM_TEST_PASS Tiny P6 APB read strobes"); + $finish; + end + initial begin + #100000; + $fatal(1, "SIM_TEST_TIMEOUT P6 APB strobes"); + end +endmodule diff --git a/tests/rtl/tiny_r2_p6_shared_tb.sv b/tests/rtl/tiny_r2_p6_shared_tb.sv new file mode 100644 index 00000000..4fbbf809 --- /dev/null +++ b/tests/rtl/tiny_r2_p6_shared_tb.sv @@ -0,0 +1,153 @@ +`timescale 1ns / 1ps +module tiny_r2_p6_shared_tb; + logic clk_i = 0, clk_aud_i = 0, rst_n_i = 0, rst_aud_n_i = 0; + always #20.833333 clk_i = !clk_i; + always #27.126736111 clk_aud_i = !clk_aud_i; + apb4_if apb4 ( + .pclk (clk_i), + .presetn(rst_n_i) + ); + axi4_stream_if #( + .DATA_WIDTH(32) + ) tx ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + axi4_stream_if #( + .DATA_WIDTH(32) + ) rx ( + .aclk (clk_i), + .aresetn(rst_n_i) + ); + i2s_if i2s (); + logic streaming = 0, wide = 0; + integer sent = 0, received = 0, matched = 0, examined = 0; + logic found = 0; + logic [31:0] value; + assign i2s.adcdat_i = i2s.dacdat_o; + assign tx.tvalid = streaming; + assign tx.tdata = wide ? 32'h120000 + 32'(sent) : + {16'(16'h2001 + 2*sent), 16'(16'h2000 + 2*sent)}; + assign tx.tstrb = 4'hf; + assign tx.tkeep = 4'hf; + assign tx.tlast = 0; + assign tx.tuser = 0; + assign tx.tid = 0; + assign tx.tdest = 0; + assign rx.tready = 1; + apb4_i2s u_dut ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .clk_aud_i (clk_aud_i), + .rst_aud_n_i (rst_aud_n_i), + .dma_tx_stall_o (), + .dma_rx_stall_o (), + .tx_underrun_evt_o(), + .rx_overrun_evt_o (), + .rx_flush_busy_o (), + .apb4 (apb4), + .tx_axis (tx), + .rx_axis (rx), + .i2s (i2s) + ); + task automatic write(input logic [31:0] address, input logic [31:0] data); + @(negedge clk_i); + apb4.paddr = address; + apb4.pwdata = data; + apb4.pstrb = 15; + apb4.pwrite = 1; + apb4.psel = 1; + apb4.penable = 0; + @(negedge clk_i); + apb4.penable = 1; + do @(posedge clk_i); while (!apb4.pready); + if (apb4.pslverr) $fatal(1, "SIM_TEST_FAIL P6 I2S APB write %h", address); + @(negedge clk_i); + apb4.psel = 0; + apb4.penable = 0; + endtask + task automatic sample (input logic [31:0] observed); + logic [31:0] first; + first = wide ? 32'h120020 : 32'h2020; + if (!found && observed == first) found = 1; + if (found && matched < 512) begin + if (observed !== first + 32'(matched)) + $fatal( + 1, + "SIM_TEST_FAIL P6 I2S sample bits=%0d index=%0d got=%h expected=%h", + wide ? 24 : 16, + matched, + observed, + first + 32'(matched) + ); + matched++; + end + examined++; + if (!found && examined > 40) + $fatal( + 1, + "SIM_TEST_FAIL P6 I2S startup/payload missing bits=%0d last=%h", + wide ? 24 : 16, + observed + ); + endtask + always @(posedge clk_i) begin + if (rst_n_i) begin + if (tx.tvalid && tx.tready) sent <= sent + 1; + if (rx.tvalid && rx.tready) begin + if (rx.tkeep !== 4'hf || rx.tstrb !== 4'hf) + $fatal(1, "SIM_TEST_FAIL P6 I2S receive qualifiers"); + if (wide) sample (rx.tdata); + else begin + sample ({16'd0, rx.tdata[15:0]}); + sample ({16'd0, rx.tdata[31:16]}); + end + received++; + end + end + end + initial begin + apb4.psel = 0; + apb4.penable = 0; + apb4.pwrite = 0; + apb4.paddr = 0; + apb4.pwdata = 0; + apb4.pstrb = 0; + apb4.pprot = 0; + for (integer mode = 0; mode < 2; mode++) begin + @(negedge clk_i); + rst_n_i = 0; + rst_aud_n_i = 0; + streaming = 0; + sent = 0; + received = 0; + matched = 0; + examined = 0; + found = 0; + wide = mode != 0; + repeat (8) @(negedge clk_i); + rst_n_i = 1; + repeat (8) @(negedge clk_aud_i); + rst_aud_n_i = 1; + repeat (32) @(negedge clk_i); + write(4, 3); + repeat (256) @(negedge clk_i); + if (u_dut.s_tx_flush_busy || u_dut.s_rx_flush_busy) + $fatal(1, "SIM_TEST_FAIL P6 initial disabled audio flush"); + write('h10, wide ? 2 : 0); + write('h18, 'h00200060); + write('hc, 3); + streaming = 1; + wait (sent >= 96); + write(0, 7); + wait (matched == 512); + $display("P6_I2S bits=%0d samples=%0d", wide ? 24 : 16, matched); + end + $display("SIM_TEST_PASS Tiny P6 I2S payload"); + $finish; + end + initial begin + #30000000; + $fatal(1, "SIM_TEST_TIMEOUT P6 I2S payload"); + end +endmodule diff --git a/tests/rtl/tiny_sysctrl_tb.sv b/tests/rtl/tiny_sysctrl_tb.sv index 39433bdd..e43b5b1b 100644 --- a/tests/rtl/tiny_sysctrl_tb.sv +++ b/tests/rtl/tiny_sysctrl_tb.sv @@ -10,6 +10,8 @@ module tiny_sysctrl_tb; .presetn(rst_n_i) ); logic fault_valid = 0; + logic [ 3:0] fault_count = 1; + logic [ 1:0] fault_master = 1; logic [31:0] fault_addr = 32'h38000000; logic [63:0] cpu_wait = 64'h123456789abcdef0; logic [63:0] dma_wait = 64'h1122334455667788; @@ -17,29 +19,166 @@ module tiny_sysctrl_tb; logic perf_enable, perf_clear, done, pass; logic [ 7:0] code; logic [31:0] value; + logic [31:0] xin_hz, ref_hz, sys_hz, mem_hz, pclk_hz, clint_hz; + logic [1:0] rcu_current; + logic rcu_busy; + logic clock_apply; + logic [1:0] clock_profile; + logic clock_done = 0; + logic [1:0] clock_current = 0; + logic clock_apply_seen = 0; + logic [4:0] rcu_fault_event = 0; + logic warm_reset = 0, rcu_irq; + always @(posedge clk_i) if (clock_apply) clock_apply_seen <= 1; tiny_sysctrl u_dut ( - .clk_i (clk_i), - .rst_n_i (rst_n_i), - .apb4 (apb4), - .fault_valid_i (fault_valid), - .fault_addr_i (fault_addr), - .fault_write_i (1'b1), - .fault_master_i(1'b1), - .fault_resp_i (2'b11), - .cpu_wait_i (cpu_wait), - .dma_wait_i (dma_wait), - .rtc_wake_i (wake), - .perf_enable_o (perf_enable), - .perf_clear_o (perf_clear), - .test_done_o (done), - .test_pass_o (pass), - .test_code_o (code) + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .clk_ref_i (clk_i), + .rst_ref_n_i (rst_n_i), + .rcu_link_ready_o(), + .apb4 (apb4), + .fault_valid_i (fault_valid), + .fault_count_i (fault_count), + .fault_addr_i (fault_addr), + .fault_write_i (1'b1), + .fault_master_i (fault_master), + .fault_resp_i (2'b11), + .cpu_wait_i (cpu_wait), + .dma_wait_i (dma_wait), + .rtc_wake_i (wake), + .clock_done_i (clock_done), + .clock_fault_i (rcu_fault_event), + .clock_current_i (clock_current), + .periph_done_i (1'b0), + .periph_fault_i (5'd0), + .gate_actual_i (14'd0), + .reset_done_i (14'd0), + .periph_apply_o (), + .periph_reset_o (), + .periph_request_o(), + .clkout_control_o(), + .clkout_idle_i (1'b1), + .pll_locked_i (1'b0), + .pll_selected_i (1'b0), + .warm_reset_i (warm_reset), + .watchdog_reset_i(1'b0), + .hart_reset_i (1'b0), + .system_reset_o (), + .rcu_irq_o (rcu_irq), + .clock_timeout_o (), + .perf_enable_o (perf_enable), + .perf_clear_o (perf_clear), + .test_done_o (done), + .test_pass_o (pass), + .test_code_o (code), + .xin_hz_o (xin_hz), + .ref_hz_o (ref_hz), + .sys_hz_o (sys_hz), + .mem_hz_o (mem_hz), + .pclk_hz_o (pclk_hz), + .clint_hz_o (clint_hz), + .rcu_current_o (rcu_current), + .rcu_busy_o (rcu_busy), + .clock_apply_o (clock_apply), + .clock_profile_o (clock_profile) ); `include "apb4_bfm.svh" initial begin apb4_idle(); repeat (5) @(negedge clk_i); rst_n_i = 1; + repeat (40) @(negedge clk_i); + apb4_read(32'h100, 0, value); + if (value != 32'h54524355) $fatal(1, "RCU identity mismatch"); + apb4_read(32'h104, 0, value); + if (value != 32'h00010000) $fatal(1, "RCU version mismatch"); + apb4_read(32'h108, 0, value); + if (value != 32'h00000203) $fatal(1, "RCU capability mismatch"); + apb4_read(32'h158, 0, value); + if (value != 0) $fatal(1, "unsupported target capability was advertised"); + apb4_read(32'h148, 0, value); + if (value != 24_000_000 || sys_hz != 24_000_000 || mem_hz != 24_000_000 || + pclk_hz != 24_000_000 || clint_hz != 1_000_000) + $fatal(1, "SAFE24 rates mismatch"); + apb4_write(32'h10c, 2, 4'hf, 0); + apb4_write(32'h114, 1, 4'hf, 1); + apb4_read(32'h134, 0, value); + if (!value[0] || rcu_current != 0) $fatal(1, "unsupported PLL request was accepted"); + apb4_write(32'h134, 1, 4'hf, 0); + apb4_write(32'h10c, 1, 4'hf, 0); + apb4_write(32'h114, 1, 4'hf, 0); + if (!clock_apply_seen || clock_profile != 1) $fatal(1, "clock apply handshake missing"); + // APB acknowledgement is not clock completion. BUSY/current persist + // until the actual backend supplies its committed result. + apb4_read(32'h118, 0, value); + if (!value[0] || rcu_current != 0) $fatal(1, "RCU completed before backend"); + apb4_write(32'h10c, 0, 4'hf, 1); + apb4_write(32'h11c, 1, 4'hf, 1); + apb4_write(32'h120, 0, 4'hf, 1); + apb4_write(32'h128, 0, 4'hf, 1); + apb4_write(32'h114, 8, 4'hf, 1); + apb4_read(32'h10c, 0, value); + if (value != 1) $fatal(1, "BUSY write changed staged profile"); + apb4_read(32'h11c, 0, value); + if (value != 240000) $fatal(1, "BUSY write changed timeout"); + apb4_write(32'h134, 1, 4'hf, 0); + @(negedge clk_i); + clock_current = 1; + clock_done = 1; + @(negedge clk_i); + clock_done = 0; + apb4_read(32'h110, 0, value); + if (value != 1 || rcu_current != 1 || sys_hz != 24_000_000) + $fatal(1, "external profile commit mismatch"); + apb4_read(32'h118, 0, value); + if (value[1] || value[4]) begin + $display("RCU status=%h fault=%h current=%h", value, u_dut.s_rcu_fault_q, rcu_current); + $fatal(1, "RCU status mismatch"); + end + apb4_write(32'h120, 1, 4'hf, 1); + apb4_write(32'h114, 2, 4'hf, 1); + apb4_read(32'h134, 0, value); + if (!value[0]) $fatal(1, "unsupported gate request was accepted"); + apb4_write(32'h134, 1, 4'hf, 0); + apb4_write(32'h10c, 4, 4'hf, 1); + apb4_write(32'h11c, 0, 4'hf, 1); + apb4_write(32'h13c, 4, 4'hf, 1); + apb4_write(32'h130, 64, 4'hf, 1); + apb4_write(32'h134, 32, 4'hf, 1); + apb4_write(32'h138, 4, 4'hf, 1); + apb4_write(32'h114, 3, 4'hf, 1); + apb4_write(32'h15c, 1, 4'hf, 1); + apb4_write(32'h15c, 32'h105, 4'hf, 1); + apb4_write(32'h15c, 32'h101, 4'hf, 0); + apb4_write(32'h15c, 32'h201, 4'hf, 1); + apb4_write(32'h114, 1, 4'hf, 1); + apb4_read(32'h15c, 0, value); + if (value != 32'h101) $fatal(1, "live CLKOUT change was accepted"); + apb4_write(32'h15c, 0, 4'hf, 0); + apb4_write(32'h134, 31, 4'hf, 0); + apb4_write(32'h138, 3, 4'hf, 0); + apb4_write(32'h13c, 3, 4'hf, 0); + if (rcu_irq) $fatal(1, "cleared RCU IRQ remains asserted"); + @(negedge clk_i); + rcu_fault_event = 8; + apb4_write(32'h134, 8, 4'hf, 0); + apb4_write(32'h138, 2, 4'hf, 0); + apb4_write(32'h130, 8, 4'hf, 0); + @(negedge clk_i); + rcu_fault_event = 0; + apb4_read(32'h134, 0, value); + if (value != 8 || !rcu_irq) $fatal(1, "RCU W1C lost simultaneous fault/IRQ"); + apb4_read(32'h130, 0, value); + if (!value[3]) $fatal(1, "RCU W1C lost simultaneous reset cause"); + @(negedge clk_i); + warm_reset = 1; + @(negedge clk_i); + warm_reset = 0; + apb4_read(32'h138, 0, value); + if (value != 2 || rcu_irq) $fatal(1, "warm reset IRQ retention/enable mismatch"); + apb4_read(32'h134, 0, value); + if (value != 8 || rcu_current != 0) $fatal(1, "warm reset fault/profile mismatch"); + apb4_read(32'h114, 1, value); apb4_write(`APB4_SYSCTRL__HP_CTRL, 1, 4'hf, 1); apb4_write(`APB4_SYSCTRL__PLL_CMD, 1, 4'hf, 1); apb4_read(32'hfff, 1, value); @@ -73,6 +212,43 @@ module tiny_sysctrl_tb; apb4_write(`APB4_SYSCTRL__FAULT_STATUS, 1, 4'hf, 0); apb4_read(`APB4_SYSCTRL__FAULT_STATUS, 0, value); if (value[0]) $fatal(1, "fault W1C failed"); + // Multiple terminal events count independently. A hardware event on the + // clear edge wins and records the new first owner without clearing count. + fork + apb4_write(`APB4_SYSCTRL__FAULT_STATUS, 1, 4'hf, 0); + begin + wait (apb4.psel && apb4.penable && apb4.pwrite); + fault_count = 5; + fault_master = 2; + fault_addr = 32'h30018004; + fault_valid = 1; + @(posedge clk_i); + @(negedge clk_i); + fault_valid = 0; + end + join + apb4_read(`APB4_SYSCTRL__FAULT_COUNT, 0, value); + if (value != 7) $fatal(1, "concurrent fault count lost"); + apb4_read(`APB4_SYSCTRL__FAULT_MASTER, 0, value); + if (value != 2) $fatal(1, "third owner truncated"); + apb4_read(`APB4_SYSCTRL__FAULT_DETAIL, 0, value); + if (value != 3) $fatal(1, "raw AXI response changed"); + apb4_read(`APB4_SYSCTRL__FAULT_STATUS, 0, value); + if (!value[0]) $fatal(1, "W1C defeated simultaneous hardware event"); + // Seed only the counter corner, then exercise the actual wide adder and + // register update. Billions of event pulses are not useful simulation. + @(negedge clk_i); + force u_dut.u_fault_count_reg.dat_o = 32'hfffffffc; + fault_count = 8; + fault_valid = 1; + @(posedge clk_i); + @(negedge clk_i); + release u_dut.u_fault_count_reg.dat_o; + @(posedge clk_i); + @(negedge clk_i); + fault_valid = 0; + apb4_read(`APB4_SYSCTRL__FAULT_COUNT, 0, value); + if (value != 32'hffffffff) $fatal(1, "multi-event saturation wrapped"); @(negedge clk_i); wake = 1; @(negedge clk_i); diff --git a/tests/rtl/tiny_watchdog_reset_tb.sv b/tests/rtl/tiny_watchdog_reset_tb.sv new file mode 100644 index 00000000..828a3e97 --- /dev/null +++ b/tests/rtl/tiny_watchdog_reset_tb.sv @@ -0,0 +1,188 @@ +`timescale 1ns / 1ps +module tiny_watchdog_reset_tb; + import wdg_pkg::*; + logic xin = 0, reset_n = 1; + always #20.833 xin = !xin; + logic ref_clk, sys_clk, pclk, backend_reset_n, por_n, system_n, ready; + logic [1:0] leaf_n; + logic wdg_reset_n, request; + logic [ 1:0] current_profile; + logic [ 4:0] clock_fault; + logic clock_done; + integer reset_events = 0; + logic [31:0] value; + wire clk_i = sys_clk; + apb4_if apb4 ( + .pclk (sys_clk), + .presetn(leaf_n[0]) + ); + apb4_if wdg_source ( + .pclk (sys_clk), + .presetn(leaf_n[0]) + ); + apb4_if wdg_apb ( + .pclk (pclk), + .presetn(leaf_n[1]) + ); + wdg_if wdg ( + .wdg_clk_i (ref_clk), + .wdg_rst_n_i(wdg_reset_n) + ); + assign wdg.debug_halted_i = 1'b0; + assign request = wdg.reset_req_o; + tiny_apb_async_bridge u_wdg_cdc ( + .src_clk_i (sys_clk), + .src_rst_n_i(leaf_n[0]), + .dst_clk_i (pclk), + .dst_rst_n_i(leaf_n[1]), + .disabled_i (1'b0), + .src_apb4 (wdg_source), + .dst_apb4 (wdg_apb) + ); + tiny_clock_reset_subsystem u_backend ( + .ref_clk_i (xin), + .rst_n_i (reset_n), + .profile_i (2'd0), + .apply_i (1'b0), + .idle_i (1'b1), + .clients_idle_i (1'b1), + .warm_reset_i (request), + .system_request_i (1'b0), + .command_ack_i (1'b1), + .timeout_i (32'd240000), + .current_profile_o(current_profile), + .ref_clk_o (ref_clk), + .sys_clk_o (sys_clk), + .pclk_o (pclk), + .system_reset_n_o (backend_reset_n), + .fault_code_o (clock_fault), + .done_o (clock_done) + ); + tiny_reset_tree #( + .LeafCount (2), + .SplitDomains(1) + ) u_tree ( + .clk_i (sys_clk), + .por_clk_i (ref_clk), + .leaf_clk_i ({pclk, sys_clk}), + .backend_reset_n_i (backend_reset_n), + .rst_n_i (reset_n), + .watchdog_reset_req_i(request), + .por_rst_n_o (por_n), + .system_rst_n_o (system_n), + .leaf_rst_n_o (leaf_n), + .cpu_ready_rst_n_o (ready) + ); + // Same functional-reset dependency as the production Tiny integration. + rst_sync #( + .STAGE(5) + ) u_wdg_functional_reset ( + .clk_i (ref_clk), + .rst_n_i(por_n && backend_reset_n), + .rst_n_o(wdg_reset_n) + ); + apb4_wdg #( + .WDG_CLOCK_HZ(24000000) + ) u_wdg ( + .apb4(wdg_apb), + .wdg (wdg) + ); + tiny_sysctrl u_rcu ( + .clk_i (sys_clk), + .rst_n_i (leaf_n[0]), + .clk_ref_i (ref_clk), + .rst_ref_n_i (por_n), + .apb4 (apb4), + .fault_valid_i (1'b0), + .fault_count_i (4'd0), + .fault_addr_i (32'd0), + .fault_write_i (1'b0), + .fault_master_i (2'd0), + .fault_resp_i (2'd0), + .cpu_wait_i (64'd0), + .dma_wait_i (64'd0), + .rtc_wake_i (1'b0), + .clock_done_i (clock_done), + .clock_fault_i (clock_fault), + .clock_current_i (current_profile), + .pll_locked_i (1'b0), + .pll_selected_i (1'b0), + .warm_reset_i (!backend_reset_n), + .watchdog_reset_i(request), + .hart_reset_i (1'b0), + .periph_done_i (1'b0), + .periph_fault_i (5'd0), + .gate_actual_i (14'd0), + .reset_done_i (14'd0), + .clkout_idle_i (1'b1) + ); + always @(posedge request) if (reset_n) reset_events++; + task automatic watchdog_access(input logic [31:0] address, input logic write_access, + input logic [31:0] data, output logic [31:0] observed); + integer budget; + @(negedge clk_i); + wdg_source.paddr = address; + wdg_source.pwrite = write_access; + wdg_source.pwdata = data; + wdg_source.pstrb = write_access ? 4'hf : 4'h0; + wdg_source.psel = 1; + @(negedge clk_i); + wdg_source.penable = 1; + budget = 0; + while (wdg_source.pready !== 1'b1 && budget < 512) begin + @(negedge clk_i); + budget++; + end + if (budget == 512 || wdg_source.pslverr !== 0) $fatal(1, "watchdog APB failed"); + observed = wdg_source.prdata; + @(negedge clk_i); + wdg_source.psel = 0; + wdg_source.penable = 0; + endtask + `include "apb4_bfm.svh" + initial begin + wdg_source.paddr = 0; + wdg_source.pwrite = 0; + wdg_source.pwdata = 0; + wdg_source.pstrb = 0; + wdg_source.psel = 0; + wdg_source.penable = 0; + wdg_source.pprot = 0; + apb4_idle(); + #1 reset_n = 0; + repeat (20) @(negedge xin); + reset_n = 1; + wait (ready); + repeat (30) @(negedge ref_clk); + for (integer iteration = 1; iteration <= 2; iteration++) begin + watchdog_access(32'h008, 1, 1, value); + watchdog_access(32'h00c, 1, 64, value); + watchdog_access(32'h018, 1, 4, value); + watchdog_access(32'h000, 1, 1, value); + wait (request); + wait (!backend_reset_n); + #1; + if (system_n !== 0 || leaf_n !== 0 || wdg_reset_n !== 0) + $fatal(1, "watchdog did not reset all functional endpoints"); + wait (backend_reset_n); + wait (ready && wdg_reset_n); + repeat (30) @(negedge ref_clk); + apb4_read(32'h130, 0, value); + if (value !== 32'd5 || request || reset_events != iteration || + u_wdg.u_wdg_core.s_reset_count_q !== 0 || current_profile !== 0) + $fatal(1, "watchdog warm-reset cause/functional state mismatch cause=%h", value); + watchdog_access(32'h000, 1, 2, value); + repeat (30) @(negedge clk_i); + watchdog_access(32'h024, 0, 0, value); + if ((value & 3) !== 1) $fatal(1, "post-reset watchdog snapshot did not complete"); + watchdog_access(32'h040, 0, 0, value); + if (value !== 0) $fatal(1, "watchdog functional reset counter did not clear"); + end + $display("SIM_TEST_PASS Tiny watchdog reset cause retained and functional state reset twice"); + $finish; + end + initial begin + #200000; + $fatal(1, "SIM_TEST_TIMEOUT Tiny watchdog reset"); + end +endmodule diff --git a/tests/test_commercial_flow.py b/tests/test_commercial_flow.py index d001e7be..ce093a44 100644 --- a/tests/test_commercial_flow.py +++ b/tests/test_commercial_flow.py @@ -421,7 +421,7 @@ def test_commercial_timing_contract_supports_tiny(tmp_path: Path) -> None: check=True, ) assert result.stdout.splitlines() == [ - "jtag,system", + "aon,audio,jtag,memory,peripheral,system", "ext_rst_n_i_pad,jtag_trst_n_i_pad", ] assert "observation {u_clock_buffer/clk_o}" in output.read_text( @@ -612,6 +612,7 @@ set ::created_clocks_named {} set ::generated_clocks {} set ::clock_groups {} set ::false_paths 0 +set ::case_selections {} proc get_ports {args} { return [lindex $args end] } proc get_pins {args} { return [list pin_[string map {/ _} [lindex $args end]]] } @@ -629,6 +630,7 @@ proc set_clock_groups {args} { lappend ::clock_groups $args } proc set_clock_uncertainty {args} {} proc set_clock_transition {args} {} proc set_false_path {args} { incr ::false_paths } +proc set_case_analysis {value pins} { lappend ::case_selections [list $value $pins] } proc all_inputs {} { return {} } proc all_outputs {} { return {} } proc current_design {args} { return {} } @@ -642,6 +644,7 @@ puts "masters=$::created_clocks_named" puts "generated=$::generated_clocks" puts "groups=[llength $::clock_groups]" puts "false_paths=$::false_paths" +puts "case_selections=$::case_selections" flow::require_commercial_clock_inventory puts "inventory-ok" """ @@ -660,8 +663,8 @@ CONSTRAINT_SMOKE_CASES = { "TINY": { "domains": "rtl/tiny/integration/clock_reset_domains.json", "pin_map": "rtl/tiny/pin_map/pin_map.json", - "masters": "clk_system clk_jtag", - "generated": "", + "masters": "clk_aon clk_jtag clk_audio", + "generated": "clk_system clk_memory clk_peripheral clk_sys_div2 clk_sys_div4 clk_clkout_source", "groups": "1", }, } @@ -713,6 +716,11 @@ def test_constraint_stack_models_product_clocks(tmp_path: Path, soc: str) -> Non assert lines["generated"].strip("-") == case["generated"] assert lines["groups"] == case["groups"] assert "inventory-ok" in result.stdout + if soc == "TINY": + for selection in ("u_sys_mux", "u_mem_mux", "u_pclk_div2_mux", "u_pclk_mux", "u_PLL_TOP_EN"): + assert selection in lines["case_selections"] + assert lines["case_selections"].count("{0 ") == 11 + assert lines["case_selections"].count("{1 ") == 4 def test_commercial_make_graph_covers_both_products(tmp_path: Path) -> None: diff --git a/tests/test_librelane_tiny_flow.py b/tests/test_librelane_tiny_flow.py index fd2da88f..600c4259 100644 --- a/tests/test_librelane_tiny_flow.py +++ b/tests/test_librelane_tiny_flow.py @@ -107,10 +107,10 @@ def test_chip_config_covers_every_onchip_sram_bank(tmp_path: Path) -> None: macro = config["MACROS"]["RM_IHPSG13_1P_1024x32_c2_bm_bist"] assert len(macro["instances"]) == 32 assert ( - "u_soc.u_sram.gen_memory.gen_bank[0].u_ram.u_mem" in macro["instances"] + "u_soc.u_sram.gen_group[0].u_group.gen_bank[0].u_ram.u_mem" in macro["instances"] ) assert ( - "u_soc.u_sram.gen_memory.gen_bank[31].u_ram.u_mem" in macro["instances"] + "u_soc.u_sram.gen_group[3].u_group.gen_bank[7].u_ram.u_mem" in macro["instances"] ) for instance in macro["instances"].values(): x, y = instance["location"] diff --git a/tests/test_publication_programming.py b/tests/test_publication_programming.py index a9b8c235..bf7dcdf5 100644 --- a/tests/test_publication_programming.py +++ b/tests/test_publication_programming.py @@ -98,6 +98,21 @@ def test_channel_coverage_cannot_drop_the_boot_channel(spec): pr.collect_programming(ROOT, spec, IDS, REFERENCE["source_revision"]) +def test_mini_channel_selection_excludes_inactive_tiny_roles(): + header = (ROOT / "crt/include/retrosoc/hal/dma.h").read_text() + selected = pr.mini_channel_enum(header) + channels = pr.constants(selected, "RS_DMA_CHANNEL_") + assert sorted(channels.values()) == list(range(6)) + assert "RS_DMA_CHANNEL_I2S_TX = RS_DMA_CHANNEL_BULK" in selected + # A Tiny allocation change must not become a Mini allocation or alias. + assert pr.mini_channel_enum(header.replace("RS_DMA_CHANNEL_I2S_TX = 6", "RS_DMA_CHANNEL_I2S_TX = 7")) == selected + with pytest.raises(ValueError, match="alias binding"): + pr.mini_channel_enum(header.replace("RS_DMA_CHANNEL_I2S_TX = RS_DMA_CHANNEL_BULK", + "RS_DMA_CHANNEL_I2S_TX = RS_DMA_CHANNEL_CRYPTO_IN")) + with pytest.raises(ValueError, match="unreviewed"): + pr.mini_channel_enum(header.replace("#ifdef RS_SOC_TINY", "#ifdef UNKNOWN_PRODUCT")) + + def test_credit_parser_fails_closed_on_unreviewed_shape(): with pytest.raises(ValueError, match="unsupported credit function"): pr.credit_values("function automatic int limits(input int master); return compute(master); endfunction", "limits", 8) diff --git a/tests/test_publication_tiny.py b/tests/test_publication_tiny.py index 01d42098..c2d6a647 100644 --- a/tests/test_publication_tiny.py +++ b/tests/test_publication_tiny.py @@ -1,204 +1,53 @@ -"""Tiny publication is parameter-bound, complete and isolated from Mini inputs.""" +"""Historical Tiny publication contracts remain separate from P6 source support.""" from __future__ import annotations -import copy -import json -import re +import os from pathlib import Path -import shutil +import subprocess +import sys import pytest from publications import tiny_reference as tiny -from publications.register_reference import parse_definitions +from publication_history import ORIGINAL_BOOK, P5_CONTRACT, snapshot ROOT = Path(__file__).resolve().parents[1] -@pytest.fixture(scope="module") -def data(): - return tiny.collect(tiny.read(ROOT / tiny.BOOK / "tiny.json"), check_snapshot=False) +@pytest.mark.parametrize("revision", [ORIGINAL_BOOK, P5_CONTRACT]) +def test_historical_publication_contracts(tmp_path, revision): + historical = snapshot(tmp_path / "source", revision) + result = subprocess.run( + [sys.executable, "-m", "pytest", "-q", "tests/test_publication_tiny.py", + "--basetemp=" + str(tmp_path / "tests"), "--tb=short"], + cwd=historical, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + env={**os.environ, "PYTHONDONTWRITEBYTECODE": "1"}, timeout=180, + ) + (tmp_path / "historical-tests.log").write_text(result.stdout) + assert result.returncode == 0, result.stdout + assert " passed" in result.stdout and " skipped" not in result.stdout -def test_tiny_configuration_and_inventory_are_derived(data): - assert (data["facts"]["hardware_isa"], data["facts"]["firmware_isa"]) == ("RV32IMC", "RV32IM") - assert data["facts"]["sram"] == {"CapacityKiB": 128, "DataWidth": 32, "IdWidth": 1} - assert data["facts"]["dma"]["NumChannels"] == 4 - assert data["facts"]["dma"]["RequestMask"] == 0x7F9 - assert data["facts"]["dma"]["EnableStreams"] == 0 - assert data["facts"]["cpu"]["ResetSyncStages"] == 5 - assert tiny.RESET_TREE in data["source_paths"] - assert len(data["catalog"]) == 14 and len(data["regions"]) == 20 and len(data["pads"]) == 52 - assert len(data["interrupts"]) == 14 and len(data["gpio"]) == 32 - assert not any(p.startswith("rtl/mini/") for p in data["source_paths"]) - assert "mpw" not in {s["destination"].split("/")[-1] for s in data["managed_sources"]} - - -def test_tiny_collection_does_not_open_mini_files(monkeypatch): - original = Path.read_text - def read(path, *args, **kwargs): - name = path.as_posix() - if "/rtl/mini/" in name or ("/publications/datasheets/" in name and "/tiny/" not in name): - raise AssertionError("Tiny opened a Mini product input: " + name) - return original(path, *args, **kwargs) - monkeypatch.setattr(Path, "read_text", read) - tiny.collect(tiny.read(ROOT / tiny.BOOK / "tiny.json"), check_snapshot=False) - - -def test_common_register_default_remains_mini_compatible(): - paths = ["rtl/ip/serial/apb4_uart_define.svh"] - assert parse_definitions(paths, {}) == parse_definitions(paths, {}, "rtl/mini/address_map/memory_map.json") - _, constants = parse_definitions(paths, {}, tiny.MAP) - assert "SOC_SYSCTRL_TEST_STATUS_OFFSET" in constants - - -def test_reset_update_does_not_bypass_reviewed_snapshot(monkeypatch): - original = tiny.subprocess.check_output - checked = [] - - def changed_snapshot(command, **kwargs): - if command[:3] == ["git", "diff", "--name-only"]: - assert tiny.RESET_TREE in command - checked.append(command) - return tiny.RESET_TREE + "\n" - return original(command, **kwargs) - - monkeypatch.setattr(tiny.subprocess, "check_output", changed_snapshot) - with pytest.raises(ValueError, match="technical sources differ from reviewed snapshot"): - tiny.collect(tiny.read(ROOT / tiny.BOOK / "tiny.json")) - assert len(checked) == 1 - - -@pytest.fixture -def source_tree(tmp_path): - sources = [tiny.TOP, tiny.RESET_TREE, tiny.ARCHINFO, tiny.SYSCTRL, "rtl/ip/core/mgmt_core_wrapper.sv", "configs/ci/ihp130-tiny.mk", - tiny.MAP, tiny.TOPOLOGY, (tiny.BOOK / "source-contract.json").as_posix()] - for relative in sources: - target = tmp_path / relative - target.parent.mkdir(parents=True, exist_ok=True) - shutil.copyfile(ROOT / relative, target) - return tmp_path - - -@pytest.mark.parametrize("path,before,after", [ - (tiny.TOP, ".CapacityKiB(128)", ".CapacityKiB(32)"), - (tiny.TOP, ".Ics55SmallBanks(1'b1)", ".Ics55SmallBanks(1'b0)"), - (tiny.TOP, ".DataWidth (32)", ".DataWidth (64)"), - (tiny.TOP, ".NumChannels (4)", ".NumChannels (8)"), - (tiny.TOP, ".EnableStreams(1'b0)", ".EnableStreams(1'b1)"), - (tiny.TOP, ".EnableAtomics (1'b0)", ".EnableAtomics (1'b1)"), - (tiny.TOP, ".ResetSyncStages (5)", ".ResetSyncStages (3)"), - ("configs/ci/ihp130-tiny.mk", "ISA := RV32IM", "ISA := RV32IMC"), - (tiny.ARCHINFO, "32'h5449_4e59", "32'h4d49_4e49"), - (tiny.SYSCTRL, "!s_test_q[31] && apb4.pwdata[31]", "s_test_q[31] && apb4.pwdata[31]"), -]) -def test_changed_hardware_or_isa_cannot_retain_publication_claim(source_tree, path, before, after): - file = source_tree / path - text = file.read_text(encoding="utf-8") - # Parameter alignment is formatting, while the altered value must still - # fail the source-bound publication contract. - pattern = r"\s*".join(re.escape(char) for char in before if not char.isspace()) - assert re.search(pattern, text) - file.write_text(re.sub(pattern, lambda _: after, text, count=1), encoding="utf-8") - with pytest.raises(ValueError): - tiny.facts(source_tree) - - -@pytest.mark.parametrize("change", ["irq_missing", "irq_number", "region_address", "region_missing"]) -def test_missing_or_wrong_maps_fail(source_tree, change): - path = source_tree / (tiny.TOPOLOGY if change.startswith("irq") else tiny.MAP) - document = tiny.read(path) - if change == "irq_missing": - document["interrupts"].pop() - elif change == "irq_number": - document["interrupts"][-1]["core_bit"] = 27 - elif change == "region_address": - next(r for r in document["regions"] if r["symbol"] == "SRAM")["base"] = "0x31000000" - else: - document["regions"].pop() - path.write_text(json.dumps(document), encoding="utf-8") - with pytest.raises(ValueError): - tiny.validate_maps(source_tree, tiny.facts(source_tree)) - - -def test_tiny_sysctrl_and_discovery_do_not_inherit_mini_controls(data): - control = data["registers"]["sysctrl"]["registers"] - assert {r["name"] for r in control} == tiny.SUPPORTED_SYSCTRL - assert not any(r["name"] in {"HP_CTRL", "PLL_CFG", "DEBUG_SELECT"} for r in control) - test = next(r for r in control if r["name"] == "TEST_STATUS") - assert [(f["name"], f["lsb"], f["msb"]) for f in test["fields"] if f["name"] != "Reserved"] == [ - ("PASS", 0, 0), ("CODE", 8, 15), ("VALID", 31, 31)] - info = data["registers"]["archinfo"]["registers"] - assert next(r for r in info if r["name"] == "SOC_ID")["reset"] == "0x54494E59" - assert next(r for r in info if r["name"] == "SRAM_BYTES")["reset"] == "0x00020000" - assert data["registers"]["dma"]["groups"][1]["count"] == 4 - - -def test_tcd_layout_is_complete_and_bounded(data): - assert len(data["tcd"]) == 17 - assert sum(f["bytes"] for f in data["tcd"]) == 64 - assert data["tcd"][-1]["offset"] == 60 - - -def test_local_capabilities_keep_fabric_and_encoding_restrictions(data): - capability = next(r for r in data["registers"]["dma"]["registers"] if r["name"] == "CAPABILITY") - assert capability["reset"] == "0x00200401" - burst = next(f for f in capability["fields"] if f["name"] == "MAX_BURST_FIELD") - assert (burst["lsb"], burst["msb"], burst["reset"]) == (24, 27, "0x0") - assert "encoding limitation" in burst["description"] - sram = next(r for r in data["registers"]["sram"]["registers"] if r["name"] == "CAPABILITY") - assert "rejects WRAP" in next(f for f in sram["fields"] if f["name"] == "WRAP")["description"] - - -@pytest.mark.parametrize("key,value", [("document_id", "RS-MINI-DS"), ("profile", "configs/ci/ihp130.mk"), ("status", "RELEASE")]) -def test_wrong_product_configuration_cannot_enter_tiny_pipeline(key, value): +def test_current_source_cannot_relabel_historical_publication(): config = tiny.read(ROOT / tiny.BOOK / "tiny.json") - config[key] = value - with pytest.raises(ValueError, match="configuration"): - tiny.validate_configuration(config) + assert config["source_revision"] == "9c706ba0ff1da1028c299cfff573010a86dfd7ce" + with pytest.raises(ValueError, match="DMA parameters changed"): + tiny.collect(config) -def test_existing_mini_output_is_never_overwritten(tmp_path, monkeypatch): - from publications import build_tiny_datasheet as builder - folder = tmp_path / "build/mini-existing" - folder.mkdir(parents=True) - manifest = folder / "manifest.json" - manifest.write_text('{"document":{"document_id":"RS-MINI-DS"}}') - before = manifest.read_bytes() - monkeypatch.setattr(builder, "ROOT", tmp_path) - with pytest.raises(ValueError, match="another product"): - builder.validate_output(folder, {"document_id": "RS-TINY-DS"}) - assert manifest.read_bytes() == before - - -@pytest.mark.parametrize("change", ["missing", "offset"]) -def test_register_inventory_cannot_silently_drop_or_move_a_definition(data, change): - refs = copy.deepcopy(data["registers"]) - if change == "missing": - refs["uart"]["registers"].pop() - else: - refs["uart"]["registers"][0]["offset"] += 4 - with pytest.raises(ValueError, match="register inventory"): - tiny.validate_register_inventory(refs, tiny.read(ROOT / tiny.BOOK / "register-contract.json")) - - -@pytest.mark.parametrize("change", ["missing", "duplicate"]) -def test_pdf_must_render_each_extracted_register_once(data, change): - from publications.build_tiny_datasheet import validate_rendered_registers - items = [dict(kind="tiny-register", family=family, key=r["key"], page=1) - for family, value in data["registers"].items() for r in value["registers"]] - assert validate_rendered_registers(data, items) == 337 - if change == "missing": - items.pop() - else: - items.append(items[0]) - with pytest.raises(ValueError, match="omitted or duplicated"): - validate_rendered_registers(data, items) - - -@pytest.mark.parametrize("key,value", [("status", "qualified"), ("current_commit_reports", [{"passed": True}])]) -def test_unmatched_qualification_claim_is_rejected(data, key, value): - evidence = copy.deepcopy(data["evidence"]) - evidence[key] = value - with pytest.raises(ValueError, match="qualification"): - tiny.validate_evidence(evidence) +@pytest.mark.parametrize("before,after", [ + ("s_req_q.write_done && !s_req_q.b_captured", "1'b1"), + ("s_r_valid && s_rready[s_req_q.owner] && s_r_data[1]", "s_r_valid"), + (".flush_i(1'b0)", ".flush_i(!enable_i)"), +]) +def test_current_reset_binding_still_rejects_unsafe_completion(tmp_path, before, after): + contract = tiny.read(ROOT / tiny.BOOK / "source-contract.json") + binding = next(item for item in contract["bindings"] + if item["file"] == "rtl/tiny/top/tiny_axi4_target.sv") + path = tmp_path / binding["file"] + path.parent.mkdir(parents=True) + original = (ROOT / binding["file"]).read_text() + assert before in original + path.write_text(original.replace(before, after)) + with pytest.raises(ValueError, match="Tiny behavior binding changed"): + tiny.require_snippets(tmp_path, binding["file"], binding["text"]) diff --git a/tests/test_script_tools.py b/tests/test_script_tools.py index 98884b6f..ca9c2206 100644 --- a/tests/test_script_tools.py +++ b/tests/test_script_tools.py @@ -1969,6 +1969,8 @@ def test_run_flow_writes_structured_result(tmp_path: Path) -> None: assert data["status"] == "passed" assert data["exit_code"] == 0 assert data["duration_seconds"] >= 0 + assert isinstance(data["parent_pid"], int) + assert isinstance(data["child_pid"], int) assert isinstance(data["peak_rss_kib"], int) assert data["peak_rss_kib"] > 0 assert log.read_text(encoding="utf-8") == "flow output\n" @@ -2120,6 +2122,41 @@ def test_run_flow_streams_bytes_without_waiting_for_newline(tmp_path: Path) -> N assert json.loads(result.read_text(encoding="utf-8"))["status"] == "passed" +def test_run_flow_persists_cancel_before_cleanup_and_ignores_second_signal(tmp_path: Path) -> None: + result = tmp_path / "cancelled.json" + process = subprocess.Popen([ + sys.executable, str(ROOT / "scripts/run_flow.py"), "--tool", "unit", + "--log", str(tmp_path / "cancelled.log"), "--result", str(result), "--", + sys.executable, "-u", "-c", + "import signal,time; signal.signal(signal.SIGTERM,signal.SIG_IGN); " + "print('READY',flush=True); time.sleep(30)", + ], text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + try: + assert process.stdout is not None + assert process.stdout.readline().strip() == "READY" + process.send_signal(signal.SIGINT) + for _ in range(100): + checkpoint = json.loads(result.read_text()) + if checkpoint["status"] == "interrupted": + break + time.sleep(0.01) + assert checkpoint["status"] == "interrupted" + assert checkpoint["child_cleanup_pending"] is True + assert "finished_at" not in checkpoint + process.send_signal(signal.SIGTERM) + _, stderr = process.communicate(timeout=10) + assert process.returncode == 130, stderr + final = json.loads(result.read_text()) + assert final["status"] == "interrupted" + assert final["child_cleanup_pending"] is False + assert final["child_exit_code"] == -signal.SIGKILL + assert final["finished_at"] >= final["started_at"] + finally: + if process.poll() is None: + process.kill() + process.wait(timeout=5) + + def test_run_flow_records_interruption(tmp_path: Path) -> None: log = tmp_path / "interrupted.log" result = tmp_path / "interrupted.json" @@ -2192,6 +2229,27 @@ def test_simulation_success_marker_and_failure_detection(tmp_path: Path) -> None assert json.loads(result.read_text(encoding="utf-8"))["status"] == "failed" +@pytest.mark.parametrize("status,exit_code", [("running", None), ("failed", 1), + ("passed", 124), ("invalid", None)]) +def test_check_simulation_requires_terminal_flow(tmp_path: Path, status: str, + exit_code: int | None) -> None: + log = tmp_path / "sim.log" + result = tmp_path / "result-sim-check.json" + log.write_text("SIM_TEST_PASS code=0\n", encoding="utf-8") + (tmp_path / "result-sim.json").write_text( + json.dumps({"status": status, "exit_code": exit_code}), + encoding="utf-8", + ) + checked = subprocess.run( + [sys.executable, str(ROOT / "scripts/check_simulation.py"), "--log", str(log), + "--result", str(result)], text=True, capture_output=True, + ) + report = json.loads(result.read_text(encoding="utf-8")) + assert checked.returncode != 0 + assert report["status"] == "failed" + assert report["flow_status"] == status + + def test_warning_baseline_rejects_new_signature(tmp_path: Path) -> None: profile = "unit-profile" log = tmp_path / "variant/sim/verilator/verilating.log" diff --git a/tests/test_tiny.py b/tests/test_tiny.py index a1116257..330520f4 100644 --- a/tests/test_tiny.py +++ b/tests/test_tiny.py @@ -35,7 +35,13 @@ def test_tiny_sources_and_abi_are_independent(tmp_path: Path) -> None: for index, target in enumerate(document["apb_targets"], start=1): name = target["name"] assert f".presetn(s_{name}_rst_n)" in interfaces - assert f"assign s_{name}_rst_n = s_leaf_rst_n[{index}];" in interfaces + if name in {"gpio", "uart0", "i2c0", "timer0", "timer1", "pwm", "i2s", "sdio", + "dma", "ws2812", "rng", "crc", "crypto"}: + assert f"tiny_peripheral_leaf u_{name}_lifecycle" in interfaces + assert f".rst_n_i(s_leaf_rst_n[{index}])" in interfaces + assert f".rst_n_o(s_{name}_rst_n)" in interfaces + else: + assert f"assign s_{name}_rst_n = s_leaf_rst_n[{index}];" in interfaces generate_filelists(filelists, output, ["+define+RETROSOC_SOC__TINY"]) generate_memory_map(TINY / "address_map/memory_map.json", output / "memory_map", "YES", 128) resolved = parse_filelists(filelists / name for name in @@ -51,7 +57,9 @@ def test_tiny_sources_and_abi_are_independent(tmp_path: Path) -> None: assert "RS_SOC_PSRAM_BASE" not in sdk irq = (output / "include/retrosoc/generated/irq_metadata.h").read_text() assert "RS_SOC_EXTERNAL_IRQ_COUNT UINT32_C(30)" in irq - assert "RS_SOC_EXT_IRQ_UART1 UINT32_C(24)" in irq + assert "RS_SOC_EXT_IRQ_UART1" not in irq + assert "RS_SOC_EXT_IRQ_I2C1" not in irq + assert "RS_SOC_EXT_IRQ_SDIO UINT32_C(8)" in irq assert "RS_SOC_EXT_IRQ_DMA UINT32_C(18)" in irq assert "RS_SOC_EXT_IRQ_NPU" not in irq # A consumer of public SDK headers must see Tiny capabilities without @@ -59,7 +67,7 @@ def test_tiny_sources_and_abi_are_independent(tmp_path: Path) -> None: if shutil.which("cc"): probe = tmp_path / "consumer.c" probe.write_text('#include \n' - '_Static_assert(RS_DMA_CHANNEL_COUNT == 4, "wrong product");\n') + '_Static_assert(RS_DMA_CHANNEL_COUNT == 8, "wrong product");\n') run(["cc", "-std=c11", "-Werror", "-I" + str(ROOT / "crt/include"), "-I" + str(output / "memory_map/include"), "-I" + str(output / "include"), "-c", str(probe), "-o", str(tmp_path / "consumer.o")]) @@ -97,16 +105,23 @@ def test_tiny_protocol_contracts(tmp_path: Path, simulator: str, block: str) -> pytest.skip("locked RTL/tool dependency unavailable") generated = tmp_path / "map" generate_memory_map(TINY / "address_map/memory_map.json", generated, "YES", 128) + generate_bindings(read_topology(TINY / "integration/soc_topology.json", + TINY / "address_map/memory_map.json"), tmp_path / "bindings") top = "tiny_axi4_fabric_tb" if block == "fabric" else "tiny_sysctrl_tb" sources = [COMMON / "utils/register.sv"] if block == "fabric": sources += [COMMON / "interface/axi4_if.sv", COMMON / "stream/round_robin_arbiter.sv", - ROOT / "rtl/ip/interconnect/axi4_error_slave.sv", TINY / "top/tiny_axi4_fabric.sv"] + COMMON / "utils/spill_register.sv", + ROOT / "rtl/ip/interconnect/axi4_error_slave.sv", + TINY / "top/tiny_axi4_target.sv", TINY / "top/tiny_axi4_fabric.sv"] else: - sources += [COMMON / "interface/apb4_if.sv", TINY / "top/tiny_sysctrl.sv"] + sources += [COMMON / "interface/apb4_if.sv", COMMON / "cdc/cdc_sync.sv", + COMMON / "cdc/cdc_rst_ctrlr.sv", COMMON / "cdc/cdc_2phase.sv", + COMMON / "clkrst/rst_sync.sv", TINY / "top/tiny_apb_async_bridge.sv", + TINY / "top/tiny_sysctrl.sv"] sources.append(ROOT / "tests/rtl" / f"{top}.sv") - includes = [f"-I{p}" for p in (COMMON, COMMON / "interface", generated / "rtl", - ROOT / "rtl/ip/peripheral", ROOT / "tests/rtl")] + includes = [f"-I{p}" for p in (COMMON, COMMON / "interface", generated / "rtl", tmp_path / "bindings/rtl", + ROOT / "rtl/ip/peripheral", TINY / "top", ROOT / "tests/rtl")] output = tmp_path / "sim" if simulator == "verilator": cache = tmp_path / "cache" diff --git a/tests/test_tiny_ics55_platform.py b/tests/test_tiny_ics55_platform.py index 0c441912..f43d2311 100644 --- a/tests/test_tiny_ics55_platform.py +++ b/tests/test_tiny_ics55_platform.py @@ -109,10 +109,13 @@ def mapped_netlist(): else "input" for k in ports}} cells = {"u_clock_buffer": cell("clock_buffer", clk_i=7, clk_o=2), "receiver": cell("P65_1233_PBMUX", PAD=1, C=7, IE="1", OE="0", CS="1"), - "u_soc.u_cpu.u_hazard3_cpu_1port.core.pc_reg": cell("DFF", CK=2), + "u_soc.u_cpu.u_hazard3_cpu_2port.core.pc_reg": cell("DFF", CK=2), "pll": cell("PLL_TOP", EN=3, CKOUT1=4, CKOUT2=5, CKTST=6), "low": cell("TIELOH7R", Z=3)} - cells.update({f"bank{i}": cell(RAM, CLK=2) for i in range(32)}) + cells.update({f"u_soc.u_sram.gen_group[{i // 8}].u_group.gen_bank[{i % 8}].u_ram.u_mem": + cell(RAM, CLK=2) for i in range(32)}) + cells.update({f"u_soc.u_crypto.u_storage.gen_bank[{i}].gen_macro.u_sram.u_mem": + cell(RAM, CLK=2) for i in range(6)}) return {"modules": { "retrosoc_tiny_asic": {"ports": {"extclk_i_pad": {"direction": "inout", "bits": [1]}, "jtag_tck_i_pad": {"direction": "inout", "bits": [11]}}, @@ -125,6 +128,7 @@ def mapped_netlist(): def test_safe24_structural_binding(mapped_netlist): result = inspect_netlist(mapped_netlist) assert len(result["sram"]) == 32 and result["cpu_clocked_cells"] == 1 + assert len(result["crypto_sram"]) == 6 assert result["pll_enable"] == 0 assert result["system_clock_source"]["port"] == "extclk_i_pad" assert [c["type"] for c in result["system_clock_source"]["path"]] == [ @@ -198,27 +202,44 @@ def test_hierarchical_constant_output_is_resolved(mapped_netlist): @pytest.mark.parametrize("mutation", ["missing_pll", "missing_ram", "live_pll", "unknown_enable", - "divided_ram", "wrong_cpu", "pll_sys"]) + "divided_ram", "wrong_cpu", "pll_sys", + "missing_crypto", "misplaced_crypto", "divided_crypto"]) def test_unsafe_or_incomplete_mapping_is_rejected(mapped_netlist, mutation): cells = mapped_netlist["modules"]["retrosoc_tiny_asic"]["cells"] + first_ram = "u_soc.u_sram.gen_group[0].u_group.gen_bank[0].u_ram.u_mem" + first_crypto = "u_soc.u_crypto.u_storage.gen_bank[0].gen_macro.u_sram.u_mem" if mutation == "missing_pll": del cells["pll"] elif mutation == "missing_ram": - del cells["bank0"] + del cells[first_ram] elif mutation == "live_pll": cells["low"]["type"] = "TIEHIH7R" elif mutation == "unknown_enable": cells["pll"]["connections"]["EN"] = [99] elif mutation == "divided_ram": - cells["bank0"]["connections"]["CLK"] = [99] + cells[first_ram]["connections"]["CLK"] = [99] elif mutation == "wrong_cpu": - cells["u_soc.u_cpu.u_hazard3_cpu_1port.core.pc_reg"]["connections"]["CK"] = [99] + cells["u_soc.u_cpu.u_hazard3_cpu_2port.core.pc_reg"]["connections"]["CK"] = [99] + elif mutation == "missing_crypto": + del cells[first_crypto] + elif mutation == "misplaced_crypto": + cells["unexpected_private_memory"] = cells.pop(first_crypto) + elif mutation == "divided_crypto": + cells[first_crypto]["connections"]["CLK"] = [99] else: cells["pll"]["connections"]["CKOUT1"] = [2] with pytest.raises(ValueError): inspect_netlist(mapped_netlist) +def test_stale_p4_macro_hierarchy_is_rejected(mapped_netlist): + cells = mapped_netlist["modules"]["retrosoc_tiny_asic"]["cells"] + first_ram = "u_soc.u_sram.gen_group[0].u_group.gen_bank[0].u_ram.u_mem" + cells["u_soc.u_sram.gen_memory.gen_bank[0].u_ram.u_mem"] = cells.pop(first_ram) + with pytest.raises(ValueError, match="hierarchy"): + inspect_netlist(mapped_netlist) + + def test_tiny_uses_32_actual_ics55_small_macros(tmp_path): assert shutil.which("verilator"), "required simulator unavailable" generated = tmp_path / "map" diff --git a/tests/test_tiny_r2_p2.py b/tests/test_tiny_r2_p2.py index 96760beb..dbccbb20 100644 --- a/tests/test_tiny_r2_p2.py +++ b/tests/test_tiny_r2_p2.py @@ -53,15 +53,17 @@ def test_reset_and_fifo_contracts(tmp_path: Path, simulator: str, fixture: str, assert "SIM_TEST_FAIL" not in content and "SIM_TEST_TIMEOUT" not in content -def test_reset_inventory_has_one_clock_and_all_existing_targets() -> None: +def test_reset_inventory_has_active_domains_and_all_existing_targets() -> None: topology = json.loads((ROOT / "rtl/tiny/integration/soc_topology.json").read_text()) inventory = json.loads((ROOT / "rtl/tiny/integration/clock_reset_domains.json").read_text()) reset = inventory["reset_distribution"] assert reset["release_edges"] == 5 assert reset["leaf_names"] == ["fabric", *(target["name"] for target in topology["apb_targets"])] - assert len(reset["leaf_names"]) == 17 - assert {domain["name"] for domain in inventory["domains"]} == {"system", "jtag"} - assert reset["clock_domain"] == "system" and reset["main_sram_payload_reset"] == "none" + assert len(reset["leaf_names"]) == 21 + assert {domain["name"] for domain in inventory["domains"]} == { + "aon", "system", "memory", "peripheral", "jtag", "audio"} + assert reset["clock_domain"] == "domain-specific" and reset["main_sram_payload_reset"] == "none" + assert inventory["local_memory"]["clock_domain"] == "system" SDC = ("create_clock -name clk_system -period 41.666666667 $clk_system_pin\n" diff --git a/tests/test_tiny_r2_p4.py b/tests/test_tiny_r2_p4.py new file mode 100644 index 00000000..b3a8d9ae --- /dev/null +++ b/tests/test_tiny_r2_p4.py @@ -0,0 +1,195 @@ +"""Tiny dual-port local-memory protocol and source-bound replay acceptance.""" +from __future__ import annotations + +import os +from pathlib import Path +import shutil +import subprocess + +import pytest + +from scripts.check_simulation import DEFAULT_FAILURE +from scripts.rtl.generate_memory_map import generate as generate_memory_map +from scripts import tiny_r2_p4 as p4 + +ROOT = Path(__file__).resolve().parents[1] +COMMON = ROOT / "rtl/managed/clusterip/common/rtl" +TINY = ROOT / "rtl/tiny/top" + + +def run(command, directory, label, timeout=180): + result = subprocess.run(command, text=True, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, timeout=timeout, + env={**os.environ, "CCACHE_DISABLE": "1"}) + (directory / f"{label}.log").write_text(result.stdout) + assert result.returncode == 0, result.stdout[-12000:] + if label != "sv2v": # Converted source contains literal assertion/error strings. + assert not DEFAULT_FAILURE.search(result.stdout), result.stdout[-12000:] + return result.stdout + + +@pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) +@pytest.mark.parametrize("pdk", ["BEHAV", "ICS55", "IHP130"]) +def test_four_group_local_memory(tmp_path, simulator, pdk): + for tool in (simulator, "sv2v", "vvp"): + assert shutil.which(tool), f"required tool unavailable: {tool}" + generate_memory_map(ROOT / "rtl/tiny/address_map/memory_map.json", tmp_path / "map", "YES", 128) + sources = [COMMON / p for p in ( + "utils/register.sv", "utils/fifo.sv", "stream/round_robin_arbiter.sv", "interface/axi4_if.sv", + "interface/axi4_addr_gen.sv", "interface/apb4_if.sv", "interface/ahbl_if.sv")] + sources += [TINY / p for p in ( + "tiny_sram_port_if.sv", "tiny_sram_group.sv", "tiny_sram_axi4.sv", + "tiny_sram.sv", "tiny_cpu_mem.sv")] + sources += [ROOT / p for p in ( + "rtl/tech/tc_sram.sv", "rtl/ip/memory/onchip_ram_reg.sv", + "rtl/ip/interconnect/ahbl2axi4.sv", "rtl/ip/interconnect/axi4_error_slave.sv", + "tests/rtl/tiny_r2_p4_memory_tb.sv")] + defines = ["-DSV_ASSRT_DISABLE", f"-DPDK_{pdk}", "-DHAVE_SRAM_MACRO"] + vendor = [] + if pdk == "IHP130": + base = ROOT / "physical/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_sram/verilog" + vendor = [base / "RM_IHPSG13_1P_core_behavioral_bm_bist.v", + base / "RM_IHPSG13_1P_1024x32_c2_bm_bist.v"] + defines += ["-DFUNCTIONAL", "-DSYNTHESIS"] + elif pdk == "ICS55": + vendor = [ROOT / ".cache/retrosoc/pdk/ics55/sram/ics55_ecos_sram_1024x32_m8/verilog/ics55_ecos_sram_1024x32_m8_core.v"] + assert all(p.is_file() for p in vendor), "locked SRAM models unavailable" + includes = [f"-I{p}" for p in (COMMON, COMMON / "interface", tmp_path / "map/rtl", + ROOT / "rtl/ip/memory")] + top = "tiny_r2_p4_memory_tb" + output = tmp_path / "sim" + if simulator == "verilator": + run([simulator, "--binary", "--timing", "--assert", "-Wno-fatal", "-j", "3", + "-DHAVE_SVA", "--top-module", top, *defines, *includes, + *(str(p) for p in sources + vendor), "--Mdir", str(tmp_path / "obj"), "-o", str(output)], + tmp_path, "compile") + launch = [str(output)] + else: + # Keep vendor models outside sv2v, as in the executable Tiny flow. + converted = run(["sv2v", *defines, *includes, *(str(p) for p in sources)], tmp_path, "sv2v") + (tmp_path / "converted.v").write_text(converted) + run([simulator, "-g2012", "-gno-specify", "-s", top, + *defines, "-o", str(output), str(tmp_path / "converted.v"), *(str(p) for p in vendor)], + tmp_path, "compile") + launch = ["vvp", str(output)] + content = run(launch, tmp_path, "sim") + assert "SIM_TEST_PASS Tiny P4 memory" in content + assert "P4_STREAM read_gap_max=1 write_gap_max=1 beats=16" in content + assert "P4_BUFFER backpressure errors reset conservation PASS" in content + + +@pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) +@pytest.mark.parametrize("p7_lifecycle", [False, True]) +def test_actual_dual_port_cpu_debug_and_reset(tmp_path, simulator, p7_lifecycle): + for tool in (simulator, "sv2v", "vvp", "riscv32-unknown-elf-gcc", "riscv32-unknown-elf-objcopy"): + assert shutil.which(tool), f"required tool unavailable: {tool}" + generate_memory_map(ROOT / "rtl/tiny/address_map/memory_map.json", tmp_path / "map", "YES", 128) + lifecycle_defines = ["-DTINY_TEST_P7"] if p7_lifecycle else [] + run(["riscv32-unknown-elf-gcc", *lifecycle_defines, "-march=rv32imc_zicsr_zifencei", "-mabi=ilp32", + "-nostdlib", "-Wl,--build-id=none", "-T", str(ROOT / "tests/rtl/tiny_r2_p4_cpu.ld"), + str(ROOT / "tests/rtl/tiny_r2_p4_cpu.S"), "-o", str(tmp_path / "cpu.elf")], tmp_path, "firmware") + run(["riscv32-unknown-elf-objcopy", "-O", "verilog", str(tmp_path / "cpu.elf"), + str(tmp_path / "cpu.hex")], tmp_path, "objcopy") + sources = [COMMON / p for p in ( + "utils/register.sv", "utils/fifo.sv", "stream/round_robin_arbiter.sv", "interface/axi4_if.sv", + "interface/axi4_addr_gen.sv", "interface/apb4_if.sv", "interface/ahbl_if.sv", + "clkrst/rst_sync.sv", "cdc/cdc_sync.sv")] + sources += [ROOT / "rtl/managed/hazard3/hdl" / line.removeprefix("/hazard3/") + for line in (ROOT / "rtl/tiny/filelist/core_hazard3.fl").read_text().splitlines() + if line.startswith("/hazard3/")] + sources += [TINY / p for p in ( + "tiny_sram_port_if.sv", "tiny_sram_group.sv", "tiny_sram_axi4.sv", + "tiny_sram.sv", "tiny_cpu_mem.sv", "tiny_clock_gate.sv", "tiny_cpu_wrapper.sv")] + sources += [ROOT / p for p in ( + "rtl/tech/tc_sram.sv", "rtl/tech/tc_clk.sv", "rtl/ip/memory/onchip_ram_reg.sv", + "rtl/ip/interconnect/ahbl2axi4.sv", "rtl/ip/core/mgmt_debug_wrapper.sv", + "rtl/ip/core/mgmt_debug_reset.sv", "tests/rtl/tiny_r2_p4_cpu_tb.sv")] + includes = [f"-I{p}" for p in (COMMON, COMMON / "interface", tmp_path / "map/rtl", + ROOT / "rtl/ip/memory", ROOT / "rtl/managed/hazard3/hdl", + ROOT / "rtl/tiny/dv")] + defines = ["-DSV_ASSRT_DISABLE", "-DPDK_BEHAV", "-DSOC_JTAG_IDCODE=32'hDEADBEEF", *lifecycle_defines] + output = tmp_path / "sim" + top = "tiny_r2_p4_cpu_tb" + if simulator == "verilator": + run([simulator, "--binary", "--timing", "--assert", "-Wno-fatal", "-j", "3", + "-DHAVE_SVA", "--top-module", top, *defines, *includes, + *(str(p) for p in sources), "--Mdir", str(tmp_path / "obj"), "-o", str(output)], + tmp_path, "compile") + launch = [str(output)] + else: + converted = run(["sv2v", *defines, *includes, *(str(p) for p in sources)], tmp_path, "sv2v") + (tmp_path / "converted.v").write_text(converted) + run([simulator, "-g2012", "-s", top, "-o", str(output), str(tmp_path / "converted.v")], + tmp_path, "compile") + launch = ["vvp", str(output)] + content = run([*launch, f"+firmware={tmp_path / 'cpu.hex'}"], tmp_path, "sim") + assert "SIM_TEST_PASS Tiny P4 CPU" in content + if p7_lifecycle: + assert "P7_CPU_LIFECYCLE_PASS" in content + + +def test_group_control_induction(tmp_path): + for tool in ("sv2v", "yosys"): + assert shutil.which(tool), f"required tool unavailable: {tool}" + sources = [COMMON / "utils/register.sv", COMMON / "utils/fifo.sv", + COMMON / "stream/round_robin_arbiter.sv", + TINY / "tiny_sram_port_if.sv", TINY / "tiny_sram_group.sv", + ROOT / "tests/rtl/tiny_r2_p4_formal.sv"] + converted = run(["sv2v", "-DFORMAL", "-DSV_ASSRT_DISABLE", f"-I{COMMON}", + *(str(p) for p in sources)], tmp_path, "sv2v") + (tmp_path / "formal.v").write_text(converted) + script = (f"read_verilog -formal {tmp_path / 'formal.v'}; " + "prep -top tiny_r2_p4_formal -flatten; memory_map; async2sync; dffunmap; opt; " + "sat -seq 12 -tempinduct -prove-asserts -verify -set-init-zero") + content = run(["yosys", "-p", script], tmp_path, "formal", timeout=180) + assert "Induction step proven: SUCCESS!" in content + + +def p4_log(): + from test_tiny_r2_p3 import sample_log + lines = [sample_log()] + for window in range(1, 9): + for port in range(2): + lines.append(f"R2_P4_LOCAL window={window} port={port} accepted=3 completed=3 " + "latency_max=3 admission_wait=0") + for group in range(4): + lines.append(f"R2_P4_BANK window={window} group={group} issues_i=2 issues_d=1 " + "issues_external=0 conflicts=0 wait_i=0 wait_d=0 wait_external=0") + return "\n".join(lines) + + +def test_p4_acceptance_retains_local_and_external_accounting(): + samples = p4.parse_log(p4_log(), {"status": "passed", "exit_code": 0}) + assert len(samples["local"]) == 16 and len(samples["banks"]) == 32 + assert samples["cases"][2]["checksum"] == 0xc4a58dc5 + + +@pytest.mark.parametrize("mutation", ["exit", "marker", "missing", "duplicate", "accounting"]) +def test_p4_rejects_incomplete_or_failed_evidence(mutation): + text = p4_log() + flow = {"status": "passed", "exit_code": 0} + if mutation == "exit": + flow["exit_code"] = 124 + elif mutation == "marker": + text += "\nSIM_TEST_TIMEOUT" + elif mutation == "missing": + text = "\n".join(line for line in text.splitlines() if not line.startswith("R2_P4_BANK window=8")) + elif mutation == "duplicate": + text += "\n" + next(line for line in text.splitlines() if line.startswith("R2_P4_LOCAL")) + else: + text = text.replace("accepted=3 completed=3", "accepted=3 completed=4") + with pytest.raises(ValueError): + p4.parse_log(text, flow) + + +@pytest.mark.parametrize("key", p4.HARDWARE_KEYS) +def test_p4_rejects_mismatched_hardware(key): + values = dict.fromkeys(p4.HARDWARE_KEYS, "same") + with pytest.raises(ValueError): + p4.compatible_hardware({"configuration": values}, {"configuration": values | {key: "other"}}) + + +def test_p4_preserves_independent_image_compiler_identity(): + hardware = dict.fromkeys(p4.HARDWARE_KEYS, "same") + p4.compatible_hardware({"configuration": hardware | {"SW_OPT": "O2"}}, + {"configuration": hardware | {"SW_OPT": "APP"}}) diff --git a/tests/test_tiny_r2_p5.py b/tests/test_tiny_r2_p5.py new file mode 100644 index 00000000..ea565906 --- /dev/null +++ b/tests/test_tiny_r2_p5.py @@ -0,0 +1,193 @@ +"""P5 target concurrency, fault accounting and retained-image acceptance.""" +from __future__ import annotations + +import shutil +import subprocess +import json +from pathlib import Path + +import pytest + +from scripts.generate_tiny import generate_bindings, generate_filelists, read_topology +from scripts.rtl.filelist import parse_filelists +from scripts.rtl.generate_memory_map import generate as generate_memory_map +from test_tiny_r2_p4 import run +from scripts import tiny_r2_p5 as p5 + +ROOT = Path(__file__).resolve().parents[1] +COMMON = ROOT / "rtl/managed/clusterip/common/rtl" +TINY = ROOT / "rtl/tiny/top" + + +def sources(tmp_path): + generate_memory_map(ROOT / "rtl/tiny/address_map/memory_map.json", tmp_path / "map", "YES", 128) + generate_bindings(read_topology(ROOT / "rtl/tiny/integration/soc_topology.json", + ROOT / "rtl/tiny/address_map/memory_map.json"), tmp_path / "bindings") + files = [COMMON / p for p in ("utils/register.sv", "utils/spill_register.sv", + "utils/fifo.sv", "stream/round_robin_arbiter.sv", "interface/axi4_if.sv", + "interface/axi4_addr_gen.sv", "interface/apb4_if.sv", "interface/ahbl_if.sv")] + files += [TINY / p for p in ("tiny_axi4_target.sv", "tiny_axi4_fabric.sv", + "tiny_axi42apb4.sv", "tiny_fault_collector.sv", "tiny_sysctrl.sv")] + files += [ROOT / "rtl/ip/interconnect/axi4_error_slave.sv"] + includes = [f"-I{p}" for p in (COMMON, COMMON / "interface", TINY, tmp_path / "map/rtl", + tmp_path / "bindings/rtl", ROOT / "rtl/ip/peripheral", ROOT / "tests/rtl")] + return files, includes + + +def simulate(tmp_path, simulator, top, files, includes, arguments=()): + for tool in (simulator, "sv2v", "vvp"): + assert shutil.which(tool), f"required tool unavailable: {tool}" + binary = tmp_path / "sim" + if simulator == "verilator": + run([simulator, "--binary", "--timing", "--assert", "-Wno-fatal", "-j", "3", + "-DHAVE_SVA", "-DSV_ASSRT_DISABLE", "--top-module", top, *includes, + *(str(p) for p in files), "--Mdir", str(tmp_path / "obj"), "-o", str(binary)], + tmp_path, "compile", timeout=300) + launch = [str(binary)] + else: + # Conversion warnings are diagnostics, never Verilog source text. + converted = subprocess.run(["sv2v", "-DSV_ASSRT_DISABLE", *includes, + *(str(p) for p in files)], text=True, + capture_output=True, timeout=300) + (tmp_path / "sv2v.log").write_text(converted.stderr) + assert converted.returncode == 0, converted.stderr + (tmp_path / "converted.v").write_text(converted.stdout) + run([simulator, "-g2012", "-s", top, "-o", str(binary), str(tmp_path / "converted.v")], + tmp_path, "compile") + launch = ["vvp", str(binary)] + log = run([*launch, *arguments], tmp_path, "sim", timeout=180) + assert "SIM_TEST_PASS Tiny P5" in log + return log + + +@pytest.mark.parametrize("simulator", ["iverilog", "verilator"]) +def test_concurrent_fabric(tmp_path, simulator): + files, includes = sources(tmp_path) + files.append(ROOT / "tests/rtl/tiny_r2_p5_fabric_tb.sv") + log = simulate(tmp_path, simulator, "tiny_r2_p5_fabric_tb", files, includes) + assert "random_reads=96 concurrent_faults=2" in log + + +@pytest.mark.parametrize("simulator", ["iverilog", "verilator"]) +def test_target_reset_isolation(tmp_path, simulator): + files, includes = sources(tmp_path) + files.append(ROOT / "rtl/ip/peripheral/dma_axi4_master.sv") + files.append(ROOT / "tests/rtl/tiny_r2_p5_reset_tb.sv") + log = simulate(tmp_path, simulator, "tiny_r2_p5_reset_tb", files, includes) + assert "SIM_TEST_PASS Tiny P5 target reset isolation" in log + assert "P5_RESET cases=28 production_dma=3 burst_beats=16 held_RB=1 first_fault=1" in log + + +@pytest.mark.parametrize("simulator", ["iverilog", "verilator"]) +def test_real_cpu_dma_progress_and_abort(tmp_path, simulator): + sources(tmp_path) + firmware = tmp_path / "cpu.elf" + run(["riscv32-unknown-elf-gcc", "-march=rv32imc_zicsr_zifencei", "-mabi=ilp32", + "-nostdlib", "-Wl,--build-id=none", "-T", str(ROOT / "tests/rtl/tiny_r2_p4_cpu.ld"), + str(ROOT / "tests/rtl/tiny_r2_p5_cpu.S"), "-o", str(firmware)], tmp_path, "firmware") + run(["riscv32-unknown-elf-objcopy", "-O", "verilog", str(firmware), str(tmp_path / "cpu.hex")], + tmp_path, "objcopy") + generate_filelists(tmp_path / "filelists", tmp_path / "bindings", []) + resolved = parse_filelists(tmp_path / "filelists" / name for name in ( + "inc.fl", "commonip.fl", "clusterip.fl", "core_hazard3.fl", "ip.fl", "tech.fl", "top.fl")) + # Only the XPI boundary is substituted; the production CPU/DMA/SoC stay real. + files = [p for p in resolved.files if p.name not in ("apb4_xpi.sv", "retrosoc_tiny_asic.sv")] + files.append(ROOT / "tests/rtl/tiny_r2_p5_system_tb.sv") + run(["python3", str(ROOT / "rtl/managed/clusterip/archinfo/scripts/generate_metadata.py"), + "--repo-root", str(ROOT), "--lock", str(ROOT / "dependencies/dependencies.lock.json"), + "--config-digest", "000000000000", "--output-sv", str(tmp_path / "archinfo_integration_metadata.svh"), + "--output-c", str(tmp_path / "archinfo_integration_metadata.h")], tmp_path, "metadata") + includes = [f"-I{p}" for p in (*resolved.incdirs, tmp_path, tmp_path / "map/rtl", + tmp_path / "bindings/rtl", ROOT / "rtl/tiny/dv")] + includes += ["-DPDK_BEHAV", "-DRETROSOC_SOC__TINY", "-DSOC_JTAG_IDCODE=32'hDEADBEEF", + "-DSOC_EXT_CLK_HZ=24000000"] + log = simulate(tmp_path, simulator, "tiny_r2_p5_system_tb", files, includes, + [f"+firmware={tmp_path / 'cpu.hex'}"]) + assert "hart_reset=1 abort_drained=1" in log + + +@pytest.mark.parametrize("kind", ["fabric", "fabric_reset", "fault"]) +def test_protocol_proofs(tmp_path, kind): + for tool in ("sv2v", "yosys"): + assert shutil.which(tool), f"required tool unavailable: {tool}" + files, includes = sources(tmp_path) + top = f"tiny_r2_p5_{'fabric' if kind == 'fabric_reset' else kind}_formal" + files.append(ROOT / f"tests/rtl/{top}.sv") + reset_define = ["-DTINY_P5_RESET_FORMAL"] if kind == "fabric_reset" else [] + converted = subprocess.run(["sv2v", "-DFORMAL", "-DSV_ASSRT_DISABLE", *reset_define, *includes, + *(str(p) for p in files)], text=True, capture_output=True, timeout=180) + (tmp_path / "sv2v.log").write_text(converted.stderr) + assert converted.returncode == 0, converted.stderr + (tmp_path / "formal.v").write_text(converted.stdout) + proof = "sat -prove-asserts -verify" if kind == "fault" else ( + "sat -seq 12 -tempinduct -prove-asserts -verify -set-init-zero") + script = (f"read_verilog -formal {tmp_path / 'formal.v'}; prep -top {top} -flatten; " + f"memory_map; async2sync; dffunmap; opt; {proof}") + log = run(["yosys", "-p", script], tmp_path, "formal", timeout=300) + assert "SUCCESS!" in log + + +def p5_log(): + from test_tiny_r2_p4 import p4_log + return p4_log() + "\n" + "\n".join( + f"R2_P5_FABRIC window={window} active_targets_max=3 " + "dma_rw_overlap_cycles=5 dma_rw_beat_overlap_cycles=2" for window in range(1, 9)) + + +def test_p5_records_keep_old_image_measurements_separate(): + samples = p5.parse_log(p5_log(), {"status": "passed", "exit_code": 0}) + assert len(samples["fabric"]) == 8 + assert len(samples["local"]) == 16 + assert p5.PHASE == "TINY-R2-P5" and p5.previous.PHASE == "TINY-R2-P4" + + +@pytest.mark.parametrize("mutation", ["missing", "duplicate", "negative", "overflow", "overlap", "serialized", "failure"]) +def test_p5_rejects_invalid_concurrency_evidence(mutation): + content = p5_log() + if mutation == "missing": + content = "\n".join(line for line in content.splitlines() if not line.startswith("R2_P5_FABRIC window=8")) + elif mutation == "duplicate": + content += "\n" + content.splitlines()[-1] + elif mutation == "negative": + content = content.replace("active_targets_max=3", "active_targets_max=-1") + elif mutation == "overflow": + content = content.replace("active_targets_max=3", "active_targets_max=24") + elif mutation == "overlap": + content = content.replace("dma_rw_beat_overlap_cycles=2", "dma_rw_beat_overlap_cycles=6") + elif mutation == "serialized": + content = content.replace("active_targets_max=3", "active_targets_max=1") + else: + content += "\nSIM_TEST_FAIL" + with pytest.raises(ValueError): + p5.parse_log(content, {"status": "passed", "exit_code": 0}) + + +@pytest.mark.parametrize("mutation", ["none", "phase", "source", "cycles", "image", "local"]) +def test_p5_reference_is_bound_to_p4_execution(tmp_path, monkeypatch, mutation): + case = {"name": "flat", "image": {"path": "immutable"}, + "samples": {"cases": [{"name": "dma16", "cycles": 17897, "instructions": 10}], + "local": [], "banks": []}} + report = {"phase": "TINY-R2-P4", "status": "measurement_complete", "source_digest": "original", + "executions": {"verilator": {}}, "comparisons": [ + {"name": "flat", "image": {"path": "immutable"}, "local": [], "banks": [], + "workloads": [{"name": "dma16", "candidate_cycles": 17897, "candidate_instructions": 10}]}]} + monkeypatch.setattr(p5.previous, "validate_replay", lambda *args: { + "source_digest": "original", "cases": [case]}) + monkeypatch.setattr(p5.b, "check_artifact", lambda artifact: tmp_path / "record.json") + if mutation == "phase": + report["phase"] = "TINY-R2-P5" + elif mutation == "source": + report["source_digest"] = "other" + elif mutation == "cycles": + report["comparisons"][0]["workloads"][0]["candidate_cycles"] = 1 + elif mutation == "image": + report["comparisons"][0]["image"]["path"] = "different" + elif mutation == "local": + report["comparisons"][0]["local"] = [{}] + path = tmp_path / "report.json" + path.write_text(json.dumps(report)) + if mutation == "none": + assert p5.reference_cases(path, ["flat"]) == [case] + else: + with pytest.raises(ValueError): + p5.reference_cases(path, ["flat"]) diff --git a/tests/test_tiny_r2_p6.py b/tests/test_tiny_r2_p6.py new file mode 100644 index 00000000..574940e3 --- /dev/null +++ b/tests/test_tiny_r2_p6.py @@ -0,0 +1,547 @@ +"""P6 product integration and fail-closed SDK/macro contracts.""" +from __future__ import annotations + +import json +import hashlib +import io +from pathlib import Path +import re +import shutil +import subprocess +import tarfile + +import pytest + +from scripts.generate_tiny import generate_bindings, read_topology +from scripts.rtl.generate_memory_map import generate as generate_memory_map +from scripts.check_clock_reset_domains import validate as validate_clocks +from test_tiny_r2_p4 import run +from test_sdio import SDIO_FULL_SOURCES, SDIO_FULL_INCDIRS +from scripts.check_simulation import DEFAULT_FAILURE +from scripts import tiny_r2_p6 as evidence + +ROOT = Path(__file__).resolve().parents[1] +TINY = ROOT / "rtl/tiny" + + +def test_native_card_model_elaborates_without_sv2v(tmp_path): + run(["iverilog", "-g2012", "-s", "sdio_native_card_model", "-o", str(tmp_path / "sim"), + str(ROOT / evidence.MODEL)], tmp_path, "compile") + + +@pytest.mark.parametrize("mutation", ["none", "expression", "body", "still_forward"]) +def test_only_continuous_declaration_relocation_is_equivalent(mutation): + after = (ROOT / evidence.MODEL).read_text() + declaration = re.search(r"^ wire \[3:0\] s_token_lanes = [^;]+;\n", after, re.M).group() + before = after.replace(declaration, "").replace(" integer s_data_bytes;\n", + declaration + " integer s_data_bytes;\n") + if mutation == "expression": + after = after.replace("s_data_bytes >= 4", "s_data_bytes >= 3") + elif mutation == "body": + after = after.replace("s_data_bytes = (bytes_i < 1) ? 1 : bytes_i;", + "s_data_bytes = 1;") + assert after != (ROOT / evidence.MODEL).read_text() + elif mutation == "still_forward": + after = before + if mutation == "none": + evidence.check_model_declaration_move(before, after) + else: + with pytest.raises(ValueError): + evidence.check_model_declaration_move(before, after) + + +@pytest.mark.parametrize("mutation", ["none", "dut", "model", "tool"]) +def test_verification_update_cannot_relabel_dut_changes(tmp_path, mutation): + after = (ROOT / evidence.MODEL).read_text() + declaration = re.search(r"^ wire \[3:0\] s_token_lanes = [^;]+;\n", after, re.M).group() + before = after.replace(declaration, "").replace(" integer s_data_bytes;\n", + declaration + " integer s_data_bytes;\n") + if mutation == "model": + after = after.replace("s_data_bytes >= 4", "s_data_bytes >= 3") + dut = "rtl/tiny/top/retrosoc_tiny.sv" + documents = [{evidence.MODEL: before, dut: "unchanged DUT"}, + {evidence.MODEL: after, dut: "changed DUT" if mutation == "dut" else "unchanged DUT"}] + snapshots = [] + maps = [] + for index, documents_by_name in enumerate(documents): + folder = tmp_path / str(index) + folder.mkdir() + hashes = {name: hashlib.sha256(text.encode()).hexdigest() for name, text in documents_by_name.items()} + maps.append(hashes) + evidence.b.dump(folder / "inputs.json", hashes) + (folder / "patch").write_text("unit-test snapshot") + with tarfile.open(folder / "source.tar", "w") as archive: + for name, text in documents_by_name.items(): + info = tarfile.TarInfo(name) + info.size = len(text.encode()) + archive.addfile(info, io.BytesIO(text.encode())) + snapshots.append({key: evidence.b.artifact(folder / name) for key, name in + (("inputs", "inputs.json"), ("patch", "patch"), ("snapshot", "source.tar"))}) + saved = {"source": snapshots[0], "tools": {"unit-test": 1}, "captured_at": "2026-10-12T00:00:00+00:00"} + evidence.b.dump(tmp_path / "source.json", saved) + changed = sorted(name for name in maps[0] if maps[0][name] != maps[1][name]) + update = {"phase": evidence.PHASE, "kind": "native_model_declaration_order", + "original_source": evidence.b.artifact(tmp_path / "source.json"), "source": snapshots[1], + "tools": {"unit-test": 2 if mutation == "tool" else 1}, "changed_inputs": changed, + "captured_at": "2026-10-12T00:01:00+00:00"} + if mutation == "none": + assert evidence.check_update(tmp_path, saved, update, maps[1]) == maps[0] + else: + with pytest.raises(ValueError): + evidence.check_update(tmp_path, saved, update, maps[1]) + + +@pytest.mark.parametrize("scope", ["consumer", "collection"]) +@pytest.mark.parametrize("mutation", ["none", "dut", "firmware", "smoke", "model", "clock_contract", "other_consumer", "tool"]) +def test_consumer_snapshot_cannot_change_running_flow_inputs(tmp_path, mutation, scope): + consumer = "publications/programming_reference.py" if scope == "consumer" else "scripts/tiny_r2_p6.py" + forbidden = { + "dut": "rtl/tiny/top/retrosoc_tiny.sv", "firmware": "app/apps/ci_smoke/tiny.c", + "smoke": "physical/smoke/sta/opensta/generate_sdc.py", "model": evidence.MODEL, + "clock_contract": "rtl/tiny/integration/clock_reset_domains.json", + "other_consumer": "scripts/regress.py" if scope == "consumer" else "publications/programming_reference.py", + } + old = {consumer: "old", **{name: "same" for name in forbidden.values()}} + new = dict(old, **{consumer: "new"}) + if mutation in forbidden: + new[forbidden[mutation]] = "changed" + snapshots = [] + for index, values in enumerate((old, new)): + folder = tmp_path / str(index) + folder.mkdir() + evidence.b.dump(folder / "inputs", values) + for name in ("snapshot", "patch"): + (folder / name).write_text("unit-test consumer artifact") + snapshots.append({name: evidence.b.artifact(folder / name) for name in ("inputs", "snapshot", "patch")}) + previous = {"source": snapshots[0], "tools": {"unit-test": 1}, "captured_at": "2026-10-12T00:00:00+00:00"} + prior = "verification-update.json" if scope == "consumer" else "consumer-update.json" + kind = "publication_and_commercial_consumers" if scope == "consumer" else "terminal_log_collection" + check = evidence.check_consumer_update if scope == "consumer" else evidence.check_collection_update + evidence.b.dump(tmp_path / prior, previous) + update = {"phase": evidence.PHASE, "kind": kind, + "previous_update": evidence.b.artifact(tmp_path / prior), + "source": snapshots[1], "tools": {"unit-test": 2 if mutation == "tool" else 1}, + "changed_inputs": sorted(name for name in old if old[name] != new[name]), + "captured_at": "2026-10-12T00:01:00+00:00"} + if mutation == "none": + check(tmp_path, previous, update, new) + else: + with pytest.raises(ValueError): + check(tmp_path, previous, update, new) + + +@pytest.mark.parametrize("simulator,top", [("verilator", "sdio_standalone_tb"), + ("verilator", "tiny_p6_sdio_pads_tb"), + ("iverilog", "tiny_p6_sdio_pads_tb")]) +def test_shared_sdio_native_data(tmp_path, simulator, top): + sources = [ROOT / name for name in (*SDIO_FULL_SOURCES, "tests/rtl/sdio_standalone_tb.sv")] + output = tmp_path / "sim" + includes = ["-I" + str(ROOT / p) for p in SDIO_FULL_INCDIRS] + if simulator == "iverilog": + converted = subprocess.run(["sv2v", *includes, *map(str, sources)], capture_output=True, + text=True, timeout=180) + assert converted.returncode == 0, converted.stderr + (tmp_path / "converted.v").write_text(converted.stdout) + run([simulator, "-g2012", "-s", top, "-o", str(output), str(tmp_path / "converted.v")], + tmp_path, "compile") + launch = ["vvp", str(output)] + else: + run([simulator, "--binary", "--timing", "--assert", "-Wno-fatal", "-j", "3", + "--top-module", top, *includes, *map(str, sources), + "--Mdir", str(tmp_path / "obj"), "-o", str(output)], tmp_path, "compile", timeout=180) + launch = [str(output)] + assert "SDIO standalone native SD model test passed" in run(launch, tmp_path, "sim") + + +@pytest.mark.parametrize("simulator", ["iverilog", "verilator"]) +def test_shared_sdio_running_clock_commands(tmp_path, simulator): + sources = [ROOT / "rtl/ip/storage" / name for name in + ("sdio_pkg.sv", "sdio_clock.sv", "sdio_command.sv")] + sources += [ROOT / "tests/rtl/sdio_command_tb.sv"] + output = tmp_path / "sim" + if simulator == "iverilog": + converted = subprocess.run(["sv2v", *map(str, sources)], capture_output=True, + text=True, timeout=60) + assert converted.returncode == 0, converted.stderr + (tmp_path / "converted.v").write_text(converted.stdout) + run([simulator, "-g2012", "-s", "sdio_command_tb", "-o", str(output), + str(tmp_path / "converted.v")], tmp_path, "compile") + launch = ["vvp", str(output)] + else: + run([simulator, "--binary", "--timing", "-Wno-fatal", "--top-module", "sdio_command_tb", + *map(str, sources), "--Mdir", str(tmp_path / "obj"), "-o", str(output)], + tmp_path, "compile", timeout=120) + launch = [str(output)] + assert "SDIO command engine test passed" in run(launch, tmp_path, "sim") + + +@pytest.mark.parametrize("simulator", ["iverilog", "verilator"]) +def test_apb_read_strobes_after_writes(tmp_path, simulator): + common = ROOT / "rtl/managed/clusterip/common/rtl" + generate_memory_map(TINY / "address_map/memory_map.json", tmp_path / "map", "YES", 128) + sources = [common / "utils/register.sv", common / "interface/axi4_if.sv", + common / "interface/apb4_if.sv", TINY / "top/tiny_axi42apb4.sv", + ROOT / "tests/rtl/tiny_r2_p6_bridge_tb.sv"] + flags = ["-DSV_ASSRT_DISABLE", "-I" + str(common), "-I" + str(common / "interface"), + "-I" + str(tmp_path / "map/rtl")] + top = "tiny_r2_p6_bridge_tb" + output = tmp_path / "sim" + if simulator == "iverilog": + converted = subprocess.run(["sv2v", *flags, *map(str, sources)], capture_output=True, + text=True, timeout=60) + assert converted.returncode == 0, converted.stderr + (tmp_path / "converted.v").write_text(converted.stdout) + run([simulator, "-g2012", "-s", top, "-o", str(output), str(tmp_path / "converted.v")], + tmp_path, "compile") + launch = ["vvp", str(output)] + else: + run([simulator, "--binary", "--timing", "--assert", "-Wno-fatal", "--top-module", top, + *flags, *map(str, sources), "--Mdir", str(tmp_path / "obj"), "-o", str(output)], + tmp_path, "compile", timeout=120) + launch = [str(output)] + assert "SIM_TEST_PASS Tiny P6 APB read strobes" in run(launch, tmp_path, "sim") + + +def p6_log(): + rows = ["R2_P6_" + name + " " + " ".join(f"{k}={v}" for k, v in fields.items()) + for name, fields in evidence.RECORDS.items()] + rows += [f"R2_P6_AUDIO bits={bits} frames=256 ws_frames=2 background_jobs=8 " + f"tcd={int(bits == 24)} elapsed_us=7000 cycles=168000 instructions=40000" + for bits in (16, 24)] + rows += [f"R2_P6_I2S_WIRE bits={bits} samples=512" for bits in (16, 24)] + rows += [f"R2_P6_AXI owner={owner} read_bytes=1024 write_bytes=1024 " + f"descriptor_bytes={256 if owner == 1 else 0} ar_stall_max=3 aw_stall_max=3 " + "w_stall_max=3 r_stall_max=3 b_stall_max=3" for owner in range(3)] + rows += ["R2_P6_CONTENTION sdio_dma_cycles=100 cpu_fetches=10"] + return "\n".join([*rows, "R2_P6_DEVICES ws_frames=4 ws_wait_max=100 fifo_max=16 " + "underflows=1 aborts=1 clock_stops=2", "SIM_TEST_PASS Tiny cycles=200000"]) + + +def test_terminal_verdict_survives_partial_uart_line(): + flow = {"status": "passed", "exit_code": 0} + content = p6_log().replace("SIM_TEST_PASS Tiny cycles=", "Tiny acceptance passed coSIM_TEST_PASS Tiny cycles=") + assert evidence.parse_log(content, flow)["uart"]["echo_bytes"] == 4 + for replacement in ("SIM_TEST_PASS Tiny JTAG transport halt/resume", "Hello retroSoC!", + "SIM_TEST_PASS Mini cycles=200000", "SIM_TEST_PASS Tiny cycles=unknown"): + with pytest.raises(ValueError): + evidence.parse_log(content.replace("SIM_TEST_PASS Tiny cycles=200000", replacement), flow) + + +@pytest.mark.parametrize("mutation", ["none", "failed_command", "missing", "duplicate", "budget", + "overflow", "false_qualification", "error", "no_terminal"]) +def test_p6_evidence_is_fail_closed(mutation): + log = p6_log() + flow = {"status": "passed", "exit_code": 0} + if mutation == "failed_command": + flow["exit_code"] = 1 + elif mutation == "missing": + log = "\n".join(line for line in log.splitlines() if not line.startswith("R2_P6_RNG")) + elif mutation == "duplicate": + log += "\n" + log.splitlines()[0] + elif mutation == "budget": + log = log.replace("ws_wait_max=100", "ws_wait_max=4801") + elif mutation == "overflow": + log = log.replace("fifo_max=16", "fifo_max=17") + elif mutation == "false_qualification": + log = log.replace("unqualified=1", "unqualified=0") + elif mutation == "error": + log += "\nSIM_TEST_FAIL" + elif mutation == "no_terminal": + log = log.replace("SIM_TEST_PASS", "Hello retroSoC!") + if mutation == "none": + assert len(evidence.parse_log(log, flow)["audio"]) == 2 + else: + with pytest.raises(ValueError): + evidence.parse_log(log, flow) + + +@pytest.mark.parametrize("mutation", ["none", "failed", "old", "unfinished", "reversed"]) +def test_p6_gate_chronology(tmp_path, mutation): + flow = {"status": "passed", "exit_code": 0, + "started_at": "2026-10-12T01:00:00+00:00", "finished_at": "2026-10-12T01:01:00+00:00"} + if mutation == "failed": + flow["exit_code"] = 1 + elif mutation == "old": + flow["started_at"] = "2026-10-11T01:00:00+00:00" + elif mutation == "unfinished": + del flow["finished_at"] + elif mutation == "reversed": + flow["finished_at"] = "2026-10-12T00:30:00+00:00" + path = tmp_path / "gate.json" + path.write_text(json.dumps(flow)) + if mutation == "none": + assert evidence.checked_flow(path, "2026-10-12T00:59:00+00:00") == flow + else: + with pytest.raises(ValueError): + evidence.checked_flow(path, "2026-10-12T00:59:00+00:00") + + +@pytest.mark.parametrize("mutation", ["none", "source", "tool", "old_model", "missing_image"]) +def test_p6_complete_collection_requires_current_inputs(tmp_path, monkeypatch, mutation): + """Exercise real artifact retention with synthetic, explicitly unit-test flows.""" + directory = tmp_path / "evidence" + directory.mkdir() + source = tmp_path / "source.sv" + source.write_text("unit-test source\n") + source_inputs = {"source.sv": evidence.b.sha256(source)} + evidence.b.dump(directory / "inputs.json", source_inputs) + for name in ("patch", "snapshot"): + (directory / name).write_text("unit-test artifact\n") + saved_source = {name: evidence.b.artifact(directory / filename) for name, filename in + (("inputs", "inputs.json"), ("patch", "patch"), ("snapshot", "snapshot"))} + variants = [tmp_path / pdk for pdk in ("ICS55", "IHP130")] + captured = "2026-10-12T00:00:00+00:00" + evidence.b.dump(directory / "source.json", {"phase": evidence.PHASE, "source": saved_source, + "tools": {"unit-test": 1}, "captured_at": captured, "variants": list(map(str, variants))}) + monkeypatch.setattr(evidence, "ROOT", tmp_path) + monkeypatch.setattr(evidence, "inputs", lambda _: {"source.sv": evidence.b.sha256(source)}) + monkeypatch.setattr(evidence, "current_tools", lambda: {"unit-test": 2 if mutation == "tool" else 1}) + monkeypatch.setattr(evidence.b, "configuration", lambda p: { + "configuration": {"PDK": p.name, "APP": "ci_smoke"}}) + monkeypatch.setattr(evidence.b, "runtime_inputs", lambda *_: {"unit-test": "explicit fixture"}) + for variant in variants: + for relative in ("sw/firmware", "sw/retrosoc_fw.hex", "sw/retrosoc_fw.map", "sw/retrosoc_fw.bin", + "sw/ld2_all_sram.lds", "sw/memory_regions.ld", "meta/manifest.json"): + path = variant / relative + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("unit-test artifact\n") + for simulator in ("iverilog", "verilator"): + folder = variant / "sim" / simulator + folder.mkdir(parents=True) + for name in ("compile.log", "simv" if simulator == "iverilog" else "emu"): + (folder / name).write_text("unit-test artifact\n") + (folder / "sim.log").write_text(p6_log()) + evidence.b.dump(folder / "result-sim-check.json", {"status": "passed"}) + for label, start, end in (("compile", "01", "02"), ("sim", "03", "04")): + evidence.b.dump(folder / f"result-{label}.json", {"status": "passed", "exit_code": 0, + "started_at": f"2026-10-12T00:{start}:00+00:00", + "finished_at": f"2026-10-12T00:{end}:00+00:00"}) + if mutation == "source": + source.write_text("changed unit-test source\n") + elif mutation == "old_model": + path = variants[0] / "sim/iverilog/result-compile.json" + flow = evidence.b.read_json(path) + flow["started_at"] = "2026-10-11T00:00:00+00:00" + evidence.b.dump(path, flow) + elif mutation == "missing_image": + (variants[0] / "sw/retrosoc_fw.hex").unlink() + if mutation == "none": + evidence.report(directory, [], variants) + report = evidence.b.read_json(directory / "report.json") + assert report["status"] == "functional_evidence_collected" + assert len(report["simulations"]) == 4 + for simulation in report["simulations"].values(): + for artifact in simulation["artifacts"]: + evidence.b.check_artifact(artifact) + else: + with pytest.raises((ValueError, FileNotFoundError)): + evidence.report(directory, [], variants) + assert not (directory / "report.json").exists() + + +@pytest.mark.parametrize("name", ["rng", "crc"]) +def test_locked_controller_contracts(tmp_path, name): + for tool in ("iverilog", "vvp", "verilator", "cc"): + assert shutil.which(tool), f"required tool unavailable: {tool}" + source = ROOT / "rtl/managed/clusterip" / name + result = subprocess.run(["make", "-C", str(source), f"BUILD_DIR={tmp_path}", + "register-check", "test"], text=True, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, timeout=180) + (tmp_path / "build.log").write_text(result.stdout) + assert result.returncode == 0, result.stdout[-12000:] + for simulator, marker in (("iverilog", f"{name.upper()}_TEST_PASS"), + ("verilator", f"APB4_{name.upper()}_TEST_PASS")): + log = (tmp_path / simulator / "test.log").read_text() + assert marker in log and not DEFAULT_FAILURE.search(log), log + + +@pytest.mark.parametrize("simulator", ["iverilog", "verilator"]) +def test_shared_i2s_payload(tmp_path, simulator): + common = ROOT / "rtl/managed/clusterip/common/rtl" + serial = ROOT / "rtl/ip/serial" + for tool in (simulator, "sv2v", "vvp"): + assert shutil.which(tool), f"required tool unavailable: {tool}" + sources = [common / name for name in ( + "interface/apb4_if.sv", "interface/axi4_stream_if.sv", "utils/register.sv", + "utils/bin2gray.sv", "utils/gray2bin.sv", "utils/spill_register.sv", + "cdc/cdc_sync.sv", "cdc/cdc_rst_ctrlr.sv", "cdc/async_reqack.sv", "cdc/cdc_fifo.sv", + "cdc/cdc_2phase.sv", "cdc/cdc_warm_flush.sv", "utils/edge_det.sv", "clkrst/rst_sync.sv")] + sources += [ROOT / "rtl/tech/tc_clk.sv"] + sources += [serial / name for name in ("i2s_pkg.sv", "i2s_reg.sv", "i2s_clkgen.sv", + "i2s_recv.sv", "i2s_send.sv", "i2s_core.sv", "apb4_i2s.sv")] + sources += [ROOT / "tests/rtl/tiny_r2_p6_shared_tb.sv"] + flags = ["-DPDK_BEHAV", "-DSV_ASSRT_DISABLE", *[f"-I{p}" for p in ( + common, common / "interface", common / "utils", common / "cdc", common / "clkrst", serial)]] + top = "tiny_r2_p6_shared_tb" + output = tmp_path / "sim" + if simulator == "iverilog": + converted = subprocess.run(["sv2v", *flags, *map(str, sources)], capture_output=True, + text=True, timeout=180) + assert converted.returncode == 0, converted.stderr + (tmp_path / "converted.v").write_text(converted.stdout) + run([simulator, "-g2012", "-s", top, "-o", str(output), str(tmp_path / "converted.v")], + tmp_path, "compile", timeout=180) + launch = ["vvp", str(output)] + else: + run([simulator, "--binary", "--timing", "--assert", "-Wno-fatal", "-j", "3", + "--top-module", top, *flags, *map(str, sources), "--Mdir", str(tmp_path / "obj"), + "-o", str(output)], tmp_path, "compile", timeout=180) + launch = [str(output)] + content = run(launch, tmp_path, "sim", timeout=180) + assert "SIM_TEST_PASS Tiny P6 I2S payload" in content + for bits in (16, 24): + assert f"P6_I2S bits={bits} samples=512" in content + + +def test_p6_map_irq_request_and_pin_contract(): + topology = read_topology(TINY / "integration/soc_topology.json", TINY / "address_map/memory_map.json") + regions = {r["symbol"]: int(r["base"], 0) for r in json.loads( + (TINY / "address_map/memory_map.json").read_text())["regions"]} + for name, base in {"RNG": 0x20001000, "CRC": 0x20006000, "WS2812": 0x10008000, + "CRYPTO": 0x1000C000, "I2S": 0x10007000, "SDIO0": 0x1000F000}.items(): + assert regions["APB4_" + name] == base + assert not {"APB4_UART1", "APB4_I2C1", "APB4_DVP", "APB4_SPI0", "APB4_PIOLITE"} & regions.keys() + irqs = {i["name"]: i["core_bit"] for i in topology["interrupts"]} + assert {k: irqs[k] for k in ("rng", "ws2812", "crypto", "i2s", "sdio")} == { + "rng": 16, "ws2812": 17, "crypto": 23, "i2s": 8, "sdio": 10} + assert {15, 19, 24, 25, 26, 27}.isdisjoint(irqs.values()) + assert irqs["rcu"] == 31 + pins = topology["gpio_alt_functions"] + assert pins[9]["alt0"]["inputs"] == ["u_uart0_if.rx_i"] + assert pins[10]["alt0"]["inputs"] == ["u_i2c0_if.scl_i"] + assert pins[11]["alt0"]["inputs"] == ["u_i2c0_if.sda_i"] + assert pins[26]["alt0"]["do"] == "u_ws2812_if.dat_o" + for pin in (24, 25): + assert pins[pin]["alt0"]["inputs"] == [f"u_pwm_if.capture_i[{pin - 24}]"] + assert pins[pin]["alt1"]["inputs"] == [] + for pin in range(12, 23): + assert pins[pin]["alt1"]["oe"] == "1'b0" + assert pins[23]["alt1"]["do"] == pins[28]["alt0"]["do"] == "s_clkout" + assert pins[23]["alt1"]["oe"] == pins[28]["alt0"]["oe"] == "s_clkout_enable" + validate_clocks(TINY / "integration/clock_reset_domains.json", ROOT) + + +def test_duplicate_input_routes_are_rejected(tmp_path): + topology = json.loads((TINY / "integration/soc_topology.json").read_text()) + topology["gpio_alt_functions"][25]["alt1"]["inputs"] = ["u_uart0_if.rx_i"] + path = tmp_path / "topology.json" + path.write_text(json.dumps(topology)) + with pytest.raises(ValueError, match="exclusive route"): + read_topology(path, TINY / "address_map/memory_map.json") + + +@pytest.mark.parametrize("enabled", ["YES", "NO"]) +def test_tiny_sva_build_selection_reaches_owned_assertions(enabled): + result = subprocess.run(["make", "-n", "CONFIG=configs/ci/ics55-tiny.mk", "SOC=TINY", + "PDK=ICS55", "SIMU=VERILATOR", f"HAVE_SVA={enabled}", + "BUILD_TIMESTAMP=2099-01-01-00-00", "comp"], cwd=ROOT, + capture_output=True, text=True, check=True) + assert ("+define+HAVE_SVA" in result.stdout) == (enabled == "YES") + assert ("+define+SV_ASSRT_DISABLE" in result.stdout) == (enabled == "NO") + + +@pytest.mark.parametrize("simulator", ["iverilog", "verilator"]) +def test_board_pullups_survive_partial_external_drivers(tmp_path, simulator): + assert shutil.which(simulator), f"required simulator unavailable: {simulator}" + generate_bindings(read_topology(TINY / "integration/soc_topology.json", + TINY / "address_map/memory_map.json"), tmp_path) + source = tmp_path / "board.sv" + drivers = "\n".join(f"assign s_gpio_{n}=oe[{n}] ? data[{n}] : 1'bz;" for n in range(32)) + source.write_text('module board;\n`include "tiny_tb_gpio.svh"\n' + + "logic clk=0; logic [31:0] oe=0, data=0; always #5 clk=!clk;\n" + drivers + + "\nassign s_gpio_17=clk; assign s_gpio_15=s_gpio_14;\n" + r''' +initial begin + #21; + if (s_gpio[11:10] !== 2'b11) $fatal(1, "lost board pullups"); + oe[10]=1; + #10; + if (s_gpio[11:10] !== 2'b10) $fatal(1, "open-drain drive/readback"); + oe[10]=0; + #10; + if (s_gpio[11:10] !== 2'b11) $fatal(1, "open-drain release/readback"); + $display("SIM_TEST_PASS Tiny P6 board pulls"); + $finish; +end +endmodule +''') + output = tmp_path / "sim" + flags = ["-I" + str(tmp_path / "rtl")] + if simulator == "iverilog": + run([simulator, "-g2012", "-s", "board", *flags, "-o", str(output), str(source)], + tmp_path, "compile") + launch = ["vvp", str(output)] + else: + run([simulator, "--binary", "--timing", "-Wno-fatal", "--top-module", "board", + *flags, str(source), "--Mdir", str(tmp_path / "obj"), "-o", str(output)], + tmp_path, "compile", timeout=180) + launch = [str(output)] + assert "SIM_TEST_PASS Tiny P6 board pulls" in run(launch, tmp_path, "sim") + + +@pytest.mark.parametrize("tiny", [True, False]) +def test_dma_sdk_request_matrix(tmp_path, tiny): + assert shutil.which("cc"), "host compiler required" + if tiny: + generate_bindings(read_topology(TINY / "integration/soc_topology.json", + TINY / "address_map/memory_map.json"), tmp_path) + else: + extension = tmp_path / "include/retrosoc/generated/user_extensions.h" + extension.parent.mkdir(parents=True) + extension.write_text("") + generate_memory_map(ROOT / ("rtl/tiny" if tiny else "rtl/mini") / "address_map/memory_map.json", + tmp_path / "map", "YES", 128 if tiny else 32) + probe = tmp_path / "probe.c" + probe.write_text(r''' +#include +#include +int main(void) { + _Alignas(64) rs_dma_tcd_t tcd = {0}; + rs_dma_config_t c = {.kind=RS_DMA_KIND_MM_TO_MM, .source=0x30000000U, + .destination=0x30010000U, .byte_count=64U, .width=RS_DMA_WIDTH_32, + .source_increment=true, .destination_increment=true, .burst_beats=16U}; + for (unsigned channel=0; channel<8; ++channel) { + for (unsigned request=0; request<16; ++request) { + c.request=(rs_dma_request_t)request; + c.kind=(request==1 || request==12) ? RS_DMA_KIND_MM_TO_STREAM : + ((request==2 || request==11 || request==13) ? RS_DMA_KIND_STREAM_TO_MM : + RS_DMA_KIND_MM_TO_MM); +#ifdef RS_SOC_TINY + bool supported=(0x31ffU & (1U << request)) != 0U; +#else + bool supported=request < 14U; +#endif + assert((rs_dma_config_validate(channel, &c)==RS_OK)==supported); + tcd.source=c.source; tcd.destination=c.destination; tcd.byte_count=64U; + tcd.control=RS_DMA_TCD_VALID | RS_DMA_TCD_SRC_INC | RS_DMA_TCD_DST_INC | + ((unsigned)c.kind << RS_DMA_TCD_KIND_SHIFT) | + (request << RS_DMA_TCD_REQUEST_SHIFT) | (16U << RS_DMA_TCD_BURST_SHIFT); + assert((rs_dma_tcd_validate(channel, &tcd)==RS_OK)==supported); + } + } + assert(rs_dma_config_validate(8, &c)!=RS_OK); + c.request=(rs_dma_request_t)16; + assert(rs_dma_config_validate(0, &c)!=RS_OK); + c.request=(rs_dma_request_t)17; + assert(rs_dma_config_validate(0, &c)!=RS_OK); + c.request=(rs_dma_request_t)255; + assert(rs_dma_config_validate(0, &c)!=RS_OK); + return 0; +} +''') + binary = tmp_path / "probe" + command = ["cc", "-std=c11", "-Wall", "-Wextra", "-Werror", + "-I" + str(ROOT / "crt/include"), "-I" + str(tmp_path / "include"), + "-I" + str(tmp_path / "map/include"), str(probe), + str(ROOT / "crt/src/hal/dma_math.c"), "-o", str(binary)] + subprocess.run(command, check=True, capture_output=True, text=True) + subprocess.run([str(binary)], check=True, capture_output=True, text=True) + + +def test_p6_reset_and_macro_policy_remains_explicit(): + text = (TINY / "top/retrosoc_tiny.sv").read_text() + assert ".clk_aud_i" in text and "s_audio_rst_n" in text + assert ".EnableStreams" in text and "16'h31ff" in text + assert "apb4_crypto u_crypto" in text + tree = (TINY / "top/tiny_reset_tree.sv").read_text() + assert "s_audio" not in tree and "crypto" not in tree + assert ".STAGE(5)" in tree diff --git a/tests/test_tiny_r2_p7.py b/tests/test_tiny_r2_p7.py new file mode 100644 index 00000000..a17e814a --- /dev/null +++ b/tests/test_tiny_r2_p7.py @@ -0,0 +1,294 @@ +"""Tiny P7 clock, reset and crossing contracts; behavioral evidence only.""" + +from __future__ import annotations + +import os +import re +import shutil +import subprocess +from pathlib import Path + +import pytest + +from scripts.check_simulation import DEFAULT_FAILURE +from scripts.generate_tiny import generate_bindings, read_topology +from scripts.tiny_r2_p7 import inspect_netlist +from scripts.rtl.generate_memory_map import generate as generate_memory_map +from test_tiny_ics55_platform import mapped_netlist # noqa: F401 -- shared synthetic fixture + +ROOT = Path(__file__).resolve().parents[1] +COMMON = ROOT / "rtl/managed/clusterip/common/rtl" + + +def run_rtl(tmp_path: Path, simulator: str, fixture: str, extra: list[Path], + defines: list[str] | None = None, plusargs: list[str] | None = None, + packages: list[Path] | None = None) -> str: + for tool in (simulator, *([] if simulator == "verilator" else ["sv2v", "vvp"])): + assert shutil.which(tool), f"required tool unavailable: {tool}" + # Put the fixture first so its explicit timescale applies during sv2v. + sources = [*(packages or []), ROOT / f"tests/rtl/{fixture}.sv", + *[COMMON / p for p in ( + "utils/register.sv", "utils/xchecker.sv", "utils/bin2gray.sv", + "utils/gray2bin.sv", "interface/apb4_if.sv", "interface/axi4_if.sv", + "cdc/cdc_sync.sv", "cdc/cdc_rst_ctrlr.sv", "cdc/cdc_2phase.sv", + "clkrst/rst_sync.sv", "clock/safe_clock_mux.sv", "clock/clock_divider.sv")], + *extra] + includes = [f"-I{p}" for p in (COMMON, COMMON / "interface", ROOT / "tests/rtl", + ROOT / "rtl/tiny/top")] + args = ["-DSV_ASSRT_DISABLE", *(defines or []), *includes, *(str(p) for p in sources)] + output = tmp_path / "sim" + + def execute(command: list[str], log: str, timeout: int = 120) -> str: + result = subprocess.run(command, cwd=ROOT, capture_output=True, text=True, + env={**os.environ, "CCACHE_DISABLE": "1"}, timeout=timeout) + content = result.stdout + result.stderr + (tmp_path / log).write_text(content) + assert result.returncode == 0, content + return content + + if simulator == "verilator": + execute([simulator, "--binary", "--timing", "--assert", "--timescale", "1ns/1ps", + "-Wno-fatal", "--top-module", fixture, *args, "--Mdir", str(tmp_path / "obj"), + "-o", str(output)], "compile.log") + launch = [str(output)] + else: + converted = subprocess.run(["sv2v", *args], cwd=ROOT, capture_output=True, + text=True, timeout=60) + assert converted.returncode == 0, converted.stderr + # sv2v strips timescale directives; restore the fixture's declared + # units so fractional PLL/reference delays are not rounded to seconds. + (tmp_path / "converted.v").write_text("`timescale 1ns / 1ps\n" + converted.stdout) + execute([simulator, "-g2012", "-s", fixture, "-o", str(output), + str(tmp_path / "converted.v")], "compile.log") + launch = ["vvp", str(output)] + content = execute([*launch, *(plusargs or [])], "sim.log", 30) + assert not DEFAULT_FAILURE.search(content), content + assert "SIM_TEST_PASS" in content, content + return content + + +@pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) +def test_tiny_apb_async_bridge(tmp_path: Path, simulator: str) -> None: + run_rtl(tmp_path, simulator, "tiny_apb_async_bridge_tb", + [ROOT / "rtl/tiny/top/tiny_apb_async_bridge.sv"]) + + +@pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) +@pytest.mark.parametrize("scenario", ["reset", "same_cycle_clear", "same_cycle_write", + "held_read_stability", "held_write_stability", "burst_abort", + "clear_pending", "same_cycle_reset_read", "same_cycle_reset_write"]) +def test_tiny_axi4_async_bridge_reset_abort(tmp_path: Path, simulator: str, scenario: str) -> None: + run_rtl(tmp_path, simulator, "tiny_axi4_async_bridge_tb", [ + COMMON / "cdc/async_reqack.sv", + COMMON / "cdc/cdc_warm_flush.sv", + COMMON / "cdc/cdc_fifo.sv", + COMMON / "utils/spill_register.sv", + ROOT / "rtl/tiny/top/tiny_axi4_async_bridge.sv", + ], plusargs=[] if scenario == "reset" else [f"+{scenario}"]) + + +def source_reset_bindings(tmp_path: Path) -> None: + """Exercise production reset/admission wiring, not a copied test policy.""" + top = (ROOT / "rtl/tiny/top/retrosoc_tiny.sv").read_text() + topology = read_topology(ROOT / "rtl/tiny/integration/soc_topology.json", + ROOT / "rtl/tiny/address_map/memory_map.json") + generate_bindings(topology, tmp_path / "bindings") + generated = (tmp_path / "bindings/rtl/tiny_apb_interfaces.svh").read_text() + + def instance(source: str, module: str, name: str) -> str: + matches = re.findall(rf"\b{module}\s*(?:#\([^;]*?\)\s*)?{name}\s*\([^;]*?\);", source) + assert len(matches) == 1, (module, name) + return matches[0] + + blocks = [instance(top, module, name) for module, name in ( + ("tiny_reset_tree", "u_reset_tree"), + ("tiny_peripheral_control", "u_peripheral_control"), + ("tiny_axi4_admission", "u_dma_admission"), + ("tiny_axi4_async_bridge", "u_dma_cdc"), + *(("cdc_sync", name) for name in ("u_block_pclk", "u_dma_idle_sync", + "u_dma_ready_sync", "u_bus_idle_ref", + "u_periph_idle_sync", "u_gate_feedback", + "u_held_feedback", "u_ready_feedback")), + )] + blocks.append(instance(generated, "tiny_peripheral_leaf", "u_dma_lifecycle")) + for source, lhs in ((top, "s_fabric_rst_n"), (top, "s_clock_safe"), + (generated, "s_xpi_rst_n"), (generated, "s_reset_ready[9]")): + matches = re.findall(rf"\bassign\s+{re.escape(lhs)}\s*=[^;]*;", source) + assert len(matches) == 1, lhs + blocks.extend(matches) + blocks += re.findall(r"\bassign\s+s_leaf_clk\[\d+\]\s*=[^;]*;", generated) + blocks.append("assign s_leaf_clk[0] = s_exec_clk;") + (tmp_path / "source_reset_bindings.svh").write_text("\n".join(blocks) + "\n") + + +@pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) +def test_tiny_production_source_reset_contract(tmp_path: Path, simulator: str) -> None: + source_reset_bindings(tmp_path) + run_rtl(tmp_path, simulator, "tiny_p7_source_reset_tb", [ + COMMON / "cdc/async_reqack.sv", COMMON / "cdc/cdc_warm_flush.sv", + COMMON / "cdc/cdc_fifo.sv", COMMON / "utils/spill_register.sv", + ROOT / "rtl/ip/peripheral/dma_axi4_master.sv", + *[ROOT / "rtl/tiny/top" / name for name in ( + "tiny_reset_tree.sv", "tiny_peripheral_control.sv", "tiny_peripheral_leaf.sv", + "tiny_axi4_admission.sv", "tiny_axi4_async_bridge.sv")], + ], defines=[f"-I{tmp_path}", f"-I{tmp_path / 'bindings/rtl'}"]) + launch = [str(tmp_path / "sim")] if simulator == "verilator" else ["vvp", str(tmp_path / "sim")] + # Compile once per simulator; reuse the binary at every supported domain + # ratio. These are digital boundary tests, not PDK/frequency qualification. + for mode in ("xin48", "xin96", "pll192", "pll240", "break_drain"): + result = subprocess.run([*launch, f"+{mode}"], cwd=ROOT, text=True, + capture_output=True, timeout=30) + content = result.stdout + result.stderr + (tmp_path / f"{mode}.log").write_text(content) + if mode == "break_drain": + # sv2v may lower $fatal to $finish(1), which vvp can return as + # exit zero. The strict Tiny verdict must still reject the log. + assert DEFAULT_FAILURE.search(content) and "P7_SOURCE_RESET_UNDRAINED" in content, content + assert "SIM_TEST_PASS" not in content, content + else: + assert result.returncode == 0 and not DEFAULT_FAILURE.search(content), content + assert "SIM_TEST_PASS Tiny P7 production source reset" in content, content + + +@pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) +def test_tiny_watchdog_reset_chain(tmp_path: Path, simulator: str) -> None: + generate_memory_map(ROOT / "rtl/tiny/address_map/memory_map.json", tmp_path / "map", "YES", 128) + run_rtl(tmp_path, simulator, "tiny_watchdog_reset_tb", [ + COMMON / "clkrst/counter.sv", + COMMON / "cdc/async_reqack.sv", COMMON / "cdc/cdc_fifo.sv", + *[ROOT / "rtl/managed/clusterip/wdg/rtl" / name for name in ( + "wdg_if.sv", "wdg_core.sv", "wdg_reg.sv", "apb4_wdg.sv")], + *[ROOT / "rtl/tiny/top" / name for name in ( + "tiny_clock_reset_subsystem.sv", "tiny_reset_tree.sv", + "tiny_apb_async_bridge.sv", "tiny_sysctrl.sv")]], + [f"-I{ROOT / 'rtl/ip/peripheral'}", f"-I{tmp_path / 'map/rtl'}", + f"-I{ROOT / 'rtl/managed/clusterip/wdg/rtl'}"], + packages=[ROOT / "rtl/managed/clusterip/wdg/rtl/wdg_pkg.sv"]) + + +@pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) +@pytest.mark.parametrize("module", ["tiny_axi4_admission", "tiny_clock_output"]) +def test_tiny_p7_boundaries(tmp_path: Path, simulator: str, module: str) -> None: + run_rtl(tmp_path, simulator, f"{module}_tb", [ROOT / f"rtl/tiny/top/{module}.sv"]) + + +@pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) +def test_tiny_peripheral_control(tmp_path: Path, simulator: str) -> None: + run_rtl(tmp_path, simulator, "tiny_peripheral_control_tb", + [ROOT / "rtl/tiny/top/tiny_peripheral_leaf.sv", + ROOT / "rtl/tiny/top/tiny_peripheral_control.sv"]) + + +@pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) +def test_tiny_clock_reset_backend_safe24(tmp_path: Path, simulator: str) -> None: + run_rtl(tmp_path, simulator, "tiny_clock_reset_subsystem_tb", + [ROOT / "rtl/tiny/top/tiny_clock_reset_subsystem.sv"]) + + +@pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) +@pytest.mark.parametrize("success", [False, True]) +def test_tiny_clock_reset_backend_pll(tmp_path: Path, simulator: str, success: bool) -> None: + run_rtl(tmp_path, simulator, "tiny_clock_reset_pll_tb", + [ROOT / "rtl/tech/tc_pll.sv", ROOT / "rtl/tiny/top/tiny_clock_reset_subsystem.sv"], + ["-DPDK_BEHAV", "-DHAVE_PLL", *(["-DTINY_PLL_SUCCESS"] if success else [])], + [] if success else ["+pll_lock_fail"]) + + +@pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) +def test_tiny_clock_reset_ics55_model(tmp_path: Path, simulator: str) -> None: + run_rtl(tmp_path, simulator, "tiny_clock_reset_pll_tb", + [ROOT / "rtl/tech/tc_pll.sv", ROOT / "rtl/tech/tc_pll_ics55_ecos.sv", + ROOT / "rtl/tiny/top/tiny_clock_reset_subsystem.sv", + ROOT / ".cache/retrosoc/sources/ics55_ecos_pll/verilog/PLL_TOP.behavioral.v"], + ["-DPDK_ICS55", "-DRETROSOC_SOC__TINY", "-DHAVE_PLL", "-DTINY_PLL_SUCCESS", + "-DTINY_PLL_NATIVE"]) + + +def test_tiny_rcu_register_parity() -> None: + sv = (ROOT / "rtl/tiny/top/tiny_rcu_regs.svh").read_text() + c = (ROOT / "crt/include/retrosoc/hal/rcu_regs.h").read_text() + sv_values = {name: int(value, 16) for name, value in + re.findall(r"`define APB4_TINY_RCU__(\w+)\s+32'h([0-9a-f]+)", sv)} + c_values = {name: int(value, 16) for name, value in + re.findall(r"#define RS_TINY_RCU_(\w+)_OFFSET\s+UINT32_C\(0x([0-9A-F]+)\)", c)} + assert len(sv_values) == 24 + assert sv_values == c_values + assert sorted(sv_values.values()) == list(range(0x100, 0x160, 4)) + + +@pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) +@pytest.mark.parametrize("xin_hz", [24000000, 48000000, 96000000]) +def test_tiny_external_sources(tmp_path: Path, simulator: str, xin_hz: int) -> None: + run_rtl(tmp_path, simulator, "tiny_clock_external_tb", + [ROOT / "rtl/tiny/top/tiny_clock_reset_subsystem.sv", + ROOT / "rtl/tiny/top/tiny_reset_tree.sv"], + [f"-DTINY_TEST_XIN_HZ={xin_hz}"]) + + +def test_aon_reset_uses_normalized_reference() -> None: + top = (ROOT / "rtl/tiny/top/retrosoc_tiny.sv").read_text() + assert re.search(r"\.por_clk_i\s*\(s_ref_clk\)", top) + + +def test_tiny_xpi_cdc_has_split_reset_endpoints() -> None: + top = (ROOT / "rtl/tiny/top/retrosoc_tiny.sv").read_text() + assert re.search(r"assign s_target_rst_n\[4\]\s*=\s*s_fabric_rst_n", top) + bridge = re.search(r"u_xpi_mem_cdc\s*\(.*?\n\s*\);", top, re.DOTALL) + assert bridge is not None + assert re.search(r"\.src_rst_n_i\s*\(s_fabric_rst_n\)", bridge.group(0)) + assert re.search(r"\.dst_rst_n_i\s*\(s_xpi_rst_n\)", bridge.group(0)) + + +@pytest.fixture +def p7_netlist(mapped_netlist): # noqa: F811 -- pytest injects the imported fixture + modules = mapped_netlist["modules"] + cells = modules["retrosoc_tiny_asic"]["cells"] + + def boundary(name, kind, **ports): + modules[kind] = {"ports": {key: {"direction": "output" if key.endswith("_o") else "input", + "bits": [i + 100]} for i, key in enumerate(ports)}, + "cells": {}} + cells[name] = {"type": kind, "connections": {key: [value] for key, value in ports.items()}} + + boundary("u_soc.u_clock_backend.u_sys_mux", "safe_clock_mux$sys", clk0_i=2, clk1_i=4, clk_o=20) + boundary("u_soc.u_clock_backend.u_mem_mux", "safe_clock_mux$mem", clk0_i=20, clk1_i=24, clk_o=21) + boundary("u_soc.u_clock_backend.u_pclk_mux", "safe_clock_mux$pclk", clk0_i=20, clk1_i=25, clk_o=22) + boundary("u_soc.u_cpu.u_cpu_gate", "tiny_clock_gate$cpu", clk_i=20, clk_o=23) + boundary("u_soc.u_crypto_lifecycle", "tiny_peripheral_leaf$crypto", clk_i=22, clk_o=26) + for name, cell in cells.items(): + if "u_soc.u_sram" in name: + cell["connections"]["CLK"] = [20] + if "u_soc.u_crypto.u_storage" in name: + cell["connections"]["CLK"] = [26] + if "u_hazard3_cpu_2port" in name: + cell["connections"]["CK"] = [23] + cells["pll"]["connections"].update(REFCLK=[2], EN=[30]) + return mapped_netlist + + +def test_p7_clock_bindings(p7_netlist) -> None: + binding = inspect_netlist(p7_netlist, "ICS55") + assert binding["main_macros"] == 32 + assert binding["private_crypto_macros"] == 6 + assert binding["pll_macros"] == 1 + + +@pytest.mark.parametrize("mutation", ["half_rate_sram", "cpu_divider", "crypto_sys", + "pll_bypass", "pll_tied", "fake_safe_source"]) +def test_p7_clock_binding_rejects_mismatch(p7_netlist, mutation: str) -> None: + cells = p7_netlist["modules"]["retrosoc_tiny_asic"]["cells"] + if mutation == "half_rate_sram": + cells["u_soc.u_sram.gen_group[0].u_group.gen_bank[0].u_ram.u_mem"]["connections"]["CLK"] = [21] + elif mutation == "cpu_divider": + cells["u_soc.u_cpu.u_hazard3_cpu_2port.core.pc_reg"]["connections"]["CK"] = [21] + elif mutation == "crypto_sys": + cells["u_soc.u_crypto_lifecycle"]["connections"]["clk_i"] = [20] + elif mutation == "pll_bypass": + cells["u_soc.u_clock_backend.u_sys_mux"]["connections"]["clk1_i"] = [2] + elif mutation == "pll_tied": + cells["pll"]["connections"]["EN"] = [3] + else: + cells["u_soc.u_clock_backend.u_sys_mux"]["connections"]["clk0_i"] = [31] + cells["pll"]["connections"]["REFCLK"] = [31] + with pytest.raises(ValueError): + inspect_netlist(p7_netlist, "ICS55")