feat: add PSRAM support and related tests

- Updated formal.mk to include PSRAM in formal verification targets and source files.
- Modified software.mk to include psram_math.c for HAL.
- Enhanced pin_map.json to map GPIO pins for PSRAM control signals.
- Refactored ip_ribp_wrapper.sv to integrate PSRAM interface and remove unused ribp interfaces.
- Updated rtl_style_audit.json to include PSRAM related files.
- Added PSRAM testbench (psram_tb.sv) for integration testing.
- Created test_psram.py to validate PSRAM controller functionality and register offsets.
- Adjusted memory map tests to reflect new PSRAM address space and size.
This commit is contained in:
maksyuki committed 2026-08-15 19:11:22 +08:00
1 parent 7eda4e6c46
commit 24d425c3e8
36 files changed
+4685 -1041

No files matched your search

+9 -10
View File
@@ -40,7 +40,9 @@ CHECK:
.equ GPIO_ALT_ENABLE, RS_SOC_RIBP_GPIO_ADMIN_BASE + 0x34
.equ GPIO_ALT_SELECT, RS_SOC_RIBP_GPIO_ADMIN_BASE + 0x38
.equ BIT_MASK, 0x3FE00000 # Bits 21 to 29 set to 1
.equ PSRAM_INIT, RS_SOC_RIBP_PSRAM_BASE + 0x08
.equ PSRAM_COMMAND, RS_SOC_RIBP_PSRAM_BASE + 0x04
.equ PSRAM_STATUS, RS_SOC_RIBP_PSRAM_BASE + 0x08
.equ PSRAM_READY, 0x20
PINMUX_PSRAM:
li t0, GPIO_ALT_ENABLE
lw t1, 0(t0)
@@ -52,17 +54,14 @@ PINMUX_PSRAM:
or t1, t1, t2
sw t1, 0(t0)
PSRAM_INIT_TRG:
li t0, PSRAM_INIT
li t0, PSRAM_COMMAND
li t1, 1
sw t1, 0(t0)
# NEW ===========================
la a0, 0x0
la a1, 2000
WAIT_PSRAM_RESET:
lw t0, 0(a0)
addi a0, a0, 4
blt a0, a1, WAIT_PSRAM_RESET
li t0, PSRAM_STATUS
WAIT_PSRAM_READY:
lw t1, 0(t0)
andi t1, t1, PSRAM_READY
beqz t1, WAIT_PSRAM_READY
LDSD_TEST:
jal ra, WR_8B_TEST
+6
View File
@@ -27,6 +27,12 @@ the `retrosoc` namespace, for example:
#include <retrosoc/lib/printf.h>
```
`<retrosoc/hal/psram.h>` provides timing calculation, configuration,
initialization, per-chip recovery, status/ID, restricted indirect commands,
interrupts, and bounded self-test for the four-chip ESP-PSRAM64H controller.
See the [controller contract](../docs/ip/psram.md) before using its destructive
self-test or changing the memory timing.
`crt/inc/` is legacy or generated compatibility material. It is not an active
public include root for the firmware build; new code must not add dependencies
on it.
+12 -3
View File
@@ -72,7 +72,10 @@ wait_psram_reset:
.equ GPIO_ALT_ENABLE, RS_SOC_RIBP_GPIO_ADMIN_BASE + 0x34
.equ GPIO_ALT_SELECT, RS_SOC_RIBP_GPIO_ADMIN_BASE + 0x38
.equ BIT_MASK, 0x3FE00000 # Bits 21 to 29 set to 1
.equ PSRAM_INIT, RS_SOC_RIBP_PSRAM_BASE + 0x08
.equ PSRAM_COMMAND, RS_SOC_RIBP_PSRAM_BASE + 0x04
.equ PSRAM_STATUS, RS_SOC_RIBP_PSRAM_BASE + 0x08
.equ PSRAM_INIT, 0x01
.equ PSRAM_READY, 0x20
PINMUX_PSRAM:
li t0, GPIO_ALT_ENABLE
@@ -85,10 +88,16 @@ PINMUX_PSRAM:
or t1, t1, t2
sw t1, 0(t0)
PSRAM_INIT_TRG:
li t0, PSRAM_INIT
li t1, 1
li t0, PSRAM_COMMAND
li t1, PSRAM_INIT
sw t1, 0(t0)
PSRAM_READY_WAIT:
li t0, PSRAM_STATUS
lw t1, 0(t0)
andi t1, t1, PSRAM_READY
beqz t1, PSRAM_READY_WAIT
app_loader_start:
la a0, _ram_lma
la a1, _ram_vma
-9
View File
@@ -11,11 +11,6 @@
#define CPU_FREQ 72 // unit: MHz
#define UART_BPS 921600 // unit: bps
#define PSRAM_NUM 4
#define PSRAM_SCLK_MIN_FREQ 12 // unit: MHz
#define PSRAM_SCLK_MAX_FREQ 133 // unit: MHz
#define PSRAM_SCLK_FREQ (CPU_FREQ / 2)
#define SPFS_MEM_START RS_SOC_FLASH_BASE
#define SPFS_MEM_OFFST RS_SOC_FLASH_SIZE
#define SRAM_MEM_START RS_SOC_SRAM_BASE
@@ -31,10 +26,6 @@
#define RS_SOC_REG32(base, offset) (*(volatile uint32_t *)(uintptr_t)((base) + (offset)))
// psram
#define reg_psram_wait RS_SOC_REG32(RS_SOC_RIBP_PSRAM_BASE, UINT32_C(0x00))
#define reg_psram_chd RS_SOC_REG32(RS_SOC_RIBP_PSRAM_BASE, UINT32_C(0x04))
#define reg_psram_init RS_SOC_REG32(RS_SOC_RIBP_PSRAM_BASE, UINT32_C(0x08))
// spisd
#define reg_spisd_mode RS_SOC_REG32(RS_SOC_RIBP_SPISD_BASE, UINT32_C(0x00))
#define reg_spisd_clkdiv RS_SOC_REG32(RS_SOC_RIBP_SPISD_BASE, UINT32_C(0x04))
+112 -3
View File
@@ -1,9 +1,118 @@
#ifndef RETROSOC_PSRAM_H
#define RETROSOC_PSRAM_H
#ifndef RETROSOC_HAL_PSRAM_H
#define RETROSOC_HAL_PSRAM_H
#include <stdbool.h>
#include <stdint.h>
#include <retrosoc/core/status.h>
#define RS_PSRAM_CHIP_COUNT UINT32_C(4)
#define RS_PSRAM_CHIP_SIZE UINT32_C(0x00800000)
#define RS_PSRAM_TOTAL_SIZE UINT32_C(0x02000000)
#define RS_PSRAM_MAX_SCLK_HZ UINT32_C(133000000)
#define RS_PSRAM_PAGE_CROSS_MAX_HZ UINT32_C(84000000)
#define RS_PSRAM_INTERRUPT_INIT_DONE UINT32_C(0x00000001)
#define RS_PSRAM_INTERRUPT_INDIRECT_DONE UINT32_C(0x00000002)
#define RS_PSRAM_INTERRUPT_ERROR UINT32_C(0x00000004)
#define RS_PSRAM_INTERRUPT_TIMEOUT UINT32_C(0x00000008)
#define RS_PSRAM_INTERRUPT_ALL UINT32_C(0x0000000F)
typedef enum {
RS_PSRAM_COMMAND_READ = 0,
RS_PSRAM_COMMAND_FAST_READ = 1,
RS_PSRAM_COMMAND_QUAD_READ = 2,
RS_PSRAM_COMMAND_WRITE = 3,
RS_PSRAM_COMMAND_QUAD_WRITE = 4,
RS_PSRAM_COMMAND_ENTER_QPI = 5,
RS_PSRAM_COMMAND_EXIT_QPI = 6,
RS_PSRAM_COMMAND_RESET_ENABLE = 7,
RS_PSRAM_COMMAND_RESET = 8,
RS_PSRAM_COMMAND_TOGGLE_WRAP = 9,
RS_PSRAM_COMMAND_READ_ID = 10,
} rs_psram_command_t;
typedef enum {
RS_PSRAM_ERROR_NONE = 0,
RS_PSRAM_ERROR_ILLEGAL = 1,
RS_PSRAM_ERROR_UNAVAILABLE = 2,
RS_PSRAM_ERROR_TIMEOUT = 3,
RS_PSRAM_ERROR_ID = 4,
RS_PSRAM_ERROR_ABORTED = 5,
RS_PSRAM_ERROR_PHY = 6,
RS_PSRAM_ERROR_PROTOCOL = 7,
} rs_psram_error_t;
typedef struct {
uint16_t half_period_cycles;
uint16_t cs_setup_cycles;
uint16_t cs_high_cycles;
uint16_t cs_hold_cycles;
uint32_t powerup_cycles;
uint32_t cs_max_low_cycles;
uint32_t access_timeout_cycles;
uint32_t actual_sclk_hz;
bool above_84mhz;
} rs_psram_timing_t;
typedef struct {
rs_psram_timing_t timing;
uint8_t chip_enable;
bool wrap32;
bool auto_initialize;
bool memory_enable;
} rs_psram_config_t;
typedef struct {
uint8_t chip_present;
uint8_t chip_ready;
uint8_t chip_qpi;
uint8_t chip_wrap32;
uint8_t chip_error;
rs_psram_error_t last_error;
uint8_t last_error_chip;
uint32_t last_error_address;
bool init_busy;
bool axi_busy;
bool indirect_busy;
bool phy_busy;
bool quiesced;
bool ready;
} rs_psram_status_t;
typedef struct {
rs_psram_command_t command;
uint8_t chip;
uint8_t length;
uint32_t address;
uint64_t write_data;
} rs_psram_indirect_t;
typedef struct {
uint32_t address;
uint32_t expected;
uint32_t actual;
} rs_psram_test_failure_t;
rs_status_t rs_psram_timing_from_hz(uint32_t source_clock_hz, uint32_t sclk_hz,
rs_psram_timing_t *timing);
rs_status_t rs_psram_configure(const rs_psram_config_t *config);
rs_status_t rs_psram_initialize(rs_timeout_t timeout);
rs_status_t rs_psram_recover(uint8_t chip, rs_timeout_t timeout);
rs_status_t rs_psram_abort(void);
rs_status_t rs_psram_get_status(rs_psram_status_t *status);
rs_status_t rs_psram_read_id(uint8_t chip, uint64_t *identifier);
rs_status_t rs_psram_indirect(const rs_psram_indirect_t *command, uint64_t *read_data,
rs_timeout_t timeout);
rs_status_t rs_psram_set_wrap(uint8_t chip, bool wrap32, rs_timeout_t timeout);
rs_status_t rs_psram_interrupt_enable(uint32_t mask);
rs_status_t rs_psram_interrupt_clear(uint32_t mask);
rs_status_t rs_psram_interrupt_test(uint32_t mask);
rs_status_t rs_psram_selftest(uintptr_t address, uint32_t size, uint32_t word_limit,
rs_psram_test_failure_t *failure);
void ip_psram_boot(void);
uint32_t xorshift32(uint32_t *state);
void ip_psram_selftest(uint32_t addr, uint32_t range);
void ip_psram_selftest(uint32_t address, uint32_t size);
#endif
+345 -132
View File
@@ -1,162 +1,375 @@
#include <stddef.h>
#include <retrosoc/core/soc.h>
#include <retrosoc/lib/printf.h>
#include <retrosoc/core/wait.h>
#include <retrosoc/hal/clock.h>
#include <retrosoc/hal/psram.h>
#include <retrosoc/lib/printf.h>
void ip_psram_boot(void) {
bool timing_pass = true;
#define RS_PSRAM_CTRL_OFFSET UINT32_C(0x000)
#define RS_PSRAM_COMMAND_OFFSET UINT32_C(0x004)
#define RS_PSRAM_STATUS_OFFSET UINT32_C(0x008)
#define RS_PSRAM_CHIP_ENABLE_OFFSET UINT32_C(0x00C)
#define RS_PSRAM_CHIP_PRESENT_OFFSET UINT32_C(0x010)
#define RS_PSRAM_CHIP_READY_OFFSET UINT32_C(0x014)
#define RS_PSRAM_CHIP_MODE_OFFSET UINT32_C(0x018)
#define RS_PSRAM_CHIP_ERROR_OFFSET UINT32_C(0x01C)
#define RS_PSRAM_CLK_CONFIG_OFFSET UINT32_C(0x020)
#define RS_PSRAM_POWERUP_CYCLES_OFFSET UINT32_C(0x024)
#define RS_PSRAM_CS_SETUP_CYCLES_OFFSET UINT32_C(0x028)
#define RS_PSRAM_CS_HIGH_CYCLES_OFFSET UINT32_C(0x02C)
#define RS_PSRAM_CS_HOLD_CYCLES_OFFSET UINT32_C(0x030)
#define RS_PSRAM_CS_MAX_LOW_CYCLES_OFFSET UINT32_C(0x034)
#define RS_PSRAM_ACCESS_TIMEOUT_CYCLES_OFFSET UINT32_C(0x038)
#define RS_PSRAM_INDIRECT_CTRL_OFFSET UINT32_C(0x040)
#define RS_PSRAM_INDIRECT_ADDR_OFFSET UINT32_C(0x044)
#define RS_PSRAM_INDIRECT_WDATA_LO_OFFSET UINT32_C(0x048)
#define RS_PSRAM_INDIRECT_WDATA_HI_OFFSET UINT32_C(0x04C)
#define RS_PSRAM_INDIRECT_RDATA_LO_OFFSET UINT32_C(0x050)
#define RS_PSRAM_INDIRECT_RDATA_HI_OFFSET UINT32_C(0x054)
#define RS_PSRAM_LAST_ERROR_OFFSET UINT32_C(0x058)
#define RS_PSRAM_LAST_ERROR_ADDR_OFFSET UINT32_C(0x05C)
#define RS_PSRAM_CHIP_ID_BASE_OFFSET UINT32_C(0x060)
#define RS_PSRAM_INTR_STATE_OFFSET UINT32_C(0x080)
#define RS_PSRAM_INTR_ENABLE_OFFSET UINT32_C(0x084)
#define RS_PSRAM_INTR_TEST_OFFSET UINT32_C(0x08C)
printf("[PSRAM] device: ESP-PSRAM64H(max %dMHz)\n", PSRAM_SCLK_MAX_FREQ);
printf(" volt: 3.3V\n");
printf(" power-up: SPI mode\n");
printf(" normal: QPI mode\n");
printf(" sclk freq: %dMHz\n", PSRAM_SCLK_FREQ);
// check
uint32_t timing_expt = 0, timing_actual = 0;
char msg_pass[20] = "\e[0;32m[PASS]\e[0m", msg_fail[20] = "\e[0;31m[FAIL]\e[0m";
#define RS_PSRAM_CTRL_ENABLE_MASK UINT32_C(0x00000001)
#define RS_PSRAM_CTRL_MEMORY_ENABLE_MASK UINT32_C(0x00000002)
#define RS_PSRAM_CTRL_AUTO_INIT_MASK UINT32_C(0x00000004)
#define RS_PSRAM_CTRL_WRAP32_MASK UINT32_C(0x00000008)
#define RS_PSRAM_COMMAND_INIT_MASK UINT32_C(0x00000001)
#define RS_PSRAM_COMMAND_RECOVER_MASK UINT32_C(0x00000002)
#define RS_PSRAM_COMMAND_ABORT_MASK UINT32_C(0x00000004)
#define RS_PSRAM_COMMAND_CHIP_SHIFT 8U
#define RS_PSRAM_STATUS_INIT_BUSY_MASK UINT32_C(0x00000001)
#define RS_PSRAM_STATUS_AXI_BUSY_MASK UINT32_C(0x00000002)
#define RS_PSRAM_STATUS_INDIRECT_BUSY_MASK UINT32_C(0x00000004)
#define RS_PSRAM_STATUS_PHY_BUSY_MASK UINT32_C(0x00000008)
#define RS_PSRAM_STATUS_QUIESCED_MASK UINT32_C(0x00000010)
#define RS_PSRAM_STATUS_READY_MASK UINT32_C(0x00000020)
#define RS_PSRAM_STATUS_BUSY_MASK UINT32_C(0x0000000F)
#define RS_PSRAM_CLK_ABOVE_84MHZ_MASK UINT32_C(0x00010000)
#define RS_PSRAM_INDIRECT_CHIP_SHIFT 8U
#define RS_PSRAM_INDIRECT_LENGTH_SHIFT 16U
#define RS_PSRAM_INDIRECT_START_MASK UINT32_C(0x80000000)
printf("[PSRAM] wait cycles(default): %d\n", reg_psram_wait);
printf("[PSRAM] chd delay cycles(defalut): %d\n", reg_psram_chd);
printf("[PSRAM] timing check\n");
timing_expt = 1000 / PSRAM_SCLK_MAX_FREQ;
timing_actual = 1000 / (PSRAM_SCLK_FREQ);
static volatile uint32_t *rs_psram_register(uint32_t offset) {
return (volatile uint32_t *)(uintptr_t)(RS_SOC_RIBP_PSRAM_BASE + offset);
}
printf("tCLK ===> expt: %dns(min)\n", timing_expt);
printf(" actul: %dns ", timing_actual);
printf("%s\n", (timing_pass &= (timing_actual >= timing_expt)) ? msg_pass : msg_fail);
static bool rs_psram_chip_valid(uint8_t chip) {
return chip < (uint8_t)RS_PSRAM_CHIP_COUNT;
}
printf("tCH/tCL ===> expt: [0.45-0.55] tCLK(min)\n");
printf(" actul: [0.45-0.55] tCLK ");
printf("%s\n", msg_pass);
static bool rs_psram_command_valid(rs_psram_command_t command) {
return (uint32_t)command <= (uint32_t)RS_PSRAM_COMMAND_READ_ID;
}
printf("tKHKL ===> expt: 1.5ns(max)\n");
printf(" actul: 1.5ns ");
printf("%s\n", msg_pass);
static bool rs_psram_config_valid(const rs_psram_config_t *config) {
return (config != NULL) && (config->chip_enable != 0U) &&
((config->chip_enable & UINT8_C(0xF0)) == 0U) &&
(config->timing.half_period_cycles != 0U) && (config->timing.cs_max_low_cycles != 0U) &&
(config->timing.access_timeout_cycles > config->timing.cs_max_low_cycles) &&
(config->timing.actual_sclk_hz != 0U) &&
(config->timing.actual_sclk_hz <= RS_PSRAM_MAX_SCLK_HZ);
}
timing_expt = 50;
timing_actual = (reg_psram_wait / 2) * (1000 / PSRAM_SCLK_FREQ);
printf("tCPH ===> expt: %dns(min)\n", timing_expt);
printf(" actul: %dns ", timing_actual);
printf("%s\n", (timing_pass &= (timing_actual >= timing_expt)) ? msg_pass : msg_fail);
static rs_status_t rs_psram_wait_interrupt(uint32_t interrupt, rs_timeout_t timeout) {
rs_status_t status;
uint32_t interrupt_state;
timing_expt = 8;
// 32(cmd+addr) + 32(data)
timing_actual = ((1000 / PSRAM_SCLK_FREQ) * ((32 + 32) / 4)) / 1000;
printf("tCEM ===> expt: %dus(max)\n", timing_expt);
printf(" actul: %dus ", timing_actual);
printf("%s\n", (timing_pass &= (timing_actual <= timing_expt)) ? msg_pass : msg_fail);
status =
rs_wait_mask(rs_psram_register(RS_PSRAM_INTR_STATE_OFFSET), interrupt, interrupt, timeout);
if (status != RS_OK) {
return status;
}
interrupt_state = *rs_psram_register(RS_PSRAM_INTR_STATE_OFFSET);
*rs_psram_register(RS_PSRAM_INTR_STATE_OFFSET) = interrupt_state & RS_PSRAM_INTERRUPT_ALL;
if ((interrupt_state & (RS_PSRAM_INTERRUPT_ERROR | RS_PSRAM_INTERRUPT_TIMEOUT)) != 0U) {
return (interrupt_state & RS_PSRAM_INTERRUPT_TIMEOUT) != 0U ? RS_ETIMEOUT : RS_EIO;
}
return RS_OK;
}
timing_expt = 2;
timing_actual = (1000 / PSRAM_SCLK_FREQ) / 2;
printf("tCSP ===> expt: %dns(min)\n", timing_expt);
printf(" actul: %dns ", timing_actual);
printf("%s\n", (timing_pass &= (timing_actual >= timing_expt)) ? msg_pass : msg_fail);
rs_status_t rs_psram_configure(const rs_psram_config_t *config) {
uint32_t status;
uint32_t control;
uint32_t clock_config;
timing_expt = 20;
timing_actual = (1000 / PSRAM_SCLK_FREQ) * (reg_psram_chd / 2 + 1);
printf("tCHD ===> expt: %dns(min)\n", timing_expt);
printf(" actul: %dns ", timing_actual);
printf("%s\n", (timing_pass &= (timing_actual >= timing_expt)) ? msg_pass : msg_fail);
timing_expt = 2;
timing_actual = (1000 / PSRAM_SCLK_FREQ) / 2;
printf("tSP ===> expt: %dns(min)\n", timing_expt);
printf(" actul: %dns ", timing_actual);
printf("%s\n", (timing_pass &= (timing_actual >= timing_expt)) ? msg_pass : msg_fail);
if (!timing_pass) {
printf("[PSRAM] timing check fail\n");
return;
if (!rs_psram_config_valid(config)) {
return RS_EINVAL;
}
status = *rs_psram_register(RS_PSRAM_STATUS_OFFSET);
if ((status & RS_PSRAM_STATUS_BUSY_MASK) != 0U) {
return RS_EIO;
}
uint32_t psram_cfg_val = (uint32_t)8;
reg_psram_wait = psram_cfg_val;
printf("[PSRAM] set wait cycles to %d, actul rd val: %d\n", psram_cfg_val, reg_psram_wait);
psram_cfg_val = (uint32_t)0;
reg_psram_chd = psram_cfg_val;
printf("[PSRAM] set chd cycles to %d, actul rd val: %d\n", psram_cfg_val, reg_psram_chd);
printf("[extern PSRAM test]\n");
// ip_psram_selftest(0x40000000, 8 * 1024 * 1024);
printf("psram self test done\n");
*rs_psram_register(RS_PSRAM_CTRL_OFFSET) = 0U;
*rs_psram_register(RS_PSRAM_CHIP_ENABLE_OFFSET) = config->chip_enable;
clock_config = config->timing.half_period_cycles;
if (config->timing.above_84mhz) {
clock_config |= RS_PSRAM_CLK_ABOVE_84MHZ_MASK;
}
*rs_psram_register(RS_PSRAM_CLK_CONFIG_OFFSET) = clock_config;
*rs_psram_register(RS_PSRAM_POWERUP_CYCLES_OFFSET) = config->timing.powerup_cycles;
*rs_psram_register(RS_PSRAM_CS_SETUP_CYCLES_OFFSET) = config->timing.cs_setup_cycles;
*rs_psram_register(RS_PSRAM_CS_HIGH_CYCLES_OFFSET) = config->timing.cs_high_cycles;
*rs_psram_register(RS_PSRAM_CS_HOLD_CYCLES_OFFSET) = config->timing.cs_hold_cycles;
*rs_psram_register(RS_PSRAM_CS_MAX_LOW_CYCLES_OFFSET) = config->timing.cs_max_low_cycles;
*rs_psram_register(RS_PSRAM_ACCESS_TIMEOUT_CYCLES_OFFSET) =
config->timing.access_timeout_cycles;
*rs_psram_register(RS_PSRAM_CHIP_ERROR_OFFSET) = UINT32_C(0xF);
*rs_psram_register(RS_PSRAM_INTR_STATE_OFFSET) = RS_PSRAM_INTERRUPT_ALL;
control = RS_PSRAM_CTRL_ENABLE_MASK;
if (config->memory_enable) {
control |= RS_PSRAM_CTRL_MEMORY_ENABLE_MASK;
}
if (config->auto_initialize) {
control |= RS_PSRAM_CTRL_AUTO_INIT_MASK;
}
if (config->wrap32) {
control |= RS_PSRAM_CTRL_WRAP32_MASK;
}
*rs_psram_register(RS_PSRAM_CTRL_OFFSET) = control;
return RS_OK;
}
rs_status_t rs_psram_initialize(rs_timeout_t timeout) {
*rs_psram_register(RS_PSRAM_INTR_STATE_OFFSET) = RS_PSRAM_INTERRUPT_ALL;
*rs_psram_register(RS_PSRAM_COMMAND_OFFSET) = RS_PSRAM_COMMAND_INIT_MASK;
return rs_psram_wait_interrupt(RS_PSRAM_INTERRUPT_INIT_DONE, timeout);
}
rs_status_t rs_psram_recover(uint8_t chip, rs_timeout_t timeout) {
if (!rs_psram_chip_valid(chip)) {
return RS_EINVAL;
}
*rs_psram_register(RS_PSRAM_INTR_STATE_OFFSET) = RS_PSRAM_INTERRUPT_ALL;
*rs_psram_register(RS_PSRAM_COMMAND_OFFSET) =
RS_PSRAM_COMMAND_RECOVER_MASK | ((uint32_t)chip << RS_PSRAM_COMMAND_CHIP_SHIFT);
return rs_psram_wait_interrupt(RS_PSRAM_INTERRUPT_INIT_DONE, timeout);
}
rs_status_t rs_psram_abort(void) {
*rs_psram_register(RS_PSRAM_COMMAND_OFFSET) = RS_PSRAM_COMMAND_ABORT_MASK;
return RS_OK;
}
rs_status_t rs_psram_get_status(rs_psram_status_t *status) {
uint32_t controller_status;
uint32_t chip_mode;
uint32_t last_error;
if (status == NULL) {
return RS_EINVAL;
}
controller_status = *rs_psram_register(RS_PSRAM_STATUS_OFFSET);
chip_mode = *rs_psram_register(RS_PSRAM_CHIP_MODE_OFFSET);
last_error = *rs_psram_register(RS_PSRAM_LAST_ERROR_OFFSET);
status->chip_present = (uint8_t)*rs_psram_register(RS_PSRAM_CHIP_PRESENT_OFFSET);
status->chip_ready = (uint8_t)*rs_psram_register(RS_PSRAM_CHIP_READY_OFFSET);
status->chip_qpi = (uint8_t)chip_mode;
status->chip_wrap32 = (uint8_t)(chip_mode >> 4U);
status->chip_error = (uint8_t)*rs_psram_register(RS_PSRAM_CHIP_ERROR_OFFSET);
status->last_error = (rs_psram_error_t)(last_error & UINT32_C(0xF));
status->last_error_chip = (uint8_t)((last_error >> 6U) & UINT32_C(0x3));
status->last_error_address = *rs_psram_register(RS_PSRAM_LAST_ERROR_ADDR_OFFSET);
status->init_busy = (controller_status & RS_PSRAM_STATUS_INIT_BUSY_MASK) != 0U;
status->axi_busy = (controller_status & RS_PSRAM_STATUS_AXI_BUSY_MASK) != 0U;
status->indirect_busy = (controller_status & RS_PSRAM_STATUS_INDIRECT_BUSY_MASK) != 0U;
status->phy_busy = (controller_status & RS_PSRAM_STATUS_PHY_BUSY_MASK) != 0U;
status->quiesced = (controller_status & RS_PSRAM_STATUS_QUIESCED_MASK) != 0U;
status->ready = (controller_status & RS_PSRAM_STATUS_READY_MASK) != 0U;
return RS_OK;
}
rs_status_t rs_psram_read_id(uint8_t chip, uint64_t *identifier) {
uint32_t offset;
uint64_t low;
uint64_t high;
if (!rs_psram_chip_valid(chip) || (identifier == NULL)) {
return RS_EINVAL;
}
offset = RS_PSRAM_CHIP_ID_BASE_OFFSET + ((uint32_t)chip * UINT32_C(8));
low = *rs_psram_register(offset);
high = *rs_psram_register(offset + UINT32_C(4)) & UINT32_C(0xFFFF);
*identifier = low | (high << 32U);
return RS_OK;
}
rs_status_t rs_psram_indirect(const rs_psram_indirect_t *command, uint64_t *read_data,
rs_timeout_t timeout) {
uint32_t control;
rs_status_t status;
if ((command == NULL) || !rs_psram_command_valid(command->command) ||
!rs_psram_chip_valid(command->chip) || (command->length == 0U) || (command->length > 8U) ||
(command->address >= RS_PSRAM_CHIP_SIZE)) {
return RS_EINVAL;
}
*rs_psram_register(RS_PSRAM_INTR_STATE_OFFSET) = RS_PSRAM_INTERRUPT_ALL;
*rs_psram_register(RS_PSRAM_INDIRECT_ADDR_OFFSET) = command->address;
*rs_psram_register(RS_PSRAM_INDIRECT_WDATA_LO_OFFSET) = (uint32_t)command->write_data;
*rs_psram_register(RS_PSRAM_INDIRECT_WDATA_HI_OFFSET) = (uint32_t)(command->write_data >> 32U);
control = (uint32_t)command->command |
((uint32_t)command->chip << RS_PSRAM_INDIRECT_CHIP_SHIFT) |
((uint32_t)(command->length - 1U) << RS_PSRAM_INDIRECT_LENGTH_SHIFT) |
RS_PSRAM_INDIRECT_START_MASK;
*rs_psram_register(RS_PSRAM_INDIRECT_CTRL_OFFSET) = control;
status = rs_psram_wait_interrupt(RS_PSRAM_INTERRUPT_INDIRECT_DONE, timeout);
if (status != RS_OK) {
return status;
}
if (read_data != NULL) {
*read_data = (uint64_t)*rs_psram_register(RS_PSRAM_INDIRECT_RDATA_LO_OFFSET) |
((uint64_t)*rs_psram_register(RS_PSRAM_INDIRECT_RDATA_HI_OFFSET) << 32U);
}
return RS_OK;
}
rs_status_t rs_psram_set_wrap(uint8_t chip, bool wrap32, rs_timeout_t timeout) {
rs_psram_status_t status;
rs_psram_indirect_t command = {
.command = RS_PSRAM_COMMAND_TOGGLE_WRAP,
.chip = chip,
.length = 1U,
.address = 0U,
.write_data = 0U,
};
if (!rs_psram_chip_valid(chip) || (rs_psram_get_status(&status) != RS_OK)) {
return RS_EINVAL;
}
if ((((status.chip_wrap32 >> chip) & UINT8_C(1)) != 0U) == wrap32) {
return RS_OK;
}
return rs_psram_indirect(&command, NULL, timeout);
}
rs_status_t rs_psram_interrupt_enable(uint32_t mask) {
if ((mask & ~RS_PSRAM_INTERRUPT_ALL) != 0U) {
return RS_EINVAL;
}
*rs_psram_register(RS_PSRAM_INTR_ENABLE_OFFSET) = mask;
return RS_OK;
}
rs_status_t rs_psram_interrupt_clear(uint32_t mask) {
if ((mask & ~RS_PSRAM_INTERRUPT_ALL) != 0U) {
return RS_EINVAL;
}
*rs_psram_register(RS_PSRAM_INTR_STATE_OFFSET) = mask;
return RS_OK;
}
rs_status_t rs_psram_interrupt_test(uint32_t mask) {
if ((mask & ~RS_PSRAM_INTERRUPT_ALL) != 0U) {
return RS_EINVAL;
}
*rs_psram_register(RS_PSRAM_INTR_TEST_OFFSET) = mask;
return RS_OK;
}
uint32_t xorshift32(uint32_t *state) {
/* Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs" */
uint32_t value;
if (state == NULL) {
return 0U;
}
uint32_t x = *state;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
*state = x;
return x;
value = *state;
value ^= value << 13U;
value ^= value >> 17U;
value ^= value << 5U;
*state = value;
return value;
}
void ip_psram_selftest(uint32_t addr, uint32_t range) {
volatile uint32_t *base_word = (uint32_t *)addr;
volatile uint16_t *base_hword = (uint16_t *)addr;
volatile uint8_t *base_byte = (uint8_t *)addr;
uint32_t test_num;
if ((addr == 0U) || ((addr & (sizeof(uint32_t) - 1U)) != 0U) || (range < sizeof(uint32_t))) {
printf("[PSRAM] self test range is invalid\n");
return;
}
test_num = range / sizeof(uint32_t);
if (test_num > 8192U) {
test_num = 8192U;
}
printf("[range: %dB] 4-bytes wr/rd test\n", 4 * test_num);
for (uint32_t i = 0U; i < test_num; ++i) {
*(base_word + i) = (uint32_t)(0x12345678 + i);
}
for (uint32_t i = 0U; i < test_num; ++i) {
if (*(base_word + i) != ((uint32_t)(0x12345678 + i)))
printf("[error] rd: %x org: %x\n", *(base_word + i), (uint32_t)(0x12345678 + i));
}
printf("[range: %dB] 2-bytes wr/rd test\n", 2 * test_num);
for (uint32_t i = 0U; i < test_num; ++i) {
*(base_hword + i) = (uint16_t)(0x5678 + i);
}
for (uint32_t i = 0U; i < test_num; ++i) {
if (*(base_hword + i) != ((uint16_t)(0x5678 + i)))
printf("[error] rd: %x org: %x\n", *(base_hword + i), (uint16_t)(0x5678 + i));
}
printf("[range: %dB] 1-bytes wr/rd test\n", test_num);
for (uint32_t i = 0U; i < test_num; ++i) {
*(base_byte + i) = (uint8_t)(0x78 + i);
}
for (uint32_t i = 0U; i < test_num; ++i) {
if (*(base_byte + i) != ((uint8_t)(0x78 + i)))
printf("[error] rd: %x org: %x\n", *(base_byte + i), (uint8_t)(0x78 + i));
}
const uint32_t cyc_count = 5U;
const uint32_t stride = 256U;
rs_status_t rs_psram_selftest(uintptr_t address, uint32_t size, uint32_t word_limit,
rs_psram_test_failure_t *failure) {
volatile uint32_t *memory;
uint32_t words;
uint32_t state;
printf("[range: %dMB] stride increments wr/rd test\n", range / 1024 / 1024);
for (uint32_t i = 1U; i <= cyc_count; ++i) {
state = i;
for (uint32_t word = 0U; word < range / sizeof(*base_word); word += stride) {
*(base_word + word) = xorshift32(&state);
if ((address < RS_SOC_PSRAM_BASE) || (address > RS_SOC_PSRAM_END) ||
((address & (sizeof(uint32_t) - 1U)) != 0U) || (size < sizeof(uint32_t)) ||
(size > (RS_SOC_PSRAM_END - (uint32_t)address + 1U)) || (word_limit == 0U)) {
return RS_EINVAL;
}
memory = (volatile uint32_t *)address;
words = size / sizeof(uint32_t);
if (words > word_limit) {
words = word_limit;
}
state = UINT32_C(0x13579BDF);
for (uint32_t index = 0U; index < words; ++index) {
memory[index] = xorshift32(&state);
}
state = UINT32_C(0x13579BDF);
for (uint32_t index = 0U; index < words; ++index) {
uint32_t expected = xorshift32(&state);
uint32_t actual = memory[index];
if (actual != expected) {
if (failure != NULL) {
failure->address = (uint32_t)address + (index * sizeof(uint32_t));
failure->expected = expected;
failure->actual = actual;
}
return RS_EIO;
}
}
return RS_OK;
}
void ip_psram_boot(void) {
rs_psram_status_t status;
rs_psram_config_t config;
uint32_t source_clock_hz;
printf("[PSRAM] ESP-PSRAM64H: 4 x 8 MiB, QPI SDR\n");
if ((rs_psram_get_status(&status) == RS_OK) && status.ready) {
printf("[PSRAM] ready mask: %x\n", status.chip_ready);
} else if ((rs_clock_get_active_hz(&source_clock_hz) != RS_OK) ||
(rs_psram_timing_from_hz(source_clock_hz, source_clock_hz / 2U, &config.timing) !=
RS_OK)) {
printf("[PSRAM] timing calculation failed\n");
return;
} else {
config.chip_enable = UINT8_C(0x0F);
config.wrap32 = false;
config.auto_initialize = false;
config.memory_enable = true;
if ((rs_psram_configure(&config) != RS_OK) ||
(rs_psram_initialize(RS_TIMEOUT_DEFAULT) != RS_OK) ||
(rs_psram_get_status(&status) != RS_OK)) {
printf("[PSRAM] initialization failed\n");
return;
}
printf("[PSRAM] ready mask: %x\n", status.chip_ready);
}
state = i;
for (uint32_t word = 0U; word < range / sizeof(*base_word); word += stride) {
if (*(base_word + word) != xorshift32(&state)) {
printf("***FAILED BYTE*** at %x\n", 4 * word);
return;
}
for (uint8_t chip = 0U; chip < (uint8_t)RS_PSRAM_CHIP_COUNT; ++chip) {
uint64_t identifier;
if (rs_psram_read_id(chip, &identifier) == RS_OK) {
printf("[PSRAM] chip %d ID: %x%08x\n", chip, (uint32_t)(identifier >> 32U),
(uint32_t)identifier);
}
printf(".");
}
}
void ip_psram_selftest(uint32_t address, uint32_t size) {
rs_psram_test_failure_t failure;
rs_status_t status;
status = rs_psram_selftest((uintptr_t)address, size, 8192U, &failure);
if (status == RS_OK) {
printf("[PSRAM] self test passed\n");
} else if (status == RS_EIO) {
printf("[PSRAM] self test failed at %x: expected %x, actual %x\n", failure.address,
failure.expected, failure.actual);
} else {
printf("[PSRAM] self test configuration invalid\n");
}
printf("stride test done\n");
printf("[PSRAM] self test done\n");
// while(1);
}
+79
View File
@@ -0,0 +1,79 @@
#include <stddef.h>
#include <retrosoc/hal/psram.h>
#define RS_NANOSECONDS_PER_SECOND UINT64_C(1000000000)
static bool rs_psram_ceil_cycles(uint32_t frequency_hz, uint32_t nanoseconds, uint32_t *cycles) {
uint64_t numerator;
uint64_t result;
numerator = ((uint64_t)frequency_hz * (uint64_t)nanoseconds) +
(RS_NANOSECONDS_PER_SECOND - UINT64_C(1));
result = numerator / RS_NANOSECONDS_PER_SECOND;
if (result > UINT32_MAX) {
return false;
}
*cycles = (uint32_t)result;
return true;
}
rs_status_t rs_psram_timing_from_hz(uint32_t source_clock_hz, uint32_t sclk_hz,
rs_psram_timing_t *timing) {
uint64_t divider;
uint64_t actual_hz;
uint64_t period_ns;
uint32_t setup_cycles;
uint32_t high_cycles;
uint32_t hold_cycles;
uint32_t output_hold_cycles;
uint64_t max_low_cycles;
if ((source_clock_hz == 0U) || (sclk_hz == 0U) || (timing == NULL) ||
(sclk_hz > RS_PSRAM_MAX_SCLK_HZ)) {
return RS_EINVAL;
}
divider = ((uint64_t)source_clock_hz + ((uint64_t)sclk_hz * UINT64_C(2)) - UINT64_C(1)) /
((uint64_t)sclk_hz * UINT64_C(2));
if ((divider == 0U) || (divider > UINT16_MAX)) {
return RS_EINVAL;
}
actual_hz = (uint64_t)source_clock_hz / (divider * UINT64_C(2));
if ((actual_hz == 0U) || (actual_hz > RS_PSRAM_MAX_SCLK_HZ)) {
return RS_EINVAL;
}
period_ns = (RS_NANOSECONDS_PER_SECOND + actual_hz - UINT64_C(1)) / actual_hz;
if (!rs_psram_ceil_cycles(source_clock_hz, 3U, &setup_cycles) ||
!rs_psram_ceil_cycles(source_clock_hz, 50U, &high_cycles) ||
!rs_psram_ceil_cycles(source_clock_hz, 20U, &hold_cycles) ||
!rs_psram_ceil_cycles(source_clock_hz, (uint32_t)(period_ns + UINT64_C(6)),
&output_hold_cycles) ||
!rs_psram_ceil_cycles(source_clock_hz, 150000U, &timing->powerup_cycles) ||
(setup_cycles > UINT16_MAX) || (high_cycles > UINT16_MAX) || (hold_cycles > UINT16_MAX) ||
(output_hold_cycles > UINT16_MAX)) {
return RS_EINVAL;
}
if (output_hold_cycles > hold_cycles) {
hold_cycles = output_hold_cycles;
}
max_low_cycles = ((uint64_t)source_clock_hz * UINT64_C(8000)) / RS_NANOSECONDS_PER_SECOND;
if ((max_low_cycles == 0U) || (max_low_cycles > UINT32_MAX)) {
return RS_EINVAL;
}
timing->half_period_cycles = (uint16_t)divider;
timing->cs_setup_cycles = (uint16_t)setup_cycles;
timing->cs_high_cycles = (uint16_t)high_cycles;
timing->cs_hold_cycles = (uint16_t)hold_cycles;
timing->cs_max_low_cycles = (uint32_t)max_low_cycles;
timing->access_timeout_cycles = source_clock_hz / 100U;
if (timing->access_timeout_cycles <= timing->cs_max_low_cycles) {
timing->access_timeout_cycles = timing->cs_max_low_cycles + 1U;
}
timing->actual_sclk_hz = (uint32_t)actual_hz;
timing->above_84mhz = actual_hz > RS_PSRAM_PAGE_CROSS_MAX_HZ;
return RS_OK;
}
+2 -1
View File
@@ -101,7 +101,8 @@ void rs_app_info(void) {
(void *)(uintptr_t)RS_SOC_RIBP_UART0_BASE);
printf(" 2 x TIMER(0,1) @%p,%p\n",
(void *)(uintptr_t)RS_SOC_RIBP_TIM0_BASE, (void *)(uintptr_t)RS_SOC_RIBP_TIM1_BASE);
printf(" 1 x PSRAM @%p\n", (void *)&reg_psram_wait);
printf(" 1 x PSRAM @%p\n",
(void *)(uintptr_t)RS_SOC_RIBP_PSRAM_BASE);
printf(" 1 x SPISD @%p\n", (void *)&reg_spisd_mode);
printf(" 1 x I2C0 @%p\n",
(void *)(uintptr_t)RS_SOC_RIBP_I2C0_BASE);
+3
View File
@@ -42,6 +42,9 @@ the root README and subsystem guides.
control, error, interrupt, DMA, HAL, and verification contracts.
- [ip/i2c.md](ip/i2c.md) defines the dual I2C command, timing, error,
recovery, DMA, HAL, and verification contracts.
- [ip/psram.md](ip/psram.md) defines the four-chip ESP-PSRAM64H AXI/RIBP
controller, register ABI, timing, isolation, recovery, HAL, and verification
contracts.
- [ip/ps2.md](ip/ps2.md) defines the Mini SoC PS/2 APB, GPIO pad,
interrupt, SDK, and managed-IP integration contract.
- The managed [RTC V2 datasheet](../rtl/managed/clusterip/rtc/doc/datasheet.md)
+230
View File
@@ -0,0 +1,230 @@
# ESP-PSRAM64H Controller
The Mini SoC PSRAM controller provides a 32 MiB memory-mapped AXI4 window over
four ESP-PSRAM64H devices and a separate RIBP management window. It is a
device-specific 4-bit SDR controller, not a generic serial-memory controller.
## Scope and Integration
| Property | Value |
| --- | --- |
| AXI4 data window | `0x40000000` - `0x41FFFFFF` |
| RIBP management window | Generated `RS_SOC_RIBP_PSRAM_BASE` window |
| Capacity | 4 chips x 8 MiB = 32 MiB |
| Chip selection | `chip = offset[24:23]` |
| Device address | `local_addr = offset[22:0]` |
| AXI data/ID/USER width | 32/1/1 bits |
| Physical protocol | ESP-PSRAM64H SPI/QPI, 4-bit SDR, SCLK idle low |
| Physical update/sample | Output on one phase, input on the opposite phase |
| Maximum device clock | 133 MHz device limit; see physical sign-off boundary |
The implementation excludes 8-data-pin or octal PSRAM, DDR/DTR, DQS, generic
CPOL/CPHA selection, ECC, arbitrary software opcodes, and raw protocol-phase
programming. The management port is for configuration, recovery, status, and
restricted commands. Bulk data must use the AXI window.
`ribp_psram` composes four blocks:
- `psram_reg`: RIBP register ABI, interrupts, and performance counters.
- `psram_axi4`: AXI protocol validation, error drain, byte strobes, and beat
address generation.
- `psram_core`: initialization, chip isolation, command arbitration, and the
read buffer.
- `psram_phy`: fixed-phase SPI/QPI command execution and timing enforcement.
## AXI4 Contract
The controller accepts one read or write burst at a time. Read addresses have
priority when read and write address valid are asserted together. Supported
transactions have all of these properties:
- aligned 1-, 2-, or 4-byte beats;
- `FIXED`, `INCR`, or legal 2/4/8/16-beat `WRAP` bursts;
- at most 16 beats;
- no exclusive access;
- no 4 KiB boundary crossing;
- both the first and last byte are inside the PSRAM window.
An invalid write address is followed by complete W-channel draining and one
`SLVERR` B response. An invalid read address returns the requested number of
R beats with `SLVERR` and the correct final `RLAST`. A disabled, absent, or
failed chip also returns `SLVERR`; it does not block healthy chip windows.
Each accepted write beat is reduced to one or more contiguous `WSTRB` runs, so
strobe holes never overwrite memory. Reads fetch an aligned 32-byte physical
line. Four fully associative entries retain fetched lines with round-robin
replacement. A write invalidates a matching line; initialization, recovery,
abort, and indirect commands invalidate all lines. This is an internal
read-only acceleration structure and is not a software-visible coherent cache.
## Initialization and Recovery
Reset defaults leave the controller and memory window enabled, but automatic
initialization disabled. This prevents commands from reaching the device
before firmware has configured the PSRAM pin mux. Firmware must configure the
pads and timing, issue `COMMAND.INIT`, and wait for the `INIT_DONE` interrupt
and `STATUS.READY`.
The controller latches an initialization request received before `tPU` has
expired. It then processes each enabled chip independently:
1. issue QPI `66h`, `99h` to recover a device left in QPI by a warm controller
reset;
2. issue SPI `66h`, `99h` to establish the cold-reset SPI state;
3. issue SPI `9Fh` and read the 48-bit identifier;
4. require KGD `5Dh` and density `26h`;
5. issue SPI `35h` to enter QPI;
6. issue QPI `C0h` when wrap32 is selected.
A failed ID, PHY timeout, or missing device sets that chip's sticky error and
continues initialization of the remaining chips. `CHIP_PRESENT` means the ID
was accepted; `CHIP_READY` means mapped accesses are permitted. `READY` is set
when the controller and memory window are enabled and at least one chip is
ready.
`COMMAND.RECOVER` reruns the sequence for one enabled chip. `COMMAND.ABORT`
forces SCLK low, all CE# outputs high, and the data outputs to a safe state.
Software should inspect `LAST_ERROR`, clear the corresponding sticky state,
and recover only the affected chip. Recovery and indirect commands quiesce new
AXI addresses until the accepted transaction is complete.
## Timing Programming
All timing registers are expressed in source-clock cycles. The HAL helper
`rs_psram_timing_from_hz(source_hz, requested_sclk_hz, &timing)` uses 64-bit
intermediates and ceiling division:
```text
half_period = ceil(source_hz / (2 * requested_sclk_hz))
actual_sclk = floor(source_hz / (2 * half_period))
powerup = ceil(source_hz * 150 us)
cs_setup = ceil(source_hz * 3 ns)
cs_high = ceil(source_hz * 50 ns)
cs_hold = max(ceil(source_hz * 20 ns),
ceil(source_hz * (actual_sclk_period + 6 ns)))
cs_max_low = floor(source_hz * 8 us)
```
The default access timeout is 1% of one source-clock second and is increased
when necessary to exceed `cs_max_low`. The helper rejects zero frequencies,
divider/counter overflow, and requests above 133 MHz. `CLK_CONFIG[16]` records
that the actual clock exceeds 84 MHz, where linear accesses must not cross a
1 KiB device page in one physical command.
The RTL provides the digital timing controls needed by ESP-PSRAM64H. A claim
above 84 MHz additionally requires pad slew, package/board delay, input sample
window, PDK timing, and STA evidence. The 133 MHz device rating alone is not a
retroSoC system sign-off.
## Register ABI
Registers are 32-bit and naturally aligned. Unaligned, unmapped, directionally
invalid, and forbidden busy-time accesses return RIBP `resp_err`. Configuration
registers accept `wstrb` partial writes. Action and RW1C registers require the
low byte strobe.
| Offset | Name | Access | Reset | Description |
| --- | --- | --- | --- | --- |
| `0x000` | `CTRL` | RW | `0x00000003` | `[0]` enable, `[1]` memory enable, `[2]` auto-init, `[3]` wrap32. |
| `0x004` | `COMMAND` | WO | `0` | One-hot `[0]` init, `[1]` recover, `[2]` abort; recover chip in `[9:8]`. |
| `0x008` | `STATUS` | RO | `0` | init/AXI/indirect/PHY busy `[3:0]`, quiesced `[4]`, ready `[5]`. |
| `0x00C` | `CHIP_ENABLE` | RW | `0xF` | Expected and enabled chip mask. |
| `0x010` | `CHIP_PRESENT` | RO | `0` | Valid KGD/density mask. |
| `0x014` | `CHIP_READY` | RO | `0` | Memory-accessible chip mask. |
| `0x018` | `CHIP_MODE` | RO | `0` | QPI mask `[3:0]`, wrap32 mask `[7:4]`. |
| `0x01C` | `CHIP_ERROR` | RW1C | `0` | Per-chip sticky error mask. |
| `0x020` | `CLK_CONFIG` | RW | `0x00000001` | Half-period `[15:0]`, above-84-MHz policy `[16]`. |
| `0x024` | `POWERUP_CYCLES` | RW | `10800` | Minimum `tPU` wait. |
| `0x028` | `CS_SETUP_CYCLES` | RW | `1` | CE# low to first rising SCLK. |
| `0x02C` | `CS_HIGH_CYCLES` | RW | `4` | Minimum command-to-command CE# high time. |
| `0x030` | `CS_HOLD_CYCLES` | RW | `3` | Last rising SCLK to CE# high. |
| `0x034` | `CS_MAX_LOW_CYCLES` | RW | `576` | Maximum CE# low watchdog. |
| `0x038` | `ACCESS_TIMEOUT_CYCLES` | RW | `100000` | Command completion watchdog. |
| `0x03C` | `TIMING_STATUS` | RO | derived | Half-period `[15:0]`, valid `[16]`, above 84 MHz `[17]`. |
| `0x040` | `INDIRECT_CTRL` | RW/action | `0` | Command `[3:0]`, chip `[9:8]`, length-minus-one `[18:16]`, start `[31]`. |
| `0x044` | `INDIRECT_ADDR` | RW | `0` | 23-bit local device address. |
| `0x048` | `INDIRECT_WDATA_LO` | RW | `0` | Write payload `[31:0]`. |
| `0x04C` | `INDIRECT_WDATA_HI` | RW | `0` | Write payload `[63:32]`. |
| `0x050` | `INDIRECT_RDATA_LO` | RO | `0` | Read payload `[31:0]`. |
| `0x054` | `INDIRECT_RDATA_HI` | RO | `0` | Read payload `[63:32]`. |
| `0x058` | `LAST_ERROR` | RO | `0` | Error class `[3:0]`, chip `[7:6]`. |
| `0x05C` | `LAST_ERROR_ADDR` | RO | `0` | First failing mapped or local address. |
| `0x060`-`0x07C` | `CHIPn_ID_LO/HI` | RO | `0` | Four 48-bit identifiers, low word then high 16 bits. |
| `0x080` | `INTR_STATE` | RW1C | `0` | Sticky interrupt state. |
| `0x084` | `INTR_ENABLE` | RW | `0` | Interrupt enable mask. |
| `0x088` | `INTR_STATUS` | RO | `0` | `INTR_STATE & INTR_ENABLE`. |
| `0x08C` | `INTR_TEST` | WO | `0` | Set selected interrupt state bits. |
| `0x090` | `PERF_CTRL` | RW/action | `0` | enable `[0]`, freeze `[1]`, clear action `[2]`. |
| `0x094` | `PERF_READ_BYTES` | RO | `0` | Saturating mapped-read byte count. |
| `0x098` | `PERF_WRITE_BYTES` | RO | `0` | Saturating mapped-write byte count. |
| `0x09C` | `PERF_COMMANDS` | RO | `0` | Saturating physical-command count. |
| `0x0A0` | `PERF_SPLITS` | RO | `0` | Saturating split count. |
| `0x0A4` | `PERF_STALL_CYCLES` | RO | `0` | Saturating AXI stall-cycle count. |
| `0x0A8` | `PERF_ERROR_COUNT` | RO | `0` | Saturating error count. |
| `0x0F8` | `IP_VERSION` | RO | `0x00010000` | ABI version 1.0. |
| `0x0FC` | `CAPABILITY` | RO | `0x20102043` | Frozen capability encoding below. |
`CAPABILITY[31:24]` is aperture MiB (`0x20`), `[23:16]` is the maximum AXI
burst beats (`0x10`), `[15:8]` is the physical read-line bytes (`0x20`),
`[7:4]` is the chip count (`4`), `[1]` advertises the restricted indirect
engine, and `[0]` advertises per-chip fault isolation. Other low feature bits
are reserved as zero.
Interrupt bits are init done `[0]`, indirect done `[1]`, error `[2]`, and
timeout `[3]`. Events win over a same-cycle RW1C clear. Error classes are none
`0`, illegal `1`, unavailable `2`, timeout `3`, ID `4`, aborted `5`, PHY `6`,
and protocol `7`.
The restricted command field accepts only `03h`, `0Bh`, `EBh`, `02h`, `38h`,
`35h`, `F5h`, `66h`, `99h`, `C0h`, and `9Fh` through command enum values
0 through 10. Payload length is 1 through 8 bytes. Mode, line width, address
phase, and dummy cycles are selected by hardware.
## HAL Sequence
Applications include `<retrosoc/hal/psram.h>` and use bounded APIs:
```c
rs_psram_config_t config;
if ((rs_psram_timing_from_hz(source_hz, target_hz, &config.timing) == RS_OK)) {
config.chip_enable = UINT8_C(0x0F);
config.wrap32 = false;
config.auto_initialize = false;
config.memory_enable = true;
if ((rs_psram_configure(&config) == RS_OK) &&
(rs_psram_initialize(timeout) == RS_OK)) {
/* The healthy CHIP_READY windows may now be accessed through AXI. */
}
}
```
The HAL also provides status and ID reads, per-chip recovery, abort, typed
indirect commands, wrap selection, interrupt control, and a bounded destructive
self-test. No dynamic memory or hosted-library dependency is introduced.
## Requirement Traceability
| ID | Requirement | Primary evidence |
| --- | --- | --- |
| PSRAM-001 | Four 8 MiB chips form one 32 MiB window. | Memory-map tests, boundary accesses in `psram_tb.sv`, full-SoC boot. |
| PSRAM-002 | Warm and cold initialization recover SPI/QPI state and validate ID. | Device model plus initialization and missing-chip scenarios in `psram_tb.sv`. |
| PSRAM-003 | One failed chip is isolated without blocking healthy chips. | Chip 2 missing-fault test; ready/error masks and healthy-chip AXI accesses. |
| PSRAM-004 | AXI legal bursts complete; illegal bursts terminate without deadlock. | Directed AXI/WSTRB/backpressure/error-drain tests and `formal-psram`. |
| PSRAM-005 | RIBP rejects invalid ABI accesses and protects active timing. | Register error tests in `psram_tb.sv`. |
| PSRAM-006 | All commands obey bounded CE#/SCLK and abort-safe behavior. | PHY assertions in `formal-psram`, model timing checks, access watchdog tests. |
| PSRAM-007 | RTL and HAL register offsets remain synchronized. | `tests/test_psram.py` ABI comparison. |
| PSRAM-008 | Timing math rounds conservatively and rejects invalid ranges. | Host cases in `tests/c/test_runtime.c`. |
| PSRAM-009 | The integrated SoC boots and executes from PSRAM. | IHP130 Verilator `SIM_TEST_PASS`; synthesis/netlist/STA regression gates. |
`python3 -m pytest -q tests/test_psram.py` runs the deterministic controller
test. `make CONFIG=configs/ci/ihp130.mk formal-psram` runs the PSRAM AXI/PHY
prove and cover jobs. Full delivery also requires the selected IHP130 firmware,
RTL lint, Yosys synthesis, Icarus netlist simulation, OpenSTA, warning, and
metric stages.
Event-driven simulation retains model timing checks based on simulation time.
The full-SoC Verilator harness uses `--no-timing`, so it disables those model
`$time` checks and relies on the controller's cycle counters. Passing either
simulation does not replace board-level SI, 133 MHz sample-window training,
package/PCB timing, or silicon characterization.
+6
View File
@@ -66,3 +66,9 @@ The Verilator harness uses a zero-delay SDRAM protocol model because Verilator
does not elaborate the tri-state delays in the Micron model. The Icarus
testbench retains that Micron timing model, so it is the reference for SDRAM
command timing while Verilator provides fast functional coverage.
The self-owned ESP-PSRAM64H controller exposes a 32-bit AXI4 data window and a
separate RIBP management plane across four independently isolated 8 MiB chips.
Its frozen architecture, register ABI, timing limits, model, formal target, and
verification evidence are documented in
[ESP-PSRAM64H Controller](../docs/ip/psram.md).
+373
View File
@@ -0,0 +1,373 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
`include "axi4_define.svh"
`include "mmap_define.svh"
module psram_axi4 (
// verilog_format: off -- preserve reviewed port alignment
input logic clk_i,
input logic rst_n_i,
input logic accept_enable_i,
output logic busy_o,
output logic stall_event_o,
output logic split_event_o,
axi4_if.slave axi4,
output logic mem_req_valid_o,
input logic mem_req_ready_i,
output logic mem_req_write_o,
output logic [1:0] mem_req_chip_o,
output logic [22:0] mem_req_addr_o,
output logic [2:0] mem_req_len_o,
output logic [31:0] mem_req_wdata_o,
input logic mem_rsp_valid_i,
output logic mem_rsp_ready_o,
input logic mem_rsp_error_i,
input logic [31:0] mem_rsp_rdata_i
// verilog_format: on
);
typedef enum logic [3:0] {
AxiIdle = 4'd0,
AxiWriteData = 4'd1,
AxiWriteRequest = 4'd2,
AxiWriteWait = 4'd3,
AxiWriteDrain = 4'd4,
AxiWriteResponse = 4'd5,
AxiReadRequest = 4'd6,
AxiReadWait = 4'd7,
AxiReadResponse = 4'd8
} psram_axi_state_e;
psram_axi_state_e s_state_q;
logic [31:0] s_addr_q;
logic [31:0] s_next_addr;
logic [ 7:0] s_len_q;
logic [ 7:0] s_beat_q;
logic [ 2:0] s_size_q;
logic [ 1:0] s_burst_q;
logic s_id_q;
logic [31:0] s_wdata_q;
logic [ 3:0] s_wstrb_q;
logic s_wlast_q;
logic [ 1:0] s_resp_q;
logic [31:0] s_rdata_q;
logic [ 2:0] s_byte_count_q;
logic s_read_invalid_q;
logic [32:0] s_write_last_addr;
logic [32:0] s_read_last_addr;
logic s_legal_write;
logic s_legal_read;
logic [ 3:0] s_write_lane_mask;
logic [ 1:0] s_active_lane;
logic [ 2:0] s_active_run_len;
logic [ 3:0] s_active_run_mask;
function automatic logic [32:0] burst_last_addr(input logic [31:0] addr, input logic [7:0] length,
input logic [2:0] size, input logic [1:0] burst);
logic [32:0] beat_bytes;
logic [32:0] burst_bytes;
logic [32:0] wrap_base;
begin
beat_bytes = 33'd1 << size;
burst_bytes = beat_bytes * ({25'd0, length} + 1'b1);
wrap_base = {1'b0, addr} & ~(burst_bytes - 1'b1);
unique case (burst)
`AXI4_BURST_TYPE_FIXED: burst_last_addr = {1'b0, addr} + beat_bytes - 1'b1;
`AXI4_BURST_TYPE_WRAP: burst_last_addr = wrap_base + burst_bytes - 1'b1;
default: burst_last_addr = {1'b0, addr} + burst_bytes - 1'b1;
endcase
end
endfunction
function automatic logic wrap_length_legal(input logic [7:0] length);
return (length == 8'd1) || (length == 8'd3) || (length == 8'd7) || (length == 8'd15);
endfunction
function automatic logic [3:0] transfer_lane_mask(input logic [2:0] size,
input logic [1:0] address);
begin
unique case (size)
3'd0: return 4'b0001 << address;
3'd1: return 4'b0011 << address;
default: return 4'b1111;
endcase
end
endfunction
function automatic logic [2:0] active_run_length(input logic [3:0] strobe,
input logic [1:0] lane);
begin
if (!strobe[lane]) return 3'd0;
unique case (lane)
2'd0: begin
if (!strobe[1]) return 3'd1;
if (!strobe[2]) return 3'd2;
if (!strobe[3]) return 3'd3;
return 3'd4;
end
2'd1: begin
if (!strobe[2]) return 3'd1;
if (!strobe[3]) return 3'd2;
return 3'd3;
end
2'd2: return strobe[3] ? 3'd2 : 3'd1;
default: return 3'd1;
endcase
end
endfunction
function automatic logic [3:0] active_run_mask(input logic [2:0] run_len, input logic [1:0] lane);
logic [3:0] ones;
begin
ones = (4'd1 << run_len) - 1'b1;
return ones << lane;
end
endfunction
always_comb begin
unique casez (s_wstrb_q)
4'b???1: s_active_lane = 2'd0;
4'b??10: s_active_lane = 2'd1;
4'b?100: s_active_lane = 2'd2;
default: s_active_lane = 2'd3;
endcase
end
assign s_active_run_len = active_run_length(s_wstrb_q, s_active_lane);
assign s_active_run_mask = active_run_mask(s_active_run_len, s_active_lane);
assign s_write_last_addr = burst_last_addr(axi4.awaddr, axi4.awlen, axi4.awsize, axi4.awburst);
assign s_read_last_addr = burst_last_addr(axi4.araddr, axi4.arlen, axi4.arsize, axi4.arburst);
assign s_legal_write =
(axi4.awlen <= 8'd15) &&
((axi4.awburst == `AXI4_BURST_TYPE_FIXED) ||
(axi4.awburst == `AXI4_BURST_TYPE_INCR) ||
((axi4.awburst == `AXI4_BURST_TYPE_WRAP) && wrap_length_legal(
axi4.awlen
))) && !axi4.awlock && (axi4.awsize <= `AXI4_BURST_SIZE_4BYTES) &&
((axi4.awaddr & ((32'd1 << axi4.awsize) - 1'b1)) == 32'd0) && !s_write_last_addr[32] &&
(axi4.awaddr[31:12] == s_write_last_addr[31:12]) &&
`SOC_ADDR_IS_PSRAM(axi4.awaddr)
&&
`SOC_ADDR_IS_PSRAM(s_write_last_addr[31:0]);
assign s_legal_read =
(axi4.arlen <= 8'd15) &&
((axi4.arburst == `AXI4_BURST_TYPE_FIXED) ||
(axi4.arburst == `AXI4_BURST_TYPE_INCR) ||
((axi4.arburst == `AXI4_BURST_TYPE_WRAP) && wrap_length_legal(
axi4.arlen
))) && !axi4.arlock && (axi4.arsize <= `AXI4_BURST_SIZE_4BYTES) &&
((axi4.araddr & ((32'd1 << axi4.arsize) - 1'b1)) == 32'd0) && !s_read_last_addr[32] &&
(axi4.araddr[31:12] == s_read_last_addr[31:12]) &&
`SOC_ADDR_IS_PSRAM(axi4.araddr)
&&
`SOC_ADDR_IS_PSRAM(s_read_last_addr[31:0]);
assign s_write_lane_mask = transfer_lane_mask(s_size_q, s_addr_q[1:0]);
assign axi4.awready = (s_state_q == AxiIdle) && accept_enable_i && !axi4.arvalid;
assign axi4.arready = (s_state_q == AxiIdle) && accept_enable_i;
assign axi4.wready = (s_state_q == AxiWriteData) || (s_state_q == AxiWriteDrain);
assign axi4.bid = s_id_q;
assign axi4.bresp = s_resp_q;
assign axi4.buser = '0;
assign axi4.bvalid = s_state_q == AxiWriteResponse;
assign axi4.rid = s_id_q;
assign axi4.rdata = s_rdata_q;
assign axi4.rresp = s_resp_q;
assign axi4.rlast = s_beat_q == s_len_q;
assign axi4.ruser = '0;
assign axi4.rvalid = s_state_q == AxiReadResponse;
assign busy_o = s_state_q != AxiIdle;
assign stall_event_o = (axi4.awvalid && !axi4.awready) ||
(axi4.wvalid && !axi4.wready) ||
(axi4.arvalid && !axi4.arready) ||
(axi4.rvalid && !axi4.rready) ||
(axi4.bvalid && !axi4.bready);
assign mem_req_valid_o = (s_state_q == AxiWriteRequest) || (s_state_q == AxiReadRequest);
assign mem_req_write_o = s_state_q == AxiWriteRequest;
assign mem_req_chip_o = s_addr_q[24:23];
assign mem_req_addr_o = (s_state_q == AxiWriteRequest) ?
({s_addr_q[22:2], 2'b00} + {21'd0, s_active_lane}) :
s_addr_q[22:0];
assign mem_req_len_o = (s_state_q == AxiWriteRequest) ? s_active_run_len : s_byte_count_q;
assign mem_req_wdata_o = s_wdata_q >> (s_active_lane * 8);
assign mem_rsp_ready_o = (s_state_q == AxiWriteWait) || (s_state_q == AxiReadWait);
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
s_state_q <= AxiIdle;
s_addr_q <= '0;
s_len_q <= '0;
s_beat_q <= '0;
s_size_q <= '0;
s_burst_q <= '0;
s_id_q <= '0;
s_wdata_q <= '0;
s_wstrb_q <= '0;
s_wlast_q <= 1'b0;
s_resp_q <= `AXI4_RESP_OKAY;
s_rdata_q <= '0;
s_byte_count_q <= '0;
s_read_invalid_q <= 1'b0;
split_event_o <= 1'b0;
end else begin
split_event_o <= 1'b0;
unique case (s_state_q)
AxiIdle: begin
s_beat_q <= '0;
s_resp_q <= `AXI4_RESP_OKAY;
s_read_invalid_q <= 1'b0;
if (axi4.arvalid && axi4.arready) begin
s_addr_q <= axi4.araddr;
s_len_q <= axi4.arlen;
s_size_q <= axi4.arsize;
s_burst_q <= axi4.arburst;
s_id_q <= axi4.arid;
s_rdata_q <= '0;
s_byte_count_q <= 3'(3'd1 << axi4.arsize);
if (s_legal_read) begin
s_state_q <= AxiReadRequest;
end else begin
s_resp_q <= `AXI4_RESP_SLAVE_ERROR;
s_read_invalid_q <= 1'b1;
s_state_q <= AxiReadResponse;
end
end else if (axi4.awvalid && axi4.awready) begin
s_addr_q <= axi4.awaddr;
s_len_q <= axi4.awlen;
s_size_q <= axi4.awsize;
s_burst_q <= axi4.awburst;
s_id_q <= axi4.awid;
if (s_legal_write) begin
s_state_q <= AxiWriteData;
end else begin
s_resp_q <= `AXI4_RESP_SLAVE_ERROR;
s_state_q <= AxiWriteDrain;
end
end
end
AxiWriteData: begin
if (axi4.wvalid && axi4.wready) begin
s_wdata_q <= axi4.wdata;
s_wstrb_q <= axi4.wstrb;
s_wlast_q <= axi4.wlast;
if ((axi4.wlast != (s_beat_q == s_len_q)) || (|(axi4.wstrb & ~s_write_lane_mask))) begin
s_resp_q <= `AXI4_RESP_SLAVE_ERROR;
if (axi4.wlast) s_state_q <= AxiWriteResponse;
else s_state_q <= AxiWriteDrain;
end else if (axi4.wstrb == 4'd0) begin
if (s_beat_q == s_len_q) begin
s_state_q <= AxiWriteResponse;
end else begin
if ((s_addr_q[22:0] == 23'h0003FF) || (s_addr_q[22:0] == 23'h7FFFFF)) begin
split_event_o <= 1'b1;
end
s_addr_q <= s_next_addr;
s_beat_q <= s_beat_q + 1'b1;
s_state_q <= AxiWriteData;
end
end else begin
s_state_q <= AxiWriteRequest;
end
end
end
AxiWriteRequest: begin
if (mem_req_valid_o && mem_req_ready_i) begin
s_wstrb_q <= s_wstrb_q & ~s_active_run_mask;
s_state_q <= AxiWriteWait;
end
end
AxiWriteWait: begin
if (mem_rsp_valid_i && mem_rsp_ready_o) begin
if (mem_rsp_error_i) begin
s_resp_q <= `AXI4_RESP_SLAVE_ERROR;
if (s_wlast_q) s_state_q <= AxiWriteResponse;
else s_state_q <= AxiWriteDrain;
end else if (s_wstrb_q != 4'd0) begin
s_state_q <= AxiWriteRequest;
end else if (s_beat_q == s_len_q) begin
s_state_q <= AxiWriteResponse;
end else begin
if ((s_addr_q[22:0] == 23'h0003FF) || (s_addr_q[22:0] == 23'h7FFFFF)) begin
split_event_o <= 1'b1;
end
s_addr_q <= s_next_addr;
s_beat_q <= s_beat_q + 1'b1;
s_state_q <= AxiWriteData;
end
end
end
AxiWriteDrain: begin
if (axi4.wvalid && axi4.wready && axi4.wlast) begin
s_state_q <= AxiWriteResponse;
end
end
AxiWriteResponse: begin
if (axi4.bvalid && axi4.bready) begin
s_state_q <= AxiIdle;
end
end
AxiReadRequest: begin
if (mem_req_valid_o && mem_req_ready_i) begin
s_state_q <= AxiReadWait;
end
end
AxiReadWait: begin
if (mem_rsp_valid_i && mem_rsp_ready_o) begin
if (mem_rsp_error_i) begin
s_resp_q <= `AXI4_RESP_SLAVE_ERROR;
s_state_q <= AxiReadResponse;
end else begin
s_rdata_q <= mem_rsp_rdata_i << (s_addr_q[1:0] * 8);
s_state_q <= AxiReadResponse;
end
end
end
AxiReadResponse: begin
if (axi4.rvalid && axi4.rready) begin
if (s_beat_q == s_len_q) begin
s_state_q <= AxiIdle;
end else begin
if ((s_addr_q[22:0] == 23'h0003FF) || (s_addr_q[22:0] == 23'h7FFFFF)) begin
split_event_o <= 1'b1;
end
s_addr_q <= s_next_addr;
s_beat_q <= s_beat_q + 1'b1;
s_rdata_q <= '0;
s_resp_q <= s_read_invalid_q ? `AXI4_RESP_SLAVE_ERROR : `AXI4_RESP_OKAY;
s_state_q <= s_read_invalid_q ? AxiReadResponse : AxiReadRequest;
end
end
end
default: s_state_q <= AxiIdle;
endcase
end
end
axi4_addr_gen #(
.ADDR_WIDTH(32)
) u_addr_gen (
.alen_i (s_len_q),
.asize_i (s_size_q),
.aburst_i(s_burst_q),
.addr_i (s_addr_q),
.addr_o (s_next_addr)
);
endmodule
+621 -328
View File
@@ -1,347 +1,640 @@
// Copyright (c) 2023-2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
// http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
module psram_core (
input logic clk_i,
input logic rst_n_i,
input logic [ 4:0] cfg_wait_i,
input logic [ 2:0] cfg_chd_i,
input logic cfg_init_i,
output logic mem_ready_o,
input logic [23:0] mem_addr_i,
input logic [31:0] mem_wdata_i,
output logic [31:0] mem_rdata_o,
input logic [ 7:0] xfer_data_bit_cnt_i,
input logic rd_st_i,
input logic wr_st_i,
output logic init_done_o,
output logic idle_o,
output logic psram_sclk_o,
output logic psram_ce_o,
input logic psram_mosi_i,
input logic psram_miso_i,
input logic psram_sio2_i,
input logic psram_sio3_i,
output logic psram_mosi_o,
output logic psram_miso_o,
output logic psram_sio2_o,
output logic psram_sio3_o,
output logic psram_sio_oen_o
// verilog_format: off -- preserve reviewed port alignment
input logic clk_i,
input logic rst_n_i,
input logic controller_enable_i,
input logic memory_enable_i,
input logic auto_init_i,
input logic wrap32_i,
input logic [3:0] chip_enable_i,
input logic [31:0] powerup_cycles_i,
input logic init_start_i,
input logic recover_start_i,
input logic [1:0] recover_chip_i,
input logic abort_i,
input logic [3:0] chip_error_clear_i,
output logic init_busy_o,
output logic indirect_busy_o,
output logic quiesced_o,
output logic global_ready_o,
output logic [3:0] chip_present_o,
output logic [3:0] chip_ready_o,
output logic [3:0] chip_qpi_o,
output logic [3:0] chip_wrap32_o,
output logic [3:0] chip_error_o,
output logic [47:0] chip0_id_o,
output logic [47:0] chip1_id_o,
output logic [47:0] chip2_id_o,
output logic [47:0] chip3_id_o,
output psram_pkg::psram_error_e last_error_o,
output logic [1:0] last_error_chip_o,
output logic [31:0] last_error_addr_o,
output logic init_done_event_o,
output logic indirect_done_event_o,
output logic error_event_o,
output logic timeout_event_o,
input logic indirect_start_i,
input psram_pkg::psram_cmd_e indirect_command_i,
input logic [1:0] indirect_chip_i,
input logic [22:0] indirect_addr_i,
input logic [3:0] indirect_length_i,
input logic [63:0] indirect_wdata_i,
output logic [63:0] indirect_rdata_o,
input logic mem_req_valid_i,
output logic mem_req_ready_o,
input logic mem_req_write_i,
input logic [1:0] mem_req_chip_i,
input logic [22:0] mem_req_addr_i,
input logic [2:0] mem_req_len_i,
input logic [31:0] mem_req_wdata_i,
output logic mem_rsp_valid_o,
input logic mem_rsp_ready_i,
output logic mem_rsp_error_o,
output logic [31:0] mem_rsp_rdata_o,
output logic [2:0] perf_read_byte_event_o,
output logic [2:0] perf_write_byte_event_o,
output logic perf_command_event_o,
output logic perf_error_event_o,
input logic axi_busy_i,
output logic phy_req_valid_o,
input logic phy_req_ready_i,
output psram_pkg::psram_cmd_e phy_req_command_o,
output logic [1:0] phy_req_chip_o,
output logic phy_req_qpi_o,
output logic [22:0] phy_req_addr_o,
output logic [5:0] phy_req_length_o,
output logic [63:0] phy_req_wdata_o,
input logic phy_busy_i,
input logic phy_done_i,
input logic phy_error_i,
input logic [255:0] phy_rdata_i
// verilog_format: on
);
// sclk(max: 144MHz ~ 6.94ns)
// 6.94 * 50000 = 347us / 2 = 174us > 150us
localparam logic [17:0] BootCounter = 18'd50_000;
typedef enum logic [4:0] {
Init = 5'd0,
ResetEnable = 5'd1,
ResetEnableToReset = 5'd2,
Reset = 5'd3,
ResetToQuadEnable = 5'd4,
QuadEnable = 5'd5,
QuadEnableToIdle = 5'd6,
Idle = 5'd7,
Send = 5'd8,
SendQpi = 5'd9,
ReadPreQpi = 5'd10,
ReadQpi = 5'd11,
WriteQpi = 5'd12,
ReadToIdle = 5'd13,
WriteToIdle = 5'd14
} psram_state_e;
import psram_pkg::*;
psram_state_e s_fsm_state;
psram_state_e s_fsm_state_tgt;
logic [17:0] s_boot_cnt;
// ca mean: cmd + addr
logic [31:0] s_xfer_ca;
logic [31:0] s_xfer_data;
logic [ 7:0] s_xfer_ca_bit_cnt;
logic [ 7:0] s_xfer_data_bit_cnt;
logic [ 7:0] s_xfer_byte_data;
logic [ 4:0] s_ce_cnt;
logic [ 3:0] s_rd_wait_cnt;
logic [ 2:0] s_cfg_chd;
logic s_dev_rst;
logic s_wr_st;
logic s_rd_st;
logic s_init_done;
typedef enum logic [3:0] {
CoreIdle = 4'd0,
CoreInitNext = 4'd1,
CoreInitIssue = 4'd2,
CoreInitWait = 4'd3,
CoreMemIssue = 4'd4,
CoreMemWait = 4'd5,
CoreMemResponse = 4'd6,
CoreIndirectIssue = 4'd7,
CoreIndirectWait = 4'd8
} psram_core_state_e;
logic s_xfer_new_byte_upd;
logic [ 7:0] s_xfer_new_byte;
typedef enum logic [2:0] {
InitQpiResetEnable = 3'd0,
InitQpiReset = 3'd1,
InitSpiResetEnable = 3'd2,
InitSpiReset = 3'd3,
InitReadId = 3'd4,
InitEnterQpi = 3'd5,
InitToggleWrap = 3'd6
} psram_init_phase_e;
psram_core_state_e s_state_q;
psram_init_phase_e s_init_phase_q;
logic [ 31:0] s_powerup_count_q;
logic s_powerup_done_q;
logic s_auto_init_started_q;
logic s_init_pending_q;
logic [ 3:0] s_init_target_q;
logic [ 1:0] s_init_chip_q;
assign init_done_o = s_init_done;
assign idle_o = s_fsm_state == Idle;
assign psram_sio_oen_o = (s_fsm_state == ReadPreQpi) | (s_fsm_state == ReadQpi);
assign mem_rdata_o = s_xfer_data;
logic [ 3:0] s_chip_present_q;
logic [ 3:0] s_chip_ready_q;
logic [ 3:0] s_chip_qpi_q;
logic [ 3:0] s_chip_wrap32_q;
logic [ 3:0] s_chip_err_q;
logic [ 47:0] s_chip_id_q [4];
psram_error_e s_last_err_q;
logic [ 1:0] s_last_err_chip_q;
logic [ 31:0] s_last_err_addr_q;
logic s_mem_write_q;
logic [ 1:0] s_mem_chip_q;
logic [ 22:0] s_mem_addr_q;
logic [ 2:0] s_mem_len_q;
logic [ 31:0] s_mem_wdata_q;
logic s_mem_rsp_err_q;
logic [ 31:0] s_mem_rsp_rdata_q;
logic [ 3:0] s_read_cache_valid_q;
logic [ 1:0] s_read_cache_chip_q [4];
logic [ 17:0] s_read_cache_base_q [4];
logic [255:0] s_read_cache_data_q [4];
logic [ 1:0] s_read_cache_replace_q;
logic [ 3:0] s_read_cache_hit;
logic [ 1:0] s_read_cache_hit_index;
logic s_indirect_pending_q;
psram_cmd_e s_indirect_cmd_q;
logic [ 1:0] s_indirect_chip_q;
logic [ 22:0] s_indirect_addr_q;
logic [ 3:0] s_indirect_len_q;
logic [ 63:0] s_indirect_wdata_q;
logic [ 63:0] s_indirect_rdata_q;
function automatic logic indirect_is_legal(input psram_cmd_e command, input logic chip_qpi,
input logic chip_enabled, input logic chip_present,
input logic [3:0] length);
logic data_command;
begin
data_command = psram_command_is_read(command) || psram_command_is_write(command);
indirect_is_legal =
chip_enabled && (length >= 4'd1) && (length <= 4'd8) &&
(command <= PsramCmdReadId) &&
(!data_command || chip_present) &&
!(((command == PsramCmdRead) || (command == PsramCmdFastRead) ||
(command == PsramCmdReadId) || (command == PsramCmdEnterQpi)) &&
chip_qpi) &&
!((command == PsramCmdExitQpi) && !chip_qpi);
end
endfunction
assign init_busy_o = (s_state_q == CoreInitNext) || (s_state_q == CoreInitIssue) ||
(s_state_q == CoreInitWait);
assign indirect_busy_o = s_indirect_pending_q ||
(s_state_q == CoreIndirectIssue) ||
(s_state_q == CoreIndirectWait);
assign quiesced_o = init_busy_o || indirect_busy_o;
assign global_ready_o = controller_enable_i && memory_enable_i && (|s_chip_ready_q);
assign chip_present_o = s_chip_present_q;
assign chip_ready_o = s_chip_ready_q;
assign chip_qpi_o = s_chip_qpi_q;
assign chip_wrap32_o = s_chip_wrap32_q;
assign chip_error_o = s_chip_err_q;
assign chip0_id_o = s_chip_id_q[0];
assign chip1_id_o = s_chip_id_q[1];
assign chip2_id_o = s_chip_id_q[2];
assign chip3_id_o = s_chip_id_q[3];
assign last_error_o = s_last_err_q;
assign last_error_chip_o = s_last_err_chip_q;
assign last_error_addr_o = s_last_err_addr_q;
assign indirect_rdata_o = s_indirect_rdata_q;
assign mem_req_ready_o = (s_state_q == CoreIdle) &&
(!s_indirect_pending_q || axi_busy_i) &&
!init_start_i && !recover_start_i;
assign mem_rsp_valid_o = s_state_q == CoreMemResponse;
assign mem_rsp_error_o = s_mem_rsp_err_q;
assign mem_rsp_rdata_o = s_mem_rsp_rdata_q;
always_comb begin
if (s_fsm_state == Init) begin
{psram_sio3_o, psram_sio2_o, psram_miso_o, psram_mosi_o} = 4'd0;
end else if (s_fsm_state != Idle) begin
if (s_fsm_state < SendQpi) begin // spi mode
psram_mosi_o = s_xfer_ca[31];
{psram_sio3_o, psram_sio2_o, psram_miso_o} = 3'd0;
end else begin // qpi mode
{psram_sio3_o, psram_sio2_o, psram_miso_o, psram_mosi_o} =
s_fsm_state == WriteQpi
? {s_xfer_byte_data[7:4]}
: {s_xfer_ca[31], s_xfer_ca[30], s_xfer_ca[29], s_xfer_ca[28]};
for (int cache_index = 0; cache_index < 4; cache_index++) begin
s_read_cache_hit[cache_index] =
s_read_cache_valid_q[cache_index] &&
(s_read_cache_chip_q[cache_index] == mem_req_chip_i) &&
(s_read_cache_base_q[cache_index] == mem_req_addr_i[22:5]);
end
unique casez (s_read_cache_hit)
4'b???1: s_read_cache_hit_index = 2'd0;
4'b??10: s_read_cache_hit_index = 2'd1;
4'b?100: s_read_cache_hit_index = 2'd2;
default: s_read_cache_hit_index = 2'd3;
endcase
end
always_comb begin
phy_req_valid_o = 1'b0;
phy_req_command_o = PsramCmdRead;
phy_req_chip_o = '0;
phy_req_qpi_o = 1'b0;
phy_req_addr_o = '0;
phy_req_length_o = 6'd1;
phy_req_wdata_o = '0;
unique case (s_state_q)
CoreInitIssue: begin
phy_req_valid_o = 1'b1;
phy_req_chip_o = s_init_chip_q;
unique case (s_init_phase_q)
InitQpiResetEnable: begin
phy_req_command_o = PsramCmdResetEnable;
phy_req_qpi_o = 1'b1;
end
InitQpiReset: begin
phy_req_command_o = PsramCmdReset;
phy_req_qpi_o = 1'b1;
end
InitSpiResetEnable: phy_req_command_o = PsramCmdResetEnable;
InitSpiReset: phy_req_command_o = PsramCmdReset;
InitReadId: begin
phy_req_command_o = PsramCmdReadId;
phy_req_length_o = 6'd6;
end
InitEnterQpi: phy_req_command_o = PsramCmdEnterQpi;
default: begin
phy_req_command_o = PsramCmdToggleWrap;
phy_req_qpi_o = 1'b1;
end
endcase
end
CoreMemIssue: begin
phy_req_valid_o = 1'b1;
phy_req_command_o = s_mem_write_q ? PsramCmdQuadWrite : PsramCmdQuadRead;
phy_req_chip_o = s_mem_chip_q;
phy_req_qpi_o = 1'b1;
phy_req_addr_o = s_mem_write_q ? s_mem_addr_q : {s_mem_addr_q[22:5], 5'd0};
phy_req_length_o = s_mem_write_q ? {3'd0, s_mem_len_q} : 6'd32;
phy_req_wdata_o = {32'd0, s_mem_wdata_q};
end
CoreIndirectIssue: begin
phy_req_valid_o = 1'b1;
phy_req_command_o = s_indirect_cmd_q;
phy_req_chip_o = s_indirect_chip_q;
phy_req_qpi_o = s_chip_qpi_q[s_indirect_chip_q];
phy_req_addr_o = s_indirect_addr_q;
phy_req_length_o = {2'd0, s_indirect_len_q};
phy_req_wdata_o = s_indirect_wdata_q;
end
default: begin
end
endcase
end
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
s_state_q <= CoreIdle;
s_init_phase_q <= InitQpiResetEnable;
s_powerup_count_q <= '0;
s_powerup_done_q <= 1'b0;
s_auto_init_started_q <= 1'b0;
s_init_pending_q <= 1'b0;
s_init_target_q <= '0;
s_init_chip_q <= '0;
s_chip_present_q <= '0;
s_chip_ready_q <= '0;
s_chip_qpi_q <= '0;
s_chip_wrap32_q <= '0;
s_chip_err_q <= '0;
s_last_err_q <= PsramErrorNone;
s_last_err_chip_q <= '0;
s_last_err_addr_q <= '0;
s_mem_write_q <= 1'b0;
s_mem_chip_q <= '0;
s_mem_addr_q <= '0;
s_mem_len_q <= 3'd1;
s_mem_wdata_q <= '0;
s_mem_rsp_err_q <= 1'b0;
s_mem_rsp_rdata_q <= '0;
s_read_cache_valid_q <= '0;
s_read_cache_replace_q <= '0;
s_indirect_pending_q <= 1'b0;
s_indirect_cmd_q <= PsramCmdRead;
s_indirect_chip_q <= '0;
s_indirect_addr_q <= '0;
s_indirect_len_q <= 4'd1;
s_indirect_wdata_q <= '0;
s_indirect_rdata_q <= '0;
init_done_event_o <= 1'b0;
indirect_done_event_o <= 1'b0;
error_event_o <= 1'b0;
timeout_event_o <= 1'b0;
perf_read_byte_event_o <= '0;
perf_write_byte_event_o <= '0;
perf_command_event_o <= 1'b0;
perf_error_event_o <= 1'b0;
for (int chip_index = 0; chip_index < 4; chip_index++) begin
s_chip_id_q[chip_index] <= '0;
end
for (int cache_index = 0; cache_index < 4; cache_index++) begin
s_read_cache_chip_q[cache_index] <= '0;
s_read_cache_base_q[cache_index] <= '0;
s_read_cache_data_q[cache_index] <= '0;
end
end else begin
{psram_sio3_o, psram_sio2_o, psram_miso_o, psram_mosi_o} = 4'd0;
init_done_event_o <= 1'b0;
indirect_done_event_o <= 1'b0;
error_event_o <= 1'b0;
timeout_event_o <= 1'b0;
perf_read_byte_event_o <= '0;
perf_write_byte_event_o <= '0;
perf_command_event_o <= 1'b0;
perf_error_event_o <= 1'b0;
s_chip_err_q <= s_chip_err_q & ~chip_error_clear_i;
if (!s_powerup_done_q) begin
if (s_powerup_count_q >= powerup_cycles_i) begin
s_powerup_done_q <= 1'b1;
end else begin
s_powerup_count_q <= s_powerup_count_q + 1'b1;
end
end
if (indirect_start_i && !s_indirect_pending_q && !indirect_busy_o) begin
s_indirect_pending_q <= 1'b1;
s_indirect_cmd_q <= indirect_command_i;
s_indirect_chip_q <= indirect_chip_i;
s_indirect_addr_q <= indirect_addr_i;
s_indirect_len_q <= indirect_length_i;
s_indirect_wdata_q <= indirect_wdata_i;
end
if (init_start_i) begin
s_init_pending_q <= 1'b1;
end
if (init_start_i || recover_start_i || abort_i || indirect_start_i) begin
s_read_cache_valid_q <= '0;
end
if (abort_i && (s_state_q == CoreIdle)) begin
s_init_pending_q <= 1'b0;
end
if ((s_state_q == CoreInitIssue) || (s_state_q == CoreMemIssue) ||
(s_state_q == CoreIndirectIssue)) begin
if (phy_req_valid_o && phy_req_ready_i) begin
perf_command_event_o <= 1'b1;
end
end
if (abort_i && (s_state_q != CoreIdle)) begin
s_last_err_q <= PsramErrorAborted;
error_event_o <= 1'b1;
perf_error_event_o <= 1'b1;
if ((s_state_q == CoreMemIssue) || (s_state_q == CoreMemWait)) begin
s_mem_rsp_err_q <= 1'b1;
s_state_q <= CoreMemResponse;
end else begin
if ((s_state_q == CoreIndirectIssue) || (s_state_q == CoreIndirectWait)) begin
s_indirect_pending_q <= 1'b0;
indirect_done_event_o <= 1'b1;
end
s_state_q <= CoreIdle;
end
end else begin
unique case (s_state_q)
CoreIdle: begin
if (!abort_i &&
((s_init_pending_q && s_powerup_done_q) || recover_start_i ||
(controller_enable_i && auto_init_i && s_powerup_done_q &&
!s_auto_init_started_q))) begin
s_init_target_q <= recover_start_i ? (4'b0001 << recover_chip_i) : chip_enable_i;
s_init_chip_q <= '0;
s_init_phase_q <= InitQpiResetEnable;
s_init_pending_q <= 1'b0;
if (!recover_start_i) begin
s_chip_present_q <= '0;
s_chip_ready_q <= '0;
s_chip_qpi_q <= '0;
s_chip_wrap32_q <= '0;
s_chip_err_q <= '0;
end else begin
s_chip_present_q[recover_chip_i] <= 1'b0;
s_chip_ready_q[recover_chip_i] <= 1'b0;
s_chip_qpi_q[recover_chip_i] <= 1'b0;
s_chip_wrap32_q[recover_chip_i] <= 1'b0;
s_chip_err_q[recover_chip_i] <= 1'b0;
end
s_auto_init_started_q <= s_auto_init_started_q || (!init_start_i && !recover_start_i);
s_state_q <= CoreInitNext;
end else if (s_indirect_pending_q && !axi_busy_i) begin
if (!controller_enable_i || !indirect_is_legal(
s_indirect_cmd_q,
s_chip_qpi_q[s_indirect_chip_q],
chip_enable_i[s_indirect_chip_q],
s_chip_present_q[s_indirect_chip_q],
s_indirect_len_q
)) begin
s_last_err_q <= PsramErrorIllegal;
s_last_err_chip_q <= s_indirect_chip_q;
s_last_err_addr_q <= {9'd0, s_indirect_addr_q};
s_indirect_pending_q <= 1'b0;
indirect_done_event_o <= 1'b1;
error_event_o <= 1'b1;
perf_error_event_o <= 1'b1;
end else begin
s_state_q <= CoreIndirectIssue;
end
end else if (mem_req_valid_i && mem_req_ready_o) begin
s_mem_write_q <= mem_req_write_i;
s_mem_chip_q <= mem_req_chip_i;
s_mem_addr_q <= mem_req_addr_i;
s_mem_len_q <= mem_req_len_i;
s_mem_wdata_q <= mem_req_wdata_i;
s_mem_rsp_err_q <= 1'b0;
s_mem_rsp_rdata_q <= '0;
if (!controller_enable_i || !memory_enable_i || !s_chip_ready_q[mem_req_chip_i]) begin
s_mem_rsp_err_q <= 1'b1;
s_last_err_q <= PsramErrorUnavailable;
s_last_err_chip_q <= mem_req_chip_i;
s_last_err_addr_q <= {7'd0, mem_req_chip_i, mem_req_addr_i};
s_chip_err_q[mem_req_chip_i] <= 1'b1;
error_event_o <= 1'b1;
perf_error_event_o <= 1'b1;
s_state_q <= CoreMemResponse;
end else if (!mem_req_write_i && (|s_read_cache_hit)) begin
s_mem_rsp_rdata_q <= 32'(s_read_cache_data_q[s_read_cache_hit_index] >>
(mem_req_addr_i[4:0] * 8));
perf_read_byte_event_o <= mem_req_len_i;
s_state_q <= CoreMemResponse;
end else begin
if (mem_req_write_i) begin
for (int cache_index = 0; cache_index < 4; cache_index++) begin
if (s_read_cache_valid_q[cache_index] &&
(s_read_cache_chip_q[cache_index] == mem_req_chip_i) &&
(s_read_cache_base_q[cache_index] == mem_req_addr_i[22:5])) begin
s_read_cache_valid_q[cache_index] <= 1'b0;
end
end
end
s_state_q <= CoreMemIssue;
end
end
end
CoreInitNext: begin
if (s_init_target_q[s_init_chip_q]) begin
s_init_phase_q <= InitQpiResetEnable;
s_state_q <= CoreInitIssue;
end else if (s_init_chip_q == 2'd3) begin
init_done_event_o <= 1'b1;
s_state_q <= CoreIdle;
end else begin
s_init_chip_q <= s_init_chip_q + 1'b1;
end
end
CoreInitIssue: begin
if (phy_req_valid_o && phy_req_ready_i) begin
s_state_q <= CoreInitWait;
end
end
CoreInitWait: begin
if (phy_done_i) begin
if (phy_error_i) begin
s_chip_err_q[s_init_chip_q] <= 1'b1;
s_chip_ready_q[s_init_chip_q] <= 1'b0;
s_last_err_q <= PsramErrorTimeout;
s_last_err_chip_q <= s_init_chip_q;
s_last_err_addr_q <= {7'd0, s_init_chip_q, 23'd0};
error_event_o <= 1'b1;
timeout_event_o <= 1'b1;
perf_error_event_o <= 1'b1;
if (s_init_chip_q == 2'd3) begin
init_done_event_o <= 1'b1;
s_state_q <= CoreIdle;
end else begin
s_init_chip_q <= s_init_chip_q + 1'b1;
s_state_q <= CoreInitNext;
end
end else if (s_init_phase_q == InitReadId) begin
s_chip_id_q[s_init_chip_q] <= phy_rdata_i[47:0];
if ((phy_rdata_i[15:8] !== PSRAM_KGD_PASS) ||
(phy_rdata_i[23:16] !== PSRAM_DENSITY_64MBIT)) begin
s_chip_err_q[s_init_chip_q] <= 1'b1;
s_chip_ready_q[s_init_chip_q] <= 1'b0;
s_last_err_q <= PsramErrorId;
s_last_err_chip_q <= s_init_chip_q;
s_last_err_addr_q <= {7'd0, s_init_chip_q, 23'd0};
error_event_o <= 1'b1;
perf_error_event_o <= 1'b1;
if (s_init_chip_q == 2'd3) begin
init_done_event_o <= 1'b1;
s_state_q <= CoreIdle;
end else begin
s_init_chip_q <= s_init_chip_q + 1'b1;
s_state_q <= CoreInitNext;
end
end else begin
s_chip_present_q[s_init_chip_q] <= 1'b1;
s_init_phase_q <= InitEnterQpi;
s_state_q <= CoreInitIssue;
end
end else if (s_init_phase_q == InitEnterQpi) begin
s_chip_qpi_q[s_init_chip_q] <= 1'b1;
if (wrap32_i) begin
s_init_phase_q <= InitToggleWrap;
s_state_q <= CoreInitIssue;
end else begin
s_chip_ready_q[s_init_chip_q] <= 1'b1;
if (s_init_chip_q == 2'd3) begin
init_done_event_o <= 1'b1;
s_state_q <= CoreIdle;
end else begin
s_init_chip_q <= s_init_chip_q + 1'b1;
s_state_q <= CoreInitNext;
end
end
end else if (s_init_phase_q == InitToggleWrap) begin
s_chip_wrap32_q[s_init_chip_q] <= 1'b1;
s_chip_ready_q[s_init_chip_q] <= 1'b1;
if (s_init_chip_q == 2'd3) begin
init_done_event_o <= 1'b1;
s_state_q <= CoreIdle;
end else begin
s_init_chip_q <= s_init_chip_q + 1'b1;
s_state_q <= CoreInitNext;
end
end else begin
s_init_phase_q <= psram_init_phase_e'(s_init_phase_q + 1'b1);
s_state_q <= CoreInitIssue;
end
end
end
CoreMemIssue: begin
if (phy_req_valid_o && phy_req_ready_i) begin
s_state_q <= CoreMemWait;
end
end
CoreMemWait: begin
if (phy_done_i) begin
s_mem_rsp_err_q <= phy_error_i;
s_mem_rsp_rdata_q <= s_mem_write_q ?
32'd0 :
32'(phy_rdata_i >> (s_mem_addr_q[4:0] * 8));
if (phy_error_i) begin
s_chip_err_q[s_mem_chip_q] <= 1'b1;
s_chip_ready_q[s_mem_chip_q] <= 1'b0;
s_last_err_q <= PsramErrorTimeout;
s_last_err_chip_q <= s_mem_chip_q;
s_last_err_addr_q <= {7'd0, s_mem_chip_q, s_mem_addr_q};
error_event_o <= 1'b1;
timeout_event_o <= 1'b1;
perf_error_event_o <= 1'b1;
end else if (s_mem_write_q) begin
perf_write_byte_event_o <= s_mem_len_q;
end else begin
s_read_cache_valid_q[s_read_cache_replace_q] <= 1'b1;
s_read_cache_chip_q[s_read_cache_replace_q] <= s_mem_chip_q;
s_read_cache_base_q[s_read_cache_replace_q] <= s_mem_addr_q[22:5];
s_read_cache_data_q[s_read_cache_replace_q] <= phy_rdata_i;
s_read_cache_replace_q <= s_read_cache_replace_q + 1'b1;
perf_read_byte_event_o <= s_mem_len_q;
end
s_state_q <= CoreMemResponse;
end
end
CoreMemResponse: begin
if (mem_rsp_valid_o && mem_rsp_ready_i) begin
s_state_q <= CoreIdle;
end
end
CoreIndirectIssue: begin
if (phy_req_valid_o && phy_req_ready_i) begin
s_state_q <= CoreIndirectWait;
end
end
CoreIndirectWait: begin
if (phy_done_i) begin
s_indirect_pending_q <= 1'b0;
s_indirect_rdata_q <= phy_rdata_i[63:0];
indirect_done_event_o <= 1'b1;
if (phy_error_i) begin
s_chip_err_q[s_indirect_chip_q] <= 1'b1;
s_chip_ready_q[s_indirect_chip_q] <= 1'b0;
s_last_err_q <= PsramErrorTimeout;
s_last_err_chip_q <= s_indirect_chip_q;
s_last_err_addr_q <= {7'd0, s_indirect_chip_q, s_indirect_addr_q};
error_event_o <= 1'b1;
timeout_event_o <= 1'b1;
perf_error_event_o <= 1'b1;
end else begin
unique case (s_indirect_cmd_q)
PsramCmdEnterQpi: begin
s_chip_qpi_q[s_indirect_chip_q] <= 1'b1;
s_chip_ready_q[s_indirect_chip_q] <= 1'b1;
end
PsramCmdExitQpi: begin
s_chip_qpi_q[s_indirect_chip_q] <= 1'b0;
s_chip_ready_q[s_indirect_chip_q] <= 1'b0;
end
PsramCmdReset: begin
s_chip_qpi_q[s_indirect_chip_q] <= 1'b0;
s_chip_wrap32_q[s_indirect_chip_q] <= 1'b0;
s_chip_ready_q[s_indirect_chip_q] <= 1'b0;
end
PsramCmdToggleWrap: begin
s_chip_wrap32_q[s_indirect_chip_q] <= ~s_chip_wrap32_q[s_indirect_chip_q];
end
PsramCmdReadId: begin
s_chip_id_q[s_indirect_chip_q] <= phy_rdata_i[47:0];
end
default: begin
end
endcase
end
s_state_q <= CoreIdle;
end
end
default: s_state_q <= CoreIdle;
endcase
end
end
end
// These protocol-state processes retain ordered update and reset priority;
// decomposing them into Common DFFs would risk changing same-cycle behavior.
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (~rst_n_i) s_rd_st <= 1'b0;
else if (rd_st_i) s_rd_st <= 1'b1;
else if (s_fsm_state == ReadToIdle) s_rd_st <= 1'b0;
end
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (~rst_n_i) s_wr_st <= 1'b0;
else if (wr_st_i) s_wr_st <= 1'b1;
else if (s_fsm_state == WriteToIdle) s_wr_st <= 1'b0;
end
// >150us, ce high, sclk low, si/so/sio[3:0] low
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (~rst_n_i) begin
s_fsm_state <= Init;
s_fsm_state_tgt <= Init;
s_boot_cnt <= BootCounter;
s_xfer_ca <= 32'd0;
s_xfer_data <= 32'd0;
s_xfer_ca_bit_cnt <= 8'd0;
s_xfer_data_bit_cnt <= 8'd0;
s_xfer_byte_data <= 8'd0;
s_ce_cnt <= 5'd0;
s_rd_wait_cnt <= 4'd0;
s_cfg_chd <= 3'd0;
s_dev_rst <= 1'b1;
s_init_done <= 1'b0;
mem_ready_o <= 1'b0;
psram_sclk_o <= 1'b0;
psram_ce_o <= 1'b1;
end else begin
mem_ready_o <= 1'b0;
/* verilator lint_off CASEINCOMPLETE */
case (s_fsm_state)
Init: begin
if (s_boot_cnt != '0) s_boot_cnt <= s_boot_cnt - 1'b1;
else if (cfg_init_i) s_fsm_state <= ResetEnable;
end
ResetEnable: begin
s_xfer_ca <= {8'h66, 24'd0};
s_xfer_ca_bit_cnt <= 8'd8;
s_cfg_chd <= 3'd0;
psram_ce_o <= 1'b0;
s_ce_cnt <= cfg_wait_i;
s_fsm_state <= Send;
s_fsm_state_tgt <= ResetEnableToReset;
end
ResetEnableToReset: begin // tCPH >= 50ns, when 192MHz, ce need keep high(>=10 cycles)
if (s_cfg_chd == cfg_chd_i) begin
if (s_ce_cnt != cfg_wait_i) psram_ce_o <= 1'b1;
if (s_ce_cnt == 5'd0) s_fsm_state <= Reset;
s_ce_cnt <= s_ce_cnt - 1'b1;
end else begin
s_cfg_chd <= s_cfg_chd + 1'b1;
end
end
Reset: begin
s_xfer_ca <= {8'h99, 24'd0};
s_xfer_ca_bit_cnt <= 8'd8;
s_cfg_chd <= 3'd0;
psram_ce_o <= 1'b0;
s_ce_cnt <= cfg_wait_i;
s_fsm_state <= Send;
s_fsm_state_tgt <= ResetToQuadEnable;
end
ResetToQuadEnable: begin
if (s_cfg_chd == cfg_chd_i) begin
if (s_ce_cnt != cfg_wait_i) psram_ce_o <= 1'b1;
if (s_ce_cnt == 5'd0) s_fsm_state <= QuadEnable;
s_ce_cnt <= s_ce_cnt - 1'b1;
end else begin
s_cfg_chd <= s_cfg_chd + 1'b1;
end
end
QuadEnable: begin
s_xfer_ca <= {8'h35, 24'd0};
s_xfer_ca_bit_cnt <= 8'd8;
s_cfg_chd <= 3'd0;
psram_ce_o <= 1'b0;
s_ce_cnt <= cfg_wait_i;
s_fsm_state <= Send;
s_fsm_state_tgt <= QuadEnableToIdle;
end
QuadEnableToIdle: begin
if (s_cfg_chd == cfg_chd_i) begin
if (s_ce_cnt != cfg_wait_i) psram_ce_o <= 1'b1;
if (s_ce_cnt == 5'd0) begin
s_fsm_state <= Idle;
s_init_done <= 1'b1;
end
s_ce_cnt <= s_ce_cnt - 1'b1;
end else begin
s_cfg_chd <= s_cfg_chd + 1'b1;
end
end
Idle: begin
psram_sclk_o <= 1'b0;
// release dev rst ctrl signal
if (s_dev_rst) begin
s_dev_rst <= 1'b0;
psram_ce_o <= 1'b1;
end else if (s_wr_st) begin
s_xfer_ca <= {8'h38, mem_addr_i[23:0]};
s_xfer_ca_bit_cnt <= 8'd32;
s_fsm_state <= SendQpi;
s_fsm_state_tgt <= WriteQpi;
s_xfer_data_bit_cnt <= 8'd0;
s_cfg_chd <= 3'd0;
psram_ce_o <= 1'b0;
end else if (s_rd_st) begin
s_xfer_ca <= {8'hEB, mem_addr_i[23:0]};
s_xfer_ca_bit_cnt <= 8'd32;
s_fsm_state <= SendQpi;
s_fsm_state_tgt <= ReadPreQpi;
s_rd_wait_cnt <= 4'd12; // wait 6 cycle afer cmd+addr accrondig to TRM
s_xfer_data_bit_cnt <= 8'd0;
s_cfg_chd <= 3'd0;
psram_ce_o <= 1'b0;
end else begin
psram_ce_o <= 1'b1;
end
end
Send: begin
psram_sclk_o <= ~psram_sclk_o;
if (psram_sclk_o) begin
s_xfer_ca_bit_cnt <= s_xfer_ca_bit_cnt - 1'b1;
s_xfer_ca <= {s_xfer_ca[30:0], 1'b1};
if (s_xfer_ca_bit_cnt == 8'd1) begin
s_fsm_state <= s_fsm_state_tgt;
end
end
end
SendQpi: begin
psram_sclk_o <= ~psram_sclk_o;
if (psram_sclk_o) begin
s_xfer_ca_bit_cnt <= s_xfer_ca_bit_cnt - 8'd4;
s_xfer_ca <= {s_xfer_ca[27:0], 4'd1};
if (s_xfer_ca_bit_cnt == 8'd4) begin
s_fsm_state <= s_fsm_state_tgt;
s_xfer_data <= mem_wdata_i;
s_xfer_byte_data <= mem_wdata_i[7:0];
end
end
end
ReadPreQpi: begin
// 6 cycles
psram_sclk_o <= ~psram_sclk_o;
s_rd_wait_cnt <= s_rd_wait_cnt - 1'b1;
if (s_rd_wait_cnt == 4'd0) s_fsm_state <= ReadQpi;
end
ReadQpi: begin
// the first 'psram_sclk_o' is 0 in this state
psram_sclk_o <= ~psram_sclk_o;
if (psram_sclk_o) begin
if (s_xfer_data_bit_cnt == 8'd8) s_xfer_data[7:0] <= s_xfer_byte_data;
else if (s_xfer_data_bit_cnt == 8'd16) s_xfer_data[15:8] <= s_xfer_byte_data;
else if (s_xfer_data_bit_cnt == 8'd24) s_xfer_data[23:16] <= s_xfer_byte_data;
s_xfer_byte_data <= {
s_xfer_byte_data[3:0], psram_sio3_i, psram_sio2_i, psram_miso_i, psram_mosi_i
};
s_xfer_data_bit_cnt <= s_xfer_data_bit_cnt + 8'd4;
if (s_xfer_data_bit_cnt == xfer_data_bit_cnt_i - 8'd4) begin
s_fsm_state <= ReadToIdle;
s_ce_cnt <= cfg_wait_i;
end
end
end
WriteQpi: begin
// the first 'psram_sclk_o' is 0 in this state
psram_sclk_o <= ~psram_sclk_o;
if (psram_sclk_o) begin
if (s_xfer_new_byte_upd) s_xfer_byte_data <= s_xfer_new_byte;
else s_xfer_byte_data <= {s_xfer_byte_data[3:0], 4'hF};
s_xfer_data_bit_cnt <= s_xfer_data_bit_cnt + 8'd4;
if (s_xfer_data_bit_cnt == xfer_data_bit_cnt_i - 8'd4) begin
s_fsm_state <= WriteToIdle;
s_ce_cnt <= cfg_wait_i;
end
end
end
ReadToIdle: begin
if (s_cfg_chd == cfg_chd_i) begin
if (s_ce_cnt != cfg_wait_i) begin
psram_ce_o <= 1'b1;
s_xfer_data[31:24] <= s_xfer_byte_data; // HACK:
end
if (s_ce_cnt == 5'd0) begin
s_fsm_state <= Idle;
mem_ready_o <= 1'b1;
end
s_ce_cnt <= s_ce_cnt - 1'b1;
end else begin
s_cfg_chd <= s_cfg_chd + 1'b1;
end
end
WriteToIdle: begin
if (s_cfg_chd == cfg_chd_i) begin
if (s_ce_cnt != cfg_wait_i) psram_ce_o <= 1'b1;
if (s_ce_cnt == 5'd0) begin
s_fsm_state <= Idle;
mem_ready_o <= 1'b1;
end
s_ce_cnt <= s_ce_cnt - 1'b1;
end else begin
s_cfg_chd <= s_cfg_chd + 1'b1;
end
end
default: begin
s_fsm_state <= Init;
psram_sclk_o <= 1'b0;
psram_ce_o <= 1'b1;
end
endcase
end
end
load_new_byte u_load_new_byte (
.xfer_data_bit_cnt_i(s_xfer_data_bit_cnt),
.wr_data_i (s_xfer_data),
.xfer_new_byte_upd_o(s_xfer_new_byte_upd),
.xfer_new_byte_o (s_xfer_new_byte)
);
logic unused_phy_busy;
assign unused_phy_busy = phy_busy_i;
endmodule
module load_new_byte (
input logic [ 7:0] xfer_data_bit_cnt_i,
input logic [31:0] wr_data_i,
output logic xfer_new_byte_upd_o,
output logic [ 7:0] xfer_new_byte_o
);
assign xfer_new_byte_upd_o = (xfer_data_bit_cnt_i == 8'd4) |
(xfer_data_bit_cnt_i == 8'd12) |
(xfer_data_bit_cnt_i == 8'd20);
assign xfer_new_byte_o = ({8{xfer_data_bit_cnt_i == 8'd4} } & wr_data_i[15:8]) |
({8{xfer_data_bit_cnt_i == 8'd12}} & wr_data_i[23:16]) |
({8{xfer_data_bit_cnt_i == 8'd20}} & wr_data_i[31:24]);
endmodule
+94
View File
@@ -0,0 +1,94 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
`ifndef RIBP_PSRAM_DEFINE_SVH
`define RIBP_PSRAM_DEFINE_SVH
// verilog_format: off -- preserve reviewed register and field alignment
`define RIBP_PSRAM_CTRL 12'h000
`define RIBP_PSRAM_COMMAND 12'h004
`define RIBP_PSRAM_STATUS 12'h008
`define RIBP_PSRAM_CHIP_ENABLE 12'h00C
`define RIBP_PSRAM_CHIP_PRESENT 12'h010
`define RIBP_PSRAM_CHIP_READY 12'h014
`define RIBP_PSRAM_CHIP_MODE 12'h018
`define RIBP_PSRAM_CHIP_ERROR 12'h01C
`define RIBP_PSRAM_CLK_CONFIG 12'h020
`define RIBP_PSRAM_POWERUP_CYCLES 12'h024
`define RIBP_PSRAM_CS_SETUP_CYCLES 12'h028
`define RIBP_PSRAM_CS_HIGH_CYCLES 12'h02C
`define RIBP_PSRAM_CS_HOLD_CYCLES 12'h030
`define RIBP_PSRAM_CS_MAX_LOW_CYCLES 12'h034
`define RIBP_PSRAM_ACCESS_TIMEOUT_CYCLES 12'h038
`define RIBP_PSRAM_TIMING_STATUS 12'h03C
`define RIBP_PSRAM_INDIRECT_CTRL 12'h040
`define RIBP_PSRAM_INDIRECT_ADDR 12'h044
`define RIBP_PSRAM_INDIRECT_WDATA_LO 12'h048
`define RIBP_PSRAM_INDIRECT_WDATA_HI 12'h04C
`define RIBP_PSRAM_INDIRECT_RDATA_LO 12'h050
`define RIBP_PSRAM_INDIRECT_RDATA_HI 12'h054
`define RIBP_PSRAM_LAST_ERROR 12'h058
`define RIBP_PSRAM_LAST_ERROR_ADDR 12'h05C
`define RIBP_PSRAM_CHIP0_ID_LO 12'h060
`define RIBP_PSRAM_CHIP0_ID_HI 12'h064
`define RIBP_PSRAM_CHIP1_ID_LO 12'h068
`define RIBP_PSRAM_CHIP1_ID_HI 12'h06C
`define RIBP_PSRAM_CHIP2_ID_LO 12'h070
`define RIBP_PSRAM_CHIP2_ID_HI 12'h074
`define RIBP_PSRAM_CHIP3_ID_LO 12'h078
`define RIBP_PSRAM_CHIP3_ID_HI 12'h07C
`define RIBP_PSRAM_INTR_STATE 12'h080
`define RIBP_PSRAM_INTR_ENABLE 12'h084
`define RIBP_PSRAM_INTR_STATUS 12'h088
`define RIBP_PSRAM_INTR_TEST 12'h08C
`define RIBP_PSRAM_PERF_CTRL 12'h090
`define RIBP_PSRAM_PERF_READ_BYTES 12'h094
`define RIBP_PSRAM_PERF_WRITE_BYTES 12'h098
`define RIBP_PSRAM_PERF_COMMANDS 12'h09C
`define RIBP_PSRAM_PERF_SPLITS 12'h0A0
`define RIBP_PSRAM_PERF_STALL_CYCLES 12'h0A4
`define RIBP_PSRAM_PERF_ERROR_COUNT 12'h0A8
`define RIBP_PSRAM_IP_VERSION 12'h0F8
`define RIBP_PSRAM_CAPABILITY 12'h0FC
`define PSRAM_CTRL_ENABLE 0
`define PSRAM_CTRL_MEMORY_ENABLE 1
`define PSRAM_CTRL_AUTO_INIT 2
`define PSRAM_CTRL_WRAP32 3
`define PSRAM_COMMAND_INIT 0
`define PSRAM_COMMAND_RECOVER 1
`define PSRAM_COMMAND_ABORT 2
`define PSRAM_COMMAND_CHIP_LSB 8
`define PSRAM_STATUS_INIT_BUSY 0
`define PSRAM_STATUS_AXI_BUSY 1
`define PSRAM_STATUS_INDIRECT_BUSY 2
`define PSRAM_STATUS_PHY_BUSY 3
`define PSRAM_STATUS_QUIESCED 4
`define PSRAM_STATUS_READY 5
`define PSRAM_CLK_HALF_PERIOD_LSB 0
`define PSRAM_CLK_HALF_PERIOD_WIDTH 16
`define PSRAM_CLK_ABOVE_84MHZ 16
`define PSRAM_INDIRECT_COMMAND_LSB 0
`define PSRAM_INDIRECT_COMMAND_WIDTH 4
`define PSRAM_INDIRECT_CHIP_LSB 8
`define PSRAM_INDIRECT_LENGTH_LSB 16
`define PSRAM_INDIRECT_START 31
`define PSRAM_INTR_INIT_DONE 0
`define PSRAM_INTR_INDIRECT_DONE 1
`define PSRAM_INTR_ERROR 2
`define PSRAM_INTR_TIMEOUT 3
`define PSRAM_PERF_ENABLE 0
`define PSRAM_PERF_FREEZE 1
`define PSRAM_PERF_CLEAR 2
`define PSRAM_IP_VERSION_VALUE 32'h0001_0000
`define PSRAM_CAPABILITY_VALUE 32'h2010_2043
// verilog_format: on
`endif
+372
View File
@@ -0,0 +1,372 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
module psram_phy (
// verilog_format: off -- preserve reviewed port alignment
input logic clk_i,
input logic rst_n_i,
input logic abort_i,
input logic req_valid_i,
output logic req_ready_o,
input psram_pkg::psram_cmd_e req_command_i,
input logic [1:0] req_chip_i,
input logic req_qpi_i,
input logic [22:0] req_addr_i,
input logic [5:0] req_length_i,
input logic [63:0] req_wdata_i,
output logic busy_o,
output logic done_o,
output logic error_o,
output logic [255:0] rdata_o,
input logic [15:0] cfg_half_period_i,
input logic [15:0] cfg_cs_setup_i,
input logic [15:0] cfg_cs_high_i,
input logic [15:0] cfg_cs_hold_i,
input logic [31:0] cfg_cs_max_low_i,
input logic [31:0] cfg_access_timeout_i,
output logic psram_sclk_o,
output logic [3:0] psram_nss_o,
output logic [3:0] psram_io_oe_o,
input logic [3:0] psram_io_di_i,
output logic [3:0] psram_io_do_o
// verilog_format: on
);
import psram_pkg::*;
typedef enum logic [3:0] {
PhyIdle = 4'd0,
PhySetup = 4'd1,
PhyCommand = 4'd2,
PhyAddress = 4'd3,
PhyDummy = 4'd4,
PhyRead = 4'd5,
PhyWrite = 4'd6,
PhyHold = 4'd7,
PhyHigh = 4'd8
} psram_phy_state_e;
psram_phy_state_e s_state_q;
psram_cmd_e s_cmd_q;
logic s_qpi_q;
logic [ 22:0] s_addr_q;
logic [ 5:0] s_len_q;
logic [ 63:0] s_wdata_q;
logic [255:0] s_rdata_q;
logic [ 62:0] s_tx_shift_q;
logic [ 7:0] s_read_byte_q;
logic [ 6:0] s_units_left_q;
logic [ 4:0] s_byte_index_q;
logic [ 2:0] s_bit_index_q;
logic s_nibble_phase_q;
logic s_stage_done_q;
logic s_tx_quad_q;
logic s_rx_quad_q;
logic [ 15:0] s_half_count_q;
logic [ 15:0] s_delay_count_q;
logic [ 31:0] s_cs_low_count_q;
logic [ 31:0] s_access_count_q;
logic s_sclk_q;
logic [ 3:0] s_nss_q;
logic [ 3:0] s_io_oe_q;
logic [ 3:0] s_io_do_q;
function automatic logic command_address_quad(input psram_cmd_e command, input logic qpi);
return qpi || (command == PsramCmdQuadRead) || (command == PsramCmdQuadWrite);
endfunction
function automatic logic command_data_quad(input psram_cmd_e command, input logic qpi);
return qpi || (command == PsramCmdQuadRead) || (command == PsramCmdQuadWrite);
endfunction
function automatic logic [3:0] command_dummy_cycles(input psram_cmd_e command);
if (command == PsramCmdFastRead) return 4'd8;
if (command == PsramCmdQuadRead) return 4'd6;
return 4'd0;
endfunction
function automatic logic [62:0] pack_write_remainder(input logic [63:0] data,
input logic [5:0] length, input logic quad);
logic [62:0] packed_data;
begin
packed_data = '0;
packed_data[62:56] = data[6:0];
for (int byte_index = 1; byte_index < 8; byte_index++) begin
if (byte_index < length) begin
packed_data[63-(byte_index*8)-:8] = data[(byte_index*8)+:8];
end
end
if (quad) return {3'd0, packed_data[59:0]};
return packed_data;
end
endfunction
assign req_ready_o = s_state_q == PhyIdle;
assign busy_o = (s_state_q != PhyIdle) && (s_state_q != PhyHigh);
assign rdata_o = s_rdata_q;
assign psram_sclk_o = s_sclk_q;
assign psram_nss_o = s_nss_q;
assign psram_io_oe_o = s_io_oe_q;
assign psram_io_do_o = s_io_do_q;
logic [7:0] s_opcode;
assign s_opcode = psram_opcode(s_cmd_q);
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
s_state_q <= PhyIdle;
s_cmd_q <= PsramCmdRead;
s_qpi_q <= 1'b0;
s_addr_q <= '0;
s_len_q <= '0;
s_wdata_q <= '0;
s_rdata_q <= '0;
s_tx_shift_q <= '0;
s_read_byte_q <= '0;
s_units_left_q <= '0;
s_byte_index_q <= '0;
s_bit_index_q <= '0;
s_nibble_phase_q <= 1'b0;
s_stage_done_q <= 1'b0;
s_tx_quad_q <= 1'b0;
s_rx_quad_q <= 1'b0;
s_half_count_q <= '0;
s_delay_count_q <= '0;
s_cs_low_count_q <= '0;
s_access_count_q <= '0;
s_sclk_q <= 1'b0;
s_nss_q <= 4'hF;
s_io_oe_q <= 4'hF;
s_io_do_q <= 4'h0;
done_o <= 1'b0;
error_o <= 1'b0;
end else begin
done_o <= 1'b0;
error_o <= 1'b0;
if ((s_state_q != PhyIdle) && (s_state_q != PhyHigh)) begin
s_cs_low_count_q <= s_cs_low_count_q + 1'b1;
s_access_count_q <= s_access_count_q + 1'b1;
end
if (abort_i && (s_state_q != PhyIdle)) begin
s_state_q <= PhyHigh;
s_sclk_q <= 1'b0;
s_nss_q <= 4'hF;
s_io_oe_q <= 4'hF;
s_io_do_q <= 4'h0;
s_delay_count_q <= '0;
done_o <= 1'b1;
error_o <= 1'b1;
end else if (((cfg_cs_max_low_i != 32'd0) &&
(s_cs_low_count_q >= cfg_cs_max_low_i)) ||
((cfg_access_timeout_i != 32'd0) &&
(s_access_count_q >= cfg_access_timeout_i))) begin
s_state_q <= PhyHigh;
s_sclk_q <= 1'b0;
s_nss_q <= 4'hF;
s_io_oe_q <= 4'hF;
s_io_do_q <= 4'h0;
s_delay_count_q <= '0;
done_o <= 1'b1;
error_o <= 1'b1;
end else begin
unique case (s_state_q)
PhyIdle: begin
s_sclk_q <= 1'b0;
s_nss_q <= 4'hF;
s_io_oe_q <= 4'hF;
s_io_do_q <= 4'h0;
s_half_count_q <= '0;
s_delay_count_q <= '0;
s_cs_low_count_q <= '0;
s_access_count_q <= '0;
if (req_valid_i && req_ready_o) begin
if ((req_length_i == 6'd0) || (req_length_i > 6'd32) || (psram_command_is_write(
req_command_i
) && (req_length_i > 6'd8)) || (cfg_half_period_i == 16'd0)) begin
done_o <= 1'b1;
error_o <= 1'b1;
end else begin
s_cmd_q <= req_command_i;
s_qpi_q <= req_qpi_i;
s_addr_q <= req_addr_i;
s_len_q <= req_length_i;
s_wdata_q <= req_wdata_i;
s_rdata_q <= '0;
s_byte_index_q <= '0;
s_bit_index_q <= '0;
s_nibble_phase_q <= 1'b0;
s_stage_done_q <= 1'b0;
s_nss_q <= ~(4'b0001 << req_chip_i);
s_delay_count_q <= '0;
s_state_q <= PhySetup;
end
end
end
PhySetup: begin
if (s_delay_count_q >= cfg_cs_setup_i) begin
s_state_q <= PhyCommand;
s_tx_quad_q <= s_qpi_q;
s_tx_shift_q <= s_qpi_q ? {3'd0, s_opcode[3:0], 56'd0} : {s_opcode[6:0], 56'd0};
s_units_left_q <= s_qpi_q ? 7'd2 : 7'd8;
s_io_oe_q <= s_qpi_q ? 4'hF : 4'h1;
s_io_do_q <= s_qpi_q ? s_opcode[7:4] : {3'd0, s_opcode[7]};
s_half_count_q <= '0;
end else begin
s_delay_count_q <= s_delay_count_q + 1'b1;
end
end
PhyCommand, PhyAddress, PhyDummy, PhyRead, PhyWrite: begin
if (s_half_count_q == (cfg_half_period_i - 1'b1)) begin
s_half_count_q <= '0;
if (!s_sclk_q) begin
s_sclk_q <= 1'b1;
if (s_state_q == PhyRead) begin
if (s_rx_quad_q) begin
if (!s_nibble_phase_q) begin
s_read_byte_q[7:4] <= psram_io_di_i;
s_nibble_phase_q <= 1'b1;
end else begin
s_rdata_q[(s_byte_index_q*8)+:8] <= {s_read_byte_q[7:4], psram_io_di_i};
s_nibble_phase_q <= 1'b0;
s_byte_index_q <= s_byte_index_q + 1'b1;
end
end else begin
s_read_byte_q <= {s_read_byte_q[6:0], psram_io_di_i[1]};
if (s_bit_index_q == 3'd7) begin
s_rdata_q[(s_byte_index_q*8)+:8] <= {s_read_byte_q[6:0], psram_io_di_i[1]};
s_bit_index_q <= '0;
s_byte_index_q <= s_byte_index_q + 1'b1;
end else begin
s_bit_index_q <= s_bit_index_q + 1'b1;
end
end
end
if (s_units_left_q == 7'd1) begin
s_stage_done_q <= 1'b1;
end
s_units_left_q <= s_units_left_q - 1'b1;
end else begin
s_sclk_q <= 1'b0;
if (s_stage_done_q) begin
s_stage_done_q <= 1'b0;
unique case (s_state_q)
PhyCommand: begin
if (psram_command_has_address(s_cmd_q)) begin
s_state_q <= PhyAddress;
s_tx_quad_q <= command_address_quad(s_cmd_q, s_qpi_q);
s_tx_shift_q <= command_address_quad(
s_cmd_q, s_qpi_q
) ? {3'd0, s_addr_q[19:0], 40'd0} : {s_addr_q, 40'd0};
s_units_left_q <= command_address_quad(s_cmd_q, s_qpi_q) ? 7'd6 : 7'd24;
s_io_oe_q <= command_address_quad(s_cmd_q, s_qpi_q) ? 4'hF : 4'h1;
s_io_do_q <= command_address_quad(
s_cmd_q, s_qpi_q
) ? {1'b0, s_addr_q[22:20]} : {3'd0, 1'b0};
end else begin
s_state_q <= PhyHold;
s_delay_count_q <= '0;
end
end
PhyAddress: begin
if (command_dummy_cycles(s_cmd_q) != 4'd0) begin
s_state_q <= PhyDummy;
s_units_left_q <= {3'd0, command_dummy_cycles(s_cmd_q)};
s_io_oe_q <= 4'h0;
s_io_do_q <= 4'h0;
end else if (psram_command_is_read(s_cmd_q)) begin
s_state_q <= PhyRead;
s_rx_quad_q <= command_data_quad(s_cmd_q, s_qpi_q);
s_units_left_q <= command_data_quad(
s_cmd_q, s_qpi_q
) ? 7'(s_len_q << 1) : 7'(s_len_q << 3);
s_io_oe_q <= 4'h0;
s_byte_index_q <= '0;
s_bit_index_q <= '0;
s_nibble_phase_q <= 1'b0;
end else if (psram_command_is_write(s_cmd_q)) begin
s_state_q <= PhyWrite;
s_tx_quad_q <= command_data_quad(s_cmd_q, s_qpi_q);
s_tx_shift_q <= pack_write_remainder(
s_wdata_q, s_len_q, command_data_quad(s_cmd_q, s_qpi_q)
);
s_units_left_q <= command_data_quad(
s_cmd_q, s_qpi_q
) ? 7'(s_len_q << 1) : 7'(s_len_q << 3);
s_io_oe_q <= command_data_quad(s_cmd_q, s_qpi_q) ? 4'hF : 4'h1;
s_io_do_q <= command_data_quad(
s_cmd_q, s_qpi_q
) ? s_wdata_q[7:4] : {3'd0, s_wdata_q[7]};
end else begin
s_state_q <= PhyHold;
s_delay_count_q <= '0;
end
end
PhyDummy: begin
s_state_q <= PhyRead;
s_rx_quad_q <= command_data_quad(s_cmd_q, s_qpi_q);
s_units_left_q <= command_data_quad(
s_cmd_q, s_qpi_q
) ? 7'(s_len_q << 1) : 7'(s_len_q << 3);
s_io_oe_q <= 4'h0;
s_byte_index_q <= '0;
s_bit_index_q <= '0;
s_nibble_phase_q <= 1'b0;
end
default: begin
s_state_q <= PhyHold;
s_delay_count_q <= '0;
end
endcase
end else if ((s_state_q == PhyCommand) ||
(s_state_q == PhyAddress) ||
(s_state_q == PhyWrite)) begin
if (s_tx_quad_q) begin
s_tx_shift_q <= {s_tx_shift_q[58:0], 4'd0};
s_io_do_q <= s_tx_shift_q[59:56];
end else begin
s_tx_shift_q <= {s_tx_shift_q[61:0], 1'b0};
s_io_do_q <= {3'd0, s_tx_shift_q[62]};
end
end
end
end else begin
s_half_count_q <= s_half_count_q + 1'b1;
end
end
PhyHold: begin
s_sclk_q <= 1'b0;
if (s_delay_count_q >= cfg_cs_hold_i) begin
s_nss_q <= 4'hF;
s_io_oe_q <= 4'hF;
s_io_do_q <= 4'h0;
s_delay_count_q <= '0;
s_state_q <= PhyHigh;
done_o <= 1'b1;
end else begin
s_delay_count_q <= s_delay_count_q + 1'b1;
end
end
PhyHigh: begin
s_sclk_q <= 1'b0;
s_nss_q <= 4'hF;
if (s_delay_count_q >= cfg_cs_high_i) begin
s_delay_count_q <= '0;
s_state_q <= PhyIdle;
end else begin
s_delay_count_q <= s_delay_count_q + 1'b1;
end
end
default: s_state_q <= PhyIdle;
endcase
end
end
end
endmodule
+78
View File
@@ -0,0 +1,78 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
package psram_pkg;
localparam logic [7:0] PSRAM_OPCODE_READ = 8'h03;
localparam logic [7:0] PSRAM_OPCODE_FAST_READ = 8'h0B;
localparam logic [7:0] PSRAM_OPCODE_QUAD_READ = 8'hEB;
localparam logic [7:0] PSRAM_OPCODE_WRITE = 8'h02;
localparam logic [7:0] PSRAM_OPCODE_QUAD_WRITE = 8'h38;
localparam logic [7:0] PSRAM_OPCODE_ENTER_QPI = 8'h35;
localparam logic [7:0] PSRAM_OPCODE_EXIT_QPI = 8'hF5;
localparam logic [7:0] PSRAM_OPCODE_RESET_ENABLE = 8'h66;
localparam logic [7:0] PSRAM_OPCODE_RESET = 8'h99;
localparam logic [7:0] PSRAM_OPCODE_TOGGLE_WRAP = 8'hC0;
localparam logic [7:0] PSRAM_OPCODE_READ_ID = 8'h9F;
localparam logic [7:0] PSRAM_KGD_PASS = 8'h5D;
localparam logic [7:0] PSRAM_DENSITY_64MBIT = 8'h26;
typedef enum logic [3:0] {
PsramCmdRead = 4'd0,
PsramCmdFastRead = 4'd1,
PsramCmdQuadRead = 4'd2,
PsramCmdWrite = 4'd3,
PsramCmdQuadWrite = 4'd4,
PsramCmdEnterQpi = 4'd5,
PsramCmdExitQpi = 4'd6,
PsramCmdResetEnable = 4'd7,
PsramCmdReset = 4'd8,
PsramCmdToggleWrap = 4'd9,
PsramCmdReadId = 4'd10
} psram_cmd_e;
typedef enum logic [3:0] {
PsramErrorNone = 4'd0,
PsramErrorIllegal = 4'd1,
PsramErrorUnavailable = 4'd2,
PsramErrorTimeout = 4'd3,
PsramErrorId = 4'd4,
PsramErrorAborted = 4'd5,
PsramErrorPhy = 4'd6,
PsramErrorProtocol = 4'd7
} psram_error_e;
function automatic logic [7:0] psram_opcode(input psram_cmd_e command);
unique case (command)
PsramCmdRead: return PSRAM_OPCODE_READ;
PsramCmdFastRead: return PSRAM_OPCODE_FAST_READ;
PsramCmdQuadRead: return PSRAM_OPCODE_QUAD_READ;
PsramCmdWrite: return PSRAM_OPCODE_WRITE;
PsramCmdQuadWrite: return PSRAM_OPCODE_QUAD_WRITE;
PsramCmdEnterQpi: return PSRAM_OPCODE_ENTER_QPI;
PsramCmdExitQpi: return PSRAM_OPCODE_EXIT_QPI;
PsramCmdResetEnable: return PSRAM_OPCODE_RESET_ENABLE;
PsramCmdReset: return PSRAM_OPCODE_RESET;
PsramCmdToggleWrap: return PSRAM_OPCODE_TOGGLE_WRAP;
PsramCmdReadId: return PSRAM_OPCODE_READ_ID;
default: return 8'h00;
endcase
endfunction
function automatic logic psram_command_has_address(input psram_cmd_e command);
return (command == PsramCmdRead) || (command == PsramCmdFastRead) ||
(command == PsramCmdQuadRead) || (command == PsramCmdWrite) ||
(command == PsramCmdQuadWrite) || (command == PsramCmdReadId);
endfunction
function automatic logic psram_command_is_read(input psram_cmd_e command);
return (command == PsramCmdRead) || (command == PsramCmdFastRead) ||
(command == PsramCmdQuadRead) || (command == PsramCmdReadId);
endfunction
function automatic logic psram_command_is_write(input psram_cmd_e command);
return (command == PsramCmdWrite) || (command == PsramCmdQuadWrite);
endfunction
endpackage
+506
View File
@@ -0,0 +1,506 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
`include "psram_define.svh"
module psram_reg (
// verilog_format: off -- preserve reviewed port alignment
input logic clk_i,
input logic rst_n_i,
ribp_if.slave ribp,
input logic init_busy_i,
input logic axi_busy_i,
input logic indirect_busy_i,
input logic phy_busy_i,
input logic quiesced_i,
input logic global_ready_i,
input logic [3:0] chip_present_i,
input logic [3:0] chip_ready_i,
input logic [3:0] chip_qpi_i,
input logic [3:0] chip_wrap32_i,
input logic [3:0] chip_error_i,
input logic [47:0] chip0_id_i,
input logic [47:0] chip1_id_i,
input logic [47:0] chip2_id_i,
input logic [47:0] chip3_id_i,
input psram_pkg::psram_error_e last_error_i,
input logic [1:0] last_error_chip_i,
input logic [31:0] last_error_addr_i,
input logic [63:0] indirect_rdata_i,
input logic init_done_event_i,
input logic indirect_done_event_i,
input logic error_event_i,
input logic timeout_event_i,
input logic [2:0] perf_read_byte_event_i,
input logic [2:0] perf_write_byte_event_i,
input logic perf_command_event_i,
input logic perf_split_event_i,
input logic perf_stall_event_i,
input logic perf_error_event_i,
output logic controller_enable_o,
output logic memory_enable_o,
output logic auto_init_o,
output logic wrap32_o,
output logic [3:0] chip_enable_o,
output logic [15:0] half_period_o,
output logic above_84mhz_o,
output logic [31:0] powerup_cycles_o,
output logic [15:0] cs_setup_cycles_o,
output logic [15:0] cs_high_cycles_o,
output logic [15:0] cs_hold_cycles_o,
output logic [31:0] cs_max_low_cycles_o,
output logic [31:0] access_timeout_cycles_o,
output logic init_start_o,
output logic recover_start_o,
output logic [1:0] recover_chip_o,
output logic abort_o,
output logic [3:0] chip_error_clear_o,
output logic indirect_start_o,
output psram_pkg::psram_cmd_e indirect_command_o,
output logic [1:0] indirect_chip_o,
output logic [22:0] indirect_addr_o,
output logic [3:0] indirect_length_o,
output logic [63:0] indirect_wdata_o,
output logic irq_o
// verilog_format: on
);
import psram_pkg::*;
localparam logic [31:0] CTRL_WRITABLE_MASK = 32'h0000_000F;
localparam logic [31:0] CLK_WRITABLE_MASK = 32'h0001_FFFF;
localparam logic [31:0] INDIRECT_WRITABLE_MASK = 32'h8007_030F;
localparam logic [31:0] PERF_WRITABLE_MASK = 32'h0000_0003;
logic s_req;
logic s_write;
logic [11:0] s_offset;
logic s_aligned;
logic s_access_err;
logic [31:0] s_read_data;
logic s_ready_q;
logic s_resp_err_q;
logic [31:0] s_rdata_q;
logic [31:0] s_ctrl_q;
logic [ 3:0] s_chip_en_q;
logic [31:0] s_clk_config_q;
logic [31:0] s_powerup_cycles_q;
logic [15:0] s_cs_setup_q;
logic [15:0] s_cs_high_q;
logic [15:0] s_cs_hold_q;
logic [31:0] s_cs_max_low_q;
logic [31:0] s_access_timeout_q;
logic [31:0] s_indirect_ctrl_q;
logic [22:0] s_indirect_addr_q;
logic [63:0] s_indirect_wdata_q;
logic [ 3:0] s_intr_state_q;
logic [ 3:0] s_intr_en_q;
logic [ 1:0] s_perf_ctrl_q;
logic [31:0] s_perf_read_bytes_q;
logic [31:0] s_perf_write_bytes_q;
logic [31:0] s_perf_commands_q;
logic [31:0] s_perf_splits_q;
logic [31:0] s_perf_stall_cycles_q;
logic [31:0] s_perf_err_count_q;
logic [ 3:0] s_intr_clear;
logic [ 3:0] s_intr_set;
logic [ 3:0] s_intr_next;
logic [31:0] s_ctrl_write_value;
logic [ 3:0] s_chip_en_write_value;
logic [31:0] s_clk_write_value;
logic [15:0] s_cs_setup_write_value;
logic [15:0] s_cs_high_write_value;
logic [15:0] s_cs_hold_write_value;
logic [31:0] s_indirect_write_value;
logic [22:0] s_indirect_addr_write_value;
logic [ 3:0] s_intr_en_write_value;
logic [ 1:0] s_perf_write_value;
logic s_busy;
logic s_timing_valid;
function automatic logic [31:0] merge_wstrb(input logic [31:0] current, input logic [31:0] value,
input logic [3:0] strobe);
logic [31:0] merged;
begin
merged = current;
for (int byte_index = 0; byte_index < 4; byte_index++) begin
if (strobe[byte_index]) begin
merged[(byte_index*8)+:8] = value[(byte_index*8)+:8];
end
end
return merged;
end
endfunction
function automatic logic [31:0] saturating_increment(input logic [31:0] value);
return (&value) ? value : value + 1'b1;
endfunction
function automatic logic [31:0] saturating_add(input logic [31:0] value,
input logic [2:0] increment);
logic [32:0] result;
begin
result = {1'b0, value} + {30'd0, increment};
return result[32] ? 32'hFFFF_FFFF : result[31:0];
end
endfunction
assign s_req = ribp.valid && !s_ready_q;
assign s_write = |ribp.wstrb;
assign s_offset = ribp.addr[11:0];
assign s_aligned = ribp.addr[1:0] == 2'b00;
assign s_busy = init_busy_i || axi_busy_i || indirect_busy_i || phy_busy_i;
assign s_timing_valid = (s_clk_config_q[15:0] != 16'd0) &&
(s_cs_max_low_q != 32'd0) &&
(s_access_timeout_q != 32'd0);
assign s_ctrl_write_value = merge_wstrb(s_ctrl_q, ribp.wdata, ribp.wstrb) & CTRL_WRITABLE_MASK;
assign s_chip_en_write_value = 4'(merge_wstrb({28'd0, s_chip_en_q}, ribp.wdata, ribp.wstrb));
assign s_clk_write_value = merge_wstrb(
s_clk_config_q, ribp.wdata, ribp.wstrb
) & CLK_WRITABLE_MASK;
assign s_cs_setup_write_value = 16'(merge_wstrb({16'd0, s_cs_setup_q}, ribp.wdata, ribp.wstrb));
assign s_cs_high_write_value = 16'(merge_wstrb({16'd0, s_cs_high_q}, ribp.wdata, ribp.wstrb));
assign s_cs_hold_write_value = 16'(merge_wstrb({16'd0, s_cs_hold_q}, ribp.wdata, ribp.wstrb));
assign s_indirect_write_value = merge_wstrb(
s_indirect_ctrl_q, ribp.wdata, ribp.wstrb
) & INDIRECT_WRITABLE_MASK;
assign s_indirect_addr_write_value = 23'(merge_wstrb(
{9'd0, s_indirect_addr_q}, ribp.wdata, ribp.wstrb
));
assign s_intr_en_write_value = 4'(merge_wstrb({28'd0, s_intr_en_q}, ribp.wdata, ribp.wstrb));
assign s_perf_write_value = 2'(merge_wstrb(
{30'd0, s_perf_ctrl_q}, ribp.wdata, ribp.wstrb
) & PERF_WRITABLE_MASK);
assign ribp.ready = s_ready_q;
assign ribp.resp_err = s_resp_err_q;
assign ribp.rdata = s_rdata_q;
always_comb begin
s_access_err = !s_aligned;
s_read_data = '0;
if (s_aligned) begin
unique case (s_offset)
`RIBP_PSRAM_CTRL: begin
s_read_data = s_ctrl_q;
s_access_err = s_write && s_busy && (s_ctrl_write_value != s_ctrl_q);
end
`RIBP_PSRAM_COMMAND: begin
s_access_err = !s_write || !ribp.wstrb[0] ||
((ribp.wdata[2:0] == 3'd0) ||
(|(ribp.wdata[2:0] & (ribp.wdata[2:0] - 1'b1)))) ||
(ribp.wdata[`PSRAM_COMMAND_RECOVER] &&
!s_chip_en_q[ribp.wdata[9:8]]);
end
`RIBP_PSRAM_STATUS: begin
s_read_data = {
26'd0, global_ready_i, quiesced_i, phy_busy_i, indirect_busy_i, axi_busy_i, init_busy_i
};
s_access_err = s_write;
end
`RIBP_PSRAM_CHIP_ENABLE: begin
s_read_data = {28'd0, s_chip_en_q};
s_access_err = s_write && s_busy;
end
`RIBP_PSRAM_CHIP_PRESENT: begin
s_read_data = {28'd0, chip_present_i};
s_access_err = s_write;
end
`RIBP_PSRAM_CHIP_READY: begin
s_read_data = {28'd0, chip_ready_i};
s_access_err = s_write;
end
`RIBP_PSRAM_CHIP_MODE: begin
s_read_data = {24'd0, chip_wrap32_i, chip_qpi_i};
s_access_err = s_write;
end
`RIBP_PSRAM_CHIP_ERROR: s_read_data = {28'd0, chip_error_i};
`RIBP_PSRAM_CLK_CONFIG: begin
s_read_data = s_clk_config_q;
s_access_err = s_write && s_busy;
if (s_write && (s_clk_write_value[15:0] == 16'd0)) begin
s_access_err = 1'b1;
end
end
`RIBP_PSRAM_POWERUP_CYCLES: begin
s_read_data = s_powerup_cycles_q;
s_access_err = s_write && s_busy;
end
`RIBP_PSRAM_CS_SETUP_CYCLES: begin
s_read_data = {16'd0, s_cs_setup_q};
s_access_err = s_write && s_busy;
end
`RIBP_PSRAM_CS_HIGH_CYCLES: begin
s_read_data = {16'd0, s_cs_high_q};
s_access_err = s_write && s_busy;
end
`RIBP_PSRAM_CS_HOLD_CYCLES: begin
s_read_data = {16'd0, s_cs_hold_q};
s_access_err = s_write && s_busy;
end
`RIBP_PSRAM_CS_MAX_LOW_CYCLES: begin
s_read_data = s_cs_max_low_q;
s_access_err = s_write && (s_busy || (ribp.wdata == 32'd0));
end
`RIBP_PSRAM_ACCESS_TIMEOUT_CYCLES: begin
s_read_data = s_access_timeout_q;
s_access_err = s_write && (s_busy || (ribp.wdata == 32'd0));
end
`RIBP_PSRAM_TIMING_STATUS: begin
s_read_data = {14'd0, s_clk_config_q[16], s_timing_valid, s_clk_config_q[15:0]};
s_access_err = s_write;
end
`RIBP_PSRAM_INDIRECT_CTRL: begin
s_read_data = s_indirect_ctrl_q & 32'h7FFF_FFFF;
if (s_write && s_indirect_write_value[`PSRAM_INDIRECT_START]) begin
s_access_err =
indirect_busy_i ||
(s_indirect_write_value[3:0] > PsramCmdReadId) ||
!s_chip_en_q[s_indirect_write_value[9:8]];
end
end
`RIBP_PSRAM_INDIRECT_ADDR: s_read_data = {9'd0, s_indirect_addr_q};
`RIBP_PSRAM_INDIRECT_WDATA_LO: s_read_data = s_indirect_wdata_q[31:0];
`RIBP_PSRAM_INDIRECT_WDATA_HI: s_read_data = s_indirect_wdata_q[63:32];
`RIBP_PSRAM_INDIRECT_RDATA_LO: begin
s_read_data = indirect_rdata_i[31:0];
s_access_err = s_write;
end
`RIBP_PSRAM_INDIRECT_RDATA_HI: begin
s_read_data = indirect_rdata_i[63:32];
s_access_err = s_write;
end
`RIBP_PSRAM_LAST_ERROR: begin
s_read_data = {24'd0, last_error_chip_i, 2'd0, last_error_i};
s_access_err = s_write;
end
`RIBP_PSRAM_LAST_ERROR_ADDR: begin
s_read_data = last_error_addr_i;
s_access_err = s_write;
end
`RIBP_PSRAM_CHIP0_ID_LO: begin
s_read_data = chip0_id_i[31:0];
s_access_err = s_write;
end
`RIBP_PSRAM_CHIP0_ID_HI: begin
s_read_data = {16'd0, chip0_id_i[47:32]};
s_access_err = s_write;
end
`RIBP_PSRAM_CHIP1_ID_LO: begin
s_read_data = chip1_id_i[31:0];
s_access_err = s_write;
end
`RIBP_PSRAM_CHIP1_ID_HI: begin
s_read_data = {16'd0, chip1_id_i[47:32]};
s_access_err = s_write;
end
`RIBP_PSRAM_CHIP2_ID_LO: begin
s_read_data = chip2_id_i[31:0];
s_access_err = s_write;
end
`RIBP_PSRAM_CHIP2_ID_HI: begin
s_read_data = {16'd0, chip2_id_i[47:32]};
s_access_err = s_write;
end
`RIBP_PSRAM_CHIP3_ID_LO: begin
s_read_data = chip3_id_i[31:0];
s_access_err = s_write;
end
`RIBP_PSRAM_CHIP3_ID_HI: begin
s_read_data = {16'd0, chip3_id_i[47:32]};
s_access_err = s_write;
end
`RIBP_PSRAM_INTR_STATE: s_read_data = {28'd0, s_intr_state_q};
`RIBP_PSRAM_INTR_ENABLE: s_read_data = {28'd0, s_intr_en_q};
`RIBP_PSRAM_INTR_STATUS: begin
s_read_data = {28'd0, s_intr_state_q & s_intr_en_q};
s_access_err = s_write;
end
`RIBP_PSRAM_INTR_TEST: s_access_err = !s_write || !ribp.wstrb[0];
`RIBP_PSRAM_PERF_CTRL: s_read_data = {30'd0, s_perf_ctrl_q};
`RIBP_PSRAM_PERF_READ_BYTES: begin
s_read_data = s_perf_read_bytes_q;
s_access_err = s_write;
end
`RIBP_PSRAM_PERF_WRITE_BYTES: begin
s_read_data = s_perf_write_bytes_q;
s_access_err = s_write;
end
`RIBP_PSRAM_PERF_COMMANDS: begin
s_read_data = s_perf_commands_q;
s_access_err = s_write;
end
`RIBP_PSRAM_PERF_SPLITS: begin
s_read_data = s_perf_splits_q;
s_access_err = s_write;
end
`RIBP_PSRAM_PERF_STALL_CYCLES: begin
s_read_data = s_perf_stall_cycles_q;
s_access_err = s_write;
end
`RIBP_PSRAM_PERF_ERROR_COUNT: begin
s_read_data = s_perf_err_count_q;
s_access_err = s_write;
end
`RIBP_PSRAM_IP_VERSION: begin
s_read_data = `PSRAM_IP_VERSION_VALUE;
s_access_err = s_write;
end
`RIBP_PSRAM_CAPABILITY: begin
s_read_data = `PSRAM_CAPABILITY_VALUE;
s_access_err = s_write;
end
default: s_access_err = 1'b1;
endcase
end
end
always_comb begin
s_intr_clear = '0;
s_intr_set = {timeout_event_i, error_event_i, indirect_done_event_i, init_done_event_i};
if (s_req && s_write && !s_access_err &&
(s_offset == `RIBP_PSRAM_INTR_STATE) && ribp.wstrb[0]) begin
s_intr_clear = ribp.wdata[3:0];
end
if (s_req && s_write && !s_access_err &&
(s_offset == `RIBP_PSRAM_INTR_TEST) && ribp.wstrb[0]) begin
s_intr_set = s_intr_set | ribp.wdata[3:0];
end
s_intr_next = (s_intr_state_q & ~s_intr_clear) | s_intr_set;
end
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
s_ready_q <= 1'b0;
s_resp_err_q <= 1'b0;
s_rdata_q <= '0;
s_ctrl_q <= 32'h0000_0003;
s_chip_en_q <= 4'hF;
s_clk_config_q <= 32'h0000_0001;
s_powerup_cycles_q <= 32'd10_800;
s_cs_setup_q <= 16'd1;
s_cs_high_q <= 16'd4;
s_cs_hold_q <= 16'd3;
s_cs_max_low_q <= 32'd576;
s_access_timeout_q <= 32'd100_000;
s_indirect_ctrl_q <= 32'h0000_0000;
s_indirect_addr_q <= '0;
s_indirect_wdata_q <= '0;
s_intr_state_q <= '0;
s_intr_en_q <= '0;
s_perf_ctrl_q <= '0;
s_perf_read_bytes_q <= '0;
s_perf_write_bytes_q <= '0;
s_perf_commands_q <= '0;
s_perf_splits_q <= '0;
s_perf_stall_cycles_q <= '0;
s_perf_err_count_q <= '0;
init_start_o <= 1'b0;
recover_start_o <= 1'b0;
recover_chip_o <= '0;
abort_o <= 1'b0;
chip_error_clear_o <= '0;
indirect_start_o <= 1'b0;
end else begin
s_ready_q <= s_req;
init_start_o <= 1'b0;
recover_start_o <= 1'b0;
abort_o <= 1'b0;
chip_error_clear_o <= '0;
indirect_start_o <= 1'b0;
if (s_req) begin
s_resp_err_q <= s_access_err;
s_rdata_q <= s_read_data;
end
if (s_req && s_write && !s_access_err) begin
unique case (s_offset)
`RIBP_PSRAM_CTRL: s_ctrl_q <= s_ctrl_write_value;
`RIBP_PSRAM_COMMAND: begin
init_start_o <= ribp.wdata[`PSRAM_COMMAND_INIT];
recover_start_o <= ribp.wdata[`PSRAM_COMMAND_RECOVER];
abort_o <= ribp.wdata[`PSRAM_COMMAND_ABORT];
recover_chip_o <= ribp.wdata[9:8];
end
`RIBP_PSRAM_CHIP_ENABLE: s_chip_en_q <= s_chip_en_write_value;
`RIBP_PSRAM_CHIP_ERROR: begin
if (ribp.wstrb[0]) chip_error_clear_o <= ribp.wdata[3:0];
end
`RIBP_PSRAM_CLK_CONFIG: s_clk_config_q <= s_clk_write_value;
`RIBP_PSRAM_POWERUP_CYCLES:
s_powerup_cycles_q <= merge_wstrb(s_powerup_cycles_q, ribp.wdata, ribp.wstrb);
`RIBP_PSRAM_CS_SETUP_CYCLES: s_cs_setup_q <= s_cs_setup_write_value;
`RIBP_PSRAM_CS_HIGH_CYCLES: s_cs_high_q <= s_cs_high_write_value;
`RIBP_PSRAM_CS_HOLD_CYCLES: s_cs_hold_q <= s_cs_hold_write_value;
`RIBP_PSRAM_CS_MAX_LOW_CYCLES:
s_cs_max_low_q <= merge_wstrb(s_cs_max_low_q, ribp.wdata, ribp.wstrb);
`RIBP_PSRAM_ACCESS_TIMEOUT_CYCLES:
s_access_timeout_q <= merge_wstrb(s_access_timeout_q, ribp.wdata, ribp.wstrb);
`RIBP_PSRAM_INDIRECT_CTRL: begin
s_indirect_ctrl_q <= s_indirect_write_value & 32'h7FFF_FFFF;
indirect_start_o <= s_indirect_write_value[`PSRAM_INDIRECT_START];
end
`RIBP_PSRAM_INDIRECT_ADDR: s_indirect_addr_q <= s_indirect_addr_write_value;
`RIBP_PSRAM_INDIRECT_WDATA_LO:
s_indirect_wdata_q[31:0] <= merge_wstrb(s_indirect_wdata_q[31:0], ribp.wdata, ribp.wstrb);
`RIBP_PSRAM_INDIRECT_WDATA_HI:
s_indirect_wdata_q[63:32] <= merge_wstrb(
s_indirect_wdata_q[63:32], ribp.wdata, ribp.wstrb
);
`RIBP_PSRAM_INTR_ENABLE: s_intr_en_q <= s_intr_en_write_value;
`RIBP_PSRAM_PERF_CTRL: s_perf_ctrl_q <= s_perf_write_value[1:0];
default: begin
end
endcase
end
s_intr_state_q <= s_intr_next;
if (s_req && s_write && !s_access_err &&
(s_offset == `RIBP_PSRAM_PERF_CTRL) &&
ribp.wdata[`PSRAM_PERF_CLEAR]) begin
s_perf_read_bytes_q <= '0;
s_perf_write_bytes_q <= '0;
s_perf_commands_q <= '0;
s_perf_splits_q <= '0;
s_perf_stall_cycles_q <= '0;
s_perf_err_count_q <= '0;
end else if (s_perf_ctrl_q[`PSRAM_PERF_ENABLE] && !s_perf_ctrl_q[`PSRAM_PERF_FREEZE]) begin
if (|perf_read_byte_event_i)
s_perf_read_bytes_q <= saturating_add(s_perf_read_bytes_q, perf_read_byte_event_i);
if (|perf_write_byte_event_i)
s_perf_write_bytes_q <= saturating_add(s_perf_write_bytes_q, perf_write_byte_event_i);
if (perf_command_event_i) s_perf_commands_q <= saturating_increment(s_perf_commands_q);
if (perf_split_event_i) s_perf_splits_q <= saturating_increment(s_perf_splits_q);
if (perf_stall_event_i)
s_perf_stall_cycles_q <= saturating_increment(s_perf_stall_cycles_q);
if (perf_error_event_i) s_perf_err_count_q <= saturating_increment(s_perf_err_count_q);
end
end
end
assign controller_enable_o = s_ctrl_q[`PSRAM_CTRL_ENABLE];
assign memory_enable_o = s_ctrl_q[`PSRAM_CTRL_MEMORY_ENABLE];
assign auto_init_o = s_ctrl_q[`PSRAM_CTRL_AUTO_INIT];
assign wrap32_o = s_ctrl_q[`PSRAM_CTRL_WRAP32];
assign chip_enable_o = s_chip_en_q;
assign half_period_o = s_clk_config_q[15:0];
assign above_84mhz_o = s_clk_config_q[`PSRAM_CLK_ABOVE_84MHZ];
assign powerup_cycles_o = s_powerup_cycles_q;
assign cs_setup_cycles_o = s_cs_setup_q;
assign cs_high_cycles_o = s_cs_high_q;
assign cs_hold_cycles_o = s_cs_hold_q;
assign cs_max_low_cycles_o = s_cs_max_low_q;
assign access_timeout_cycles_o = s_access_timeout_q;
assign indirect_command_o = psram_cmd_e'(s_indirect_ctrl_q[3:0]);
assign indirect_chip_o = s_indirect_ctrl_q[9:8];
assign indirect_length_o = {1'b0, s_indirect_ctrl_q[18:16]} + 1'b1;
assign indirect_addr_o = s_indirect_addr_q;
assign indirect_wdata_o = s_indirect_wdata_q;
assign irq_o = |(s_intr_state_q & s_intr_en_q);
endmodule
+257 -287
View File
@@ -1,53 +1,5 @@
// Copyright (c) 2023-2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
// http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
//
// NOTE: for supporting cross page xfer, the max freq of 'sclk' is 84MHz
// when 'sclk' is 144MHz(max, ~6.94ns), according to TRM:
//
// tCLK(min: 6.94ns) is meet, dont have 8'h03 rd oper(min: 30.3ns)
//
// tCH/tCL(min:0.45%, max: 0.55%) tCLK is meet
//
// tKHKL(max: 1.5ns) is meet
//
// tCPH(min: 50ns) -> 50 / 6.94 = ~7.2 so wait cycles need to >= ceil(7.2) = 8,
// so 's_cfg_wait' = 8 * 2 >= 16 in default, reset value is '18'
// for lower freq, can modify this value for performance tuning
//
// tCEM(max: 8us) is enough long for just 32bits xfer:
// QPI mode: (32(cmd+addr) + 32(data)) / 4 = 16 cycles + 6 wait cycles = 22 cycles
// for min sclk 12MHz, ~83ns * 22 = 1826ns = 1.826us
//
// tCSP(min: 2.5ns) is meet
// sclk keeps 6.94 / 2 = 3.47ns low at least after ce activing
//
// tCHD(min: 20ns) > tACLK + tCLK
// sclk keep 6.94 * 1.5 = 10.41 at least(no meet!)
// need to set 's_cfg_chd' = ceil(ceil(20 / 6.94) - 1.5) * 2 = 3, reset value is '4'
// for lower freq, can modify this value for performance tuning
//
// tSP(min: 2ns) is meet
// data keeps 6.94 / 2 = 3.47ns low at least befer sclk
`ifndef RIBP_PSRAM_DEF_SV
`define RIBP_PSRAM_DEF_SV
// verilog_format: off -- preserve reviewed column alignment
`define RIBP_PSRAM_WAIT 8'h00
`define RIBP_PSRAM_CHD 8'h04
`define RIBP_PSRAM_INIT 8'h08
// verilog_format: on
`endif
`include "mmap_define.svh"
interface psram_if ();
logic sck_o;
@@ -65,265 +17,283 @@ interface psram_if ();
output io_do_o,
output irq_o
);
endinterface
module ribp_psram (
// verilog_format: off -- preserve reviewed column alignment
input logic clk_i,
input logic rst_n_i,
ribp_if.slave ribp,
psram_if.dut psram
// verilog_format: off -- preserve reviewed port alignment
input logic clk_i,
input logic rst_n_i,
ribp_if.slave cfg_ribp,
axi4_if.slave mem_axi4,
psram_if.dut psram
// verilog_format: on
);
// clk_i/rst_n_i drive configuration and memory transfers. Configuration
// accesses respond immediately; one memory transaction remains outstanding
// until the PSRAM core asserts ready, and no response errors are generated.
typedef enum logic [5:0] {
Idle = 6'd0,
WriteStart = 6'd1,
Write = 6'd2,
ReadStart = 6'd3,
Read = 6'd4
} psram_state_e;
import psram_pkg::*;
// reg
logic [ 4:0] s_cfg_wait;
logic [ 2:0] s_cfg_chd;
logic s_cfg_init;
logic s_cfg_reg_sel;
logic s_mem_sel;
logic s_mem_ready;
logic [31:0] s_mem_rdata;
logic s_controller_en;
logic s_memory_en;
logic s_auto_init;
logic s_wrap32;
logic [ 3:0] s_chip_en;
logic [ 15:0] s_half_period;
logic s_above_84mhz;
logic [ 31:0] s_powerup_cycles;
logic [ 15:0] s_cs_setup_cycles;
logic [ 15:0] s_cs_high_cycles;
logic [ 15:0] s_cs_hold_cycles;
logic [ 31:0] s_cs_max_low_cycles;
logic [ 31:0] s_access_timeout_cycles;
logic s_init_start;
logic s_recover_start;
logic [ 1:0] s_recover_chip;
logic s_abort;
logic [ 3:0] s_chip_err_clear;
logic s_rd_st;
logic s_wr_st;
logic [ 7:0] s_xfer_data_bit_cnt;
psram_state_e s_fsm_state;
logic [23:0] s_mem_addr;
logic [31:0] s_mem_wdata;
logic s_init_busy;
logic s_axi_busy;
logic s_indirect_busy;
logic s_phy_busy;
logic s_quiesced;
logic s_global_ready;
logic [ 3:0] s_chip_present;
logic [ 3:0] s_chip_ready;
logic [ 3:0] s_chip_qpi;
logic [ 3:0] s_chip_wrap32;
logic [ 3:0] s_chip_err;
logic [ 47:0] s_chip0_id;
logic [ 47:0] s_chip1_id;
logic [ 47:0] s_chip2_id;
logic [ 47:0] s_chip3_id;
psram_error_e s_last_err;
logic [ 1:0] s_last_err_chip;
logic [ 31:0] s_last_err_addr;
logic s_psram_ce;
logic s_psram_sio_oen;
logic [ 1:0] s_disp_addr_ofst;
logic [ 7:0] s_disp_xfer_bit_cnt;
logic [31:0] s_disp_wdata;
logic s_init_done;
logic s_core_idle;
logic s_mem_valid_re;
logic s_indirect_start;
psram_cmd_e s_indirect_cmd;
logic [ 1:0] s_indirect_chip;
logic [ 22:0] s_indirect_addr;
logic [ 3:0] s_indirect_len;
logic [ 63:0] s_indirect_wdata;
logic [ 63:0] s_indirect_rdata;
logic s_mem_req_valid;
logic s_mem_req_ready;
logic s_mem_req_write;
logic [ 1:0] s_mem_req_chip;
logic [ 22:0] s_mem_req_addr;
logic [ 2:0] s_mem_req_len;
logic [ 31:0] s_mem_req_wdata;
logic s_mem_rsp_valid;
logic s_mem_rsp_ready;
logic s_mem_rsp_err;
logic [ 31:0] s_mem_rsp_rdata;
// verilog_format: off -- preserve reviewed column alignment
assign psram.nss_o[0] = (~s_init_done) || (s_init_done && ribp.addr[24:23] == 2'd0) ? s_psram_ce : 1'b1;
assign psram.nss_o[1] = (~s_init_done) || (s_init_done && ribp.addr[24:23] == 2'd1) ? s_psram_ce : 1'b1;
assign psram.nss_o[2] = (~s_init_done) || (s_init_done && ribp.addr[24:23] == 2'd2) ? s_psram_ce : 1'b1;
assign psram.nss_o[3] = (~s_init_done) || (s_init_done && ribp.addr[24:23] == 2'd3) ? s_psram_ce : 1'b1;
assign psram.io_oe_o[0] = ~s_psram_sio_oen;
assign psram.io_oe_o[1] = ~s_psram_sio_oen;
assign psram.io_oe_o[2] = ~s_psram_sio_oen;
assign psram.io_oe_o[3] = ~s_psram_sio_oen;
assign psram.irq_o = 1'b0;
// verilog_format: on
logic s_phy_req_valid;
logic s_phy_req_ready;
psram_cmd_e s_phy_req_cmd;
logic [ 1:0] s_phy_req_chip;
logic s_phy_req_qpi;
logic [ 22:0] s_phy_req_addr;
logic [ 5:0] s_phy_req_len;
logic [ 63:0] s_phy_req_wdata;
logic s_phy_done;
logic s_phy_err;
logic [255:0] s_phy_rdata;
logic s_init_done_event;
logic s_indirect_done_event;
logic s_err_event;
logic s_timeout_event;
logic [ 2:0] s_perf_read_byte_event;
logic [ 2:0] s_perf_write_byte_event;
logic s_perf_cmd_event;
logic s_perf_split_event;
logic s_perf_stall_event;
logic s_perf_err_event;
assign s_mem_sel = `SOC_ADDR_IS_PSRAM(ribp.addr);
assign s_cfg_reg_sel = `SOC_ADDR_IS_RIBP_PSRAM(ribp.addr);
assign ribp.ready = s_mem_sel ? s_mem_ready : 1'b1;
assign ribp.resp_err = 1'b0;
always_comb begin
ribp.rdata = '0;
if (s_mem_sel) begin
ribp.rdata = s_mem_rdata;
end else if (ribp.addr[7:0] == `RIBP_PSRAM_WAIT) begin
ribp.rdata = {27'd0, s_cfg_wait};
end else if (ribp.addr[7:0] == `RIBP_PSRAM_CHD) begin
ribp.rdata = {29'd0, s_cfg_chd};
end else if (ribp.addr[7:0] == `RIBP_PSRAM_INIT) begin
ribp.rdata = {31'd0, s_cfg_init};
end
end
psram_reg u_psram_reg (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.ribp (cfg_ribp),
.init_busy_i (s_init_busy),
.axi_busy_i (s_axi_busy),
.indirect_busy_i (s_indirect_busy),
.phy_busy_i (s_phy_busy),
.quiesced_i (s_quiesced),
.global_ready_i (s_global_ready),
.chip_present_i (s_chip_present),
.chip_ready_i (s_chip_ready),
.chip_qpi_i (s_chip_qpi),
.chip_wrap32_i (s_chip_wrap32),
.chip_error_i (s_chip_err),
.chip0_id_i (s_chip0_id),
.chip1_id_i (s_chip1_id),
.chip2_id_i (s_chip2_id),
.chip3_id_i (s_chip3_id),
.last_error_i (s_last_err),
.last_error_chip_i (s_last_err_chip),
.last_error_addr_i (s_last_err_addr),
.indirect_rdata_i (s_indirect_rdata),
.init_done_event_i (s_init_done_event),
.indirect_done_event_i (s_indirect_done_event),
.error_event_i (s_err_event),
.timeout_event_i (s_timeout_event),
.perf_read_byte_event_i (s_perf_read_byte_event),
.perf_write_byte_event_i(s_perf_write_byte_event),
.perf_command_event_i (s_perf_cmd_event),
.perf_split_event_i (s_perf_split_event),
.perf_stall_event_i (s_perf_stall_event),
.perf_error_event_i (s_perf_err_event),
.controller_enable_o (s_controller_en),
.memory_enable_o (s_memory_en),
.auto_init_o (s_auto_init),
.wrap32_o (s_wrap32),
.chip_enable_o (s_chip_en),
.half_period_o (s_half_period),
.above_84mhz_o (s_above_84mhz),
.powerup_cycles_o (s_powerup_cycles),
.cs_setup_cycles_o (s_cs_setup_cycles),
.cs_high_cycles_o (s_cs_high_cycles),
.cs_hold_cycles_o (s_cs_hold_cycles),
.cs_max_low_cycles_o (s_cs_max_low_cycles),
.access_timeout_cycles_o(s_access_timeout_cycles),
.init_start_o (s_init_start),
.recover_start_o (s_recover_start),
.recover_chip_o (s_recover_chip),
.abort_o (s_abort),
.chip_error_clear_o (s_chip_err_clear),
.indirect_start_o (s_indirect_start),
.indirect_command_o (s_indirect_cmd),
.indirect_chip_o (s_indirect_chip),
.indirect_addr_o (s_indirect_addr),
.indirect_length_o (s_indirect_len),
.indirect_wdata_o (s_indirect_wdata),
.irq_o (psram.irq_o)
);
// wait cycles(mmio)
// Configuration and transfer state share ordered request/reset priority, so
// these processes remain intact rather than splitting behavior across DFFs.
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (~rst_n_i) s_cfg_wait <= 5'd18;
else if (ribp.valid && ribp.wstrb[0] && s_cfg_reg_sel &&
(ribp.addr[7:0] == `RIBP_PSRAM_WAIT)) begin
s_cfg_wait <= ribp.wdata[4:0];
end
end
// extra cycle for tCHD(mmio)
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (~rst_n_i) s_cfg_chd <= 3'd4;
else if (ribp.valid && ribp.wstrb[0] && s_cfg_reg_sel &&
(ribp.addr[7:0] == `RIBP_PSRAM_CHD)) begin
s_cfg_chd <= ribp.wdata[2:0];
end
end
// init device/switch qpi mode
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (~rst_n_i) s_cfg_init <= '0;
else if (ribp.valid && ribp.wstrb[0] && s_cfg_reg_sel &&
(ribp.addr[7:0] == `RIBP_PSRAM_INIT)) begin
s_cfg_init <= ribp.wdata[0];
end
end
edge_det_sync_re #(
.DATA_WIDTH(1)
) u_mem_valid_edge_det_sync_re (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (ribp.valid),
.re_o (s_mem_valid_re)
psram_axi4 u_psram_axi4 (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.accept_enable_i(!s_quiesced),
.busy_o (s_axi_busy),
.stall_event_o (s_perf_stall_event),
.split_event_o (s_perf_split_event),
.axi4 (mem_axi4),
.mem_req_valid_o(s_mem_req_valid),
.mem_req_ready_i(s_mem_req_ready),
.mem_req_write_o(s_mem_req_write),
.mem_req_chip_o (s_mem_req_chip),
.mem_req_addr_o (s_mem_req_addr),
.mem_req_len_o (s_mem_req_len),
.mem_req_wdata_o(s_mem_req_wdata),
.mem_rsp_valid_i(s_mem_rsp_valid),
.mem_rsp_ready_o(s_mem_rsp_ready),
.mem_rsp_error_i(s_mem_rsp_err),
.mem_rsp_rdata_i(s_mem_rsp_rdata)
);
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (~rst_n_i) begin
s_rd_st <= '0;
s_wr_st <= '0;
s_xfer_data_bit_cnt <= '0;
s_fsm_state <= Idle;
s_mem_addr <= '0;
s_mem_wdata <= '0;
end else begin
if (s_mem_sel) begin
/* verilator lint_off CASEINCOMPLETE */
case (s_fsm_state)
Idle: begin
if (s_mem_valid_re && (|ribp.wstrb)) begin
s_fsm_state <= WriteStart;
s_xfer_data_bit_cnt <= s_disp_xfer_bit_cnt;
s_mem_addr <= {1'b0, ribp.addr[22:0]} + {22'd0, s_disp_addr_ofst};
s_mem_wdata <= s_disp_wdata;
end else if (s_mem_valid_re && (~(|ribp.wstrb))) begin
s_fsm_state <= ReadStart;
s_xfer_data_bit_cnt <= 8'd32;
s_mem_addr <= {1'b0, ribp.addr[22:0]};
s_mem_wdata <= ribp.wdata; // NOTE: no used
end
end
WriteStart: begin
if (s_core_idle) begin
s_wr_st <= 1'b1;
s_fsm_state <= Write;
end
end
Write: begin
s_wr_st <= 1'b0;
if (s_mem_ready) s_fsm_state <= Idle;
end
ReadStart: begin
if (s_core_idle) begin
s_rd_st <= 1'b1;
s_fsm_state <= Read;
end
end
Read: begin
s_rd_st <= 1'b0;
if (s_mem_ready) s_fsm_state <= Idle;
end
default: begin
s_fsm_state <= Idle;
s_rd_st <= 1'b0;
s_wr_st <= 1'b0;
end
endcase
end
end
end
psram_core u_psram_core (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.cfg_wait_i (s_cfg_wait),
.cfg_chd_i (s_cfg_chd),
.cfg_init_i (s_cfg_init),
.mem_ready_o (s_mem_ready),
.mem_addr_i (s_mem_addr),
.mem_wdata_i (s_mem_wdata),
.mem_rdata_o (s_mem_rdata),
.xfer_data_bit_cnt_i(s_xfer_data_bit_cnt),
.rd_st_i (s_rd_st),
.wr_st_i (s_wr_st),
.init_done_o (s_init_done),
.idle_o (s_core_idle),
.psram_sclk_o (psram.sck_o),
.psram_ce_o (s_psram_ce),
.psram_mosi_i (psram.io_di_i[0]),
.psram_miso_i (psram.io_di_i[1]),
.psram_sio2_i (psram.io_di_i[2]),
.psram_sio3_i (psram.io_di_i[3]),
.psram_mosi_o (psram.io_do_o[0]),
.psram_miso_o (psram.io_do_o[1]),
.psram_sio2_o (psram.io_do_o[2]),
.psram_sio3_o (psram.io_do_o[3]),
.psram_sio_oen_o (s_psram_sio_oen)
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.controller_enable_i (s_controller_en),
.memory_enable_i (s_memory_en),
.auto_init_i (s_auto_init),
.wrap32_i (s_wrap32),
.chip_enable_i (s_chip_en),
.powerup_cycles_i (s_powerup_cycles),
.init_start_i (s_init_start),
.recover_start_i (s_recover_start),
.recover_chip_i (s_recover_chip),
.abort_i (s_abort),
.chip_error_clear_i (s_chip_err_clear),
.init_busy_o (s_init_busy),
.indirect_busy_o (s_indirect_busy),
.quiesced_o (s_quiesced),
.global_ready_o (s_global_ready),
.chip_present_o (s_chip_present),
.chip_ready_o (s_chip_ready),
.chip_qpi_o (s_chip_qpi),
.chip_wrap32_o (s_chip_wrap32),
.chip_error_o (s_chip_err),
.chip0_id_o (s_chip0_id),
.chip1_id_o (s_chip1_id),
.chip2_id_o (s_chip2_id),
.chip3_id_o (s_chip3_id),
.last_error_o (s_last_err),
.last_error_chip_o (s_last_err_chip),
.last_error_addr_o (s_last_err_addr),
.init_done_event_o (s_init_done_event),
.indirect_done_event_o (s_indirect_done_event),
.error_event_o (s_err_event),
.timeout_event_o (s_timeout_event),
.indirect_start_i (s_indirect_start),
.indirect_command_i (s_indirect_cmd),
.indirect_chip_i (s_indirect_chip),
.indirect_addr_i (s_indirect_addr),
.indirect_length_i (s_indirect_len),
.indirect_wdata_i (s_indirect_wdata),
.indirect_rdata_o (s_indirect_rdata),
.mem_req_valid_i (s_mem_req_valid),
.mem_req_ready_o (s_mem_req_ready),
.mem_req_write_i (s_mem_req_write),
.mem_req_chip_i (s_mem_req_chip),
.mem_req_addr_i (s_mem_req_addr),
.mem_req_len_i (s_mem_req_len),
.mem_req_wdata_i (s_mem_req_wdata),
.mem_rsp_valid_o (s_mem_rsp_valid),
.mem_rsp_ready_i (s_mem_rsp_ready),
.mem_rsp_error_o (s_mem_rsp_err),
.mem_rsp_rdata_o (s_mem_rsp_rdata),
.perf_read_byte_event_o (s_perf_read_byte_event),
.perf_write_byte_event_o(s_perf_write_byte_event),
.perf_command_event_o (s_perf_cmd_event),
.perf_error_event_o (s_perf_err_event),
.axi_busy_i (s_axi_busy),
.phy_req_valid_o (s_phy_req_valid),
.phy_req_ready_i (s_phy_req_ready),
.phy_req_command_o (s_phy_req_cmd),
.phy_req_chip_o (s_phy_req_chip),
.phy_req_qpi_o (s_phy_req_qpi),
.phy_req_addr_o (s_phy_req_addr),
.phy_req_length_o (s_phy_req_len),
.phy_req_wdata_o (s_phy_req_wdata),
.phy_busy_i (s_phy_busy),
.phy_done_i (s_phy_done),
.phy_error_i (s_phy_err),
.phy_rdata_i (s_phy_rdata)
);
wr_dispatcher u_wr_dispatcher (
.wstrb_i (ribp.wstrb),
.wdata_i (ribp.wdata),
.addr_ofst_o (s_disp_addr_ofst),
.xfer_bit_cnt_o(s_disp_xfer_bit_cnt),
.wdata_o (s_disp_wdata)
psram_phy u_psram_phy (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.abort_i (s_abort),
.req_valid_i (s_phy_req_valid),
.req_ready_o (s_phy_req_ready),
.req_command_i (s_phy_req_cmd),
.req_chip_i (s_phy_req_chip),
.req_qpi_i (s_phy_req_qpi),
.req_addr_i (s_phy_req_addr),
.req_length_i (s_phy_req_len),
.req_wdata_i (s_phy_req_wdata),
.busy_o (s_phy_busy),
.done_o (s_phy_done),
.error_o (s_phy_err),
.rdata_o (s_phy_rdata),
.cfg_half_period_i (s_half_period),
.cfg_cs_setup_i (s_cs_setup_cycles),
.cfg_cs_high_i (s_cs_high_cycles),
.cfg_cs_hold_i (s_cs_hold_cycles),
.cfg_cs_max_low_i (s_cs_max_low_cycles),
.cfg_access_timeout_i(s_access_timeout_cycles),
.psram_sclk_o (psram.sck_o),
.psram_nss_o (psram.nss_o),
.psram_io_oe_o (psram.io_oe_o),
.psram_io_di_i (psram.io_di_i),
.psram_io_do_o (psram.io_do_o)
);
endmodule
logic unused_above_84mhz;
assign unused_above_84mhz = s_above_84mhz;
module wr_dispatcher (
input logic [ 3:0] wstrb_i,
input logic [31:0] wdata_i,
output logic [ 1:0] addr_ofst_o,
output logic [ 7:0] xfer_bit_cnt_o,
output logic [31:0] wdata_o
);
always_comb begin
wdata_o = wdata_i;
case (wstrb_i)
4'b0001: begin
addr_ofst_o = 2'd0;
xfer_bit_cnt_o = 8'd8;
wdata_o[7:0] = wdata_i[7:0];
end
4'b0010: begin
addr_ofst_o = 2'd1;
xfer_bit_cnt_o = 8'd8;
wdata_o[7:0] = wdata_i[15:8];
end
4'b0100: begin
addr_ofst_o = 2'd2;
xfer_bit_cnt_o = 8'd8;
wdata_o[7:0] = wdata_i[23:16];
end
4'b1000: begin
addr_ofst_o = 2'd3;
xfer_bit_cnt_o = 8'd8;
wdata_o[7:0] = wdata_i[31:24];
end
4'b0011: begin
addr_ofst_o = 2'd0;
xfer_bit_cnt_o = 8'd16;
wdata_o[15:0] = wdata_i[15:0];
end
4'b1100: begin
addr_ofst_o = 2'd2;
xfer_bit_cnt_o = 8'd16;
wdata_o[15:0] = wdata_i[31:16];
end
4'b1111: begin
addr_ofst_o = 2'd0;
xfer_bit_cnt_o = 8'd32;
wdata_o[31:0] = wdata_i[31:0];
end
default: begin
addr_ofst_o = 2'd0;
xfer_bit_cnt_o = 8'd32;
wdata_o = wdata_i[31:0];
end
endcase
end
endmodule
+1 -1
View File
@@ -67,7 +67,7 @@
{"symbol": "APB_USER_IP", "base": "0x20007000", "size": "0x00001000", "route": "apb", "kind": "active", "public": true, "user_access": "rw"},
{"symbol": "SRAM", "base": "0x30000000", "size": "0x00020000", "route": "ram", "kind": "active", "public": true, "linker": "SRAM", "user_access": "rw", "burst": "incr16"},
{"symbol": "SDRAM", "base": "0x38000000", "size": "0x04000000", "route": "axi4", "kind": "active", "public": true, "linker": "SDRAM", "user_access": "rw", "burst": "incr16"},
{"symbol": "PSRAM", "base": "0x40000000", "size": "0x00800000", "route": "axi4", "kind": "active", "public": true, "linker": "PSRAM", "user_access": "rw", "burst": "incr16"},
{"symbol": "PSRAM", "base": "0x40000000", "size": "0x02000000", "route": "axi4", "kind": "active", "public": true, "linker": "PSRAM", "user_access": "rw", "burst": "incr16"},
{"symbol": "OPIPSRAM", "base": "0x48000000", "size": "0x08000000", "route": "reserved", "kind": "reserved", "public": false, "user_access": "none"},
{"symbol": "XPI", "base": "0x50000000", "size": "0x10000000", "route": "axi4", "kind": "active", "public": true, "user_access": "rw", "burst": "incr16"},
{"symbol": "SPISD", "base": "0x60000000", "size": "0x40000000", "route": "axi4", "kind": "active", "public": true, "user_access": "rw", "burst": "incr16"}
+520 -224
View File
@@ -1,263 +1,559 @@
// Copyright (c) 2023-2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
// You may obtain a copy of the Mulan PSL v2 at:
// http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
//
// Behavioral Verilog simulation model for a 64Mbit Quad SPI (QSPI) PSRAM.
// This model simulates the external interface and functionality of the PSRAM,
// allowing a controller (e.g., ESP32) to interact with it for read and write
// operations. It does not model internal proprietary logic or exact timing
// parameters beyond basic clock edge synchronization.
//
// Assumptions:
// - Memory Size: 64 Mbits (8 MBytes)
// - Interface: Quad SPI (QSPI)
// - Supported Commands:
// - Quad Read (0xEB)
// - Quad Write (0x38)
// - Dummy Cycles: Assumes 6 dummy cycles for Quad Read (common for many QSPI devices).
// - Address Width: 23 bits (for 8 MBytes).
// - Data Width: 8 bits (byte-addressable).
// - Clock Polarity/Phase: Assumes CPOL=0, CPHA=0 (SPI Mode 0), where data is
// sampled on the rising edge of sclk and driven on the falling edge.
// - Tri-state Logic: Proper handling of IO lines for input and output.
//
// Usage:
// Connect this module to your QSPI master controller in your simulation environment.
//
// 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.
`timescale 1 ns / 1 ps
/* verilator lint_off WIDTHTRUNC */
module ESP_PSRAM64H #(
parameter ID = 0
parameter integer ID = 0,
parameter integer MEMORY_BYTES = 8 * 1024 * 1024,
parameter integer INITIALIZE_MEMORY = 0,
parameter [ 7:0] INITIAL_MEMORY_VALUE = 8'h00,
parameter integer POWER_UP_CHECK = 1,
parameter time POWER_UP_TIME_NS = 150000,
parameter integer TIMING_CHECK = 1,
parameter time MIN_SCLK_HIGH_NS = 3,
parameter time MIN_SCLK_LOW_NS = 3,
parameter time MIN_CS_HIGH_NS = 2,
parameter time MIN_CS_LOW_NS = 2,
parameter integer TIMING_VIOLATION_FATAL = 0,
parameter integer INJECT_MISSING_DEVICE = 0,
parameter integer INJECT_FAILED_DEVICE = 0,
parameter integer INJECT_TIMING_VIOLATION = 0,
parameter integer DEFAULT_WRAP32 = 0,
parameter [ 7:0] MANUFACTURER_ID = 8'h0d,
parameter [ 7:0] KGD = 8'h5d,
parameter [ 7:0] DENSITY = 8'h26,
parameter [23:0] EXTENDED_ID = 24'h000000
) (
input wire sclk, // Serial Clock (SPI Clock)
input wire csn, // Chip Select (Active Low)
inout wire [3:0] sio // Quad SPI Data Line 0 (MOSI/MISO)
// Add other pins like RST_N if your specific application uses it and you want to model it.
input wire sclk,
input wire csn,
inout wire [3:0] sio
);
// --- Parameters ---
// Memory depth in bytes (64 Mbits / 8 bits/byte = 8 MBytes)
localparam MEM_DEPTH_BYTES = 8 * 1024 * 1024;
// localparam MEM_DEPTH_BYTES = 72 * 1024; // for debug
// Address width required for MEM_DEPTH_BYTES
localparam ADDR_BITS = 24;
localparam [4:0] ST_IDLE = 5'd0;
localparam [4:0] ST_SPI_CMD = 5'd1;
localparam [4:0] ST_QPI_CMD = 5'd2;
localparam [4:0] ST_SPI_ADDR = 5'd3;
localparam [4:0] ST_QUAD_ADDR = 5'd4;
localparam [4:0] ST_ID_ADDR = 5'd5;
localparam [4:0] ST_DUMMY = 5'd6;
localparam [4:0] ST_READ = 5'd7;
localparam [4:0] ST_WRITE = 5'd8;
localparam [4:0] ST_ID = 5'd9;
// --- Internal Memory Array ---
// mem_array: Stores the 8-bit (byte) data. Indexed by address.
reg [7:0] mem_array[0:MEM_DEPTH_BYTES-1];
localparam [3:0] DUMMY_FAST_READ = 4'd8;
localparam [3:0] DUMMY_QUAD_READ = 4'd6;
// --- State Machine Definitions ---
parameter S_IDLE = 3'd0;
parameter S_CMD_SPI_RECV = 3'd1;
parameter S_CMD_QPI_RECV = 3'd2;
parameter S_ADDR_RECEIVE = 3'd3;
parameter S_DUMMY_CYCLES = 3'd4;
parameter S_DATA_XFER_READ = 3'd5;
parameter S_DATA_XFER_WRITE = 3'd6;
reg [ 7:0] mem_array [0:MEMORY_BYTES-1];
reg [2:0] r_fsm_state;
reg [ 4:0] r_state;
reg r_csn_active;
reg r_qpi_mode;
reg r_reset_armed;
reg r_wrap32;
reg r_power_up_done;
reg [7:0] command_byte; // Stores the received command byte
reg [ADDR_BITS-1:0] current_address; // Stores the current address for read/write
reg [5:0] bit_counter; // Counter for bits/nibbles within a state (e.g., 2 nibbles for command, 6 for address)
reg [2:0] byte_counter;
reg [5:0] dummy_cycle_counter; // Counter for dummy cycles
reg [ 7:0] r_command;
reg [ 7:0] r_command_shift;
reg [ 2:0] r_command_bit_count;
reg r_command_nibble_count;
reg [23:0] r_address_shift;
reg [23:0] r_current_address;
reg [ 4:0] r_address_count;
reg [ 3:0] r_dummy_count;
reg [ 3:0] r_dummy_length;
reg [ 7:0] r_write_shift;
reg [ 2:0] r_write_count;
reg io_output_enable; // Controls whether the PSRAM drives the IO lines (1'b1 for output, 1'b0 for input)
reg [3:0] qspi_data_in; // Buffer for 4-bit data input from controller
reg [3:0] qspi_data_out; // Buffer for 4-bit data output to controller
reg qspi_mode;
reg r_output_enable;
reg r_output_quad;
reg [ 3:0] r_output_nibble;
reg r_output_bit;
reg [ 2:0] r_output_bit_count;
reg [ 2:0] r_output_nibble_count;
reg [ 5:0] r_id_bit_count;
reg r_timing_reported;
reg r_timing_injected;
time r_last_csn_rise_time;
time r_last_csn_fall_time;
time r_last_sclk_posedge_time;
time r_last_sclk_negedge_time;
reg r_have_csn_rise;
reg r_have_csn_fall;
reg r_have_sclk_posedge;
reg r_have_sclk_negedge;
wire [47:0] w_id_value;
wire w_device_present;
wire w_power_ready;
assign w_id_value = {
MANUFACTURER_ID, (INJECT_FAILED_DEVICE != 0) ? 8'h55 : KGD, DENSITY, EXTENDED_ID
};
assign w_device_present = INJECT_MISSING_DEVICE == 0;
assign w_power_ready = r_power_up_done;
assign sio[0] = (w_device_present && (csn === 1'b0) && r_output_enable &&
r_output_quad)
? r_output_nibble[0]
: 1'bz;
assign sio[1] = (w_device_present && (csn === 1'b0) && r_output_enable)
? (r_output_quad ? r_output_nibble[1] : r_output_bit)
: 1'bz;
assign sio[2] = (w_device_present && (csn === 1'b0) && r_output_enable &&
r_output_quad)
? r_output_nibble[2]
: 1'bz;
assign sio[3] = (w_device_present && (csn === 1'b0) && r_output_enable &&
r_output_quad)
? r_output_nibble[3]
: 1'bz;
function integer memory_index;
input [23:0] address;
integer address_value;
begin
if (MEMORY_BYTES <= 0) begin
memory_index = 0;
end else begin
address_value = {8'd0, address};
memory_index = address_value % MEMORY_BYTES;
end
end
endfunction
function [23:0] next_address;
input [23:0] address;
input wrap32;
integer address_value;
integer address_offset;
begin
if (MEMORY_BYTES <= 0) begin
next_address = 24'd0;
end else begin
address_value = {8'd0, address};
address_value = address_value % MEMORY_BYTES;
if (wrap32) begin
address_offset = address_value & 31;
if (address_offset == 31) address_value = address_value - 31;
else address_value = address_value + 1;
end else begin
address_offset = address_value & 1023;
if (address_offset == 1023) address_value = address_value - 1023;
else address_value = address_value + 1;
end
if (address_value >= MEMORY_BYTES) address_value = 0;
next_address = address_value;
end
end
endfunction
task report_timing_violation;
begin
if (!r_timing_reported) begin
r_timing_reported <= 1'b1;
if (TIMING_VIOLATION_FATAL != 0)
$fatal(1, "ESP_PSRAM64H[%0d]: timing violation at %0t", ID, $time);
else $display("ESP_PSRAM64H[%0d]: timing violation at %0t", ID, $time);
end
end
endtask
always @(posedge sclk or negedge sclk or negedge csn or posedge csn) begin
case (r_fsm_state)
S_IDLE: begin
// csn == 0 sclk == 0
if (csn == 0) begin
bit_counter <= '0;
byte_counter <= '0;
dummy_cycle_counter <= '0;
current_address <= '0;
command_byte <= '0;
io_output_enable <= '0;
if (~qspi_mode) r_fsm_state <= S_CMD_SPI_RECV;
else r_fsm_state <= S_CMD_QPI_RECV;
end
if (csn === 1'b1) begin
if (r_csn_active) begin
if (TIMING_CHECK != 0 && r_have_csn_fall &&
(MIN_CS_LOW_NS > 0) &&
(($time - r_last_csn_fall_time) < MIN_CS_LOW_NS))
report_timing_violation();
r_last_csn_rise_time <= $time;
r_have_csn_rise <= 1'b1;
end
S_CMD_SPI_RECV: begin
if (csn == 0 && sclk == 1) begin
command_byte[7-bit_counter] <= sio[0];
bit_counter <= bit_counter + 1;
if (bit_counter == 7) begin
bit_counter <= 0;
// verilog_format: off
case ({command_byte[7:1], sio[0]})
// verilog_format: on
8'h66: r_fsm_state <= S_IDLE; // reset ena
8'h99: r_fsm_state <= S_IDLE; // reset
8'h35: begin
r_fsm_state <= S_IDLE; // enter QPI mode
qspi_mode <= 1'b1;
end
default: r_fsm_state <= S_IDLE;
endcase
end
r_csn_active <= 1'b0;
r_state <= ST_IDLE;
r_output_enable <= 1'b0;
r_command_bit_count <= 3'd0;
r_command_nibble_count <= 1'b0;
r_address_count <= 5'd0;
r_write_count <= 3'd0;
r_dummy_count <= 4'd0;
end else if (csn === 1'b0) begin
if (!r_csn_active) begin
if (TIMING_CHECK != 0 && r_have_csn_rise &&
(MIN_CS_HIGH_NS > 0) &&
(($time - r_last_csn_rise_time) < MIN_CS_HIGH_NS))
report_timing_violation();
if (INJECT_TIMING_VIOLATION != 0 && !r_timing_injected) begin
r_timing_injected <= 1'b1;
report_timing_violation();
end
end
S_CMD_QPI_RECV: begin
if (csn == 0 && sclk == 1) begin
qspi_data_in = {sio[3], sio[2], sio[1], sio[0]};
if (bit_counter == 0) begin
command_byte[7:4] <= qspi_data_in;
end else begin
command_byte[3:0] <= qspi_data_in;
end
bit_counter <= bit_counter + 1;
if (bit_counter == 1) begin
bit_counter <= 0;
case ({
command_byte[7:4], qspi_data_in
})
8'hEB: r_fsm_state <= S_ADDR_RECEIVE; // Quad Read command
8'h38: r_fsm_state <= S_ADDR_RECEIVE; // Quad Write command
default: r_fsm_state <= S_IDLE;
endcase
end
end
end
S_ADDR_RECEIVE: begin
if (csn == 0 && sclk == 1) begin
// Sample 4 bits for address. Address is 23 bits, so 6 nibbles + 1 bit.
// For simplicity, we'll assume 6 nibbles (24 bits) and ignore the highest bit if 23-bit address.
qspi_data_in = {sio[3], sio[2], sio[1], sio[0]};
// Store nibbles into current_address (MSB first)
current_address[ADDR_BITS-1-(bit_counter*4)-:4] <= qspi_data_in;
bit_counter <= bit_counter + 1;
if (bit_counter == ((ADDR_BITS + 3) / 4) - 1) begin // After all address bits (e.g., 23 bits -> 6 nibbles)
bit_counter <= 0; // Reset for next stage
// Transition to Dummy cycles or data transfer based on command
if (command_byte == 8'hEB) begin // Quad Read
r_fsm_state <= S_DUMMY_CYCLES; // PSRAM64H typically has dummy cycles
end else if (command_byte == 8'h38) begin // Quad Write
r_fsm_state <= S_DATA_XFER_WRITE;
end else begin
r_fsm_state <= S_IDLE; // Should not happen if command was valid
end
end
end
end
S_DUMMY_CYCLES: begin
if (csn == 0 && sclk == 1) begin
dummy_cycle_counter <= dummy_cycle_counter + 1;
if (dummy_cycle_counter == 5) begin
dummy_cycle_counter <= 0;
bit_counter <= 0;
io_output_enable <= 1'b1;
r_fsm_state <= S_DATA_XFER_READ;
end
r_last_csn_fall_time <= $time;
r_have_csn_fall <= 1'b1;
r_have_sclk_posedge <= 1'b0;
r_have_sclk_negedge <= 1'b0;
r_csn_active <= 1'b1;
r_output_enable <= 1'b0;
r_command_shift <= 8'd0;
r_command <= 8'd0;
r_command_bit_count <= 3'd0;
r_command_nibble_count <= 1'b0;
r_address_shift <= 24'd0;
r_address_count <= 5'd0;
r_write_shift <= 8'd0;
r_write_count <= 3'd0;
r_dummy_count <= 4'd0;
r_output_bit_count <= 3'd0;
r_output_nibble_count <= 3'd0;
r_id_bit_count <= 6'd0;
if (!w_power_ready || !w_device_present) begin
r_state <= ST_IDLE;
end else if (r_qpi_mode) begin
r_state <= ST_QPI_CMD;
end else begin
// Check datasheet for exact number of dummy cycles (e.g., 6 cycles for Quad Read)
if (dummy_cycle_counter == 6) begin
end
if (dummy_cycle_counter <= 5) begin
// $display("time: %t", $time);
end
r_state <= ST_SPI_CMD;
end
end
end else if (!r_power_up_done && !w_power_ready) begin
r_state <= ST_IDLE;
end else if (sclk === 1'b1) begin
if (TIMING_CHECK != 0 && r_have_sclk_negedge &&
(MIN_SCLK_LOW_NS > 0) &&
(($time - r_last_sclk_negedge_time) < MIN_SCLK_LOW_NS))
report_timing_violation();
r_last_sclk_posedge_time <= $time;
r_have_sclk_posedge <= 1'b1;
S_DATA_XFER_READ: begin
if (csn == 1) begin
r_fsm_state <= S_IDLE;
io_output_enable <= 1'b0;
end else if (csn == 0 && sclk == 0) begin
// Read 4 bits from mem_array and drive onto IO lines
// Data is read byte by byte, then split into nibbles
qspi_data_out <= mem_array[current_address][7-(bit_counter*4)-:4]; // MSB first nibble
bit_counter <= bit_counter + 1;
if (bit_counter == 1) begin // After 2 nibbles (1 byte)
bit_counter <= 0;
byte_counter <= byte_counter + 1;
current_address <= current_address + 1; // Increment address for burst read
// Handle address overflow if needed, though for simulation, it might wrap around.
if (current_address == MEM_DEPTH_BYTES - 1) begin
current_address <= 0; // Wrap around if end of memory
end
end else begin
if (byte_counter == 3'd4) begin
byte_counter <= 3'd0;
r_fsm_state <= S_IDLE;
io_output_enable <= 1'b0;
case (r_state)
ST_SPI_CMD: begin
r_command_shift <= {r_command_shift[6:0], sio[0]};
if (r_command_bit_count == 3'd7) begin
r_command <= {r_command_shift[6:0], sio[0]};
r_command_bit_count <= 3'd0;
case ({
r_command_shift[6:0], sio[0]
})
8'h03: begin
r_address_count <= 5'd0;
r_state <= ST_SPI_ADDR;
end
8'h0b: begin
r_address_count <= 5'd0;
r_state <= ST_SPI_ADDR;
end
8'heb: begin
r_address_count <= 5'd0;
r_state <= ST_QUAD_ADDR;
end
8'h02: begin
r_address_count <= 5'd0;
r_state <= ST_SPI_ADDR;
end
8'h38: begin
r_address_count <= 5'd0;
r_state <= ST_QUAD_ADDR;
end
8'h35: begin
r_qpi_mode <= 1'b1;
r_state <= ST_IDLE;
end
8'h9f: begin
r_address_count <= 5'd0;
r_state <= ST_ID_ADDR;
end
8'h66: begin
r_reset_armed <= 1'b1;
r_state <= ST_IDLE;
end
8'h99: begin
if (r_reset_armed) begin
r_qpi_mode <= 1'b0;
r_wrap32 <= (DEFAULT_WRAP32 != 0);
end
r_reset_armed <= 1'b0;
r_state <= ST_IDLE;
end
8'hc0: begin
r_wrap32 <= ~r_wrap32;
r_state <= ST_IDLE;
end
default: begin
r_state <= ST_IDLE;
end
endcase
end else begin
r_command_bit_count <= r_command_bit_count + 1'b1;
end
end
end
end
S_DATA_XFER_WRITE: begin
if (csn == 1) begin
r_fsm_state <= S_IDLE;
io_output_enable <= 1'b0;
end else if (csn == 0 && sclk == 1) begin
// Sample 4 bits from IO lines and write to mem_array
qspi_data_in = {sio[3], sio[2], sio[1], sio[0]};
// Write nibbles into mem_array (MSB first)
mem_array[current_address][7-(bit_counter*4)-:4] <= qspi_data_in;
bit_counter <= bit_counter + 1;
if (bit_counter == 1) begin // After 2 nibbles (1 byte)
bit_counter <= 0;
current_address <= current_address + 1; // Increment address for burst write
if (current_address == MEM_DEPTH_BYTES - 1) begin
current_address <= 0; // Wrap around if end of memory
ST_QPI_CMD: begin
if (!r_command_nibble_count) begin
r_command_shift[7:4] <= sio;
r_command_nibble_count <= 1'b1;
end else begin
r_command_shift[3:0] <= sio;
r_command <= {r_command_shift[7:4], sio};
r_command_nibble_count <= 1'b0;
case ({
r_command_shift[7:4], sio
})
8'heb: begin
r_address_count <= 5'd0;
r_state <= ST_QUAD_ADDR;
end
8'h02: begin
r_address_count <= 5'd0;
r_state <= ST_QUAD_ADDR;
end
8'h38: begin
r_address_count <= 5'd0;
r_state <= ST_QUAD_ADDR;
end
8'hf5: begin
r_qpi_mode <= 1'b0;
r_state <= ST_IDLE;
end
8'h66: begin
r_reset_armed <= 1'b1;
r_state <= ST_IDLE;
end
8'h99: begin
if (r_reset_armed) begin
r_qpi_mode <= 1'b0;
r_wrap32 <= (DEFAULT_WRAP32 != 0);
end
r_reset_armed <= 1'b0;
r_state <= ST_IDLE;
end
8'hc0: begin
r_wrap32 <= ~r_wrap32;
r_state <= ST_IDLE;
end
default: begin
r_state <= ST_IDLE;
end
endcase
end
end
end
end
default: begin
r_fsm_state <= S_IDLE;
io_output_enable <= 1'b0;
end
endcase
end
// --- Tri-state Buffer for IO Lines ---
assign sio[0] = io_output_enable ? qspi_data_out[0] : 1'bz;
assign sio[1] = io_output_enable ? qspi_data_out[1] : 1'bz;
assign sio[2] = io_output_enable ? qspi_data_out[2] : 1'bz;
assign sio[3] = io_output_enable ? qspi_data_out[3] : 1'bz;
ST_SPI_ADDR: begin
r_address_shift <= (r_address_shift << 1) | {23'd0, sio[0]};
if (r_address_count == 5'd23) begin
r_current_address <= {r_address_shift[22:0], sio[0]};
r_address_count <= 5'd0;
if (r_command == 8'h03) begin
r_output_quad <= 1'b0;
r_output_bit_count <= 3'd0;
r_state <= ST_READ;
end else if (r_command == 8'h0b) begin
r_output_quad <= 1'b0;
r_dummy_length <= DUMMY_FAST_READ;
r_dummy_count <= 4'd0;
r_state <= ST_DUMMY;
end else if (r_command == 8'h02) begin
r_write_count <= 3'd0;
r_state <= ST_WRITE;
end else begin
r_state <= ST_IDLE;
end
end else begin
r_address_count <= r_address_count + 1'b1;
end
end
// --- Initial Memory Content (Optional) ---
initial begin
integer i;
for (i = 0; i < MEM_DEPTH_BYTES; i = i + 1) begin
mem_array[i] = i[7:0]; // Initialize with address lower 8 bits
ST_QUAD_ADDR: begin
r_address_shift <= (r_address_shift << 4) | {20'd0, sio};
if (r_address_count == 5'd5) begin
r_current_address <= {r_address_shift[19:0], sio};
r_address_count <= 5'd0;
if (r_command == 8'heb) begin
r_output_quad <= 1'b1;
r_dummy_length <= DUMMY_QUAD_READ;
r_dummy_count <= 4'd0;
r_state <= ST_DUMMY;
end else if ((r_command == 8'h02) || (r_command == 8'h38)) begin
r_write_count <= 3'd0;
r_state <= ST_WRITE;
end else begin
r_state <= ST_IDLE;
end
end else begin
r_address_count <= r_address_count + 1'b1;
end
end
ST_ID_ADDR: begin
r_address_shift <= (r_address_shift << 1) | {23'd0, sio[0]};
if (r_address_count == 5'd23) begin
r_address_count <= 5'd0;
r_id_bit_count <= 6'd0;
r_output_quad <= 1'b0;
r_state <= ST_ID;
end else begin
r_address_count <= r_address_count + 1'b1;
end
end
ST_DUMMY: begin
if ((r_dummy_count + 1'b1) >= r_dummy_length) begin
r_dummy_count <= 4'd0;
r_output_bit_count <= 3'd0;
r_output_nibble_count <= 3'd0;
r_state <= ST_READ;
end else begin
r_dummy_count <= r_dummy_count + 1'b1;
end
end
ST_WRITE: begin
if ((r_command == 8'h02) && !r_qpi_mode) begin
r_write_shift <= {r_write_shift[6:0], sio[0]};
if (r_write_count == 3'd7) begin
if (w_device_present)
mem_array[memory_index(r_current_address)] <= {r_write_shift[6:0], sio[0]};
r_write_count <= 3'd0;
r_current_address <= next_address(r_current_address, r_wrap32);
end else begin
r_write_count <= r_write_count + 1'b1;
end
end else begin
if (r_write_count == 3'd0) begin
r_write_shift[7:4] <= sio;
r_write_count <= 3'd1;
end else begin
if (w_device_present)
mem_array[memory_index(r_current_address)] <= {r_write_shift[7:4], sio};
r_write_count <= 3'd0;
r_current_address <= next_address(r_current_address, r_wrap32);
end
end
end
ST_READ: begin
r_state <= ST_READ;
end
ST_ID: begin
r_state <= ST_ID;
end
default: begin
r_state <= ST_IDLE;
end
endcase
end else begin
if (TIMING_CHECK != 0 && r_have_sclk_posedge &&
(MIN_SCLK_HIGH_NS > 0) &&
(($time - r_last_sclk_posedge_time) < MIN_SCLK_HIGH_NS))
report_timing_violation();
r_last_sclk_negedge_time <= $time;
r_have_sclk_negedge <= 1'b1;
case (r_state)
ST_READ: begin
r_output_enable <= 1'b1;
if (r_output_quad) begin
if (r_output_nibble_count == 3'd0) begin
r_output_nibble <= mem_array[memory_index(r_current_address)][7:4];
r_output_nibble_count <= 3'd1;
end else begin
r_output_nibble <= mem_array[memory_index(r_current_address)][3:0];
r_output_nibble_count <= 3'd0;
r_current_address <= next_address(r_current_address, r_wrap32);
end
end else begin
r_output_bit <= mem_array[memory_index(r_current_address)][3'd7-r_output_bit_count];
if (r_output_bit_count == 3'd7) begin
r_output_bit_count <= 3'd0;
r_current_address <= next_address(r_current_address, r_wrap32);
end else begin
r_output_bit_count <= r_output_bit_count + 1'b1;
end
end
end
ST_ID: begin
r_output_enable <= 1'b1;
r_output_bit <= w_id_value[6'd47-r_id_bit_count];
if (r_id_bit_count == 6'd47) r_id_bit_count <= 6'd0;
else r_id_bit_count <= r_id_bit_count + 1'b1;
end
default: begin
r_output_enable <= 1'b0;
end
endcase
end
end
qspi_mode = 1'b0;
r_fsm_state = S_IDLE;
io_output_enable = 1'b0;
$display("PSRAM Model: Initialized memory with address pattern for device %0d.", ID);
end
// --- Debugging (Optional) ---
// Use $display statements to monitor state changes and data transfers.
// initial begin
// $monitor("Time: %0t, csn: %b, sclk: %b, sio[0]: %b, sio[1]: %b, sio[2]: %b, sio[3]: %b, State: %s, Cmd: %h, Addr: %h, BitCnt: %d, DummyCnt: %d, IO_OE: %b",
// $time, csn, sclk, sio[0], sio[1], sio[2], sio[3],
// (r_fsm_state == S_IDLE) ? "IDLE" :
// (r_fsm_state == S_CMD_QPI_RECV) ? "CMD_REC" :
// (r_fsm_state == S_ADDR_RECEIVE) ? "ADDR_REC" :
// (r_fsm_state == S_DUMMY_CYCLES) ? "DUMMY" :
// (r_fsm_state == S_DATA_XFER_READ) ? "READ" :
// (r_fsm_state == S_DATA_XFER_WRITE) ? "WRITE" : "UNKNOWN",
// command_byte, current_address, bit_counter, dummy_cycle_counter, io_output_enable);
// end
integer i;
/* verilator lint_off STMTDLY */
initial begin : POWER_UP_BLOCK
if ((POWER_UP_CHECK != 0) && (POWER_UP_TIME_NS > 0)) begin
#(POWER_UP_TIME_NS);
r_power_up_done = 1'b1;
end
end
/* verilator lint_on STMTDLY */
initial begin
r_state = ST_IDLE;
r_csn_active = 1'b0;
r_qpi_mode = 1'b0;
r_reset_armed = 1'b0;
r_wrap32 = (DEFAULT_WRAP32 != 0);
r_power_up_done = (POWER_UP_CHECK == 0) || (POWER_UP_TIME_NS <= 0);
r_timing_reported = 1'b0;
r_timing_injected = 1'b0;
r_command = 8'd0;
r_command_shift = 8'd0;
r_command_bit_count = 3'd0;
r_command_nibble_count = 1'b0;
r_address_shift = 24'd0;
r_current_address = 24'd0;
r_address_count = 5'd0;
r_dummy_count = 4'd0;
r_dummy_length = 4'd0;
r_write_shift = 8'd0;
r_write_count = 3'd0;
r_output_enable = 1'b0;
r_output_quad = 1'b0;
r_output_nibble = 4'd0;
r_output_bit = 1'b0;
r_output_bit_count = 3'd0;
r_output_nibble_count = 3'd0;
r_id_bit_count = 6'd0;
r_have_csn_rise = 1'b0;
r_have_csn_fall = 1'b0;
r_have_sclk_posedge = 1'b0;
r_have_sclk_negedge = 1'b0;
r_last_csn_rise_time = $time;
r_last_csn_fall_time = $time;
r_last_sclk_posedge_time = $time;
r_last_sclk_negedge_time = $time;
if (INITIALIZE_MEMORY != 0) begin
for (i = 0; i < MEMORY_BYTES; i = i + 1) mem_array[i] = INITIAL_MEMORY_VALUE;
end
$display("ESP_PSRAM64H[%0d]: %0d-byte model, SPI reset mode, QPI disabled", ID, MEMORY_BYTES);
if (INJECT_MISSING_DEVICE != 0)
$display("ESP_PSRAM64H[%0d]: missing-device fault injection enabled", ID);
else if (INJECT_FAILED_DEVICE != 0)
$display("ESP_PSRAM64H[%0d]: failed-device fault injection enabled", ID);
end
endmodule
+20 -4
View File
@@ -39,10 +39,12 @@ module retrosoc_tb;
wire s_psram_sck;
wire s_psram_nss0;
wire s_psram_nss1;
wire s_psram_dat0;
wire s_psram_dat1;
wire s_psram_dat2;
wire s_psram_dat3;
wire s_psram_nss2;
wire s_psram_nss3;
tri1 s_psram_dat0;
tri1 s_psram_dat1;
tri1 s_psram_dat2;
tri1 s_psram_dat3;
wire s_i2c0_sda_io;
wire s_i2c0_scl_io;
wire s_xpi_sck_o;
@@ -167,6 +169,20 @@ module retrosoc_tb;
.sio ({s_psram_dat3, s_psram_dat2, s_psram_dat1, s_psram_dat0})
);
pullup u_psram_nss2_pullup (s_psram_nss2);
ESP_PSRAM64H #(2) u_ESP_PSRAM64H_2 (
.sclk(s_psram_sck),
.csn (s_psram_nss2),
.sio ({s_psram_dat3, s_psram_dat2, s_psram_dat1, s_psram_dat0})
);
pullup u_psram_nss3_pullup (s_psram_nss3);
ESP_PSRAM64H #(3) u_ESP_PSRAM64H_3 (
.sclk(s_psram_sck),
.csn (s_psram_nss3),
.sio ({s_psram_dat3, s_psram_dat2, s_psram_dat1, s_psram_dat0})
);
mic #(16) u_mic (
.sck_i(s_i2s_sclk),
+42 -5
View File
@@ -37,10 +37,13 @@ module retrosoc_top (
wire s_rst_n;
wire s_psram_sck;
wire s_psram_nss0;
wire s_psram_dat0;
wire s_psram_dat1;
wire s_psram_dat2;
wire s_psram_dat3;
wire s_psram_nss1;
wire s_psram_nss2;
wire s_psram_nss3;
tri1 s_psram_dat0;
tri1 s_psram_dat1;
tri1 s_psram_dat2;
tri1 s_psram_dat3;
wire s_xpi_nss0_o;
wire s_xpi_sck_o;
wire s_xpi_dat0_io;
@@ -86,12 +89,46 @@ module retrosoc_top (
.io3(s_xpi_dat3_io)
);
ESP_PSRAM64H u_ESP_PSRAM64H (
ESP_PSRAM64H #(
.ID (0),
.POWER_UP_CHECK(0),
.TIMING_CHECK (0)
) u_ESP_PSRAM64H_0 (
.sclk(s_psram_sck),
.csn (s_psram_nss0),
.sio ({s_psram_dat3, s_psram_dat2, s_psram_dat1, s_psram_dat0})
);
ESP_PSRAM64H #(
.ID (1),
.POWER_UP_CHECK(0),
.TIMING_CHECK (0)
) u_ESP_PSRAM64H_1 (
.sclk(s_psram_sck),
.csn (s_psram_nss1),
.sio ({s_psram_dat3, s_psram_dat2, s_psram_dat1, s_psram_dat0})
);
ESP_PSRAM64H #(
.ID (2),
.POWER_UP_CHECK(0),
.TIMING_CHECK (0)
) u_ESP_PSRAM64H_2 (
.sclk(s_psram_sck),
.csn (s_psram_nss2),
.sio ({s_psram_dat3, s_psram_dat2, s_psram_dat1, s_psram_dat0})
);
ESP_PSRAM64H #(
.ID (3),
.POWER_UP_CHECK(0),
.TIMING_CHECK (0)
) u_ESP_PSRAM64H_3 (
.sclk(s_psram_sck),
.csn (s_psram_nss3),
.sio ({s_psram_dat3, s_psram_dat2, s_psram_dat1, s_psram_dat0})
);
sdram_verilator_model u_sdram_verilator_model (
.clk_i (s_sdram_clk),
.cke_i (s_sdram_cke),
+4
View File
@@ -1,4 +1,8 @@
/ip/memory/psram_pkg.sv
/ip/memory/psram_phy.sv
/ip/memory/psram_core.sv
/ip/memory/psram_axi4.sv
/ip/memory/psram_reg.sv
/ip/memory/ribp_psram.sv
/ip/memory/ribp_opipsram.sv
/ip/storage/spisd_init.sv
@@ -20,6 +20,7 @@ COMMON_RTL = ROOT / "rtl/managed/clusterip/common/rtl"
INTERCONNECT = ROOT / "rtl/ip/interconnect"
PERIPHERAL = ROOT / "rtl/ip/peripheral"
SERIAL = ROOT / "rtl/ip/serial"
MEMORY = ROOT / "rtl/ip/memory"
TOP = ROOT / "rtl/mini/top"
@@ -168,6 +169,15 @@ def source_files(target: str) -> list[Path]:
ROOT / "rtl/ip/multimedia/dvp_reg.sv",
SCRIPT_DIR / "dvp_formal.sv",
]
if target == "psram":
return [
COMMON_RTL / "interface/axi4_if.sv",
COMMON_RTL / "interface/axi4_addr_gen.sv",
MEMORY / "psram_pkg.sv",
MEMORY / "psram_axi4.sv",
MEMORY / "psram_phy.sv",
SCRIPT_DIR / "psram_formal.sv",
]
raise ValueError(f"unsupported formal target: {target}")
@@ -191,6 +201,7 @@ def generate(
INTERCONNECT,
PERIPHERAL,
SERIAL,
MEMORY,
],
files=source_files(target),
).deduplicate()
@@ -214,6 +225,7 @@ def parse_args() -> argparse.Namespace:
"timer",
"clint",
"dvp",
"psram",
),
required=True,
)
+239
View File
@@ -0,0 +1,239 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
module psram_formal_design (
// verilog_format: off -- preserve reviewed port alignment
input logic clk_i,
output logic rst_n_i,
output logic f_past_valid,
output logic awvalid,
output logic awready,
output logic [31:0] awaddr,
output logic [7:0] awlen,
output logic [2:0] awsize,
output logic [1:0] awburst,
output logic wvalid,
output logic wready,
output logic [31:0] wdata,
output logic [3:0] wstrb,
output logic wlast,
output logic bvalid,
output logic bready,
output logic [1:0] bresp,
output logic arvalid,
output logic arready,
output logic [31:0] araddr,
output logic [7:0] arlen,
output logic [2:0] arsize,
output logic [1:0] arburst,
output logic rvalid,
output logic rready,
output logic [31:0] rdata,
output logic [1:0] rresp,
output logic rlast,
output logic mem_req_valid,
output logic mem_req_ready,
output logic mem_req_write,
output logic [1:0] mem_req_chip,
output logic [22:0] mem_req_addr,
output logic [2:0] mem_req_len,
output logic [31:0] mem_req_wdata,
output logic phy_abort,
output logic phy_req_valid,
output logic phy_req_ready,
output logic [3:0] phy_req_cmd,
output logic [1:0] phy_req_chip,
output logic phy_req_qpi,
output logic [22:0] phy_req_addr,
output logic [5:0] phy_req_len,
output logic [63:0] phy_req_wdata,
output logic phy_busy,
output logic phy_done,
output logic phy_error,
output logic phy_sclk,
output logic [3:0] phy_nss,
output logic [3:0] phy_io_oe,
output logic [3:0] phy_io_do
// verilog_format: on
);
import psram_pkg::*;
axi4_if #(
.ADDR_WIDTH(32),
.DATA_WIDTH(32),
.ID_WIDTH (1),
.USER_WIDTH(1)
) axi4 (
.aclk (clk_i),
.aresetn(rst_n_i)
);
(* anyseq *)logic f_awvalid;
(* anyseq *)logic [31:0] f_awaddr;
(* anyseq *)logic [ 7:0] f_awlen;
(* anyseq *)logic [ 2:0] f_awsize;
(* anyseq *)logic [ 1:0] f_awburst;
(* anyseq *)logic f_awlock;
(* anyseq *)logic f_wvalid;
(* anyseq *)logic [31:0] f_wdata;
(* anyseq *)logic [ 3:0] f_wstrb;
(* anyseq *)logic f_wlast;
(* anyseq *)logic f_bready;
(* anyseq *)logic f_arvalid;
(* anyseq *)logic [31:0] f_araddr;
(* anyseq *)logic [ 7:0] f_arlen;
(* anyseq *)logic [ 2:0] f_arsize;
(* anyseq *)logic [ 1:0] f_arburst;
(* anyseq *)logic f_arlock;
(* anyseq *)logic f_rready;
(* anyseq *)logic f_mem_rsp_error;
(* anyseq *)logic [31:0] f_mem_rsp_rdata;
(* anyseq *)logic f_phy_abort;
(* anyseq *)logic f_phy_req_valid;
(* anyseq *)psram_cmd_e f_phy_req_cmd;
(* anyseq *)logic [ 1:0] f_phy_req_chip;
(* anyseq *)logic f_phy_req_qpi;
(* anyseq *)logic [22:0] f_phy_req_addr;
(* anyseq *)logic [ 5:0] f_phy_req_len;
(* anyseq *)logic [63:0] f_phy_req_wdata;
(* anyseq *)logic [ 3:0] f_phy_io_di;
logic f_mem_rsp_valid_q;
logic mem_rsp_ready;
assign axi4.awid = 1'b0;
assign axi4.awaddr = f_awaddr;
assign axi4.awlen = f_awlen;
assign axi4.awsize = f_awsize;
assign axi4.awburst = f_awburst;
assign axi4.awlock = f_awlock;
assign axi4.awcache = '0;
assign axi4.awprot = '0;
assign axi4.awqos = '0;
assign axi4.awregion = '0;
assign axi4.awuser = '0;
assign axi4.awvalid = f_awvalid;
assign axi4.wdata = f_wdata;
assign axi4.wstrb = f_wstrb;
assign axi4.wlast = f_wlast;
assign axi4.wuser = '0;
assign axi4.wvalid = f_wvalid;
assign axi4.bready = f_bready;
assign axi4.arid = 1'b0;
assign axi4.araddr = f_araddr;
assign axi4.arlen = f_arlen;
assign axi4.arsize = f_arsize;
assign axi4.arburst = f_arburst;
assign axi4.arlock = f_arlock;
assign axi4.arcache = '0;
assign axi4.arprot = '0;
assign axi4.arqos = '0;
assign axi4.arregion = '0;
assign axi4.aruser = '0;
assign axi4.arvalid = f_arvalid;
assign axi4.rready = f_rready;
assign awvalid = axi4.awvalid;
assign awready = axi4.awready;
assign awaddr = axi4.awaddr;
assign awlen = axi4.awlen;
assign awsize = axi4.awsize;
assign awburst = axi4.awburst;
assign wvalid = axi4.wvalid;
assign wready = axi4.wready;
assign wdata = axi4.wdata;
assign wstrb = axi4.wstrb;
assign wlast = axi4.wlast;
assign bvalid = axi4.bvalid;
assign bready = axi4.bready;
assign bresp = axi4.bresp;
assign arvalid = axi4.arvalid;
assign arready = axi4.arready;
assign araddr = axi4.araddr;
assign arlen = axi4.arlen;
assign arsize = axi4.arsize;
assign arburst = axi4.arburst;
assign rvalid = axi4.rvalid;
assign rready = axi4.rready;
assign rdata = axi4.rdata;
assign rresp = axi4.rresp;
assign rlast = axi4.rlast;
assign mem_req_ready = 1'b1;
psram_axi4 u_axi4 (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.accept_enable_i(1'b1),
.busy_o (),
.stall_event_o (),
.split_event_o (),
.axi4 (axi4),
.mem_req_valid_o(mem_req_valid),
.mem_req_ready_i(mem_req_ready),
.mem_req_write_o(mem_req_write),
.mem_req_chip_o (mem_req_chip),
.mem_req_addr_o (mem_req_addr),
.mem_req_len_o (mem_req_len),
.mem_req_wdata_o(mem_req_wdata),
.mem_rsp_valid_i(f_mem_rsp_valid_q),
.mem_rsp_ready_o(mem_rsp_ready),
.mem_rsp_error_i(f_mem_rsp_error),
.mem_rsp_rdata_i(f_mem_rsp_rdata)
);
assign phy_abort = f_phy_abort;
assign phy_req_valid = f_phy_req_valid;
assign phy_req_cmd = f_phy_req_cmd;
assign phy_req_chip = f_phy_req_chip;
assign phy_req_qpi = f_phy_req_qpi;
assign phy_req_addr = f_phy_req_addr;
assign phy_req_len = f_phy_req_len;
assign phy_req_wdata = f_phy_req_wdata;
psram_phy u_phy (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.abort_i (f_phy_abort),
.req_valid_i (f_phy_req_valid),
.req_ready_o (phy_req_ready),
.req_command_i (f_phy_req_cmd),
.req_chip_i (f_phy_req_chip),
.req_qpi_i (f_phy_req_qpi),
.req_addr_i (f_phy_req_addr),
.req_length_i (f_phy_req_len),
.req_wdata_i (f_phy_req_wdata),
.busy_o (phy_busy),
.done_o (phy_done),
.error_o (phy_error),
.rdata_o (),
.cfg_half_period_i (16'd1),
.cfg_cs_setup_i (16'd1),
.cfg_cs_high_i (16'd2),
.cfg_cs_hold_i (16'd2),
.cfg_cs_max_low_i (32'd200),
.cfg_access_timeout_i(32'd200),
.psram_sclk_o (phy_sclk),
.psram_nss_o (phy_nss),
.psram_io_oe_o (phy_io_oe),
.psram_io_di_i (f_phy_io_di),
.psram_io_do_o (phy_io_do)
);
initial begin
rst_n_i = 1'b0;
f_past_valid = 1'b0;
f_mem_rsp_valid_q = 1'b0;
end
always_ff @(posedge clk_i) begin
rst_n_i <= 1'b1;
f_past_valid <= 1'b1;
if (!rst_n_i) begin
f_mem_rsp_valid_q <= 1'b0;
end else begin
if (mem_req_valid && mem_req_ready) f_mem_rsp_valid_q <= 1'b1;
else if (f_mem_rsp_valid_q && mem_rsp_ready) f_mem_rsp_valid_q <= 1'b0;
end
end
endmodule
+141
View File
@@ -0,0 +1,141 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
module psram_formal;
(* anyseq *) (* gclk *)reg clk_i;
logic rst_n_i;
logic f_past_valid;
logic awvalid;
logic awready;
logic [31:0] awaddr;
logic [ 7:0] awlen;
logic [ 2:0] awsize;
logic [ 1:0] awburst;
logic wvalid;
logic wready;
logic [31:0] wdata;
logic [ 3:0] wstrb;
logic wlast;
logic bvalid;
logic bready;
logic [ 1:0] bresp;
logic arvalid;
logic arready;
logic [31:0] araddr;
logic [ 7:0] arlen;
logic [ 2:0] arsize;
logic [ 1:0] arburst;
logic rvalid;
logic rready;
logic [31:0] rdata;
logic [ 1:0] rresp;
logic rlast;
logic mem_req_valid;
logic mem_req_ready;
logic mem_req_write;
logic [ 1:0] mem_req_chip;
logic [22:0] mem_req_addr;
logic [ 2:0] mem_req_len;
logic [31:0] mem_req_wdata;
logic phy_abort;
logic phy_req_valid;
logic phy_req_ready;
logic [ 3:0] phy_req_cmd;
logic [ 1:0] phy_req_chip;
logic phy_req_qpi;
logic [22:0] phy_req_addr;
logic [ 5:0] phy_req_len;
logic [63:0] phy_req_wdata;
logic phy_busy;
logic phy_done;
logic phy_error;
logic phy_sclk;
logic [ 3:0] phy_nss;
logic [ 3:0] phy_io_oe;
logic [ 3:0] phy_io_do;
psram_formal_design u_design (.*);
always @(posedge clk_i) begin
if (f_past_valid && $past(rst_n_i)) begin
if ($past(awvalid && !awready)) begin
assume (awvalid);
assume (awaddr == $past(awaddr));
assume (awlen == $past(awlen));
assume (awsize == $past(awsize));
assume (awburst == $past(awburst));
end
if ($past(wvalid && !wready)) begin
assume (wvalid);
assume (wdata == $past(wdata));
assume (wstrb == $past(wstrb));
assume (wlast == $past(wlast));
end
if ($past(arvalid && !arready)) begin
assume (arvalid);
assume (araddr == $past(araddr));
assume (arlen == $past(arlen));
assume (arsize == $past(arsize));
assume (arburst == $past(arburst));
end
if ($past(bvalid && !bready)) begin
assert (bvalid);
assert (bresp == $past(bresp));
end
if ($past(rvalid && !rready)) begin
assert (rvalid);
assert (rdata == $past(rdata));
assert (rresp == $past(rresp));
assert (rlast == $past(rlast));
end
if ($past(mem_req_valid && !mem_req_ready)) begin
assert (mem_req_valid);
assert (mem_req_write == $past(mem_req_write));
assert (mem_req_chip == $past(mem_req_chip));
assert (mem_req_addr == $past(mem_req_addr));
assert (mem_req_len == $past(mem_req_len));
assert (mem_req_wdata == $past(mem_req_wdata));
end
if ($past(phy_req_valid && !phy_req_ready)) begin
assume (phy_req_valid);
assume (phy_req_cmd == $past(phy_req_cmd));
assume (phy_req_chip == $past(phy_req_chip));
assume (phy_req_qpi == $past(phy_req_qpi));
assume (phy_req_addr == $past(phy_req_addr));
assume (phy_req_len == $past(phy_req_len));
assume (phy_req_wdata == $past(phy_req_wdata));
end
if ($past(phy_abort && phy_busy)) begin
assert (phy_nss == 4'hF);
assert (!phy_sclk);
assert (phy_done);
assert (phy_error);
end
end
if (rst_n_i) begin
assume (phy_req_cmd <= 4'd10);
assume ((phy_req_len >= 6'd1) && (phy_req_len <= 6'd8));
assert (!((awvalid && awready) && (arvalid && arready)));
assert ((~phy_nss & ((~phy_nss) - 1'b1)) == 4'd0);
if (phy_done) begin
assert (phy_nss == 4'hF);
assert (!phy_sclk);
end
cover (awvalid && awready);
cover (bvalid && (bresp == 2'b00));
cover (bvalid && (bresp == 2'b10));
cover (arvalid && arready);
cover (rvalid && (rresp == 2'b00));
cover (rvalid && (rresp == 2'b10));
cover (mem_req_valid && mem_req_write);
cover (mem_req_valid && !mem_req_write);
cover (phy_done && !phy_error);
cover (phy_done && phy_error);
end
end
endmodule
+12 -4
View File
@@ -8,10 +8,11 @@ FORMAL_DEPTH ?= 20
FORMAL_WS2812_DEPTH ?= 120
FORMAL_I2C_DEPTH ?= 80
FORMAL_CLINT_DEPTH ?= 32
FORMAL_PSRAM_DEPTH ?= 32
FORMAL_TIMEOUT ?= 60
FORMAL_WS2812_TIMEOUT ?= 120
FORMAL_I2C_TIMEOUT ?= 300
FORMAL_TARGETS := bus rib_adapter rib2apb sysctrl pll_rcu gpio ws2812 uart i2c timer clint dvp
FORMAL_TARGETS := bus rib_adapter rib2apb sysctrl pll_rcu gpio ws2812 uart i2c timer clint dvp psram
FORMAL_FILELIST_GENERATOR := $(RTL_PATH)/formal/generate_formal_filelist.py
FORMAL_SBY_GENERATOR := $(RTL_PATH)/formal/generate_sby_config.py
FORMAL_RESULT_GENERATOR := $(RTL_PATH)/formal/formal_results.py
@@ -39,6 +40,8 @@ FORMAL_SOURCE_FILES := $(RTL_PATH)/formal/bus_formal.sv \
$(RTL_PATH)/formal/clint_formal_props.sv \
$(RTL_PATH)/formal/dvp_formal.sv \
$(RTL_PATH)/formal/dvp_formal_props.sv \
$(RTL_PATH)/formal/psram_formal.sv \
$(RTL_PATH)/formal/psram_formal_props.sv \
$(RTL_PATH)/top/rib_bus.sv \
$(RTL_PATH)/top/rib_error_slave.sv \
$(RTL_PATH)/top/rib_if.sv \
@@ -84,6 +87,9 @@ FORMAL_SOURCE_FILES := $(RTL_PATH)/formal/bus_formal.sv \
$(ROOT_PATH)/rtl/ip/multimedia/dvp_core.sv \
$(ROOT_PATH)/rtl/ip/multimedia/dvp_reg.sv \
$(ROOT_PATH)/rtl/ip/multimedia/axi4s_dvp.sv \
$(ROOT_PATH)/rtl/ip/memory/psram_pkg.sv \
$(ROOT_PATH)/rtl/ip/memory/psram_axi4.sv \
$(ROOT_PATH)/rtl/ip/memory/psram_phy.sv \
$(ROOT_PATH)/rtl/managed/clusterip/common/rtl/clkrst/counter.sv \
$(RTL_PATH)/top/rcu.sv \
$(RTL_PATH)/top/pll_rcu_controller.sv \
@@ -124,13 +130,13 @@ $(FORMAL_DIR)/%/prove.sby: $(FORMAL_DIR)/%/design.v $(RTL_PATH)/formal/%_formal_
$(FORMAL_SBY_GENERATOR)
python3 $(FORMAL_SBY_GENERATOR) --top $*_formal --input $< \
--properties $(RTL_PATH)/formal/$*_formal_props.sv --solver $(FORMAL_SOLVER) \
--mode prove --depth $(if $(filter ws2812,$*),$(FORMAL_WS2812_DEPTH),$(if $(filter i2c,$*),$(FORMAL_I2C_DEPTH),$(if $(filter clint,$*),$(FORMAL_CLINT_DEPTH),$(FORMAL_DEPTH)))) --output $@
--mode prove --depth $(if $(filter ws2812,$*),$(FORMAL_WS2812_DEPTH),$(if $(filter i2c,$*),$(FORMAL_I2C_DEPTH),$(if $(filter clint,$*),$(FORMAL_CLINT_DEPTH),$(if $(filter psram,$*),$(FORMAL_PSRAM_DEPTH),$(FORMAL_DEPTH))))) --output $@
$(FORMAL_DIR)/%/cover.sby: $(FORMAL_DIR)/%/design.v $(RTL_PATH)/formal/%_formal_props.sv \
$(FORMAL_SBY_GENERATOR)
python3 $(FORMAL_SBY_GENERATOR) --top $*_formal --input $< \
--properties $(RTL_PATH)/formal/$*_formal_props.sv --solver $(FORMAL_SOLVER) \
--mode cover --depth $(if $(filter ws2812,$*),$(FORMAL_WS2812_DEPTH),$(if $(filter i2c,$*),$(FORMAL_I2C_DEPTH),$(if $(filter clint,$*),$(FORMAL_CLINT_DEPTH),$(FORMAL_DEPTH)))) \
--mode cover --depth $(if $(filter ws2812,$*),$(FORMAL_WS2812_DEPTH),$(if $(filter i2c,$*),$(FORMAL_I2C_DEPTH),$(if $(filter clint,$*),$(FORMAL_CLINT_DEPTH),$(if $(filter psram,$*),$(FORMAL_PSRAM_DEPTH),$(FORMAL_DEPTH))))) \
$(if $(filter i2c,$*),--no-vcd) --output $@
$(FORMAL_SOLVER_WRAPPER): $(ROOT_PATH)/scripts/bitwuzla_smt2.py
@@ -191,10 +197,12 @@ formal-clint: $(FORMAL_DIR)/clint/.stamp | manifest
formal-dvp: $(FORMAL_DIR)/dvp/.stamp | manifest
formal-psram: $(FORMAL_DIR)/psram/.stamp | manifest
formal-doctor:
$(MAKE) FORMAL=YES SIMU=IVERILOG SYNTH=YOSYS STA=NONE doctor
formal-clean:
python3 $(ROOT_PATH)/scripts/clean.py --root $(ROOT_PATH) --path $(FORMAL_DIR)
.PHONY: formal formal-bus formal-rib-adapter formal-rib2apb formal-sysctrl formal-pll-rcu formal-gpio formal-ws2812 formal-uart formal-i2c formal-timer formal-clint formal-dvp formal-doctor formal-clean
.PHONY: formal formal-bus formal-rib-adapter formal-rib2apb formal-sysctrl formal-pll-rcu formal-gpio formal-ws2812 formal-uart formal-i2c formal-timer formal-clint formal-dvp formal-psram formal-doctor formal-clean
+1
View File
@@ -97,6 +97,7 @@ CRT_SRCS := $(ROOT_PATH)/crt/arch/riscv/startup.S \
$(ROOT_PATH)/crt/src/hal/dvp.c \
$(ROOT_PATH)/crt/src/hal/perf.c \
$(ROOT_PATH)/crt/src/hal/lcd.c \
$(ROOT_PATH)/crt/src/hal/psram_math.c \
$(ROOT_PATH)/crt/src/hal/psram.c \
$(ROOT_PATH)/crt/src/hal/spisd.c \
$(ROOT_PATH)/crt/src/hal/qspi.c \
+5
View File
@@ -233,6 +233,9 @@
"gpio_24_io_pad": "s_psram_dat1",
"gpio_25_io_pad": "s_psram_dat2",
"gpio_26_io_pad": "s_psram_dat3",
"gpio_27_io_pad": "s_psram_nss1",
"gpio_28_io_pad": "s_psram_nss2",
"gpio_29_io_pad": "s_psram_nss3",
"xpi_sck_o_pad": "s_xpi_sck_o",
"xpi_nss0_o_pad": "s_xpi_nss0_o",
"xpi_dat0_io_pad": "s_xpi_dat0_io",
@@ -302,6 +305,8 @@
"gpio_25_io_pad": "s_psram_dat2",
"gpio_26_io_pad": "s_psram_dat3",
"gpio_27_io_pad": "s_psram_nss1",
"gpio_28_io_pad": "s_psram_nss2",
"gpio_29_io_pad": "s_psram_nss3",
"uart0_tx_o_pad": "s_uart0_tx",
"uart0_rx_i_pad": "s_uart0_rx",
"xpi_sck_o_pad": "s_xpi_sck_o",
+5 -21
View File
@@ -54,8 +54,6 @@ module ip_ribp_wrapper (
`include "ribp_interfaces.svh"
ribp_if ribp ();
ribp_if u_psram_data_ribp_if ();
ribp_if u_psram_target_ribp_if ();
ribp_if u_xpi_data_ribp_if ();
ribp_if u_xpi_target_ribp_if ();
ribp_if u_spisd_data_ribp_if ();
@@ -106,13 +104,6 @@ module ip_ribp_wrapper (
.ribp (ribp)
);
axi42ribp_burst u_psram_axi42ribp (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.axi4 (psram_axi4),
.ribp (u_psram_data_ribp_if)
);
axi42ribp_burst u_xpi_axi42ribp (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
@@ -127,14 +118,6 @@ module ip_ribp_wrapper (
.ribp (u_spisd_data_ribp_if)
);
ribp_arbiter2 u_psram_ribp_arbiter (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.cfg (u_psram_ribp_if),
.data (u_psram_data_ribp_if),
.target (u_psram_target_ribp_if)
);
ribp_arbiter2 u_xpi_ribp_arbiter (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
@@ -220,10 +203,11 @@ module ip_ribp_wrapper (
);
ribp_psram u_rib_psram (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.ribp (u_psram_target_ribp_if),
.psram (psram)
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.cfg_ribp(u_psram_ribp_if),
.mem_axi4(psram_axi4),
.psram (psram)
);
ribp_spisd u_rib_spisd (
+5
View File
@@ -88,7 +88,12 @@
],
"rtl/ip/memory": [
"axi4_sdram.sv",
"psram_axi4.sv",
"psram_core.sv",
"psram_define.svh",
"psram_phy.sv",
"psram_pkg.sv",
"psram_reg.sv",
"ribp_opipsram.sv",
"ribp_psram.sv",
"sdram_clkgen.sv",
+1
View File
@@ -25,6 +25,7 @@ TEST_SOURCES = (
"crt/src/hal/ws2812_math.c",
"crt/src/hal/uart_math.c",
"crt/src/hal/timer_math.c",
"crt/src/hal/psram_math.c",
"rtl/managed/clusterip/ps2/sw/src/ps2.c",
"rtl/managed/clusterip/ps2/sw/src/ps2_keyboard.c",
"rtl/managed/clusterip/ps2/sw/src/ps2_mouse.c",
+32 -6
View File
@@ -9,6 +9,7 @@
#include <retrosoc/hal/gpio.h>
#include <retrosoc/hal/i2c.h>
#include <retrosoc/hal/lcd.h>
#include <retrosoc/hal/psram.h>
#include <retrosoc/hal/spisd.h>
#include <retrosoc/hal/timer.h>
#include <retrosoc/hal/uart.h>
@@ -182,6 +183,7 @@ static int test_timer_helpers(void) {
(period.load != 71999U)) {
return 1;
}
if ((rs_timer_period_from_ms(72000000U, 1000U, &period) != RS_OK) || (period.prescale != 0U) ||
(period.load != 71999999U)) {
return 2;
@@ -195,6 +197,30 @@ static int test_timer_helpers(void) {
return 0;
}
static int test_psram_helpers(void) {
rs_psram_timing_t timing;
if ((rs_psram_timing_from_hz(72000000U, 36000000U, &timing) != RS_OK) ||
(timing.half_period_cycles != 1U) || (timing.cs_setup_cycles != 1U) ||
(timing.cs_high_cycles != 4U) || (timing.cs_hold_cycles != 3U) ||
(timing.powerup_cycles != 10800U) || (timing.cs_max_low_cycles != 576U) ||
(timing.actual_sclk_hz != 36000000U) || timing.above_84mhz) {
return 1;
}
if ((rs_psram_timing_from_hz(192000000U, 133000000U, &timing) != RS_OK) ||
(timing.half_period_cycles != 1U) || (timing.actual_sclk_hz != 96000000U) ||
!timing.above_84mhz) {
return 2;
}
if ((rs_psram_timing_from_hz(0U, 36000000U, &timing) != RS_EINVAL) ||
(rs_psram_timing_from_hz(72000000U, 0U, &timing) != RS_EINVAL) ||
(rs_psram_timing_from_hz(72000000U, 134000000U, &timing) != RS_EINVAL) ||
(rs_psram_timing_from_hz(72000000U, 36000000U, NULL) != RS_EINVAL)) {
return 3;
}
return 0;
}
static int test_uart_helpers(void) {
rs_uart_timing_t timing;
@@ -267,8 +293,8 @@ static int test_ps2_decoders(void) {
ps2_mouse_event_t mouse_event;
ps2_keyboard_decoder_init(&keyboard);
if (!ps2_keyboard_decode_byte(&keyboard, UINT8_C(0x1C), &key_event) ||
!key_event.pressed || key_event.extended || (key_event.scan_code != UINT16_C(0x001C))) {
if (!ps2_keyboard_decode_byte(&keyboard, UINT8_C(0x1C), &key_event) || !key_event.pressed ||
key_event.extended || (key_event.scan_code != UINT16_C(0x001C))) {
return 1;
}
if (ps2_keyboard_decode_byte(&keyboard, UINT8_C(0xE0), &key_event) ||
@@ -282,8 +308,8 @@ static int test_ps2_decoders(void) {
if (ps2_mouse_decode_byte(&mouse, UINT8_C(0x08), &mouse_event) ||
ps2_mouse_decode_byte(&mouse, UINT8_C(0x02), &mouse_event) ||
ps2_mouse_decode_byte(&mouse, UINT8_C(0x01), &mouse_event) ||
!ps2_mouse_decode_byte(&mouse, UINT8_C(0x3F), &mouse_event) ||
(mouse_event.dx != 2) || (mouse_event.dy != 1) || (mouse_event.wheel != -1) ||
!ps2_mouse_decode_byte(&mouse, UINT8_C(0x3F), &mouse_event) || (mouse_event.dx != 2) ||
(mouse_event.dy != 1) || (mouse_event.wheel != -1) ||
((mouse_event.buttons & UINT8_C(0x18)) != UINT8_C(0x18))) {
return 3;
}
@@ -330,8 +356,8 @@ static int test_video_parser(void) {
int main(void) {
const int results[] = {
test_string_helpers(), test_formatter(), test_compiler_helpers(), test_wait_helper(),
test_ws2812_helpers(), test_timer_helpers(), test_uart_helpers(), test_i2c_helpers(),
test_gpio_helpers(), test_ps2_decoders(), test_wav_parser(),
test_ws2812_helpers(), test_timer_helpers(), test_psram_helpers(), test_uart_helpers(),
test_i2c_helpers(), test_gpio_helpers(), test_ps2_decoders(), test_wav_parser(),
test_video_parser(),
};
+437
View File
@@ -0,0 +1,437 @@
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
`timescale 1ns / 1ps
`include "axi4_define.svh"
`include "psram_define.svh"
module psram_tb;
localparam logic [31:0] PsramBase = 32'h4000_0000;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
logic [31:0] reg_data;
logic [31:0] read_data;
logic [31:0] held_data;
logic [ 1:0] held_resp;
logic held_last;
ribp_if cfg_ribp ();
axi4_if #(
.ADDR_WIDTH(32),
.DATA_WIDTH(32),
.ID_WIDTH (1),
.USER_WIDTH(1)
) mem_axi4 (
.aclk (clk_i),
.aresetn(rst_n_i)
);
psram_if psram ();
tri1 [3:0] psram_sio;
always #5 clk_i = ~clk_i;
for (genvar io_index = 0; io_index < 4; io_index++) begin : gen_psram_io
assign psram_sio[io_index] = psram.io_oe_o[io_index] ? psram.io_do_o[io_index] : 1'bz;
end
assign psram.io_di_i = psram_sio;
ribp_psram u_dut (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.cfg_ribp(cfg_ribp),
.mem_axi4(mem_axi4),
.psram (psram)
);
ESP_PSRAM64H #(
.ID (0),
.MEMORY_BYTES (4096),
.INITIALIZE_MEMORY(1),
.POWER_UP_CHECK (0),
.TIMING_CHECK (0)
) u_psram0 (
.sclk(psram.sck_o),
.csn (psram.nss_o[0]),
.sio (psram_sio)
);
ESP_PSRAM64H #(
.ID (1),
.MEMORY_BYTES (4096),
.INITIALIZE_MEMORY(1),
.POWER_UP_CHECK (0),
.TIMING_CHECK (0)
) u_psram1 (
.sclk(psram.sck_o),
.csn (psram.nss_o[1]),
.sio (psram_sio)
);
ESP_PSRAM64H #(
.ID (2),
.MEMORY_BYTES (4096),
.INITIALIZE_MEMORY (1),
.POWER_UP_CHECK (0),
.TIMING_CHECK (0),
.INJECT_MISSING_DEVICE(1)
) u_psram2 (
.sclk(psram.sck_o),
.csn (psram.nss_o[2]),
.sio (psram_sio)
);
ESP_PSRAM64H #(
.ID (3),
.MEMORY_BYTES (4096),
.INITIALIZE_MEMORY(1),
.POWER_UP_CHECK (0),
.TIMING_CHECK (0)
) u_psram3 (
.sclk(psram.sck_o),
.csn (psram.nss_o[3]),
.sio (psram_sio)
);
task automatic init_axi4;
begin
mem_axi4.awid = '0;
mem_axi4.awaddr = '0;
mem_axi4.awlen = '0;
mem_axi4.awsize = `AXI4_BURST_SIZE_4BYTES;
mem_axi4.awburst = `AXI4_BURST_TYPE_INCR;
mem_axi4.awlock = `AXI4_LOCK_NORM;
mem_axi4.awcache = `AXI4_CACHE_NO_BUF;
mem_axi4.awprot = `AXI4_PROT_DATA;
mem_axi4.awqos = `AXI4_QOS_NORMAL;
mem_axi4.awregion = `AXI4_REGION_NORMAL;
mem_axi4.awuser = '0;
mem_axi4.awvalid = 1'b0;
mem_axi4.wdata = '0;
mem_axi4.wstrb = '0;
mem_axi4.wlast = 1'b0;
mem_axi4.wuser = '0;
mem_axi4.wvalid = 1'b0;
mem_axi4.bready = 1'b0;
mem_axi4.arid = '0;
mem_axi4.araddr = '0;
mem_axi4.arlen = '0;
mem_axi4.arsize = `AXI4_BURST_SIZE_4BYTES;
mem_axi4.arburst = `AXI4_BURST_TYPE_INCR;
mem_axi4.arlock = `AXI4_LOCK_NORM;
mem_axi4.arcache = `AXI4_CACHE_NO_BUF;
mem_axi4.arprot = `AXI4_PROT_DATA;
mem_axi4.arqos = `AXI4_QOS_NORMAL;
mem_axi4.arregion = `AXI4_REGION_NORMAL;
mem_axi4.aruser = '0;
mem_axi4.arvalid = 1'b0;
mem_axi4.rready = 1'b0;
end
endtask
task automatic ribp_write(input logic [31:0] offset, input logic [31:0] data,
input logic [3:0] strobe, input logic expected_error);
begin
@(negedge clk_i);
cfg_ribp.addr = offset;
cfg_ribp.wdata = data;
cfg_ribp.wstrb = strobe;
cfg_ribp.valid = 1'b1;
do @(negedge clk_i); while (!cfg_ribp.ready);
if (cfg_ribp.resp_err != expected_error) begin
$fatal(1, "RIBP write response mismatch at %08x", offset);
end
cfg_ribp.valid = 1'b0;
cfg_ribp.wstrb = '0;
@(negedge clk_i);
end
endtask
task automatic ribp_read(input logic [31:0] offset, input logic expected_error,
output logic [31:0] data);
begin
@(negedge clk_i);
cfg_ribp.addr = offset;
cfg_ribp.wdata = '0;
cfg_ribp.wstrb = '0;
cfg_ribp.valid = 1'b1;
do @(negedge clk_i); while (!cfg_ribp.ready);
if (cfg_ribp.resp_err != expected_error) begin
$fatal(1, "RIBP read response mismatch at %08x", offset);
end
data = cfg_ribp.rdata;
cfg_ribp.valid = 1'b0;
@(negedge clk_i);
end
endtask
task automatic wait_for_init;
begin
reg_data = '0;
for (int poll = 0; poll < 200; poll++) begin
repeat (20) @(posedge clk_i);
ribp_read(`RIBP_PSRAM_INTR_STATE, 1'b0, reg_data);
if (reg_data[`PSRAM_INTR_INIT_DONE]) return;
end
$fatal(1, "PSRAM initialization timed out");
end
endtask
task automatic wait_for_indirect;
begin
reg_data = '0;
for (int poll = 0; poll < 100; poll++) begin
repeat (10) @(posedge clk_i);
ribp_read(`RIBP_PSRAM_INTR_STATE, 1'b0, reg_data);
if (reg_data[`PSRAM_INTR_INDIRECT_DONE]) return;
end
$fatal(1, "PSRAM indirect command timed out");
end
endtask
task automatic axi_write_single(input logic [31:0] address, input logic [2:0] size,
input logic [31:0] data, input logic [3:0] strobe,
input logic [1:0] expected_response);
begin
@(negedge clk_i);
mem_axi4.awaddr = address;
mem_axi4.awlen = 8'd0;
mem_axi4.awsize = size;
mem_axi4.awburst = `AXI4_BURST_TYPE_INCR;
mem_axi4.awvalid = 1'b1;
do @(posedge clk_i); while (!mem_axi4.awready);
@(negedge clk_i);
mem_axi4.awvalid = 1'b0;
mem_axi4.wdata = data;
mem_axi4.wstrb = strobe;
mem_axi4.wlast = 1'b1;
mem_axi4.wvalid = 1'b1;
do @(posedge clk_i); while (!mem_axi4.wready);
@(negedge clk_i);
mem_axi4.wvalid = 1'b0;
do @(negedge clk_i); while (!mem_axi4.bvalid);
if (mem_axi4.bresp != expected_response) begin
$fatal(1, "AXI write response mismatch at %08x", address);
end
mem_axi4.bready = 1'b1;
@(posedge clk_i);
@(negedge clk_i);
mem_axi4.bready = 1'b0;
mem_axi4.wlast = 1'b0;
@(negedge clk_i);
end
endtask
task automatic axi_read_single(input logic [31:0] address, input logic [2:0] size,
input logic [1:0] expected_response, output logic [31:0] data);
begin
@(negedge clk_i);
mem_axi4.araddr = address;
mem_axi4.arlen = 8'd0;
mem_axi4.arsize = size;
mem_axi4.arburst = `AXI4_BURST_TYPE_INCR;
mem_axi4.arvalid = 1'b1;
do @(posedge clk_i); while (!mem_axi4.arready);
@(negedge clk_i);
mem_axi4.arvalid = 1'b0;
do @(negedge clk_i); while (!mem_axi4.rvalid);
held_data = mem_axi4.rdata;
held_resp = mem_axi4.rresp;
held_last = mem_axi4.rlast;
repeat (3) begin
@(negedge clk_i);
if (!mem_axi4.rvalid || (mem_axi4.rdata != held_data) ||
(mem_axi4.rresp != held_resp) || (mem_axi4.rlast != held_last)) begin
$fatal(1, "AXI read response changed under backpressure");
end
end
if ((held_resp != expected_response) || !held_last) begin
$fatal(1, "AXI read response mismatch at %08x", address);
end
data = held_data;
mem_axi4.rready = 1'b1;
@(negedge clk_i);
mem_axi4.rready = 1'b0;
end
endtask
task automatic axi_write_burst4(input logic [31:0] address);
begin
@(negedge clk_i);
mem_axi4.awaddr = address;
mem_axi4.awlen = 8'd3;
mem_axi4.awsize = `AXI4_BURST_SIZE_4BYTES;
mem_axi4.awburst = `AXI4_BURST_TYPE_INCR;
mem_axi4.awvalid = 1'b1;
do @(posedge clk_i); while (!mem_axi4.awready);
@(negedge clk_i);
mem_axi4.awvalid = 1'b0;
for (int beat = 0; beat < 4; beat++) begin
mem_axi4.wdata = 32'hA500_1000 + beat;
mem_axi4.wstrb = 4'hF;
mem_axi4.wlast = beat == 3;
mem_axi4.wvalid = 1'b1;
do @(posedge clk_i); while (!mem_axi4.wready);
@(negedge clk_i);
mem_axi4.wvalid = 1'b0;
end
do @(negedge clk_i); while (!mem_axi4.bvalid);
if (mem_axi4.bresp != `AXI4_RESP_OKAY) $fatal(1, "AXI burst write failed");
mem_axi4.bready = 1'b1;
@(posedge clk_i);
@(negedge clk_i);
mem_axi4.bready = 1'b0;
mem_axi4.wlast = 1'b0;
@(negedge clk_i);
end
endtask
task automatic axi_read_burst4(input logic [31:0] address);
begin
@(negedge clk_i);
mem_axi4.araddr = address;
mem_axi4.arlen = 8'd3;
mem_axi4.arsize = `AXI4_BURST_SIZE_4BYTES;
mem_axi4.arburst = `AXI4_BURST_TYPE_INCR;
mem_axi4.arvalid = 1'b1;
do @(posedge clk_i); while (!mem_axi4.arready);
@(negedge clk_i);
mem_axi4.arvalid = 1'b0;
for (int beat = 0; beat < 4; beat++) begin
if (beat == 1) repeat (3) @(negedge clk_i);
do @(negedge clk_i); while (!mem_axi4.rvalid);
if ((mem_axi4.rdata != (32'hA500_1000 + beat)) ||
(mem_axi4.rresp != `AXI4_RESP_OKAY) ||
(mem_axi4.rlast != (beat == 3))) begin
$fatal(1, "AXI burst read beat %0d failed", beat);
end
mem_axi4.rready = 1'b1;
@(posedge clk_i);
@(negedge clk_i);
mem_axi4.rready = 1'b0;
end
end
endtask
task automatic axi_illegal_read;
integer beat;
begin
@(negedge clk_i);
mem_axi4.araddr = PsramBase + 32'h400;
mem_axi4.arlen = 8'd16;
mem_axi4.arsize = `AXI4_BURST_SIZE_4BYTES;
mem_axi4.arburst = `AXI4_BURST_TYPE_INCR;
mem_axi4.arvalid = 1'b1;
do @(posedge clk_i); while (!mem_axi4.arready);
@(negedge clk_i);
mem_axi4.arvalid = 1'b0;
beat = 0;
while (beat < 17) begin
do @(negedge clk_i); while (!mem_axi4.rvalid);
if ((mem_axi4.rresp != `AXI4_RESP_SLAVE_ERROR) || (mem_axi4.rlast != (beat == 16))) begin
$fatal(1, "illegal AXI burst response mismatch");
end
mem_axi4.rready = 1'b1;
@(posedge clk_i);
@(negedge clk_i);
mem_axi4.rready = 1'b0;
beat = beat + 1;
end
end
endtask
initial begin
cfg_ribp.valid = 1'b0;
cfg_ribp.addr = '0;
cfg_ribp.wdata = '0;
cfg_ribp.wstrb = '0;
init_axi4();
repeat (5) @(posedge clk_i);
rst_n_i = 1'b1;
$display("PSRAM_TB_STAGE register_setup");
ribp_write(`RIBP_PSRAM_POWERUP_CYCLES, 32'd1, 4'hF, 1'b0);
ribp_write(`RIBP_PSRAM_CS_HIGH_CYCLES, 32'd2, 4'hF, 1'b0);
ribp_write(`RIBP_PSRAM_CS_HOLD_CYCLES, 32'd3, 4'hF, 1'b0);
ribp_write(`RIBP_PSRAM_CS_MAX_LOW_CYCLES, 32'd512, 4'hF, 1'b0);
ribp_write(`RIBP_PSRAM_ACCESS_TIMEOUT_CYCLES, 32'd1024, 4'hF, 1'b0);
ribp_write(`RIBP_PSRAM_PERF_CTRL, 32'd1, 4'h1, 1'b0);
ribp_write(`RIBP_PSRAM_STATUS, 32'd1, 4'h1, 1'b1);
ribp_read(32'h0000_0002, 1'b1, reg_data);
ribp_read(32'h0000_00B0, 1'b1, reg_data);
ribp_write(`RIBP_PSRAM_COMMAND, 32'h0000_0003, 4'h1, 1'b1);
$display("PSRAM_TB_STAGE initialization");
ribp_write(`RIBP_PSRAM_COMMAND, 32'h0000_0001, 4'h1, 1'b0);
wait_for_init();
ribp_read(`RIBP_PSRAM_CHIP_PRESENT, 1'b0, reg_data);
if (reg_data[3:0] != 4'b1011) $fatal(1, "unexpected present mask");
ribp_read(`RIBP_PSRAM_CHIP_READY, 1'b0, reg_data);
if (reg_data[3:0] != 4'b1011) $fatal(1, "unexpected ready mask");
ribp_read(`RIBP_PSRAM_CHIP_ERROR, 1'b0, reg_data);
if (reg_data[3:0] != 4'b0100) $fatal(1, "missing-device error not isolated");
ribp_read(`RIBP_PSRAM_CHIP0_ID_LO, 1'b0, reg_data);
if (reg_data != 32'h0026_5D0D) $fatal(1, "chip ID mismatch");
ribp_read(`RIBP_PSRAM_CHIP_MODE, 1'b0, reg_data);
if (reg_data[3:0] != 4'b1011) $fatal(1, "QPI mode mask mismatch");
$display("PSRAM_TB_STAGE basic_axi");
axi_write_single(PsramBase + 32'h100, `AXI4_BURST_SIZE_4BYTES, 32'hDEAD_BEEF, 4'hF,
`AXI4_RESP_OKAY);
axi_read_single(PsramBase + 32'h100, `AXI4_BURST_SIZE_4BYTES, `AXI4_RESP_OKAY, read_data);
if (read_data != 32'hDEAD_BEEF) $fatal(1, "word readback mismatch");
axi_write_single(PsramBase + 32'h101, `AXI4_BURST_SIZE_1BYTE, 32'h0000_AA00, 4'h2,
`AXI4_RESP_OKAY);
axi_read_single(PsramBase + 32'h100, `AXI4_BURST_SIZE_4BYTES, `AXI4_RESP_OKAY, read_data);
if (read_data != 32'hDEAD_AAEF) $fatal(1, "byte write address or lane mismatch");
axi_write_single(PsramBase + 32'h104, `AXI4_BURST_SIZE_4BYTES, 32'd0, 4'hF, `AXI4_RESP_OKAY);
axi_write_single(PsramBase + 32'h104, `AXI4_BURST_SIZE_4BYTES, 32'h4433_2211, 4'b0101,
`AXI4_RESP_OKAY);
axi_read_single(PsramBase + 32'h104, `AXI4_BURST_SIZE_4BYTES, `AXI4_RESP_OKAY, read_data);
if (read_data != 32'h0033_0011) $fatal(1, "masked write mismatch");
$display("PSRAM_TB_STAGE burst_axi");
axi_write_burst4(PsramBase + 32'h200);
axi_read_burst4(PsramBase + 32'h200);
axi_illegal_read();
axi_read_single(PsramBase + 32'h0100_0000, `AXI4_BURST_SIZE_4BYTES, `AXI4_RESP_SLAVE_ERROR,
read_data);
axi_write_single(PsramBase + 32'h0180_0300, `AXI4_BURST_SIZE_4BYTES, 32'hC001_CAFE, 4'hF,
`AXI4_RESP_OKAY);
axi_read_single(PsramBase + 32'h0180_0300, `AXI4_BURST_SIZE_4BYTES, `AXI4_RESP_OKAY, read_data);
if (read_data != 32'hC001_CAFE) $fatal(1, "healthy chip after missing device failed");
$display("PSRAM_TB_STAGE indirect");
ribp_write(`RIBP_PSRAM_INTR_STATE, 32'hF, 4'h1, 1'b0);
ribp_write(`RIBP_PSRAM_INDIRECT_CTRL, 32'h8000_0009, 4'hF, 1'b0);
wait_for_indirect();
ribp_read(`RIBP_PSRAM_CHIP_MODE, 1'b0, reg_data);
if (!reg_data[4]) $fatal(1, "wrap32 mode did not update");
$display("PSRAM_TB_STAGE timeout");
ribp_write(`RIBP_PSRAM_CS_MAX_LOW_CYCLES, 32'd20, 4'hF, 1'b0);
axi_read_single(PsramBase + 32'h0080_0800, `AXI4_BURST_SIZE_4BYTES, `AXI4_RESP_SLAVE_ERROR,
read_data);
ribp_read(`RIBP_PSRAM_CHIP_READY, 1'b0, reg_data);
if (reg_data[3:0] != 4'b1001) $fatal(1, "timeout fault was not isolated");
ribp_write(`RIBP_PSRAM_CS_MAX_LOW_CYCLES, 32'd512, 4'hF, 1'b0);
axi_read_single(PsramBase + 32'h0180_0300, `AXI4_BURST_SIZE_4BYTES, `AXI4_RESP_OKAY, read_data);
if (read_data != 32'hC001_CAFE) $fatal(1, "healthy chip failed after timeout");
$display("PSRAM_TB_STAGE counters");
ribp_read(`RIBP_PSRAM_PERF_READ_BYTES, 1'b0, reg_data);
if (reg_data == 32'd0) $fatal(1, "read performance counter did not advance");
ribp_read(`RIBP_PSRAM_PERF_WRITE_BYTES, 1'b0, reg_data);
if (reg_data == 32'd0) $fatal(1, "write performance counter did not advance");
ribp_read(`RIBP_PSRAM_PERF_COMMANDS, 1'b0, reg_data);
if (reg_data == 32'd0) $fatal(1, "command performance counter did not advance");
$display("PSRAM controller integration test passed");
$finish;
end
endmodule
+3 -3
View File
@@ -50,12 +50,12 @@ def test_generated_artifacts_share_the_capacity_baseline(tmp_path: Path) -> None
header = (tmp_path / "include/retrosoc/generated/memory_map.h").read_text(encoding="utf-8")
linker = (tmp_path / "linker/memory_regions.ld").read_text(encoding="utf-8")
assert "`define SOC_ADDR_PSRAM_END 32'h407FFFFF" in rtl
assert "`define SOC_ADDR_PSRAM_END 32'h41FFFFFF" in rtl
assert "`define SOC_ADDR_XPI_END 32'h5FFFFFFF" in rtl
assert "`define SOC_ADDR_IS_FLASH(addr) ((addr) <= `SOC_ADDR_FLASH_END)" in rtl
assert "`define CPU_RESET_ADDR `SOC_CPU_RESET_ADDR" in rtl
assert "`define SOC_ADDR_IS_RESERVED(addr)" in rtl
assert "RS_SOC_PSRAM_SIZE UINT32_C(0x00800000)" in header
assert "RS_SOC_PSRAM_SIZE UINT32_C(0x02000000)" in header
assert "RS_SOC_SDRAM_SIZE UINT32_C(0x04000000)" in header
assert "RS_SOC_SPISD_SIZE UINT32_C(0x40000000)" in header
assert "#ifdef __ASSEMBLER__" in header
@@ -78,7 +78,7 @@ def test_generated_artifacts_share_the_capacity_baseline(tmp_path: Path) -> None
assert "RS_SOC_SYSCTRL_PLL_STATUS_OFFSET UINT32_C(0x0000001C)" in header
assert "RS_SOC_SYSCTRL_TEST_STATUS_OFFSET UINT32_C(0x00000084)" in header
assert "RS_SOC_SYSCTRL_RTC_WAKE_STATUS_OFFSET UINT32_C(0x00000088)" in header
assert "PSRAM (wxa!ri) : ORIGIN = 0x40000000, LENGTH = 0x00800000" in linker
assert "PSRAM (wxa!ri) : ORIGIN = 0x40000000, LENGTH = 0x02000000" in linker
def test_user_ip_is_always_emitted_for_the_fixed_platform(tmp_path: Path) -> None:
+100
View File
@@ -0,0 +1,100 @@
"""Directed tests for the ESP-PSRAM64H controller and its handwritten ABI."""
from __future__ import annotations
import os
import re
import shutil
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def test_psram_controller_data_integrity_and_fault_isolation(tmp_path: Path) -> None:
verilator = shutil.which("verilator")
if verilator is None:
return
generated = tmp_path / "generated"
subprocess.run(
[
sys.executable,
str(ROOT / "rtl/mini/address_map/generate_memory_map.py"),
"--map",
str(ROOT / "rtl/mini/address_map/memory_map.json"),
"--output-dir",
str(generated),
"--have-sram-if",
"NO",
],
check=True,
)
output = tmp_path / "psram_tb"
ccache_tmp = tmp_path / "ccache"
ccache_tmp.mkdir()
subprocess.run(
[
verilator,
"--binary",
"--timing",
"-Wno-fatal",
"--top-module",
"psram_tb",
"-I" + str(generated / "rtl"),
"-I" + str(ROOT / "rtl/ip/memory"),
"-I" + str(ROOT / "rtl/managed/clusterip/common/rtl"),
"-I" + str(ROOT / "rtl/managed/clusterip/common/rtl/interface"),
str(ROOT / "rtl/managed/clusterip/common/rtl/interface/axi4_if.sv"),
str(ROOT / "rtl/managed/clusterip/common/rtl/interface/ribp_if.sv"),
str(ROOT / "rtl/managed/clusterip/common/rtl/interface/axi4_addr_gen.sv"),
str(ROOT / "rtl/ip/memory/psram_pkg.sv"),
str(ROOT / "rtl/ip/memory/psram_axi4.sv"),
str(ROOT / "rtl/ip/memory/psram_core.sv"),
str(ROOT / "rtl/ip/memory/psram_phy.sv"),
str(ROOT / "rtl/ip/memory/psram_reg.sv"),
str(ROOT / "rtl/ip/memory/ribp_psram.sv"),
str(ROOT / "rtl/mini/dv/model/ESP_PSRAM64H.sv"),
str(ROOT / "tests/rtl/psram_tb.sv"),
"-Mdir",
str(tmp_path / "obj"),
"-o",
str(output),
],
check=True,
text=True,
capture_output=True,
env={
**os.environ,
"CCACHE_DIR": str(ccache_tmp),
"CCACHE_TEMPDIR": str(ccache_tmp),
},
)
result = subprocess.run([output], check=True, text=True, capture_output=True)
assert "PSRAM controller integration test passed" in result.stdout
def test_psram_handwritten_register_offsets_match_hal() -> None:
rtl_text = (ROOT / "rtl/ip/memory/psram_define.svh").read_text(encoding="utf-8")
hal_text = (ROOT / "crt/src/hal/psram.c").read_text(encoding="utf-8")
rtl_offsets = {
name: int(value, 16)
for name, value in re.findall(
r"`define\s+RIBP_PSRAM_([A-Z0-9_]+)\s+12'h([0-9A-Fa-f]+)",
rtl_text,
)
}
hal_offsets = {
name: int(value, 16)
for name, value in re.findall(
r"#define\s+RS_PSRAM_([A-Z0-9_]+)_OFFSET\s+UINT32_C\(0x([0-9A-Fa-f]+)\)",
hal_text,
)
}
assert hal_offsets
for name, value in hal_offsets.items():
rtl_name = "CHIP0_ID_LO" if name == "CHIP_ID_BASE" else name
assert rtl_offsets[rtl_name] == value