mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
feat(tiny): implement R2-P7 clock and reset lifecycle
Integrate the Tiny RCU, normalized REF24 control, external-input profiles, single PLL backend, SYS/MEM/PCLK clocks, CPU-only WFI gating, peripheral lifecycle control and retained debug behavior. Keep CPU and all 32 main SRAM macros on SYS and the six private Crypto macros on PCLK. Add the handwritten RCU register/HAL contract, RS_EBUSY, bounded SDK operations and focused firmware. Preserve shared-IP defaults and the ICS55/PLL SAFE24 boot and explicit IHP130/no-PLL compatibility profiles. Use APB/AXI domain bridges and source-owned response holders to preserve backpressured responses, complete aborted bursts and prevent stale replay. Verify production DMA source-reset drain and common XPI endpoint reset with extracted integration bindings and an independent scoreboard. Add source-bound macro/clock audits, SAFE24 observational STA constraints and commercial-adapter host checks. No P8 functionality or operating-point qualification is included. See TINY-R2-P7 in docs/ip/tiny-soc.md. Validation: 54 P7 directed tests pass, including both simulators, ten source-reset ratio runs and two expected-negative drain mutations. RTL format/style, clock/reset inventory, Ruff and both Tiny PDK PR dry-runs pass. Retained 06-00 synthesis and strict netlist boots pass for both PDKs; STA remains negative and observational. Source comparison confirms only the follow-up tests changed after that backend campaign. Evidence: build/ics55-tiny-2026-10-14-06-00-ff4b5cef6eb5/meta/ tiny-r2-p7-source-reset/summary.json. Earlier firmware, HAL and compatibility results and the non-clean full-suite attempt retain their original attribution. Full Icarus firmware is deferred to final Tiny qualification; this commit does not constitute human phase acceptance or physical signoff.
This commit is contained in:
84 files changed
+6785
-232
No files matched your search
@@ -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
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <retrosoc/hal/i2c.h>
|
||||
#include <retrosoc/hal/onchip_sram.h>
|
||||
#include <retrosoc/hal/pwm.h>
|
||||
#include <retrosoc/hal/rcu.h>
|
||||
#include <retrosoc/hal/rtc.h>
|
||||
#include <retrosoc/hal/sysctrl.h>
|
||||
#include <retrosoc/hal/timer.h>
|
||||
@@ -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);
|
||||
|
||||
@@ -2,3 +2,6 @@ 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
|
||||
ifeq ($(TINY_P7_ACCEPTANCE),YES)
|
||||
APP_SRCS := $(ROOT_PATH)/app/apps/ci_smoke/tiny_r2_p7.c
|
||||
endif
|
||||
@@ -0,0 +1,171 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <retrosoc/core/soc.h>
|
||||
#include <retrosoc/arch/riscv/system_base.h>
|
||||
#include <retrosoc/hal/clint.h>
|
||||
#include <retrosoc/hal/dma.h>
|
||||
#include <retrosoc/hal/rcu.h>
|
||||
#include <retrosoc/hal/rcu_regs.h>
|
||||
#include <retrosoc/hal/sysctrl.h>
|
||||
#include <retrosoc/hal/timer.h>
|
||||
#include <pwm_regs.h>
|
||||
|
||||
/* 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 (;;) {
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef RETROSOC_HAL_RCU_H
|
||||
#define RETROSOC_HAL_RCU_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <retrosoc/core/status.h>
|
||||
|
||||
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
|
||||
@@ -0,0 +1,46 @@
|
||||
#ifndef RETROSOC_HAL_RCU_REGS_H
|
||||
#define RETROSOC_HAL_RCU_REGS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
||||
@@ -0,0 +1,261 @@
|
||||
#include <stddef.h>
|
||||
|
||||
#include <retrosoc/core/soc.h>
|
||||
#include <retrosoc/hal/rcu.h>
|
||||
#include <retrosoc/hal/rcu_regs.h>
|
||||
#if defined(__riscv) && defined(RS_SOC_TINY)
|
||||
#include <retrosoc/arch/riscv/system_base.h>
|
||||
#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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 {} {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -21,8 +21,12 @@ if {[info exists ::env(OPENSTA_TOP)] && $::env(OPENSTA_TOP) ne ""} {
|
||||
link_design $top_design
|
||||
read_sdc $::env(OPENSTA_SDC)
|
||||
if {$top_design eq "retrosoc_tiny_asic"} {
|
||||
if {[info exists ::env(OPENSTA_TINY_P7)] && $::env(OPENSTA_TINY_P7) eq "YES"} {
|
||||
source [file join [file dirname [info script]] tiny_r2_p7_checks.tcl]
|
||||
} else {
|
||||
source [file join [file dirname [info script]] tiny_r2_p4_checks.tcl]
|
||||
}
|
||||
}
|
||||
if {[info exists ::env(OPENSTA_TINY_ICS55)] && $::env(OPENSTA_TINY_ICS55) eq "YES"} {
|
||||
source [file join [file dirname [info script]] tiny_ics55_checks.tcl]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -12,10 +12,12 @@
|
||||
// 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 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),
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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,6 +44,10 @@ module apb4_crypto #(
|
||||
logic [11:0] s_offset;
|
||||
logic [31:0] s_wdata;
|
||||
logic [ 3:0] s_strb;
|
||||
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;
|
||||
|
||||
@@ -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,6 +67,8 @@ module apb4_i2c #(
|
||||
logic s_recovery_failed_event;
|
||||
logic s_core_cmd_flush;
|
||||
|
||||
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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,6 +54,8 @@ module apb4_uart #(
|
||||
logic s_tx_flow_blocked;
|
||||
logic s_cts_change;
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -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,6 +34,8 @@ module apb4_ws2812 #(
|
||||
logic s_done, s_underflow, s_aborted;
|
||||
logic s_irq, s_dat;
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -12,16 +12,19 @@
|
||||
|
||||
module apb4_sdio #(
|
||||
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;
|
||||
@@ -150,6 +157,7 @@ module apb4_sdio #(
|
||||
|
||||
sdio_core #(
|
||||
.InputClockHz (InputClockHz),
|
||||
.UseRuntimeClockHz(UseRuntimeClockHz),
|
||||
.AddrWidth (AddrWidth),
|
||||
.DataWidth (DataWidth),
|
||||
.DescCount (DescCount)
|
||||
@@ -157,6 +165,7 @@ module apb4_sdio #(
|
||||
.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),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
module sdio_core #(
|
||||
parameter int InputClockHz = 72_000_000,
|
||||
parameter bit UseRuntimeClockHz = 1'b0,
|
||||
parameter int AddrWidth = 32,
|
||||
parameter int DataWidth = 32,
|
||||
parameter int DescCount = 16
|
||||
@@ -20,6 +21,7 @@ module sdio_core #(
|
||||
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,
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
@@ -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": [
|
||||
|
||||
+6
-5
@@ -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
|
||||
@@ -30,8 +31,8 @@ module tc_pll #(
|
||||
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;
|
||||
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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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_<peripheral>_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"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
+13
-7
@@ -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)
|
||||
|
||||
+692
-64
File diff suppressed because it is too large.
Load diff
@@ -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),
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,432 @@
|
||||
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
|
||||
// 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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,
|
||||
@@ -65,13 +69,22 @@ module tiny_cpu_mem (
|
||||
.aresetn(rst_n_i)
|
||||
);
|
||||
logic s_adapter_idle;
|
||||
logic s_clock_pause;
|
||||
logic [1:0] s_clock_front_idle;
|
||||
assign s_clock_pause = ClockPause && clock_pause_i;
|
||||
// A pipeline-overlap address acknowledged with the preceding AHB response
|
||||
// remains in the input register until resume. Only target admissions are
|
||||
// blocked: every accepted bank/AXI operation and response still drains.
|
||||
assign clock_idle_o = (&s_clock_front_idle) && (s_slow_q.state == SlowIdle) && s_adapter_idle;
|
||||
|
||||
for (genvar port_index = 0; port_index < 2; port_index++) begin : gen_port
|
||||
assign local_fault_valid_o[port_index] =
|
||||
(s_req_q[port_index].state == ErrorLast) && !s_req_q[port_index].external_error;
|
||||
assign local_fault_addr_o[port_index] = s_req_q[port_index].addr;
|
||||
assign local_fault_write_o[port_index] = s_req_q[port_index].write;
|
||||
assign s_slow_req[port_index] = s_req_q[port_index].state == SlowIssue;
|
||||
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:
|
||||
|
||||
@@ -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_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
|
||||
@@ -67,10 +89,12 @@ module tiny_cpu_wrapper #(
|
||||
|
||||
mgmt_debug_wrapper #(
|
||||
.JtagIdcode (`SOC_JTAG_IDCODE),
|
||||
.ResetSyncStages(ResetSyncStages)
|
||||
.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),
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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])
|
||||
);
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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}]"
|
||||
|
||||
@@ -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 <stdint.h>",
|
||||
"#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));")
|
||||
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}];")
|
||||
bridges.append(f"assign s_target_rst_n[{index + 5}] = s_{name}_rst_n;")
|
||||
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")
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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())
|
||||
@@ -12,6 +12,8 @@
|
||||
#include <retrosoc/hal/crypto.h>
|
||||
#include <retrosoc/hal/gpio.h>
|
||||
#include <retrosoc/hal/clock.h>
|
||||
#include <retrosoc/hal/rcu.h>
|
||||
#include <retrosoc/hal/rcu_regs.h>
|
||||
#include <retrosoc/hal/extension.h>
|
||||
#include <retrosoc/hal/dma.h>
|
||||
#include <retrosoc/hal/fabric_monitor.h>
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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 (),
|
||||
@@ -220,11 +237,21 @@ tiny_cpu_wrapper #(
|
||||
repeat (6) jtag_tick(1, 0, ignored);
|
||||
@(negedge clk_i);
|
||||
rst_n_i = 1;
|
||||
cold_rst_n = 1;
|
||||
repeat (12) jtag_tick(1, 0, ignored);
|
||||
jtag_idle(1);
|
||||
jtag_ir(5'h11);
|
||||
wait (starts == 1);
|
||||
$display("P4_CPU_STAGE boot-and-boundaries");
|
||||
if (P7Lifecycle) begin
|
||||
wait (gated);
|
||||
repeat (10) @(negedge clk_i);
|
||||
irq = 2; // Tiny wrapper timer IRQ is input bit 1.
|
||||
wait (!gated);
|
||||
repeat (10) @(negedge clk_i);
|
||||
irq = 0;
|
||||
wait (gated);
|
||||
end
|
||||
jtag_dmi(1, 7'h10, 1, status);
|
||||
jtag_dmi(1, 7'h10, 32'h80000001, status);
|
||||
wait (halted);
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -19,9 +19,23 @@ 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),
|
||||
.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),
|
||||
@@ -32,17 +46,139 @@ module tiny_sysctrl_tb;
|
||||
.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)
|
||||
.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);
|
||||
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
+11
-2
@@ -35,6 +35,12 @@ 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
|
||||
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)
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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")
|
||||
Reference in new issue
Block a user