mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
feat(tiny): implement R2-P8 XPI PSRAM transport
Add optional Tiny NSS1 PSRAM initialization, identity and geometry checks, mapped CPU/DMA workload coverage, and retained pin-level device verification. Harden shared XPI byte lanes, burst boundaries, response completion and timeout behavior while preserving NOR/SRAM-only boot and Mini compatibility.\n\nValidation: focused P8/XPI tests, ICS55 and IHP130 Verilator P8 replays, Tiny netlist boot, synthesis and observational STA completed. Full long-running Icarus firmware remains deferred to final Tiny qualification.
This commit is contained in:
23 files changed
+1181
-35
No files matched your search
@@ -86,6 +86,7 @@ SW_OPT ?= APP
|
||||
SW_LTO ?= NO
|
||||
WS2812_P3_ACCEPTANCE ?= NO
|
||||
TINY_P7_ACCEPTANCE ?= NO
|
||||
TINY_P8_ACCEPTANCE ?= NO
|
||||
COREMARK_MODE ?= quick
|
||||
NPU_P5_ACCEPTANCE ?= NO
|
||||
NPU_P6_ACCEPTANCE ?= NO
|
||||
@@ -131,6 +132,9 @@ endif
|
||||
ifeq ($(TINY_P7_ACCEPTANCE),YES)
|
||||
CONFIG_KEY_VARS += TINY_P7_ACCEPTANCE
|
||||
endif
|
||||
ifeq ($(TINY_P8_ACCEPTANCE),YES)
|
||||
CONFIG_KEY_VARS += TINY_P8_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))
|
||||
@@ -292,6 +296,12 @@ $(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))
|
||||
$(call validate_value,TINY_P8_ACCEPTANCE,$(VALID_BOOL))
|
||||
ifeq ($(TINY_P8_ACCEPTANCE),YES)
|
||||
ifneq ($(SOC)/$(APP)/$(HAVE_CSR)/$(TINY_P7_ACCEPTANCE),TINY/ci_smoke/YES/NO)
|
||||
$(error TINY_P8_ACCEPTANCE requires SOC=TINY APP=ci_smoke HAVE_CSR=YES TINY_P7_ACCEPTANCE=NO)
|
||||
endif
|
||||
endif
|
||||
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)
|
||||
@@ -1239,4 +1249,4 @@ regress-nightly:
|
||||
python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite nightly $(if $(filter TINY,$(SOC)),--soc TINY --pdk $(PDK))
|
||||
|
||||
sim-asm: asm
|
||||
$(MAKE) SIM_FIRMWARE_NAME=$(ASM_FIRMWARE_NAME) sim
|
||||
$(MAKE) SIM_FIRMWARE_NAME=$(ASM_FIRMWARE_NAME) sim
|
||||
@@ -4,4 +4,7 @@ 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
|
||||
endif
|
||||
ifeq ($(TINY_P8_ACCEPTANCE),YES)
|
||||
APP_SRCS := $(ROOT_PATH)/app/apps/ci_smoke/tiny_r2_p8.c
|
||||
endif
|
||||
@@ -0,0 +1,180 @@
|
||||
#include <retrosoc/arch/riscv/system_base.h>
|
||||
#include <retrosoc/hal/dma.h>
|
||||
#include <retrosoc/hal/gpio.h>
|
||||
#include <retrosoc/hal/rcu.h>
|
||||
#include <retrosoc/hal/sysctrl.h>
|
||||
#include <retrosoc/hal/uart.h>
|
||||
#include <retrosoc/hal/xpi_psram.h>
|
||||
#include <retrosoc/lib/printf.h>
|
||||
|
||||
#define RS_P8_WORDS UINT32_C(256)
|
||||
#define RS_P8_ADDRESS UINT32_C(0x54000c00)
|
||||
static _Alignas(64) uint32_t rs_p8_source[RS_P8_WORDS];
|
||||
static _Alignas(64) uint32_t rs_p8_readback[RS_P8_WORDS];
|
||||
static volatile uint32_t rs_p8_cpu_progress;
|
||||
|
||||
typedef struct {
|
||||
uint32_t sys_hz, mem_hz;
|
||||
uint32_t isolated_cycles, contended_cycles, progress;
|
||||
} rs_p8_measurement_t;
|
||||
static rs_p8_measurement_t rs_p8_results[3];
|
||||
|
||||
static bool rs_p8_dma(rs_dma_session_t *session, uintptr_t source, uintptr_t destination,
|
||||
bool contend, uint32_t *cycles) {
|
||||
const rs_dma_config_t config = {.kind = RS_DMA_KIND_MM_TO_MM,
|
||||
.request = RS_DMA_REQUEST_SOFTWARE,
|
||||
.source = source,
|
||||
.destination = destination,
|
||||
.byte_count = sizeof(rs_p8_source),
|
||||
.width = RS_DMA_WIDTH_32,
|
||||
.source_increment = true,
|
||||
.destination_increment = true,
|
||||
.burst_beats = RS_XPI_PSRAM_BURST_BEATS};
|
||||
rs_dma_status_t state = {0};
|
||||
rs_timeout_t timeout = RS_TIMEOUT_DEFAULT;
|
||||
uint64_t start;
|
||||
if (rs_dma_session_configure(session, &config) != RS_OK ||
|
||||
rs_sysctrl_set_perf_control(true, true, false) != RS_OK) {
|
||||
return false;
|
||||
}
|
||||
__FENCE(rw, rw);
|
||||
start = __get_rv_cycle();
|
||||
if (rs_dma_session_start(session) != RS_OK) {
|
||||
return false;
|
||||
}
|
||||
do {
|
||||
if (contend) {
|
||||
/* Defined competing SRAM work, with no race on the DMA buffers. */
|
||||
rs_p8_cpu_progress++;
|
||||
}
|
||||
if (timeout-- == 0U || rs_dma_get_status(session->channel, &state) != RS_OK) {
|
||||
return false;
|
||||
}
|
||||
} while (state.busy);
|
||||
*cycles = (uint32_t)(__get_rv_cycle() - start);
|
||||
__FENCE(rw, rw);
|
||||
if (rs_sysctrl_set_perf_control(false, false, false) != RS_OK) {
|
||||
return false;
|
||||
}
|
||||
return state.done && !state.error && !state.aborted &&
|
||||
state.bytes_done == sizeof(rs_p8_source) && state.remaining == 0U;
|
||||
}
|
||||
|
||||
static bool rs_p8_profile(rs_tiny_rcu_profile_t profile, rs_p8_measurement_t *measurement) {
|
||||
rs_tiny_rcu_snapshot_t clocks;
|
||||
rs_xpi_psram_t device;
|
||||
rs_dma_session_t session;
|
||||
volatile uint32_t *const memory = (volatile uint32_t *)(uintptr_t)RS_P8_ADDRESS;
|
||||
volatile uint32_t *const first = (volatile uint32_t *)(uintptr_t)RS_XPI_PSRAM_BASE;
|
||||
volatile uint32_t *const last = (volatile uint32_t *)(uintptr_t)UINT32_C(0x547ffffc);
|
||||
bool pass = true;
|
||||
uint32_t unused_cycles;
|
||||
if (rs_tiny_rcu_request_profile(profile, RS_TIMEOUT_DEFAULT) != RS_OK ||
|
||||
rs_tiny_rcu_get_snapshot(&clocks) != RS_OK ||
|
||||
rs_xpi_psram_initialize(clocks.mem_hz, RS_TIMEOUT_DEFAULT, &device) != RS_OK ||
|
||||
rs_xpi_psram_validate_dma(&device, RS_P8_ADDRESS, sizeof(rs_p8_source), 4U) != RS_OK) {
|
||||
return false;
|
||||
}
|
||||
measurement->sys_hz = clocks.sys_hz;
|
||||
measurement->mem_hz = clocks.mem_hz;
|
||||
*first = 0U;
|
||||
*(volatile uint8_t *)(uintptr_t)(RS_XPI_PSRAM_BASE + 1U) = UINT8_C(0xa5);
|
||||
*(volatile uint16_t *)(uintptr_t)(RS_XPI_PSRAM_BASE + 2U) = UINT16_C(0xbeef);
|
||||
*last = UINT32_C(0x2468ace0);
|
||||
if (*first != UINT32_C(0xbeefa500) || *last != UINT32_C(0x2468ace0)) {
|
||||
return false;
|
||||
}
|
||||
if (rs_dma_session_acquire(&session, RS_DMA_CHANNEL_BULK) != RS_OK) {
|
||||
return false;
|
||||
}
|
||||
for (uint32_t round = 0U; round < 2U && pass; round++) {
|
||||
for (uint32_t i = 0U; i < RS_P8_WORDS; i++) {
|
||||
rs_p8_source[i] = UINT32_C(0x13579bdf) ^ (i * UINT32_C(0x01010101)) ^ round;
|
||||
rs_p8_readback[i] = 0U;
|
||||
}
|
||||
*(volatile uint32_t *)(uintptr_t)(RS_P8_ADDRESS - 4U) = UINT32_C(0x55aa55aa);
|
||||
memory[RS_P8_WORDS] = UINT32_C(0xaa55aa55);
|
||||
rs_p8_cpu_progress = 0U;
|
||||
pass =
|
||||
rs_p8_dma(&session, (uintptr_t)rs_p8_source, RS_P8_ADDRESS, round != 0U,
|
||||
round == 0U ? &measurement->isolated_cycles : &measurement->contended_cycles);
|
||||
measurement->progress = rs_p8_cpu_progress;
|
||||
for (uint32_t i = 0U; i < RS_P8_WORDS && pass; i++) {
|
||||
pass = memory[i] == rs_p8_source[i];
|
||||
}
|
||||
if (pass) {
|
||||
pass = rs_p8_dma(&session, RS_P8_ADDRESS, (uintptr_t)rs_p8_readback, false,
|
||||
&unused_cycles);
|
||||
}
|
||||
for (uint32_t i = 0U; i < RS_P8_WORDS && pass; i++) {
|
||||
pass = rs_p8_readback[i] == rs_p8_source[i];
|
||||
}
|
||||
pass = pass &&
|
||||
(*(volatile uint32_t *)(uintptr_t)(RS_P8_ADDRESS - 4U) == UINT32_C(0x55aa55aa)) &&
|
||||
(memory[RS_P8_WORDS] == UINT32_C(0xaa55aa55));
|
||||
}
|
||||
if (!pass && rs_dma_abort_wait(session.channel, RS_TIMEOUT_DEFAULT) != RS_OK) {
|
||||
return false; /* Static buffers remain owned; terminal failure follows. */
|
||||
}
|
||||
return rs_dma_session_release(&session) == RS_OK && pass && measurement->progress != 0U;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
const rs_gpio_config_t pin = {.mode = RS_GPIO_MODE_ALT0,
|
||||
.pull = RS_GPIO_PULL_NONE,
|
||||
.trigger = RS_GPIO_TRIGGER_NONE,
|
||||
.output_high = true};
|
||||
rs_tiny_rcu_snapshot_t clocks;
|
||||
rs_xpi_psram_t device;
|
||||
rs_status_t status;
|
||||
const uint32_t nor_word = *(volatile const uint32_t *)(uintptr_t)UINT32_C(0x50000000);
|
||||
uint32_t count = 0U;
|
||||
uint8_t code = 0U;
|
||||
__disable_irq();
|
||||
if (rs_gpio_configure(29U, &pin) != RS_OK || rs_tiny_rcu_get_snapshot(&clocks) != RS_OK) {
|
||||
code = 1U;
|
||||
} else {
|
||||
status = rs_xpi_psram_initialize(clocks.mem_hz, RS_TIMEOUT_DEFAULT, &device);
|
||||
if (status == RS_ENOTSUP && !device.initialized && device.capacity_bytes == 0U) {
|
||||
(void)rs_uart_init(24000000U, 921600U);
|
||||
printf("P8_ABSENT bounded optional-device rejection\n");
|
||||
code =
|
||||
(*(volatile const uint32_t *)(uintptr_t)UINT32_C(0x50000000) == nor_word) ? 0U : 2U;
|
||||
} else if (status != RS_OK) {
|
||||
code = 3U;
|
||||
} else {
|
||||
if (!rs_p8_profile(RS_TINY_RCU_PROFILE_SAFE24, &rs_p8_results[count++])) {
|
||||
code = 4U;
|
||||
}
|
||||
if (code == 0U && (clocks.capability & 12U) == 12U) {
|
||||
if (!rs_p8_profile(RS_TINY_RCU_PROFILE_PLL192, &rs_p8_results[count++]) ||
|
||||
!rs_p8_profile(RS_TINY_RCU_PROFILE_PLL240, &rs_p8_results[count++])) {
|
||||
code = 5U;
|
||||
}
|
||||
} else if (code == 0U && clocks.xin_hz != 24000000U) {
|
||||
if (!rs_p8_profile(RS_TINY_RCU_PROFILE_XIN, &rs_p8_results[count++])) {
|
||||
code = 6U;
|
||||
}
|
||||
}
|
||||
if (code == 0U && rs_tiny_rcu_request_profile(RS_TINY_RCU_PROFILE_SAFE24,
|
||||
RS_TIMEOUT_DEFAULT) != RS_OK) {
|
||||
code = 7U;
|
||||
}
|
||||
if (code == 0U &&
|
||||
*(volatile const uint32_t *)(uintptr_t)UINT32_C(0x50000000) != nor_word) {
|
||||
code = 8U;
|
||||
}
|
||||
}
|
||||
}
|
||||
(void)rs_uart_init(24000000U, 921600U);
|
||||
for (uint32_t i = 0U; i < count; i++) {
|
||||
printf("P8_TRANSPORT sys_hz=%u mem_hz=%u bytes=1024 burst=4 isolated_cycles=%u "
|
||||
"contended_cycles=%u progress=%u\n",
|
||||
rs_p8_results[i].sys_hz, rs_p8_results[i].mem_hz, rs_p8_results[i].isolated_cycles,
|
||||
rs_p8_results[i].contended_cycles, rs_p8_results[i].progress);
|
||||
}
|
||||
printf("P8_RESULT code=%u profiles=%u\n", code, count);
|
||||
rs_sysctrl_write_test_status(code == 0U, code);
|
||||
for (;;) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef RETROSOC_HAL_XPI_PSRAM_H
|
||||
#define RETROSOC_HAL_XPI_PSRAM_H
|
||||
|
||||
#include <retrosoc/hal/xpi.h>
|
||||
|
||||
#define RS_XPI_PSRAM_BASE UINT32_C(0x54000000)
|
||||
#define RS_XPI_PSRAM_BYTES UINT32_C(0x00800000)
|
||||
#define RS_XPI_PSRAM_BURST_BEATS UINT8_C(4)
|
||||
|
||||
typedef struct {
|
||||
bool initialized;
|
||||
uint32_t memory_clock_hz;
|
||||
uint32_t capacity_bytes;
|
||||
uint8_t id[6];
|
||||
} rs_xpi_psram_t;
|
||||
|
||||
/* ESP-PSRAM64H reference profile: slot 1, LUT 8..11, QPI SDR, SCK=MEM/2.
|
||||
* Caller owns XPI exclusively, routes NSS1, and executes code/IRQs from SRAM.
|
||||
* Clock changes/reset invalidate this software session; explicitly initialize
|
||||
* again before using memory. This is not physical part/rate qualification. */
|
||||
rs_status_t rs_xpi_psram_timing(uint32_t memory_clock_hz, rs_xpi_slot_config_t *config);
|
||||
rs_status_t rs_xpi_psram_initialize(uint32_t memory_clock_hz, rs_timeout_t timeout,
|
||||
rs_xpi_psram_t *device);
|
||||
/* Direct DMA uses SOFTWARE request and mapped addresses, not XPI TX/RX FIFO.
|
||||
* Align bases to 16 bytes for four-beat bursts; the DMA splits at 4 KiB. */
|
||||
rs_status_t rs_xpi_psram_validate_dma(const rs_xpi_psram_t *device, uint32_t address,
|
||||
uint32_t bytes, uint8_t burst_beats);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,147 @@
|
||||
#include <retrosoc/hal/clint.h>
|
||||
#include <retrosoc/hal/xpi_psram.h>
|
||||
|
||||
#define RS_XPI_PSRAM_SLOT UINT8_C(1)
|
||||
#define RS_XPI_PSRAM_COMMAND_SEQUENCE UINT8_C(10)
|
||||
#define RS_XPI_PSRAM_ID_SEQUENCE UINT8_C(11)
|
||||
|
||||
static rs_status_t rs_xpi_psram_delay(uint32_t microseconds, rs_timeout_t timeout) {
|
||||
uint64_t first, now;
|
||||
const uint64_t ticks =
|
||||
((uint64_t)microseconds * RS_CLINT_TIMEBASE_HZ + UINT64_C(999999)) / UINT64_C(1000000);
|
||||
if (rs_clint_get_time(&first) != RS_OK) {
|
||||
return RS_EIO;
|
||||
}
|
||||
while (timeout-- != 0U) {
|
||||
if (rs_clint_get_time(&now) != RS_OK) {
|
||||
return RS_EIO;
|
||||
}
|
||||
if (now - first >= ticks) {
|
||||
return RS_OK;
|
||||
}
|
||||
}
|
||||
return RS_ETIMEOUT;
|
||||
}
|
||||
|
||||
static rs_status_t rs_xpi_psram_command(uint8_t opcode, rs_xpi_pads_t pads, rs_timeout_t timeout) {
|
||||
const uint16_t lut[] = {rs_xpi_instruction(RS_XPI_INSTR_COMMAND, pads, opcode), 0U};
|
||||
const rs_xpi_transfer_t transfer = {.slot = RS_XPI_PSRAM_SLOT,
|
||||
.sequence = RS_XPI_PSRAM_COMMAND_SEQUENCE};
|
||||
rs_status_t status = rs_xpi_enable(false, timeout);
|
||||
if (status == RS_OK) {
|
||||
status = rs_xpi_lut_write(RS_XPI_PSRAM_COMMAND_SEQUENCE, lut, 2U, timeout);
|
||||
}
|
||||
if (status == RS_OK) {
|
||||
status = rs_xpi_enable(true, timeout);
|
||||
}
|
||||
if (status == RS_OK) {
|
||||
status = rs_xpi_transfer(&transfer, timeout);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
rs_status_t rs_xpi_psram_initialize(uint32_t memory_clock_hz, rs_timeout_t timeout,
|
||||
rs_xpi_psram_t *device) {
|
||||
const uint16_t read_lut[] = {UINT16_C(0x18eb), UINT16_C(0x2818), UINT16_C(0x4006),
|
||||
UINT16_C(0x6800), 0U};
|
||||
const uint16_t write_lut[] = {UINT16_C(0x1838), UINT16_C(0x2818), UINT16_C(0x5800), 0U};
|
||||
const uint16_t id_lut[] = {UINT16_C(0x109f), UINT16_C(0x2018), UINT16_C(0x6000), 0U};
|
||||
rs_xpi_slot_config_t config;
|
||||
rs_xpi_transfer_t transfer = {
|
||||
.slot = RS_XPI_PSRAM_SLOT, .sequence = RS_XPI_PSRAM_ID_SEQUENCE, .byte_count = 3U};
|
||||
rs_status_t status;
|
||||
if (device == NULL) {
|
||||
return RS_EINVAL;
|
||||
}
|
||||
*device = (rs_xpi_psram_t){0};
|
||||
status = rs_xpi_psram_timing(memory_clock_hz, &config);
|
||||
if (status != RS_OK) {
|
||||
return status;
|
||||
}
|
||||
status = rs_xpi_probe();
|
||||
if (status != RS_OK) {
|
||||
return status;
|
||||
}
|
||||
/* Reject locks before issuing any potentially faulting configuration write. */
|
||||
if ((RS_XPI_REG(RS_XPI_REG_CONFIG_LOCK) &
|
||||
(RS_XPI_CONFIG_LOCK_GLOBAL | RS_XPI_CONFIG_LOCK_SLOT1 | RS_XPI_CONFIG_LOCK_LUT)) != 0U) {
|
||||
return RS_EIO;
|
||||
}
|
||||
status = rs_xpi_enable(false, timeout);
|
||||
if (status != RS_OK) {
|
||||
return status;
|
||||
}
|
||||
status = rs_xpi_slot_configure(RS_XPI_PSRAM_SLOT, &config, timeout);
|
||||
if (status == RS_OK) {
|
||||
status = rs_xpi_lut_write(8U, read_lut, 5U, timeout);
|
||||
}
|
||||
if (status == RS_OK) {
|
||||
status = rs_xpi_lut_write(9U, write_lut, 4U, timeout);
|
||||
}
|
||||
if (status == RS_OK) {
|
||||
status = rs_xpi_lut_write(RS_XPI_PSRAM_ID_SEQUENCE, id_lut, 4U, timeout);
|
||||
}
|
||||
if (status == RS_OK) {
|
||||
status = rs_xpi_enable(true, timeout);
|
||||
}
|
||||
if (status == RS_OK) {
|
||||
status = rs_xpi_psram_delay(150U, timeout);
|
||||
}
|
||||
/* Exit QPI is harmless when the device is already in SPI reset mode. */
|
||||
if (status == RS_OK) {
|
||||
status = rs_xpi_psram_command(UINT8_C(0xf5), RS_XPI_PADS_4, timeout);
|
||||
}
|
||||
if (status == RS_OK) {
|
||||
status = rs_xpi_psram_command(UINT8_C(0x66), RS_XPI_PADS_1, timeout);
|
||||
}
|
||||
if (status == RS_OK) {
|
||||
status = rs_xpi_psram_command(UINT8_C(0x99), RS_XPI_PADS_1, timeout);
|
||||
}
|
||||
if (status == RS_OK) {
|
||||
status = rs_xpi_psram_delay(1U, timeout);
|
||||
}
|
||||
if (status == RS_OK) {
|
||||
transfer.rx_data = device->id;
|
||||
status = rs_xpi_transfer(&transfer, timeout);
|
||||
}
|
||||
if ((status == RS_OK) &&
|
||||
((device->id[0] != UINT8_C(0x0d)) || (device->id[1] != UINT8_C(0x5d)) ||
|
||||
(device->id[2] != UINT8_C(0x26)))) {
|
||||
status = RS_ENOTSUP;
|
||||
}
|
||||
if (status == RS_OK) {
|
||||
status = rs_xpi_psram_command(UINT8_C(0x35), RS_XPI_PADS_1, timeout);
|
||||
}
|
||||
if (status == RS_OK) {
|
||||
status = rs_xpi_enable(false, timeout);
|
||||
}
|
||||
if (status == RS_OK) {
|
||||
config.memory_read_enable = true;
|
||||
config.memory_write_enable = true;
|
||||
status = rs_xpi_slot_configure(RS_XPI_PSRAM_SLOT, &config, timeout);
|
||||
}
|
||||
if (status != RS_OK) {
|
||||
/* Keep the first error snapshot. Never publish capacity after a failed
|
||||
* probe, and leave mapped NSS1 disabled while restoring NOR access. */
|
||||
rs_status_t cleanup = rs_xpi_abort(timeout);
|
||||
if (cleanup == RS_OK) {
|
||||
cleanup = rs_xpi_enable(false, timeout);
|
||||
}
|
||||
config.memory_read_enable = false;
|
||||
config.memory_write_enable = false;
|
||||
if (cleanup == RS_OK) {
|
||||
cleanup = rs_xpi_slot_configure(RS_XPI_PSRAM_SLOT, &config, timeout);
|
||||
}
|
||||
if (cleanup == RS_OK) {
|
||||
cleanup = rs_xpi_enable(true, timeout);
|
||||
}
|
||||
return (cleanup == RS_OK) ? status : cleanup;
|
||||
}
|
||||
status = rs_xpi_enable(true, timeout);
|
||||
if (status == RS_OK) {
|
||||
device->capacity_bytes = RS_XPI_PSRAM_BYTES;
|
||||
device->memory_clock_hz = memory_clock_hz;
|
||||
device->initialized = true;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#include <retrosoc/hal/xpi_psram.h>
|
||||
|
||||
rs_status_t rs_xpi_psram_timing(uint32_t memory_clock_hz, rs_xpi_slot_config_t *config) {
|
||||
if ((config == NULL) ||
|
||||
((memory_clock_hz != UINT32_C(24000000)) && (memory_clock_hz != UINT32_C(48000000)) &&
|
||||
(memory_clock_hz != UINT32_C(96000000)) && (memory_clock_hz != UINT32_C(120000000)))) {
|
||||
return RS_EINVAL;
|
||||
}
|
||||
config->enable = true;
|
||||
config->memory_read_enable = false;
|
||||
config->memory_write_enable = false;
|
||||
config->mode3 = false;
|
||||
config->device_size = RS_XPI_PSRAM_BYTES;
|
||||
config->read_sequence = UINT8_C(8);
|
||||
config->write_sequence = UINT8_C(9);
|
||||
config->clock_divider = UINT8_C(0);
|
||||
config->cs_setup_cycles = UINT8_C(1);
|
||||
config->cs_hold_cycles =
|
||||
(uint8_t)(((uint64_t)memory_clock_hz * UINT64_C(20) + UINT64_C(999999999)) /
|
||||
UINT64_C(1000000000));
|
||||
config->cs_high_cycles =
|
||||
(uint8_t)(((uint64_t)memory_clock_hz * UINT64_C(50) + UINT64_C(999999999)) /
|
||||
UINT64_C(1000000000));
|
||||
/* Includes core setup/hold/high and consumer stalls. Leave two MEM edges
|
||||
* of margin below the reference device's 8 us active-CS limit. */
|
||||
config->timeout_cycles = (memory_clock_hz / UINT32_C(125000)) - UINT32_C(2);
|
||||
config->burst_boundary = UINT32_C(1024);
|
||||
return RS_OK;
|
||||
}
|
||||
|
||||
rs_status_t rs_xpi_psram_validate_dma(const rs_xpi_psram_t *device, uint32_t address,
|
||||
uint32_t bytes, uint8_t burst_beats) {
|
||||
uint32_t offset;
|
||||
uint32_t cursor;
|
||||
const uint32_t burst_bytes = (uint32_t)burst_beats * UINT32_C(4);
|
||||
if (device == NULL) {
|
||||
return RS_EINVAL;
|
||||
}
|
||||
if (!device->initialized || (device->capacity_bytes != RS_XPI_PSRAM_BYTES)) {
|
||||
return RS_ENOTSUP;
|
||||
}
|
||||
if ((address < RS_XPI_PSRAM_BASE) || (bytes == 0U) || ((bytes & 3U) != 0U) ||
|
||||
((burst_beats != 1U) && (burst_beats != 2U) && (burst_beats != 4U)) ||
|
||||
((address & ((uint32_t)burst_beats * 4U - 1U)) != 0U)) {
|
||||
return RS_EINVAL;
|
||||
}
|
||||
offset = address - RS_XPI_PSRAM_BASE;
|
||||
if ((offset >= device->capacity_bytes) || (bytes > device->capacity_bytes - offset)) {
|
||||
return RS_EINVAL;
|
||||
}
|
||||
/* The XPI slot rejects a serial-boundary crossing command, and the DMA
|
||||
* itself cannot cross a 4 KiB AXI page. Validate every emitted burst before
|
||||
* admitting the session; do not rely on the target to fail mid-frame. */
|
||||
for (cursor = 0U; cursor < bytes; cursor += burst_bytes) {
|
||||
uint32_t length = bytes - cursor;
|
||||
const uint32_t serial_offset = (offset + cursor) & UINT32_C(0x3ff);
|
||||
const uint32_t page_offset = (offset + cursor) & UINT32_C(0xfff);
|
||||
if (length > burst_bytes) {
|
||||
length = burst_bytes;
|
||||
}
|
||||
if ((length > UINT32_C(0x400) - serial_offset) ||
|
||||
(length > UINT32_C(0x1000) - page_offset)) {
|
||||
return RS_EINVAL;
|
||||
}
|
||||
}
|
||||
return RS_OK;
|
||||
}
|
||||
+52
-16
@@ -83,6 +83,7 @@ module xpi_mm (
|
||||
logic [1:0] s_resp_d, s_resp_q;
|
||||
logic [31:0] s_rdata_d, s_rdata_q;
|
||||
logic s_rvalid_d, s_rvalid_q;
|
||||
logic s_word_complete_d, s_word_complete_q;
|
||||
logic [2:0] s_byte_idx_d, s_byte_idx_q;
|
||||
logic [6:0] s_cursor_d, s_cursor_q;
|
||||
logic [6:0] s_run_end_d, s_run_end_q;
|
||||
@@ -198,7 +199,7 @@ module xpi_mm (
|
||||
axi4.awburst, axi4.awlen
|
||||
) && !axi4.awlock && (axi4.awsize <= `AXI4_BURST_SIZE_4BYTES) &&
|
||||
((axi4.awaddr & ((32'd1 << axi4.awsize) - 1'b1)) == 32'd0) &&
|
||||
(axi4.awaddr[31:12] == s_write_last_addr[31:12]);
|
||||
(axi4.awaddr[31:12] == s_write_last_addr[31:12]) && s_write_boundary_ok;
|
||||
|
||||
assign s_legal_read = address_supported(
|
||||
axi4.araddr
|
||||
@@ -216,7 +217,7 @@ module xpi_mm (
|
||||
axi4.arburst, axi4.arlen
|
||||
) && !axi4.arlock && (axi4.arsize <= `AXI4_BURST_SIZE_4BYTES) &&
|
||||
((axi4.araddr & ((32'd1 << axi4.arsize) - 1'b1)) == 32'd0) &&
|
||||
(axi4.araddr[31:12] == s_read_last_addr[31:12]);
|
||||
(axi4.araddr[31:12] == s_read_last_addr[31:12]) && s_read_boundary_ok;
|
||||
|
||||
assign axi4.awready = (s_state_q == Idle) && accept_enable_i && !axi4.arvalid;
|
||||
assign axi4.arready = (s_state_q == Idle) && accept_enable_i;
|
||||
@@ -247,7 +248,7 @@ module xpi_mm (
|
||||
(16'd1 << s_size_q));
|
||||
|
||||
assign core_tx_valid_o = s_state_q == WriteStream;
|
||||
assign core_rx_ready_o = (s_state_q == ReadData) && !s_rvalid_q;
|
||||
assign core_rx_ready_o = (s_state_q == ReadData) && !s_word_complete_q;
|
||||
assign core_tx_data_o = s_write_buffer_q[s_tx_beat][(s_tx_lane*8)+:8];
|
||||
|
||||
assign s_err_code_q = xpi_error_e'(s_err_code_bits_q);
|
||||
@@ -306,8 +307,13 @@ module xpi_mm (
|
||||
end
|
||||
|
||||
always_comb begin
|
||||
s_write_lane_mask = (4'd1 << s_size_q) - 1'b1;
|
||||
s_read_lane = {1'b0, s_addr_q[1:0]} + s_byte_idx_q;
|
||||
unique case (s_size_q)
|
||||
3'd0: s_write_lane_mask = 4'b0001;
|
||||
3'd1: s_write_lane_mask = 4'b0011;
|
||||
3'd2: s_write_lane_mask = 4'b1111;
|
||||
default: s_write_lane_mask = 4'b0000;
|
||||
endcase
|
||||
s_read_lane = {1'b0, s_addr_q[1:0]} + s_byte_idx_q;
|
||||
end
|
||||
|
||||
for (genvar write_index = 0; write_index < 16; write_index++) begin : write_buffer_block
|
||||
@@ -339,6 +345,7 @@ module xpi_mm (
|
||||
s_resp_d = s_resp_q;
|
||||
s_rdata_d = s_rdata_q;
|
||||
s_rvalid_d = s_rvalid_q;
|
||||
s_word_complete_d = s_word_complete_q;
|
||||
s_byte_idx_d = s_byte_idx_q;
|
||||
s_cursor_d = s_cursor_q;
|
||||
s_run_end_d = s_run_end_q;
|
||||
@@ -362,6 +369,7 @@ module xpi_mm (
|
||||
s_resp_d = `AXI4_RESP_OKAY;
|
||||
s_rdata_d = '0;
|
||||
s_rvalid_d = 1'b0;
|
||||
s_word_complete_d = 1'b0;
|
||||
s_byte_idx_d = '0;
|
||||
s_cursor_d = '0;
|
||||
s_frame_done_d = 1'b0;
|
||||
@@ -423,37 +431,45 @@ module xpi_mm (
|
||||
s_rdata_d[(s_read_lane*8)+:8] = core_rx_data_i;
|
||||
perf_read_byte_event_o = 1'b1;
|
||||
if (s_byte_idx_q == ((3'd1 << s_size_q) - 1'b1)) begin
|
||||
s_byte_idx_d = '0;
|
||||
s_rvalid_d = 1'b1;
|
||||
s_byte_idx_d = '0;
|
||||
s_word_complete_d = 1'b1;
|
||||
// Hold the physical command's last beat until STOP/CS completion
|
||||
// so a late sequence error cannot follow a successful AXI end.
|
||||
s_rvalid_d = s_coalesce_q && (s_beat_q != s_len_q);
|
||||
end else begin
|
||||
s_byte_idx_d = s_byte_idx_q + 1'b1;
|
||||
end
|
||||
end
|
||||
if (core_done_i) begin
|
||||
s_frame_done_d = 1'b1;
|
||||
if (core_error_i) begin
|
||||
s_resp_d = `AXI4_RESP_SLAVE_ERROR;
|
||||
if (core_error_i || !s_word_complete_q || (s_coalesce_q && (s_beat_q != s_len_q))) begin
|
||||
s_transaction_err_d = 1'b1;
|
||||
s_err_code_d = core_error_code_i;
|
||||
s_err_code_d = core_error_i ? core_error_code_i : XpiErrorSequence;
|
||||
s_err_addr_d = s_addr_q;
|
||||
s_err_slot_d = s_slot_q;
|
||||
s_err_pc_d = core_error_pc_i;
|
||||
axi_error_event_o = 1'b1;
|
||||
if (!s_rvalid_q) begin
|
||||
s_rdata_d = '0;
|
||||
s_rvalid_d = 1'b1;
|
||||
s_resp_d = `AXI4_RESP_SLAVE_ERROR;
|
||||
s_rdata_d = '0;
|
||||
s_rvalid_d = 1'b1;
|
||||
s_word_complete_d = 1'b1;
|
||||
end
|
||||
end else begin
|
||||
s_rvalid_d = 1'b1;
|
||||
end
|
||||
end
|
||||
if (s_rvalid_q && axi4.rready) begin
|
||||
s_rvalid_d = 1'b0;
|
||||
s_rdata_d = '0;
|
||||
s_rvalid_d = 1'b0;
|
||||
s_word_complete_d = 1'b0;
|
||||
s_rdata_d = '0;
|
||||
if (s_beat_q == s_len_q) begin
|
||||
s_state_d = (s_frame_done_q || core_done_i) ? Idle : ReadFinish;
|
||||
end else begin
|
||||
s_addr_d = s_next_addr;
|
||||
s_beat_d = s_beat_q + 1'b1;
|
||||
if (s_transaction_err_q || core_error_i) begin
|
||||
if (s_transaction_err_d) begin
|
||||
s_resp_d = `AXI4_RESP_SLAVE_ERROR;
|
||||
s_state_d = ReadErrorResponse;
|
||||
end else if (s_coalesce_q) begin
|
||||
s_state_d = ReadData;
|
||||
@@ -516,10 +532,12 @@ module xpi_mm (
|
||||
axi_error_event_o = 1'b1;
|
||||
s_state_d = axi4.wlast ? WriteResponse : WriteDrain;
|
||||
end else if (s_beat_q == s_len_q) begin
|
||||
s_addr_d = s_start_addr_q;
|
||||
s_beat_d = '0;
|
||||
s_cursor_d = '0;
|
||||
s_state_d = WriteScan;
|
||||
end else begin
|
||||
s_addr_d = s_next_addr;
|
||||
s_beat_d = s_beat_q + 1'b1;
|
||||
end
|
||||
end
|
||||
@@ -549,7 +567,17 @@ module xpi_mm (
|
||||
end
|
||||
|
||||
WriteStream: begin
|
||||
if (core_tx_valid_o && core_tx_ready_i) begin
|
||||
// The core can time out or abort before consuming all buffered bytes.
|
||||
// AXI W is already drained; complete B rather than wait for a dead core.
|
||||
if (core_done_i) begin
|
||||
s_resp_d = `AXI4_RESP_SLAVE_ERROR;
|
||||
s_err_code_d = core_error_i ? core_error_code_i : XpiErrorSequence;
|
||||
s_err_addr_d = s_addr_q;
|
||||
s_err_slot_d = s_slot_q;
|
||||
s_err_pc_d = core_error_pc_i;
|
||||
axi_error_event_o = 1'b1;
|
||||
s_state_d = WriteResponse;
|
||||
end else if (core_tx_valid_o && core_tx_ready_i) begin
|
||||
perf_write_byte_event_o = 1'b1;
|
||||
s_cursor_d = s_cursor_q + 1'b1;
|
||||
if ((s_cursor_q + 1'b1) >= s_run_end_q) begin
|
||||
@@ -609,6 +637,14 @@ module xpi_mm (
|
||||
.dat_i (s_state_d),
|
||||
.dat_o (s_state_bits_q)
|
||||
);
|
||||
dffr #(
|
||||
.DATA_WIDTH(1)
|
||||
) u_word_complete_dffr (
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i(rst_n_i),
|
||||
.dat_i (s_word_complete_d),
|
||||
.dat_o (s_word_complete_q)
|
||||
);
|
||||
dffr #(
|
||||
.DATA_WIDTH(32)
|
||||
) u_addr_dffr (
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/model/uart_model.sv
|
||||
/device_model/mic.sv
|
||||
/device_model/w25q128jvxim.sv
|
||||
/model/ESP_PSRAM64H.sv
|
||||
/device_model/ESP_PSRAM64H.sv
|
||||
/model/DVP_CAMERA.sv
|
||||
/third_party/model/sdram/sdr.v
|
||||
/tb/retrosoc_tb.sv
|
||||
@@ -11,7 +11,7 @@ SOC_VSRC_HOME += $(RTL_PATH)/dv/verilator/rtl
|
||||
SOC_COMPILE_HOME := $(BUILD_DIR)/emu_compile
|
||||
|
||||
SOC_VXXFILES := $(RTL_FLIST)
|
||||
SOC_VXXFILES += $(RTL_PATH)/dv/model/ESP_PSRAM64H.sv
|
||||
SOC_VXXFILES += $(ROOT_PATH)/rtl/model/ESP_PSRAM64H.sv
|
||||
SOC_VXXFILES += $(RTL_PATH)/dv/verilator/rtl/flash_read_binder.sv
|
||||
SOC_VXXFILES += $(RTL_PATH)/dv/verilator/rtl/flash_read_byte_binder.sv
|
||||
SOC_VXXFILES += $(RTL_PATH)/dv/verilator/rtl/QSPIFlash.sv
|
||||
@@ -51,7 +51,7 @@ VERILATOR_DEPFILE := $(BUILD_DIR)/verilate.d
|
||||
VERILATOR_EMU := $(BUILD_DIR)/emu
|
||||
RTL_LINT_STAMP := $(RTL_LINT_DIR)/rtl-lint.stamp
|
||||
RTL_LINT_DEPFILE := $(RTL_LINT_DIR)/rtl-lint.d
|
||||
VERILATOR_EXTRA_SOURCES := $(RTL_PATH)/dv/model/ESP_PSRAM64H.sv \
|
||||
VERILATOR_EXTRA_SOURCES := $(ROOT_PATH)/rtl/model/ESP_PSRAM64H.sv \
|
||||
$(RTL_PATH)/dv/verilator/rtl/flash_read_binder.sv \
|
||||
$(RTL_PATH)/dv/verilator/rtl/flash_read_byte_binder.sv \
|
||||
$(RTL_PATH)/dv/verilator/rtl/QSPIFlash.sv \
|
||||
@@ -144,4 +144,4 @@ clean:
|
||||
python3 $(ROOT_PATH)/scripts/clean.py --root $(ROOT_PATH) --path $(BUILD_DIR)
|
||||
python3 $(ROOT_PATH)/scripts/clean.py --root $(ROOT_PATH) --path $(RTL_LINT_DIR)
|
||||
|
||||
.PHONY: lint rtl-lint check-rtl-lint comp sim debug-sim clean
|
||||
.PHONY: lint rtl-lint check-rtl-lint comp sim debug-sim clean
|
||||
+4
-2
@@ -168,6 +168,8 @@ CRT_SRCS := $(ROOT_PATH)/crt/arch/riscv/startup.S \
|
||||
$(ROOT_PATH)/crt/src/hal/spisd_math.c \
|
||||
$(ROOT_PATH)/crt/src/hal/spisd.c \
|
||||
$(ROOT_PATH)/crt/src/hal/xpi.c \
|
||||
$(ROOT_PATH)/crt/src/hal/xpi_psram_math.c \
|
||||
$(ROOT_PATH)/crt/src/hal/xpi_psram.c \
|
||||
$(ROOT_PATH)/crt/src/hal/fabric_monitor.c \
|
||||
$(ROOT_PATH)/crt/src/hal/resource.c \
|
||||
$(ROOT_PATH)/crt/src/hal/extension.c \
|
||||
@@ -180,7 +182,7 @@ CFLAGS += -DRS_SOC_TINY
|
||||
TINY_CRT_NAMES := startup.S clzsi2.c divdi3.c ffssi2.c udivdi3.c umoddi3.c \
|
||||
stdlib.c string.c console.c printf.c archinfo.c clint.c uart_math.c uart.c \
|
||||
gpio_math.c gpio.c timer_math.c timer.c pwm.c rtc.c wdg.c watchdog.c i2c_math.c i2c.c \
|
||||
dma_math.c dma.c onchip_sram.c xpi.c rng.c crc.c ws2812_math.c ws2812.c \
|
||||
dma_math.c dma.c onchip_sram.c xpi.c xpi_psram.c xpi_psram_math.c rng.c crc.c ws2812_math.c ws2812.c \
|
||||
crypto.c crypto_lifecycle.c i2s_math.c i2s.c sdio_math.c sdio.c rcu.c
|
||||
CRT_SRCS := $(foreach source,$(CRT_SRCS),$(if $(filter $(notdir $(source)),$(TINY_CRT_NAMES)),$(source)))
|
||||
CRT_SRCS += $(ROOT_PATH)/crt/src/hal/sysctrl.c
|
||||
@@ -305,4 +307,4 @@ firmware: $(FIRMWARE_ELF)
|
||||
|
||||
firmware asm: | manifest
|
||||
|
||||
.PHONY: FORCE_VERSION
|
||||
.PHONY: FORCE_VERSION
|
||||
@@ -8,7 +8,7 @@
|
||||
// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
// See the Mulan PSL v2 for more details.
|
||||
//
|
||||
// Behavioral model for the external ESP-PSRAM64H 64-Mbit PSRAM.
|
||||
// Shared behavioral model for the external ESP-PSRAM64H 64-Mbit PSRAM.
|
||||
// Only SDR SPI and QPI transactions are modeled. The model intentionally
|
||||
// does not initialize the memory array unless INITIALIZE_MEMORY is enabled.
|
||||
|
||||
@@ -23,13 +23,28 @@ module retrosoc_tiny_tb;
|
||||
wire s_jtag_trst_n = s_rst_n;
|
||||
wire s_jtag_tdo;
|
||||
`include "tiny_tb_gpio.svh"
|
||||
wire s_xpi_sck;
|
||||
wire [3:0] s_xpi_nss;
|
||||
tri [3:0] s_xpi_data;
|
||||
integer s_cycles;
|
||||
wire s_xpi_sck;
|
||||
wire [3:0] s_xpi_nss;
|
||||
tri1 [3:0] s_xpi_data;
|
||||
// Optional board population; the unchanged default has no PSRAM dependency.
|
||||
wire s_psram_csn = ($test$plusargs(
|
||||
"tiny_p8_psram"
|
||||
) && !$test$plusargs(
|
||||
"p8_absent"
|
||||
)) ? s_gpio[29] : 1'b1;
|
||||
ESP_PSRAM64H #(
|
||||
.TIMING_VIOLATION_FATAL(1),
|
||||
.MIN_CS_HIGH_NS (50)
|
||||
) u_psram (
|
||||
.sclk(s_xpi_sck),
|
||||
.csn (s_psram_csn),
|
||||
.sio (s_xpi_data)
|
||||
);
|
||||
integer s_cycles;
|
||||
`ifndef RETROSOC_SOC__TINY_NETLIST
|
||||
`include "tiny_p6_devices.svh"
|
||||
`include "tiny_p7_observers.svh"
|
||||
`include "tiny_p8_observers.svh"
|
||||
`endif
|
||||
integer s_max_cycles = 20000000;
|
||||
integer s_done_cycles = 0;
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// Verification-only MEM-clock observations. No DUT state is driven.
|
||||
`define _P8_AXI u_dut.u_soc.u_xpi_mem_axi4_if
|
||||
logic s_p8_previous_enable = 0;
|
||||
logic s_p8_finished = 0;
|
||||
longint unsigned s_p8_cycles = 0, s_p8_write_bytes = 0, s_p8_read_bytes = 0;
|
||||
longint unsigned s_p8_stall_run[5], s_p8_stall_max[5];
|
||||
longint unsigned s_p8_all_writes = 0;
|
||||
integer s_p8_window = 0;
|
||||
wire [4:0] s_p8_stalls = {
|
||||
`_P8_AXI.awvalid && !`_P8_AXI.awready,
|
||||
`_P8_AXI.wvalid && !`_P8_AXI.wready,
|
||||
`_P8_AXI.arvalid && !`_P8_AXI.arready,
|
||||
`_P8_AXI.bvalid && !`_P8_AXI.bready,
|
||||
`_P8_AXI.rvalid && !`_P8_AXI.rready
|
||||
};
|
||||
always @(posedge u_dut.u_soc.s_backend_mem_clk) begin
|
||||
if ($test$plusargs("tiny_p8_psram") && u_dut.u_soc.s_xpi_rst_n) begin
|
||||
if (`_P8_AXI.wvalid && `_P8_AXI.wready) s_p8_all_writes++;
|
||||
if (u_dut.u_soc.s_perf_enable) begin
|
||||
if (!s_p8_previous_enable) begin
|
||||
s_p8_cycles = 0;
|
||||
s_p8_write_bytes = 0;
|
||||
s_p8_read_bytes = 0;
|
||||
for (int i = 0; i < 5; i++) begin
|
||||
s_p8_stall_run[i] = 0;
|
||||
s_p8_stall_max[i] = 0;
|
||||
end
|
||||
end
|
||||
s_p8_cycles++;
|
||||
if (`_P8_AXI.wvalid && `_P8_AXI.wready) s_p8_write_bytes += $countones(`_P8_AXI.wstrb);
|
||||
if (`_P8_AXI.rvalid && `_P8_AXI.rready) s_p8_read_bytes += 4;
|
||||
for (int i = 0; i < 5; i++) begin
|
||||
s_p8_stall_run[i] = s_p8_stalls[i] ? s_p8_stall_run[i] + 1 : 0;
|
||||
if (s_p8_stall_run[i] > s_p8_stall_max[i]) s_p8_stall_max[i] = s_p8_stall_run[i];
|
||||
end
|
||||
end else if (s_p8_previous_enable) begin
|
||||
$display(
|
||||
"P8_XPI_WINDOW index=%0d mem_cycles=%0d write_bytes=%0d read_bytes=%0d cs_max=%0d aw_max=%0d w_max=%0d ar_max=%0d b_max=%0d r_max=%0d",
|
||||
s_p8_window, s_p8_cycles, s_p8_write_bytes, s_p8_read_bytes, s_p8_cs_max,
|
||||
s_p8_stall_max[0], s_p8_stall_max[1], s_p8_stall_max[2], s_p8_stall_max[3],
|
||||
s_p8_stall_max[4]);
|
||||
s_p8_window++;
|
||||
end
|
||||
s_p8_previous_enable = u_dut.u_soc.s_perf_enable;
|
||||
if (u_dut.s_test_done && !s_p8_finished) begin
|
||||
s_p8_finished = 1;
|
||||
if (!$test$plusargs("p8_absent") && (s_p8_window < 4 || s_p8_all_writes == 0))
|
||||
$fatal(1, "SIM_TEST_FAIL P8 missing mapped transport evidence");
|
||||
if ($test$plusargs("p8_absent") && s_p8_all_writes != 0)
|
||||
$fatal(1, "SIM_TEST_FAIL P8 absent device exposed mapped writes");
|
||||
end
|
||||
end
|
||||
end
|
||||
`undef _P8_AXI
|
||||
|
||||
time s_p8_cs_start, s_p8_cs_max = 0;
|
||||
always @(negedge s_psram_csn) s_p8_cs_start = $time;
|
||||
always @(posedge s_psram_csn) begin
|
||||
if ($test$plusargs("tiny_p8_psram") && s_p8_cs_start != 0) begin
|
||||
if ($time - s_p8_cs_start > 8000) $fatal(1, "SIM_TEST_FAIL P8 active CS exceeds 8 us");
|
||||
if ($time - s_p8_cs_start > s_p8_cs_max) s_p8_cs_max = $time - s_p8_cs_start;
|
||||
end
|
||||
end
|
||||
@@ -1,3 +1,4 @@
|
||||
/dv/tiny_qspi_nor.sv
|
||||
/device_model/ESP_PSRAM64H.sv
|
||||
/tests/rtl/sdio_native_model.sv
|
||||
/dv/retrosoc_tiny_tb.sv
|
||||
@@ -225,7 +225,8 @@ def generate_filelists(output: Path, generated: Path, defines: list[str],
|
||||
prefixes = {"top": TINY / "top", "dv": TINY / "dv", "ip": ROOT / "rtl/ip",
|
||||
"tech": ROOT / "rtl/tech", "clusterip": ROOT / "rtl/managed/clusterip",
|
||||
"hazard3": ROOT / "rtl/managed/hazard3/hdl", "physical": ROOT / "physical",
|
||||
"cache": ROOT / ".cache", "tests": ROOT / "tests"}
|
||||
"cache": ROOT / ".cache", "tests": ROOT / "tests",
|
||||
"device_model": ROOT / "rtl/model"}
|
||||
suffix = "_yosys" if synthesis == "YOSYS" else "_verilator" if simulator == "VERILATOR" else ""
|
||||
pdk_name = "pdk_ihp130.fl" if pdk == "IHP130" else f"pdk_ics55{suffix}.fl"
|
||||
templates = [*sorted((TINY / "filelist").glob("*.fl")),
|
||||
|
||||
@@ -27,6 +27,7 @@ TEST_SOURCES = (
|
||||
"crt/src/hal/uart_math.c",
|
||||
"crt/src/hal/timer_math.c",
|
||||
"crt/src/hal/psram_math.c",
|
||||
"crt/src/hal/xpi_psram_math.c",
|
||||
"crt/src/hal/sdram_math.c",
|
||||
"crt/src/hal/sdio_math.c",
|
||||
"crt/src/hal/usb2_math.c",
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Retain P8 inputs and validate CPU/DMA pin-level workload evidence."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from datetime import datetime, timezone
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
from scripts import tiny_r2_baseline as evidence # noqa: E402
|
||||
from scripts.check_simulation import DEFAULT_FAILURE # noqa: E402
|
||||
|
||||
PHASE = "TINY-R2-P8"
|
||||
|
||||
|
||||
def measurements(content: str, config: dict, absent: bool = False) -> dict:
|
||||
if DEFAULT_FAILURE.search(content) or "SIM_TEST_PASS Tiny cycles=" not in content:
|
||||
raise ValueError("missing strict Tiny terminal success or forbidden error marker")
|
||||
rows = [dict((k, int(v)) for k, v in re.findall(r"(\w+)=(\d+)", line))
|
||||
for line in content.splitlines() if line.startswith("P8_TRANSPORT ")]
|
||||
windows = [dict((k, int(v)) for k, v in re.findall(r"(\w+)=(\d+)", line))
|
||||
for line in content.splitlines() if line.startswith("P8_XPI_WINDOW ")]
|
||||
terminal = re.findall(r"P8_RESULT code=(\d+) profiles=(\d+)", content)
|
||||
if len(terminal) != 1 or terminal[0] != ("0", str(len(rows))):
|
||||
raise ValueError("missing or inconsistent firmware workload result")
|
||||
if absent:
|
||||
if rows or windows or "P8_ABSENT bounded optional-device rejection" not in content:
|
||||
raise ValueError("absent-device run must reject initialization and perform no mapped DMA")
|
||||
return {"mode": "absent", "profiles": [], "windows": []}
|
||||
expected = [(24000000, 24000000)]
|
||||
if config["PDK"] == "ICS55":
|
||||
expected += [(192000000, 96000000), (240000000, 120000000)]
|
||||
elif int(config["EXT_CLK_HZ"]) != 24000000:
|
||||
expected += [(int(config["EXT_CLK_HZ"]), int(config["EXT_CLK_HZ"]))]
|
||||
if [(r.get("sys_hz"), r.get("mem_hz")) for r in rows] != expected:
|
||||
raise ValueError("missing, duplicated or unexpected clock profiles")
|
||||
if len(windows) != 4 * len(rows):
|
||||
raise ValueError("missing isolated/contended write and readback observation windows")
|
||||
for index, row in enumerate(rows):
|
||||
if row.get("bytes") != 1024 or row.get("burst") != 4 or row.get("progress", 0) <= 0:
|
||||
raise ValueError("incorrect workload size/burst or no concurrent CPU progress")
|
||||
for name in ("isolated", "contended"):
|
||||
cycles = row.get(f"{name}_cycles", 0)
|
||||
if cycles <= 0:
|
||||
raise ValueError("invalid measured cycle count")
|
||||
row[f"{name}_bytes_per_second"] = row["bytes"] * row["sys_hz"] / cycles
|
||||
for offset, window in enumerate(windows[index * 4:index * 4 + 4]):
|
||||
counts = (window.get("write_bytes"), window.get("read_bytes"))
|
||||
if (counts != ((1024, 0) if offset % 2 == 0 else (0, 1024)) or
|
||||
window.get("index") != index * 4 + offset or window.get("mem_cycles", 0) <= 0):
|
||||
raise ValueError("wrong mapped-path byte accounting")
|
||||
for channel in ("aw", "w", "ar", "b", "r"):
|
||||
stall = window.get(f"{channel}_max", -1)
|
||||
if not 0 <= stall <= window["mem_cycles"]:
|
||||
raise ValueError("missing or impossible backpressure measurement")
|
||||
window[f"{channel}_max_ns"] = stall * 1e9 / row["mem_hz"]
|
||||
return {"mode": "present", "profiles": rows, "windows": windows,
|
||||
"limits": "Digital model only; measured stalls apply only to this finite workload. "
|
||||
"No physical frequency, unrestricted contention or camera rate qualification."}
|
||||
|
||||
|
||||
def source_inputs() -> dict:
|
||||
inputs = evidence.source_inputs(ROOT, "ICS55") | evidence.source_inputs(ROOT, "IHP130")
|
||||
for folder in ("rtl/model", "tests", "configs/ci"):
|
||||
for path in (ROOT / folder).rglob("*"):
|
||||
if path.is_file() and path.suffix in (".sv", ".svh", ".py", ".c", ".h", ".mk"):
|
||||
inputs[str(path.relative_to(ROOT))] = evidence.sha256(path)
|
||||
return inputs
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("action", choices=("capture", "report"))
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
parser.add_argument("--source", type=Path)
|
||||
parser.add_argument("--variant-root", type=Path)
|
||||
parser.add_argument("--simulator", choices=("verilator", "iverilog"))
|
||||
parser.add_argument("--attempt", type=Path)
|
||||
parser.add_argument("--absent", action="store_true")
|
||||
args = parser.parse_args()
|
||||
if args.variant_root:
|
||||
args.variant_root = args.variant_root.resolve()
|
||||
try:
|
||||
if args.output.exists():
|
||||
raise ValueError("evidence already exists; select a new attempt")
|
||||
inputs = source_inputs()
|
||||
if args.action == "capture":
|
||||
if not all((args.variant_root, args.simulator)):
|
||||
raise ValueError("capture requires a built --variant-root and --simulator")
|
||||
runtime = evidence.runtime_inputs(ROOT, args.variant_root, args.simulator)
|
||||
for path in (args.variant_root / "sw/firmware",
|
||||
args.variant_root / "sw/retrosoc_fw.hex",
|
||||
args.variant_root / "sim" / args.simulator /
|
||||
("emu" if args.simulator == "verilator" else "simv")):
|
||||
runtime[str(path.resolve().relative_to(ROOT))] = evidence.sha256(path)
|
||||
evidence.dump(args.output, {
|
||||
"phase": PHASE, "repository": evidence.git_info(ROOT),
|
||||
"captured_at": datetime.now(timezone.utc).isoformat(),
|
||||
"variant": str(args.variant_root.resolve()), "simulator": args.simulator,
|
||||
"runtime_inputs": runtime,
|
||||
"specification": evidence.artifact(ROOT / "docs/ip/tiny-soc.md"),
|
||||
"source_inputs": inputs, "source_digest": evidence.digest_map(inputs),
|
||||
"tools": evidence.tools_identity(ROOT),
|
||||
"snapshot": evidence.snapshot(ROOT, args.output.parent / "source", inputs),
|
||||
})
|
||||
else:
|
||||
if not all((args.source, args.variant_root, args.simulator, args.attempt)):
|
||||
raise ValueError("report requires --source --variant-root --simulator --attempt")
|
||||
source = evidence.read_json(args.source)
|
||||
if source.get("phase") != PHASE or source.get("source_inputs") != inputs:
|
||||
raise ValueError("source changed since the retained snapshot")
|
||||
if (source.get("variant") != str(args.variant_root.resolve()) or
|
||||
source.get("simulator") != args.simulator):
|
||||
raise ValueError("runtime snapshot belongs to another variant or simulator")
|
||||
for name, digest in source["runtime_inputs"].items():
|
||||
if evidence.sha256(ROOT / name) != digest:
|
||||
raise ValueError(f"runtime input changed after capture: {name}")
|
||||
manifest = evidence.read_json(args.variant_root / "meta/manifest.json")
|
||||
config = manifest["configuration"]
|
||||
if (config.get("SOC") != "TINY" or config.get("TINY_P8_ACCEPTANCE") != "YES" or
|
||||
config.get("PDK") not in ("ICS55", "IHP130")):
|
||||
raise ValueError("not an explicit Tiny P8 configuration")
|
||||
result = evidence.read_json(args.attempt / "result-sim.json")
|
||||
log = args.attempt / "sim.log"
|
||||
if (result.get("status") != "passed" or result.get("exit_code") != 0 or
|
||||
Path(result.get("log", "")).resolve() != log.resolve()):
|
||||
raise ValueError("simulation command did not complete successfully with this log")
|
||||
if datetime.fromisoformat(result["started_at"]) < datetime.fromisoformat(source["captured_at"]):
|
||||
raise ValueError("simulation predates the source/runtime snapshot")
|
||||
command = result.get("command", [])
|
||||
binary = args.variant_root / "sim" / args.simulator / (
|
||||
"emu" if args.simulator == "verilator" else "simv")
|
||||
firmware_arg = "+firmware=" + str((args.variant_root / "sw/retrosoc_fw.hex").resolve())
|
||||
if (str(binary.resolve()) not in command or firmware_arg not in command or
|
||||
"+tiny_p8_psram" not in command or (("+p8_absent" in command) != args.absent)):
|
||||
raise ValueError("simulation command does not match captured binary/firmware/mode")
|
||||
report = measurements(log.read_text(), config, args.absent)
|
||||
artifacts = [log, args.attempt / "result-sim.json", args.source,
|
||||
args.variant_root / "sw/firmware",
|
||||
args.variant_root / "sw/retrosoc_fw.hex",
|
||||
args.variant_root / "sim" / args.simulator /
|
||||
("emu" if args.simulator == "verilator" else "simv")]
|
||||
evidence.dump(args.output, {"phase": PHASE, "status": "passed",
|
||||
"configuration": config, "simulator": args.simulator, "measurements": report,
|
||||
"source_digest": source["source_digest"],
|
||||
"artifacts": [evidence.artifact(p) for p in artifacts]})
|
||||
print(f"{PHASE} {args.action}: PASS ({args.output})")
|
||||
return 0
|
||||
except (OSError, ValueError, KeyError) as error:
|
||||
print(f"{PHASE}: {error}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+34
-4
@@ -25,6 +25,7 @@
|
||||
#include <retrosoc/hal/memory.h>
|
||||
#include <retrosoc/hal/npu.h>
|
||||
#include <retrosoc/hal/psram.h>
|
||||
#include <retrosoc/hal/xpi_psram.h>
|
||||
#include <retrosoc/hal/resource.h>
|
||||
#include <retrosoc/hal/sdram.h>
|
||||
#include <retrosoc/hal/sdio.h>
|
||||
@@ -638,6 +639,36 @@ static int test_sdram_helpers(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_xpi_psram_helpers(void) {
|
||||
rs_xpi_slot_config_t config;
|
||||
rs_xpi_psram_t device = {.initialized = true, .capacity_bytes = RS_XPI_PSRAM_BYTES};
|
||||
const uint32_t rates[] = {24000000U, 48000000U, 96000000U, 120000000U};
|
||||
for (uint32_t i = 0U; i < 4U; i++) {
|
||||
if (rs_xpi_psram_timing(rates[i], &config) != RS_OK || config.memory_write_enable ||
|
||||
config.memory_read_enable || config.device_size != RS_XPI_PSRAM_BYTES ||
|
||||
config.timeout_cycles >= rates[i] / 125000U || config.burst_boundary != 1024U ||
|
||||
(uint64_t)config.cs_high_cycles * 1000000000U < (uint64_t)rates[i] * 50U) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (rs_xpi_psram_timing(UINT32_MAX, &config) != RS_EINVAL ||
|
||||
rs_xpi_psram_timing(24000000U, NULL) != RS_EINVAL ||
|
||||
rs_xpi_psram_validate_dma(&device, RS_XPI_PSRAM_BASE, RS_XPI_PSRAM_BYTES, 4U) != RS_OK ||
|
||||
rs_xpi_psram_validate_dma(&device, 0x54000c00U, 1024U, 4U) != RS_OK ||
|
||||
rs_xpi_psram_validate_dma(&device, 0x547ffffcU, 4U, 1U) != RS_OK ||
|
||||
rs_xpi_psram_validate_dma(&device, 0x547ffffcU, 8U, 1U) != RS_EINVAL ||
|
||||
rs_xpi_psram_validate_dma(&device, 0x540003f4U, 64U, 4U) != RS_EINVAL ||
|
||||
rs_xpi_psram_validate_dma(&device, 0x54000ff4U, 16U, 4U) != RS_EINVAL ||
|
||||
rs_xpi_psram_validate_dma(&device, 0x540003fcU, 64U, 4U) != RS_EINVAL ||
|
||||
rs_xpi_psram_validate_dma(&device, RS_XPI_PSRAM_BASE, UINT32_MAX, 4U) != RS_EINVAL ||
|
||||
rs_xpi_psram_validate_dma(&device, RS_XPI_PSRAM_BASE, 64U, 16U) != RS_EINVAL ||
|
||||
rs_xpi_psram_validate_dma(&device, RS_XPI_PSRAM_BASE, 0U, 4U) != RS_EINVAL) {
|
||||
return 2;
|
||||
}
|
||||
device.initialized = false;
|
||||
return rs_xpi_psram_validate_dma(&device, RS_XPI_PSRAM_BASE, 4U, 1U) == RS_ENOTSUP ? 0 : 3;
|
||||
}
|
||||
|
||||
static int test_psram_helpers(void) {
|
||||
rs_psram_timing_t timing;
|
||||
|
||||
@@ -3193,13 +3224,11 @@ static int test_tiny_rcu_boundaries(void) {
|
||||
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_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 ||
|
||||
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;
|
||||
}
|
||||
@@ -3222,6 +3251,7 @@ int main(void) {
|
||||
test_ws2812_completion_race(),
|
||||
test_timer_helpers(),
|
||||
test_psram_helpers(),
|
||||
test_xpi_psram_helpers(),
|
||||
test_sdram_helpers(),
|
||||
test_uart_helpers(),
|
||||
test_i2s_helpers(),
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
// Production APB/AXI wrapper and real serial device. No memory backdoor.
|
||||
module tiny_p8_xpi_tb;
|
||||
import xpi_pkg::*;
|
||||
logic s_clk = 0, s_rst_n = 0;
|
||||
apb4_if apb (
|
||||
s_clk,
|
||||
s_rst_n
|
||||
);
|
||||
axi4_if #(
|
||||
.ADDR_WIDTH(32),
|
||||
.DATA_WIDTH(32),
|
||||
.ID_WIDTH (1),
|
||||
.USER_WIDTH(1)
|
||||
) axi ();
|
||||
xpi_if pins ();
|
||||
tri1 [3:0] s_data;
|
||||
logic s_idle, s_tx_stall, s_rx_stall, s_irq;
|
||||
logic [31:0] s_value;
|
||||
integer s_commands = 0;
|
||||
|
||||
always #20.833 s_clk = ~s_clk;
|
||||
for (genvar i = 0; i < 4; i++) begin : gen_pads
|
||||
assign s_data[i] = pins.io_oe_o[i] ? pins.io_do_o[i] : 1'bz;
|
||||
end
|
||||
assign pins.io_di_i = s_data;
|
||||
always @(negedge pins.nss_o[1]) s_commands++;
|
||||
|
||||
apb4_xpi u_dut (
|
||||
.clk_i (s_clk),
|
||||
.rst_n_i (s_rst_n),
|
||||
.dma_xfer_done_i (1'b0),
|
||||
.lifecycle_idle_o(s_idle),
|
||||
.dma_tx_stall_o (s_tx_stall),
|
||||
.dma_rx_stall_o (s_rx_stall),
|
||||
.irq_o (s_irq),
|
||||
.apb4 (apb),
|
||||
.mem_axi4 (axi),
|
||||
.xpi (pins)
|
||||
);
|
||||
ESP_PSRAM64H #(
|
||||
.POWER_UP_CHECK (1),
|
||||
.TIMING_VIOLATION_FATAL(1)
|
||||
) u_ram (
|
||||
.sclk(pins.sck_o),
|
||||
.csn (pins.nss_o[1]),
|
||||
.sio (s_data)
|
||||
);
|
||||
|
||||
task automatic access (input bit write, input logic [11:0] addr, input logic [31:0] data,
|
||||
output logic [31:0] result);
|
||||
@(negedge s_clk);
|
||||
apb.psel = 1;
|
||||
apb.penable = 0;
|
||||
apb.pwrite = write;
|
||||
apb.paddr = {20'd0, addr};
|
||||
apb.pwdata = data;
|
||||
apb.pstrb = 4'hf;
|
||||
@(negedge s_clk);
|
||||
apb.penable = 1;
|
||||
do @(posedge s_clk); while (!apb.pready);
|
||||
if (apb.pslverr) $fatal(1, "SIM_TEST_FAIL APB addr=%h", addr);
|
||||
result = apb.prdata;
|
||||
@(negedge s_clk);
|
||||
apb.psel = 0;
|
||||
apb.penable = 0;
|
||||
endtask
|
||||
task automatic wr(input logic [11:0] addr, input logic [31:0] data);
|
||||
logic [31:0] unused;
|
||||
access (1, addr, data, unused);
|
||||
endtask
|
||||
task automatic rd(input logic [11:0] addr, output logic [31:0] data);
|
||||
access (0, addr, 0, data);
|
||||
endtask
|
||||
task automatic idle;
|
||||
logic [31:0] status;
|
||||
do rd(12'h010, status); while ((status & 32'h0e) != 0);
|
||||
endtask
|
||||
task automatic program_lut(input int seq, input logic [15:0] a, b, c, d);
|
||||
wr(12'(12'h200 + seq * 16), {b, a});
|
||||
wr(12'(12'h204 + seq * 16), {d, c});
|
||||
wr(12'(12'h208 + seq * 16), 0);
|
||||
wr(12'(12'h20c + seq * 16), 0);
|
||||
endtask
|
||||
task automatic command(input logic [7:0] opcode, input logic [1:0] pads);
|
||||
idle();
|
||||
wr(12'h00c, 0);
|
||||
program_lut(3, xpi_instr(XpiInstrCommand, pads, opcode), 0, 0, 0);
|
||||
wr(12'h00c, 1);
|
||||
wr(12'h024, 255);
|
||||
wr(12'h048, 0);
|
||||
wr(12'h04c, 0);
|
||||
wr(12'h050, 32'h31);
|
||||
wr(12'h014, 1);
|
||||
idle();
|
||||
rd(12'h024, s_value);
|
||||
if ((s_value & 32'h61) != 1) $fatal(1, "SIM_TEST_FAIL command %h status=%h", opcode, s_value);
|
||||
endtask
|
||||
|
||||
task automatic write_burst(input logic [31:0] addr, input int beats, input logic [2:0] size,
|
||||
input logic [1:0] expected, input logic [31:0] pattern,
|
||||
input logic [3:0] strobes);
|
||||
@(negedge s_clk);
|
||||
axi.awaddr = addr;
|
||||
axi.awlen = 8'(beats - 1);
|
||||
axi.awsize = size;
|
||||
axi.awvalid = 1;
|
||||
do @(posedge s_clk); while (!axi.awready);
|
||||
@(negedge s_clk);
|
||||
axi.awvalid = 0;
|
||||
for (int b = 0; b < beats; b++) begin
|
||||
axi.wdata = pattern + 32'(b);
|
||||
axi.wstrb = size < 2 ? 4'(((1 << (1 << size)) - 1) << ((addr + (b << size)) & 3)) : strobes;
|
||||
axi.wlast = (b == beats - 1);
|
||||
axi.wvalid = 1;
|
||||
do @(posedge s_clk); while (!axi.wready);
|
||||
@(negedge s_clk);
|
||||
axi.wvalid = 0;
|
||||
end
|
||||
do @(posedge s_clk); while (!axi.bvalid);
|
||||
if (axi.bresp !== expected)
|
||||
$fatal(1, "SIM_TEST_FAIL write addr=%h resp=%h expected=%h", addr, axi.bresp, expected);
|
||||
@(negedge s_clk);
|
||||
endtask
|
||||
task automatic read_burst(input logic [31:0] addr, input int beats, input logic [1:0] expected,
|
||||
input logic [31:0] pattern);
|
||||
@(negedge s_clk);
|
||||
axi.araddr = addr;
|
||||
axi.arlen = 8'(beats - 1);
|
||||
axi.arvalid = 1;
|
||||
do @(posedge s_clk); while (!axi.arready);
|
||||
@(negedge s_clk);
|
||||
axi.arvalid = 0;
|
||||
for (int b = 0; b < beats; b++) begin
|
||||
do @(posedge s_clk); while (!axi.rvalid);
|
||||
if (axi.rresp !== expected || axi.rlast !== (b == beats - 1))
|
||||
$fatal(1, "SIM_TEST_FAIL read response addr=%h beat=%0d resp=%h", addr, b, axi.rresp);
|
||||
if (expected == 0 && axi.rdata !== pattern + 32'(b))
|
||||
$fatal(
|
||||
1,
|
||||
"SIM_TEST_FAIL readback addr=%h beat=%0d got=%h expected=%h",
|
||||
addr,
|
||||
b,
|
||||
axi.rdata,
|
||||
pattern + 32'(b)
|
||||
);
|
||||
@(negedge s_clk);
|
||||
end
|
||||
idle();
|
||||
endtask
|
||||
|
||||
task automatic held_read;
|
||||
@(negedge s_clk);
|
||||
axi.araddr = 32'h54000000;
|
||||
axi.arlen = 1;
|
||||
axi.arvalid = 1;
|
||||
axi.rready = 0;
|
||||
do @(posedge s_clk); while (!axi.arready);
|
||||
@(negedge s_clk);
|
||||
axi.arvalid = 0;
|
||||
do @(posedge s_clk); while (!axi.rvalid);
|
||||
for (int i = 0; i < 250; i++) begin
|
||||
@(negedge s_clk);
|
||||
if (!axi.rvalid || axi.rresp !== 0 || axi.rdata !== 32'h12345678 || axi.rlast)
|
||||
$fatal(1, "SIM_TEST_FAIL held read changed across core timeout");
|
||||
end
|
||||
axi.rready = 1;
|
||||
@(posedge s_clk);
|
||||
@(negedge s_clk);
|
||||
do @(posedge s_clk); while (!axi.rvalid);
|
||||
if (axi.rresp !== 2 || !axi.rlast) $fatal(1, "SIM_TEST_FAIL timeout tail");
|
||||
@(negedge s_clk);
|
||||
idle();
|
||||
endtask
|
||||
|
||||
initial begin
|
||||
#2000000;
|
||||
$fatal(1, "SIM_TEST_TIMEOUT Tiny P8 XPI");
|
||||
end
|
||||
initial begin
|
||||
apb.psel = 0;
|
||||
apb.penable = 0;
|
||||
apb.pwrite = 0;
|
||||
apb.paddr = 0;
|
||||
apb.pwdata = 0;
|
||||
apb.pstrb = 0;
|
||||
apb.pprot = 0;
|
||||
axi.awid = 0;
|
||||
axi.awaddr = 0;
|
||||
axi.awlen = 0;
|
||||
axi.awsize = 2;
|
||||
axi.awburst = 1;
|
||||
axi.awlock = 0;
|
||||
axi.awcache = 0;
|
||||
axi.awprot = 0;
|
||||
axi.awqos = 0;
|
||||
axi.awregion = 0;
|
||||
axi.awuser = 0;
|
||||
axi.awvalid = 0;
|
||||
axi.wdata = 0;
|
||||
axi.wstrb = 0;
|
||||
axi.wlast = 0;
|
||||
axi.wuser = 0;
|
||||
axi.wvalid = 0;
|
||||
axi.bready = 1;
|
||||
axi.arid = 0;
|
||||
axi.araddr = 0;
|
||||
axi.arlen = 0;
|
||||
axi.arsize = 2;
|
||||
axi.arburst = 1;
|
||||
axi.arlock = 0;
|
||||
axi.arcache = 0;
|
||||
axi.arprot = 0;
|
||||
axi.arqos = 0;
|
||||
axi.arregion = 0;
|
||||
axi.aruser = 0;
|
||||
axi.arvalid = 0;
|
||||
axi.rready = 1;
|
||||
repeat (5) @(negedge s_clk);
|
||||
s_rst_n = 1;
|
||||
#150000;
|
||||
wr(12'h00c, 0);
|
||||
wr(12'h120, 1);
|
||||
wr(12'h124, 32'h800000);
|
||||
wr(12'h128, 32'h21);
|
||||
wr(12'h12c, 32'h02010100);
|
||||
wr(12'h130, 192);
|
||||
wr(12'h134, 1024);
|
||||
program_lut(1, 16'h18eb, 16'h2818, 16'h4006, 16'h6800);
|
||||
program_lut(2, 16'h1838, 16'h2818, 16'h5800, 0);
|
||||
// Read ID: SPI 9f, 24 address bits, six bytes, no dummy clocks.
|
||||
program_lut(4, 16'h109f, 16'h2018, 16'h6000, 0);
|
||||
wr(12'h00c, 1);
|
||||
command(8'h66, 0);
|
||||
command(8'h99, 0);
|
||||
#100;
|
||||
wr(12'h024, 255);
|
||||
wr(12'h048, 0);
|
||||
wr(12'h04c, 6);
|
||||
wr(12'h050, 32'h41);
|
||||
wr(12'h014, 1);
|
||||
idle();
|
||||
rd(12'h044, s_value);
|
||||
if (s_value[23:0] !== 24'h265d0d) $fatal(1, "SIM_TEST_FAIL PSRAM identity %h", s_value);
|
||||
rd(12'h044, s_value);
|
||||
command(8'h35, 0);
|
||||
idle();
|
||||
wr(12'h00c, 0);
|
||||
wr(12'h120, 7);
|
||||
wr(12'h00c, 1);
|
||||
|
||||
if ($test$plusargs("boundary")) begin
|
||||
read_burst(32'h540003fc, 2, 2, 0);
|
||||
read_burst(32'h54000ffc, 2, 2, 0);
|
||||
read_burst(32'h54800000, 1, 2, 0);
|
||||
write_burst(32'h540003fc, 2, 2, 2, 0, 4'hf);
|
||||
write_burst(32'h54000ffc, 2, 2, 2, 0, 4'hf);
|
||||
write_burst(32'h54800000, 1, 2, 2, 0, 4'hf);
|
||||
write_burst(32'h00000000, 1, 2, 2, 0, 4'hf);
|
||||
end else if ($test$plusargs("lanes")) begin
|
||||
write_burst(32'h54000200, 1, 2, 0, 32'h00000000, 4'hf);
|
||||
write_burst(32'h54000200, 4, 0, 0, 32'h44332211, 4'hf);
|
||||
read_burst(32'h54000200, 1, 0, 32'h44332211);
|
||||
write_burst(32'h54000200, 2, 1, 0, 32'h88776655, 4'hf);
|
||||
read_burst(32'h54000200, 1, 0, 32'h88776655);
|
||||
write_burst(32'h54000200, 1, 2, 0, 32'h01020304, 4'h5);
|
||||
read_burst(32'h54000200, 1, 0, 32'h88026604);
|
||||
write_burst(32'h54000200, 1, 2, 0, 32'hffffffff, 4'h0);
|
||||
read_burst(32'h54000200, 1, 0, 32'h88026604);
|
||||
end else if ($test$plusargs("timeout")) begin
|
||||
// A mapped TX must retire with error even if the core times out
|
||||
// before accepting its first payload byte.
|
||||
idle();
|
||||
wr(12'h00c, 0);
|
||||
wr(12'h130, 1);
|
||||
wr(12'h00c, 1);
|
||||
write_burst(32'h54000000, 1, 2, 2, 32'h12345678, 4'h1);
|
||||
end else if ($test$plusargs("short_read")) begin
|
||||
idle();
|
||||
wr(12'h00c, 0);
|
||||
program_lut(1, 0, 0, 0, 0);
|
||||
wr(12'h00c, 1);
|
||||
read_burst(32'h54000000, 1, 2, 0);
|
||||
end else if (!$test$plusargs("identity")) begin
|
||||
write_burst(32'h54000000, 1, 2, 0, 32'h12345678, 4'hf);
|
||||
if ($test$plusargs("held_read")) held_read();
|
||||
else if ($test$plusargs("late_error")) begin
|
||||
idle();
|
||||
wr(12'h00c, 0);
|
||||
wr(12'h218, 32'h0000f000);
|
||||
wr(12'h00c, 1);
|
||||
read_burst(32'h54000000, 1, 2, 0);
|
||||
end else begin
|
||||
read_burst(32'h54000000, 1, 0, 32'h12345678);
|
||||
write_burst(32'h547ffffc, 1, 2, 0, 32'h89abcdef, 4'hf);
|
||||
read_burst(32'h547ffffc, 1, 0, 32'h89abcdef);
|
||||
end
|
||||
end
|
||||
$display("SIM_TEST_PASS Tiny P8 XPI pins");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
+1
-1
@@ -59,7 +59,7 @@ def test_psram_controller_data_integrity_and_fault_isolation(tmp_path: Path) ->
|
||||
str(ROOT / "rtl/ip/memory/psram_phy.sv"),
|
||||
str(ROOT / "rtl/ip/memory/psram_reg.sv"),
|
||||
str(ROOT / "rtl/ip/memory/apb4_psram.sv"),
|
||||
str(ROOT / "rtl/mini/dv/model/ESP_PSRAM64H.sv"),
|
||||
str(ROOT / "rtl/model/ESP_PSRAM64H.sv"),
|
||||
str(ROOT / "tests/rtl/psram_tb.sv"),
|
||||
"-Mdir",
|
||||
str(tmp_path / "obj"),
|
||||
|
||||
@@ -49,6 +49,9 @@ def test_tiny_sources_and_abi_are_independent(tmp_path: Path) -> None:
|
||||
for source in resolved.files:
|
||||
assert "/rtl/mini/" not in str(source)
|
||||
assert not any(token in source.name.lower() for token in ("rib", "vexii", "radio"))
|
||||
devices = parse_filelists([filelists / "tb.fl"])
|
||||
assert ROOT / "rtl/model/ESP_PSRAM64H.sv" in devices.files
|
||||
assert all("/rtl/mini/" not in str(source) for source in devices.files)
|
||||
rtl = (output / "memory_map/rtl/mmap_define.svh").read_text()
|
||||
sdk = (output / "memory_map/include/retrosoc/generated/memory_map.h").read_text()
|
||||
assert "RIB" not in rtl
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
"""Pin-level XPI PSRAM transport; no fast NOR or separate RAM controller."""
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
from test_tiny_r2_p7 import COMMON, ROOT, run_rtl
|
||||
from scripts.check_simulation import DEFAULT_FAILURE
|
||||
|
||||
|
||||
@pytest.fixture(scope="module", params=["iverilog", "verilator"])
|
||||
def xpi_simulator(request, tmp_path_factory):
|
||||
simulator = request.param
|
||||
tmp_path = tmp_path_factory.mktemp(f"p8-{simulator}")
|
||||
run_rtl(tmp_path, simulator, "tiny_p8_xpi_tb", [
|
||||
COMMON / "utils/fifo.sv", COMMON / "interface/axi4_addr_gen.sv",
|
||||
*[ROOT / "rtl/ip/storage" / name for name in (
|
||||
"xpi_if.sv", "xpi_reg.sv", "xpi_mm.sv", "xpi_indirect.sv",
|
||||
"xpi_clkgen.sv", "xpi_core.sv", "apb4_xpi.sv")],
|
||||
ROOT / "rtl/model/ESP_PSRAM64H.sv",
|
||||
], defines=[f"-I{ROOT / 'rtl/ip/storage'}", "-DRETROSOC_SOC__TINY"],
|
||||
packages=[ROOT / "rtl/ip/storage/xpi_pkg.sv"], plusargs=["+identity"])
|
||||
launch = [str(tmp_path / "sim")] if simulator == "verilator" else ["vvp", str(tmp_path / "sim")]
|
||||
return launch, tmp_path
|
||||
|
||||
|
||||
@pytest.mark.parametrize("scenario", ["mapped", "boundary", "lanes", "timeout", "held_read",
|
||||
"short_read", "late_error"])
|
||||
def test_xpi_psram_pins(xpi_simulator, scenario: str) -> None:
|
||||
launch, directory = xpi_simulator
|
||||
result = subprocess.run([*launch, f"+{scenario}"], capture_output=True, text=True, timeout=30)
|
||||
log = result.stdout + result.stderr
|
||||
(directory / f"{scenario}.log").write_text(log)
|
||||
assert result.returncode == 0 and not DEFAULT_FAILURE.search(log), log
|
||||
assert "SIM_TEST_PASS Tiny P8 XPI pins" in log, log
|
||||
@@ -0,0 +1,63 @@
|
||||
"""Reject incomplete P8 transport/optional-device evidence."""
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
from scripts.tiny_r2_p8 import measurements
|
||||
|
||||
CONFIG = {"PDK": "IHP130", "EXT_CLK_HZ": "24000000"}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def valid_log():
|
||||
return "\n".join([
|
||||
"P8_TRANSPORT sys_hz=24000000 mem_hz=24000000 bytes=1024 burst=4 "
|
||||
"isolated_cycles=24000 contended_cycles=48000 progress=10",
|
||||
*[f"P8_XPI_WINDOW index={i} mem_cycles=24000 "
|
||||
f"write_bytes={1024 if i % 2 == 0 else 0} read_bytes={0 if i % 2 == 0 else 1024} "
|
||||
"aw_max=10 w_max=20 ar_max=30 b_max=40 r_max=50" for i in range(4)],
|
||||
"P8_RESULT code=0 profiles=1", "SIM_TEST_PASS Tiny cycles=100000",
|
||||
])
|
||||
|
||||
|
||||
def test_p8_measurement_units(valid_log):
|
||||
result = measurements(valid_log, CONFIG)
|
||||
assert result["profiles"][0]["isolated_bytes_per_second"] == 1024000
|
||||
assert result["profiles"][0]["contended_bytes_per_second"] == 512000
|
||||
assert result["windows"][0]["b_max_ns"] == pytest.approx(40e9 / 24e6)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("old,new", [
|
||||
("SIM_TEST_PASS Tiny cycles=100000", "Hello retroSoC!"),
|
||||
("profiles=1", "profiles=0"), ("code=0", "code=3"),
|
||||
("write_bytes=1024", "write_bytes=1020"), ("progress=10", "progress=0"),
|
||||
("aw_max=10", "aw_max=25000"), ("isolated_cycles=24000", "isolated_cycles=0"),
|
||||
])
|
||||
def test_p8_rejects_incomplete_measurements(valid_log, old, new):
|
||||
with pytest.raises(ValueError):
|
||||
measurements(valid_log.replace(old, new), CONFIG)
|
||||
|
||||
|
||||
def test_p8_does_not_promote_missing_profiles_or_errors(valid_log):
|
||||
with pytest.raises(ValueError):
|
||||
measurements(valid_log, {"PDK": "ICS55", "EXT_CLK_HZ": "24000000"})
|
||||
with pytest.raises(ValueError):
|
||||
measurements(valid_log + "\nSIM_TEST_FAIL", CONFIG)
|
||||
with pytest.raises(ValueError):
|
||||
measurements(valid_log, CONFIG, absent=True)
|
||||
|
||||
|
||||
def test_p8_optional_absence_has_separate_verdict():
|
||||
log = ("P8_ABSENT bounded optional-device rejection\nP8_RESULT code=0 profiles=0\n"
|
||||
"SIM_TEST_PASS Tiny cycles=100000\n")
|
||||
assert measurements(log, CONFIG, absent=True)["mode"] == "absent"
|
||||
with pytest.raises(ValueError):
|
||||
measurements(log, CONFIG)
|
||||
|
||||
|
||||
def test_p8_observer_labels_match_axi_channels():
|
||||
source = (Path(__file__).resolve().parents[1] /
|
||||
"rtl/tiny/dv/tiny_p8_observers.svh").read_text()
|
||||
order = source.split("wire [4:0] s_p8_stalls = {", 1)[1].split("};", 1)[0]
|
||||
channels = [line.split("`_P8_AXI.", 1)[1].split("valid", 1)[0]
|
||||
for line in order.splitlines() if "`_P8_AXI." in line]
|
||||
assert channels == ["aw", "w", "ar", "b", "r"]
|
||||
Reference in new issue
Block a user