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:
maksyuki committed 2026-10-15 11:57:18 +08:00
1 parent 655c5871ea
commit f6190f91e6
23 files changed
+1168 -22

No files matched your search

+10
View File
@@ -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)
+3
View File
@@ -5,3 +5,6 @@ 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
ifeq ($(TINY_P8_ACCEPTANCE),YES)
APP_SRCS := $(ROOT_PATH)/app/apps/ci_smoke/tiny_r2_p8.c
endif
+180
View File
@@ -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 (;;) {
}
}
+29
View File
@@ -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
+147
View File
@@ -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;
}
+67
View File
@@ -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;
}
+46 -10
View File
@@ -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,7 +307,12 @@ module xpi_mm (
end
always_comb begin
s_write_lane_mask = (4'd1 << s_size_q) - 1'b1;
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
@@ -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;
@@ -424,36 +432,44 @@ module xpi_mm (
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_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_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_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 (
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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 \
+3 -1
View File
@@ -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
@@ -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.
+16 -1
View File
@@ -25,11 +25,26 @@ module retrosoc_tiny_tb;
`include "tiny_tb_gpio.svh"
wire s_xpi_sck;
wire [3:0] s_xpi_nss;
tri [3:0] s_xpi_data;
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;
+63
View File
@@ -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
View File
@@ -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
+2 -1
View File
@@ -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")),
+1
View File
@@ -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",
+158
View File
@@ -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
View File
@@ -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(),
+303
View File
@@ -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
View File
@@ -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"),
+3
View File
@@ -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
+34
View File
@@ -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
+63
View File
@@ -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"]