mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
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:
36 files changed
+4685
-1041
No files matched your search
+9
-10
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 *)®_psram_wait);
|
||||
printf(" 1 x PSRAM @%p\n",
|
||||
(void *)(uintptr_t)RS_SOC_RIBP_PSRAM_BASE);
|
||||
printf(" 1 x SPISD @%p\n", (void *)®_spisd_mode);
|
||||
printf(" 1 x I2C0 @%p\n",
|
||||
(void *)(uintptr_t)RS_SOC_RIBP_I2C0_BASE);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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 \
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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(),
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user