mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-06 14:55:14 +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
+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