diff --git a/Makefile b/Makefile index bc7e8671..c8248c45 100644 --- a/Makefile +++ b/Makefile @@ -57,7 +57,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. @@ -85,6 +85,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 NPU_P6_ACCEPTANCE ?= NO @@ -127,6 +128,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)) @@ -287,6 +291,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) @@ -383,7 +393,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 diff --git a/app/apps/ci_smoke/tiny.c b/app/apps/ci_smoke/tiny.c index 5553feef..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 @@ -29,6 +30,7 @@ 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); @@ -251,6 +253,7 @@ 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; @@ -268,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) || @@ -316,6 +328,7 @@ int main(void) { 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 240ff06f..4478e0e2 100644 --- a/app/apps/ci_smoke/tiny.mk +++ b/app/apps/ci_smoke/tiny.mk @@ -1,4 +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 -APP_SRCS += $(ROOT_PATH)/app/apps/ci_smoke/tiny_r2_p6.c \ No newline at end of file +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_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/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/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/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/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/physical/commercial/config/products/tiny.mk b/physical/commercial/config/products/tiny.mk index 7c478d5d..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 audio +# 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 b4658361..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 system (SAFE24 external clock), jtag and -# the independent GPIO17 AUDIO reference. 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 audio} + 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 clk_audio} + 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/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 a8e84504..baa5d755 100644 --- a/physical/smoke/sta/opensta/opensta.tcl +++ b/physical/smoke/sta/opensta/opensta.tcl @@ -21,7 +21,11 @@ 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"} { - source [file join [file dirname [info script]] tiny_r2_p4_checks.tcl] + 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_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 daf01a88..e69bd544 100644 --- a/publications/datasheets/system-reference.json +++ b/publications/datasheets/system-reference.json @@ -4500,7 +4500,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.", @@ -4510,7 +4511,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/rtl/ip/core/mgmt_debug_wrapper.sv b/rtl/ip/core/mgmt_debug_wrapper.sv index 91ba858f..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, @@ -156,7 +158,7 @@ module mgmt_debug_wrapper #( .ResetSyncStages(ResetSyncStages) ) u_mgmt_debug_reset ( .clk_i (clk_i), - .rst_n_i (rst_n_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), 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/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_core.sv b/rtl/ip/storage/sdio_core.sv index 2e4b9059..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, @@ -236,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, diff --git a/rtl/mk/software.mk b/rtl/mk/software.mk index 70a877d7..741dc220 100644 --- a/rtl/mk/software.mk +++ b/rtl/mk/software.mk @@ -120,6 +120,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 \ @@ -180,7 +181,7 @@ 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 rng.c crc.c ws2812_math.c ws2812.c \ - crypto.c crypto_lifecycle.c i2s_math.c i2s.c sdio_math.c sdio.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 diff --git a/rtl/rtl_style_audit.json b/rtl/rtl_style_audit.json index d41e1a1b..41edcd49 100644 --- a/rtl/rtl_style_audit.json +++ b/rtl/rtl_style_audit.json @@ -441,12 +441,22 @@ "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_sram.sv", "tiny_sram_axi4.sv", "tiny_sram_group.sv", "tiny_sram_port_if.sv", - "tiny_sysctrl.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/dv/retrosoc_tiny_tb.sv b/rtl/tiny/dv/retrosoc_tiny_tb.sv index 9e32acab..1cef5b6f 100644 --- a/rtl/tiny/dv/retrosoc_tiny_tb.sv +++ b/rtl/tiny/dv/retrosoc_tiny_tb.sv @@ -6,7 +6,10 @@ 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; @@ -26,6 +29,7 @@ module retrosoc_tiny_tb; 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; @@ -47,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); @@ -57,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); diff --git a/rtl/tiny/dv/tiny_p6_devices.svh b/rtl/tiny/dv/tiny_p6_devices.svh index 123de212..460bee1e 100644 --- a/rtl/tiny/dv/tiny_p6_devices.svh +++ b/rtl/tiny/dv/tiny_p6_devices.svh @@ -1,6 +1,6 @@ // Verification-only external devices on the Gen1 GPIO routes. No DUT state is driven. logic s_p6_io_trace_started = 0; -always @(negedge s_clk) begin +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; @@ -62,7 +62,7 @@ initial begin 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 s_clk) begin +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]) @@ -124,7 +124,7 @@ logic s_p6_expected_high; logic s_p6_audio_reference = 1'b0; logic s_p6_audio_running = 1'b0; initial begin - repeat (200) @(negedge s_clk); + 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; @@ -135,12 +135,12 @@ 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 s_clk); + repeat (4800) @(negedge u_dut.u_soc.s_ref_clk); s_p6_clock_stops++; s_p6_audio_running = 1; end end -always @(negedge s_clk) begin +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; @@ -218,8 +218,11 @@ for (genvar owner = 0; owner < 3; owner++) begin : gen_p6_axi_observer 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 s_clk) begin - if (s_rst_n) begin + 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; @@ -297,7 +300,7 @@ 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 s_clk) begin +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. 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/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/top.fl b/rtl/tiny/filelist/top.fl index 5ea33070..288d99ff 100644 --- a/rtl/tiny/filelist/top.fl +++ b/rtl/tiny/filelist/top.fl @@ -5,6 +5,15 @@ /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 diff --git a/rtl/tiny/integration/clock_reset_domains.json b/rtl/tiny/integration/clock_reset_domains.json index af67d80d..ffc83066 100644 --- a/rtl/tiny/integration/clock_reset_domains.json +++ b/rtl/tiny/integration/clock_reset_domains.json @@ -1,14 +1,14 @@ { "schema_version": 2, "external_fabric": { - "phase": "TINY-R2-P6", + "phase": "TINY-R2-P7", "clock_domain": "system", "target_controllers": "u_soc.u_fabric.gen_target[%d].u_controller", "target_count": 27, - "apb_bridges": "u_soc.u__bridge; corresponding existing peripheral reset leaf", + "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": "not exposed; quiescence-controlled RCU remains P7" + "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", @@ -22,32 +22,69 @@ "main_sram_payload_reset": "none", "local_uncontended_max_cycles": 3 }, - "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" - } - }, "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" } }, { @@ -85,7 +122,7 @@ "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", @@ -110,16 +147,103 @@ "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": "six private macro banks; reset invalidates then scrubs/readbacks; CPU does not wait for READY", - "clock_domain": "system" + "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", @@ -128,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", @@ -138,7 +273,7 @@ }, { "name": "i2s_configuration", - "source": "system", + "source": "peripheral", "destination": "audio", "primitive": "cdc_2phase", "path": "rtl/ip/serial/apb4_i2s.sv", @@ -146,7 +281,7 @@ }, { "name": "i2s_commands", - "source": "system", + "source": "peripheral", "destination": "audio", "primitive": "cdc_2phase", "path": "rtl/ip/serial/apb4_i2s.sv", @@ -154,7 +289,7 @@ }, { "name": "i2s_tx_samples", - "source": "system", + "source": "peripheral", "destination": "audio", "primitive": "cdc_fifo_warm_flush", "path": "rtl/ip/serial/apb4_i2s.sv", @@ -163,7 +298,7 @@ { "name": "i2s_rx_samples", "source": "audio", - "destination": "system", + "destination": "peripheral", "primitive": "cdc_fifo_warm_flush", "path": "rtl/ip/serial/apb4_i2s.sv", "instance": "u_rx_cdc_fifo" @@ -171,7 +306,7 @@ { "name": "i2s_tx_occupancy", "source": "audio", - "destination": "system", + "destination": "peripheral", "primitive": "cdc_2phase", "path": "rtl/ip/serial/apb4_i2s.sv", "instance": "u_tx_pop_cnt_cdc" @@ -179,10 +314,108 @@ { "name": "i2s_rx_occupancy", "source": "audio", - "destination": "system", + "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 9dae7d3d..0692b805 100644 --- a/rtl/tiny/integration/soc_topology.json +++ b/rtl/tiny/integration/soc_topology.json @@ -253,6 +253,11 @@ "name": "crypto", "core_bit": 23, "signal": "s_crypto_irq" + }, + { + "name": "rcu", + "core_bit": 31, + "signal": "s_rcu_irq" } ], "gpio_alt_functions": [ @@ -588,8 +593,8 @@ }, "alt1": { "inputs": [], - "do": "1'b0", - "oe": "1'b0" + "do": "s_clkout", + "oe": "s_clkout_enable" } }, { @@ -652,8 +657,8 @@ "pin": 28, "alt0": { "inputs": [], - "do": "1'b0", - "oe": "1'b0" + "do": "s_clkout", + "oe": "s_clkout_enable" }, "alt1": { "inputs": [], @@ -706,7 +711,7 @@ "banks": 6, "bank_bytes": 4096, "macro_instance": "u_soc.u_crypto.u_storage.gen_bank[{bank}].gen_macro.u_sram.u_mem", - "clock_domain": "system", - "logical_domain": "PCLK (SAFE24 alias)" + "clock_domain": "peripheral", + "logical_domain": "PCLK" } } diff --git a/rtl/tiny/mk/config.mk b/rtl/tiny/mk/config.mk index 1b89e8b9..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) diff --git a/rtl/tiny/top/retrosoc_tiny.sv b/rtl/tiny/top/retrosoc_tiny.sv index 5677dc40..55b3add3 100644 --- a/rtl/tiny/top/retrosoc_tiny.sv +++ b/rtl/tiny/top/retrosoc_tiny.sv @@ -27,7 +27,9 @@ module retrosoc_tiny ( 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_write; logic [ 1:0] s_fault_master; @@ -47,12 +49,70 @@ module retrosoc_tiny ( logic [4:0] s_tick_count_d, s_tick_count_q; logic s_tick; 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; - ahbl_if u_cpu_ahbl_if[2] ( - .hclk (clk_i), + logic s_i2s_reset_ready; + logic s_done_pclk_rst_n, s_done_mem_rst_n; + `include "tiny_apb_interfaces.svh" +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] (); @@ -62,11 +122,10 @@ module retrosoc_tiny ( .ID_WIDTH (1), .USER_WIDTH(1) ) u_sram_axi4_if[4] ( - .aclk (clk_i), + .aclk (s_exec_clk), .aresetn(s_sram_rst_n) ); - `include "tiny_apb_interfaces.svh" -uart_if u_uart0_if (); + uart_if u_uart0_if (); i2c_if u_i2c0_if (); pwm_if u_pwm_if (); clint_if u_clint_if (); @@ -77,14 +136,14 @@ uart_if u_uart0_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), @@ -92,57 +151,466 @@ uart_if u_uart0_if (); .ID_WIDTH (1), .USER_WIDTH(1) ) u_masters_axi4_if[3] ( - .aclk (clk_i), + .aclk (s_exec_clk), .aresetn(s_master_rst_n) ); + 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_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 (clk_i), + .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, s_sdio_rst_n}; - assign s_target_rst_n[4] = s_xpi_rst_n; + 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) + .LeafCount (`RETROSOC_TINY__LEAF_COUNT), + .SplitDomains(1'b1) ) u_reset_tree ( - .clk_i (clk_i), + .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), @@ -155,18 +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) ); tiny_cpu_wrapper #( .ExternalIrqCount(30), .EnableAtomics (1'b0), - .ResetSyncStages (5) + .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), @@ -175,14 +647,20 @@ uart_if u_uart0_if (); .jtag_tdo_o (jtag_tdo_o), .debug_halted_o(s_debug_halted), .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 u_cpu_mem ( - .clk_i (clk_i), + 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), @@ -192,21 +670,21 @@ uart_if u_uart0_if (); .slow_axi4 (u_masters_axi4_if[0]) ); tiny_axi4_fabric u_fabric ( - .clk_i(clk_i), - .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), + .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(), // Reserved lifecycle observations; RCU remains P7. - .target_idle_o(), // The P5 testbench checks all accepted-work barriers. - .cpu_wait_o(s_cpu_wait), - .dma_wait_o(s_dma_wait) + .owner_idle_o (s_owner_idle), + .target_idle_o (s_target_idle), + .cpu_wait_o (s_cpu_wait), + .dma_wait_o (s_dma_wait) ); for (genvar group = 0; group < 4; group++) begin : gen_sram_attachment assign s_target_rst_n[group] = s_sram_rst_n; @@ -216,7 +694,7 @@ uart_if u_uart0_if (); ); end tiny_sram u_sram ( - .clk_i (clk_i), + .clk_i (s_exec_clk), .rst_n_i (s_sram_rst_n), .local_ports (u_cpu_local_if), .external_ports(u_sram_axi4_if), @@ -224,29 +702,76 @@ uart_if u_uart0_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[4]), - .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) ); `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[`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[`RETROSOC_TINY__PROTOCOL_ERROR]) ); @@ -264,27 +789,107 @@ axi4_error_slave #( .write_o (s_fault_write), .resp_o (s_fault_resp) ); - 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_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 (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) +`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) ); @@ -293,7 +898,7 @@ axi4_error_slave #( .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), @@ -309,52 +914,67 @@ axi4_error_slave #( // 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_debug_halted; + 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) + .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) @@ -376,25 +996,26 @@ apb4_uart u_uart0 ( .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 = !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_dma_stall[0]; - assign u_dma_req_if.qspi_rx_proc = !s_dma_stall[1]; + 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]; @@ -423,37 +1044,44 @@ apb4_uart u_uart0 ( .rst_n_o(s_audio_rst_n) ); apb4_i2s u_i2s ( - .clk_i (clk_i), - .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) + .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) + .InputClockHz (24_000_000), + .UseRuntimeClockHz(1'b1) ) u_sdio ( - .clk_i (clk_i), - .rst_n_i (s_sdio_rst_n), - .apb4 (u_sdio_apb4_if), - .dma_axi4(u_masters_axi4_if[2]), - .sdio (u_sdio_if) + .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 ( - .clk_i (clk_i), - .rst_n_i(s_ws2812_rst_n), - .apb4 (u_ws2812_apb4_if), - .ws2812 (u_ws2812_if) + .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 ( - .clk_i (clk_i), + .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), @@ -464,7 +1092,7 @@ apb4_uart u_uart0 ( ); apb4_crc u_crc (.apb4(u_crc_apb4_if)); rng_deterministic_source u_rng_source ( - .clk_i (clk_i), + .clk_i (s_rng_clk), .rst_n_i (s_rng_rst_n), .enable_i (s_rng_en), .ready_i (s_rng_ready), 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_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_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 index 9caae697..5a229270 100644 --- a/rtl/tiny/top/tiny_cpu_mem.sv +++ b/rtl/tiny/top/tiny_cpu_mem.sv @@ -5,10 +5,14 @@ // 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 ( +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, @@ -64,14 +68,23 @@ module tiny_cpu_mem ( .aclk (clk_i), .aresetn(rst_n_i) ); - logic s_adapter_idle; + 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; + 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) || @@ -87,7 +100,8 @@ module tiny_cpu_mem ( 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; + 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; @@ -118,7 +132,9 @@ module tiny_cpu_mem ( `SOC_ADDR_IS_SRAM(s_req_q[port_index].addr) ? LocalIssue : SlowIssue; end - LocalIssue: if (local_ports[port_index].req_ready) s_req_d[port_index].state = LocalWait; + 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: diff --git a/rtl/tiny/top/tiny_cpu_wrapper.sv b/rtl/tiny/top/tiny_cpu_wrapper.sv index 530d2b8e..20bbc7f3 100644 --- a/rtl/tiny/top/tiny_cpu_wrapper.sv +++ b/rtl/tiny/top/tiny_cpu_wrapper.sv @@ -13,11 +13,15 @@ module tiny_cpu_wrapper #( parameter int ExternalIrqCount = 30, parameter bit EnableAtomics = 1'b0, - parameter int ResetSyncStages = 3 + 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, @@ -26,13 +30,16 @@ module tiny_cpu_wrapper #( 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_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; @@ -58,6 +65,21 @@ module tiny_cpu_wrapper #( 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 @@ -66,11 +88,13 @@ module tiny_cpu_wrapper #( assign s_hart_idle = bus_idle_i && (!s_reset_pending || s_dbg_halted); mgmt_debug_wrapper #( - .JtagIdcode (`SOC_JTAG_IDCODE), - .ResetSyncStages(ResetSyncStages) + .JtagIdcode (`SOC_JTAG_IDCODE), + .ResetSyncStages (ResetSyncStages), + .SeparateSystemReset(PreserveDebug) ) u_mgmt_debug_wrapper ( .clk_i (clk_i), - .rst_n_i (rst_n_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), @@ -86,18 +110,18 @@ module tiny_cpu_wrapper #( .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_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), - .dbg_instr_caught_exception_i(s_dbg_instr_caught_exception), - .dbg_instr_caught_ebreak_i (s_dbg_instr_caught_ebreak), + .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), - .dbg_sbus_err_i (s_dbg_sbus_err), + .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) ); @@ -123,7 +147,7 @@ module tiny_cpu_wrapper #( .EXTENSION_XH3BEXTM (1), .EXTENSION_XH3IRQ (1), .EXTENSION_XH3PMPM (0), - .EXTENSION_XH3POWER (0), + .EXTENSION_XH3POWER (EnableWfiGate), .CSR_M_MANDATORY (1), .CSR_M_TRAP (1), .CSR_COUNTER (1), @@ -152,12 +176,12 @@ module tiny_cpu_wrapper #( .BRANCH_PREDICTOR (1), .MTVEC_WMASK (32'hfffffffd) ) u_hazard3_cpu_2port ( - .clk (clk_i), + .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 (), + .clk_en (s_cpu_clk_en), .unblock_out (), .unblock_in (1'b0), .i_haddr (cpu[0].haddr), @@ -198,14 +222,14 @@ module tiny_cpu_wrapper #( .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), + .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), + .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), 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_sysctrl.sv b/rtl/tiny/top/tiny_sysctrl.sv index 13f9db10..fd730151 100644 --- a/rtl/tiny/top/tiny_sysctrl.sv +++ b/rtl/tiny/top/tiny_sysctrl.sv @@ -1,10 +1,19 @@ // 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, @@ -15,12 +24,66 @@ module tiny_sysctrl ( 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; @@ -38,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; @@ -91,6 +268,275 @@ 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; @@ -152,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 ( 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/generate_tiny.py b/scripts/generate_tiny.py index 631c0606..22d04c40 100644 --- a/scripts/generate_tiny.py +++ b/scripts/generate_tiny.py @@ -16,6 +16,9 @@ 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: @@ -77,9 +80,9 @@ def read_topology(path: Path, memory_map: Path) -> dict: 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": "system", "logical_domain": "PCLK (SAFE24 alias)", + "clock_domain": "peripheral", "logical_domain": "PCLK", }: - raise ValueError("Tiny P6 requires six private Crypto banks on SAFE24 PCLK") + raise ValueError("Tiny requires six private Crypto banks on PCLK") return document @@ -111,7 +114,7 @@ def generate_bindings(document: dict, output: Path, pdk: str = "IHP130", 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 = '0;"] + 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)", @@ -119,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})") @@ -164,13 +167,51 @@ def generate_bindings(document: dict, output: Path, pdk: str = "IHP130", reset_assignments = [] for index, target in enumerate(document["apb_targets"]): name = target["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}];") - bridges.append(f"assign s_target_rst_n[{index + 5}] = s_{name}_rst_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(clk_i), .rst_n_i(s_{name}_rst_n), " - f".axi4(u_targets_axi4_if[{index + 5}]), .apb4(u_{name}_apb4_if));") + 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") 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/tiny_ics55_platform.py b/scripts/tiny_ics55_platform.py index 6db9dfb6..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,9 +171,15 @@ 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") 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 985bb920..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,6 +67,7 @@ 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; @@ -3148,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(), @@ -3156,6 +3214,7 @@ 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(), 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_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 index ce8b62a0..ce5ae705 100644 --- a/tests/rtl/tiny_r2_p4_cpu.S +++ b/tests/rtl/tiny_r2_p4_cpu.S @@ -41,6 +41,16 @@ _start: 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 diff --git a/tests/rtl/tiny_r2_p4_cpu_tb.sv b/tests/rtl/tiny_r2_p4_cpu_tb.sv index f45b53f9..54b00783 100644 --- a/tests/rtl/tiny_r2_p4_cpu_tb.sv +++ b/tests/rtl/tiny_r2_p4_cpu_tb.sv @@ -1,6 +1,13 @@ `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; @@ -46,15 +53,21 @@ module tiny_r2_p4_cpu_tb; tiny_cpu_wrapper #( .EnableAtomics (0), + .EnableWfiGate (P7Lifecycle), + .PreserveDebug (P7Lifecycle), .ResetSyncStages(5) ) u_cpu ( .clk_i (clk_i), .rst_n_i (rst_n_i), - .irq_i (32'd0), + .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 (rst_n_i), + .jtag_trst_n_i (cold_rst_n), .jtag_tdo_o (s_jtag_tdo), .debug_halted_o(halted), .bus_idle_i (idle), @@ -62,10 +75,14 @@ tiny_cpu_wrapper #( .quiesce_o (quiesce), .cpu (cpu) ); - tiny_cpu_mem u_cpu_mem ( + 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 (), @@ -219,12 +236,22 @@ tiny_cpu_wrapper #( cfg.pstrb = 15; repeat (6) jtag_tick(1, 0, ignored); @(negedge clk_i); - rst_n_i = 1; + 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); @@ -279,6 +306,41 @@ tiny_cpu_wrapper #( 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 diff --git a/tests/rtl/tiny_r2_p5_system_tb.sv b/tests/rtl/tiny_r2_p5_system_tb.sv index 37a89c3b..76fb24ad 100644 --- a/tests/rtl/tiny_r2_p5_system_tb.sv +++ b/tests/rtl/tiny_r2_p5_system_tb.sv @@ -112,6 +112,7 @@ endmodule // 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, @@ -128,6 +129,7 @@ module apb4_xpi ( 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); diff --git a/tests/rtl/tiny_sysctrl_tb.sv b/tests/rtl/tiny_sysctrl_tb.sv index b0d92800..e43b5b1b 100644 --- a/tests/rtl/tiny_sysctrl_tb.sv +++ b/tests/rtl/tiny_sysctrl_tb.sv @@ -19,30 +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_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), - .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); 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 177d2148..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() == [ - "audio,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 clk_audio", - "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_tiny.py b/tests/test_tiny.py index 3c35ed21..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 @@ -109,10 +115,13 @@ def test_tiny_protocol_contracts(tmp_path: Path, simulator: str, block: str) -> 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", tmp_path / "bindings/rtl", - ROOT / "rtl/ip/peripheral", ROOT / "tests/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_r2_p2.py b/tests/test_tiny_r2_p2.py index 601c30ef..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"]) == 21 - assert {domain["name"] for domain in inventory["domains"]} == {"system", "jtag", "audio"} - assert reset["clock_domain"] == "system" and reset["main_sram_payload_reset"] == "none" + 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 index fd6a92cd..b3a8d9ae 100644 --- a/tests/test_tiny_r2_p4.py +++ b/tests/test_tiny_r2_p4.py @@ -79,11 +79,13 @@ def test_four_group_local_memory(tmp_path, simulator, pdk): @pytest.mark.parametrize("simulator", ["verilator", "iverilog"]) -def test_actual_dual_port_cpu_debug_and_reset(tmp_path, simulator): +@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) - run(["riscv32-unknown-elf-gcc", "-march=rv32imc_zicsr_zifencei", "-mabi=ilp32", + 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"), @@ -97,7 +99,7 @@ def test_actual_dual_port_cpu_debug_and_reset(tmp_path, simulator): 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_cpu_wrapper.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", @@ -105,7 +107,7 @@ def test_actual_dual_port_cpu_debug_and_reset(tmp_path, simulator): 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"] + 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": @@ -122,6 +124,8 @@ def test_actual_dual_port_cpu_debug_and_reset(tmp_path, simulator): 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): diff --git a/tests/test_tiny_r2_p5.py b/tests/test_tiny_r2_p5.py index 51f7e8fd..ea565906 100644 --- a/tests/test_tiny_r2_p5.py +++ b/tests/test_tiny_r2_p5.py @@ -29,7 +29,7 @@ def sources(tmp_path): 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", tmp_path / "map/rtl", + 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 @@ -99,7 +99,8 @@ def test_real_cpu_dma_progress_and_abort(tmp_path, simulator): "--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"] + 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 diff --git a/tests/test_tiny_r2_p6.py b/tests/test_tiny_r2_p6.py index 9e94a242..574940e3 100644 --- a/tests/test_tiny_r2_p6.py +++ b/tests/test_tiny_r2_p6.py @@ -404,7 +404,8 @@ def test_p6_map_irq_request_and_pin_contract(): 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, 31}.isdisjoint(irqs.values()) + 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"] @@ -413,8 +414,10 @@ def test_p6_map_irq_request_and_pin_contract(): 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, 24): + 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) 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")