feat: add GPIO V2 support and related tests

- Updated  to include  for testing.
- Modified  to include GPIO header and added tests for GPIO functionality.
- Created a new testbench  for GPIO V2, covering register and pad-control contracts.
- Removed obsolete  and its associated test.
- Updated  to include GPIO admin base routing.
- Added a new Python test  for GPIO V2 register and pad-control contract.
- Removed  as its functionality is now covered in .
- Updated memory map tests to ensure GPIO admin base is correctly referenced.
- Adjusted formal filelist generation to reflect changes in GPIO handling.
- Enhanced SOC topology tests to include GPIO interrupt handling.
This commit is contained in:
maksyuki committed 2026-08-08 17:59:02 +08:00
1 parent 4b40dbf262
commit d517ce0fea
53 files changed
+2512 -1200

No files matched your search

+57 -64
View File
@@ -3,6 +3,7 @@
#include <retrosoc/core/status.h>
#include <retrosoc/core/wait.h>
#include <retrosoc/hal/gpio.h>
#include <retrosoc/hal/lcd.h>
#include <retrosoc/hal/spisd.h>
#include <retrosoc/hal/timer.h>
@@ -24,17 +25,12 @@ unsigned long long __umoddi3(unsigned long long dividend, unsigned long long div
static uint8_t storage[32];
static uint32_t image_call_count;
void putch(char ch)
{
void putch(char ch) {
(void)ch;
}
void lcd_fill_image(uint16_t x_start,
uint16_t y_start,
uint16_t x_end,
uint16_t y_end,
uint32_t *data)
{
void lcd_fill_image(uint16_t x_start, uint16_t y_start, uint16_t x_end, uint16_t y_end,
uint32_t *data) {
(void)x_start;
(void)y_start;
(void)x_end;
@@ -43,8 +39,7 @@ void lcd_fill_image(uint16_t x_start,
++image_call_count;
}
rs_status_t rs_spisd_read_bytes(void *buffer, size_t byte_count, uintptr_t address)
{
rs_status_t rs_spisd_read_bytes(void *buffer, size_t byte_count, uintptr_t address) {
size_t offset;
if ((buffer == NULL) || (address < TEST_STORAGE_ADDRESS)) {
@@ -60,11 +55,8 @@ rs_status_t rs_spisd_read_bytes(void *buffer, size_t byte_count, uintptr_t addre
return RS_OK;
}
static rs_status_t test_reader_read(void *context,
uint32_t offset,
void *buffer,
size_t byte_count)
{
static rs_status_t test_reader_read(void *context, uint32_t offset, void *buffer,
size_t byte_count) {
const uint8_t *bytes = context;
if ((bytes == NULL) || (buffer == NULL)) {
@@ -76,8 +68,7 @@ static rs_status_t test_reader_read(void *context,
return RS_OK;
}
static int test_string_helpers(void)
{
static int test_string_helpers(void) {
char buffer[8] = {'x', '\0'};
int32_t value;
@@ -87,12 +78,10 @@ static int test_string_helpers(void)
if (strncmp("left", "right", 0U) != 0) {
return 2;
}
if (rs_strlcpy(buffer, "abcdefghi", sizeof(buffer)) != 9U ||
strcmp(buffer, "abcdefg") != 0) {
if (rs_strlcpy(buffer, "abcdefghi", sizeof(buffer)) != 9U || strcmp(buffer, "abcdefg") != 0) {
return 3;
}
if (rs_strlcat(buffer, "x", sizeof(buffer)) != 8U ||
strcmp(buffer, "abcdefg") != 0) {
if (rs_strlcat(buffer, "x", sizeof(buffer)) != 8U || strcmp(buffer, "abcdefg") != 0) {
return 4;
}
if (rs_strtoi32("-2147483648", &value) != RS_OK || value != INT32_MIN ||
@@ -103,12 +92,11 @@ static int test_string_helpers(void)
return 0;
}
static int test_formatter(void)
{
static int test_formatter(void) {
char buffer[32];
if (rs_snprintf(buffer, sizeof(buffer), "%#08x:%lld:%zu", 0x2AU, -42LL,
(size_t)7U) != 14 || strcmp(buffer, "0x00002a:-42:7") != 0) {
if (rs_snprintf(buffer, sizeof(buffer), "%#08x:%lld:%zu", 0x2AU, -42LL, (size_t)7U) != 14 ||
strcmp(buffer, "0x00002a:-42:7") != 0) {
return 1;
}
if (rs_snprintf(buffer, 4U, "abcdef") != 6 || strcmp(buffer, "abc") != 0) {
@@ -124,29 +112,25 @@ static int test_formatter(void)
return 0;
}
static int test_compiler_helpers(void)
{
static int test_compiler_helpers(void) {
if ((__clzsi2(0U) != 32) || (__clzsi2(1U) != 31) || (__ffssi2(0U) != 0) ||
(__ffssi2(1U) != 1) || (__ffssi2(0x10U) != 5)) {
return 1;
}
if ((__udivdi3(UINT64_MAX, 3U) != 6148914691236517205ULL) ||
(__umoddi3(UINT64_MAX, 3U) != 0U) ||
(__udivdi3(UINT64_MAX, UINT64_MAX) != 1U) ||
(__umoddi3(UINT64_MAX, 3U) != 0U) || (__udivdi3(UINT64_MAX, UINT64_MAX) != 1U) ||
(__umoddi3(100U, 7U) != 2U) || (__divdi3(-100LL, 7LL) != -14LL)) {
return 2;
}
return 0;
}
static int test_wait_helper(void)
{
static int test_wait_helper(void) {
volatile uint32_t value = 0x30U;
if (rs_wait_mask(&value, 0x30U, 0x30U, 1U) != RS_OK ||
rs_wait_mask(&value, 0x40U, 0x40U, 1U) != RS_ETIMEOUT ||
rs_wait_mask(NULL, 1U, 1U, 1U) != RS_EINVAL ||
rs_wait_value(&value, 0x30U, 1U) != RS_OK ||
rs_wait_mask(NULL, 1U, 1U, 1U) != RS_EINVAL || rs_wait_value(&value, 0x30U, 1U) != RS_OK ||
rs_wait_not_value(&value, 0U, 1U) != RS_OK ||
rs_wait_not_value(&value, 0x30U, 1U) != RS_ETIMEOUT) {
return 1;
@@ -154,8 +138,7 @@ static int test_wait_helper(void)
return 0;
}
static int test_ws2812_helpers(void)
{
static int test_ws2812_helpers(void) {
const rs_ws2812_config_t config = {
72000000U,
RS_WS2812_DEFAULT_BIT_PERIOD_NS,
@@ -168,9 +151,9 @@ static int test_ws2812_helpers(void)
rs_ws2812_timing_t timing;
if ((rs_ws2812_pack_grb(0x12U, 0x34U, 0x56U) != UINT32_C(0x341256)) ||
(rs_ws2812_timing_from_ns(&config, &timing) != RS_OK) ||
(timing.bit_cycles != 90U) || (timing.t0h_cycles != 25U) ||
(timing.t1h_cycles != 50U) || (timing.reset_cycles != 21600U)) {
(rs_ws2812_timing_from_ns(&config, &timing) != RS_OK) || (timing.bit_cycles != 90U) ||
(timing.t0h_cycles != 25U) || (timing.t1h_cycles != 50U) ||
(timing.reset_cycles != 21600U)) {
return 1;
}
invalid.t0h_ns = invalid.t1h_ns;
@@ -187,16 +170,15 @@ static int test_ws2812_helpers(void)
return 0;
}
static int test_timer_helpers(void)
{
static int test_timer_helpers(void) {
rs_timer_period_t period;
if ((rs_timer_period_from_ms(72000000U, 1U, &period) != RS_OK) ||
(period.prescale != 0U) || (period.load != 71999U)) {
if ((rs_timer_period_from_ms(72000000U, 1U, &period) != RS_OK) || (period.prescale != 0U) ||
(period.load != 71999U)) {
return 1;
}
if ((rs_timer_period_from_ms(72000000U, 1000U, &period) != RS_OK) ||
(period.prescale != 0U) || (period.load != 71999999U)) {
if ((rs_timer_period_from_ms(72000000U, 1000U, &period) != RS_OK) || (period.prescale != 0U) ||
(period.load != 71999999U)) {
return 2;
}
if ((rs_timer_period_from_ms(0U, 1U, &period) != RS_EINVAL) ||
@@ -208,13 +190,32 @@ static int test_timer_helpers(void)
return 0;
}
static int test_wav_parser(void)
{
static int test_gpio_helpers(void) {
rs_gpio_filter_timing_t timing;
if ((rs_gpio_filter_timing_from_us(72000000U, 2U, 3U, &timing) != RS_OK) ||
(timing.divider != 143U) || (timing.stable_samples != 3U)) {
return 1;
}
if ((rs_gpio_filter_timing_from_us(1U, 1U, 1U, &timing) != RS_OK) || (timing.divider != 0U)) {
return 2;
}
if ((rs_gpio_filter_timing_from_us(0U, 1U, 1U, &timing) != RS_EINVAL) ||
(rs_gpio_filter_timing_from_us(72000000U, 0U, 1U, &timing) != RS_EINVAL) ||
(rs_gpio_filter_timing_from_us(72000000U, 2U, 0U, &timing) != RS_EINVAL) ||
(rs_gpio_filter_timing_from_us(72000000U, 2U, 16U, &timing) != RS_EINVAL) ||
(rs_gpio_filter_timing_from_us(72000000U, 1000U, 3U, &timing) != RS_EINVAL) ||
(rs_gpio_filter_timing_from_us(72000000U, 2U, 3U, NULL) != RS_EINVAL)) {
return 3;
}
return 0;
}
static int test_wav_parser(void) {
static const uint8_t wav[] = {
'R', 'I', 'F', 'F', 40U, 0U, 0U, 0U, 'W', 'A', 'V', 'E',
'f', 'm', 't', ' ', 16U, 0U, 0U, 0U, 1U, 0U, 1U, 0U,
0x40U, 0x1FU, 0U, 0U, 0x80U, 0x3EU, 0U, 0U, 2U, 0U, 16U, 0U,
'd', 'a', 't', 'a', 4U, 0U, 0U, 0U, 0U, 1U, 2U, 3U,
'R', 'I', 'F', 'F', 40U, 0U, 0U, 0U, 'W', 'A', 'V', 'E', 'f', 'm', 't', ' ',
16U, 0U, 0U, 0U, 1U, 0U, 1U, 0U, 0x40U, 0x1FU, 0U, 0U, 0x80U, 0x3EU, 0U, 0U,
2U, 0U, 16U, 0U, 'd', 'a', 't', 'a', 4U, 0U, 0U, 0U, 0U, 1U, 2U, 3U,
};
rs_wav_info_t info;
const rs_wav_reader_t reader = {test_reader_read, (void *)wav, sizeof(wav)};
@@ -223,15 +224,13 @@ static int test_wav_parser(void)
info.block_align != 2U || info.data_offset != 44U || info.data_size != 4U) {
return 1;
}
if (rs_wav_parse(&(rs_wav_reader_t){test_reader_read, (void *)wav, 12U}, &info) !=
RS_EFORMAT) {
if (rs_wav_parse(&(rs_wav_reader_t){test_reader_read, (void *)wav, 12U}, &info) != RS_EFORMAT) {
return 2;
}
return 0;
}
static int test_video_parser(void)
{
static int test_video_parser(void) {
rs_video_info_t info;
(void)memset(storage, 0, sizeof(storage));
@@ -249,17 +248,11 @@ static int test_video_parser(void)
return 0;
}
int main(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_wav_parser(),
test_video_parser(),
test_string_helpers(), test_formatter(), test_compiler_helpers(),
test_wait_helper(), test_ws2812_helpers(), test_timer_helpers(),
test_gpio_helpers(), test_wav_parser(), test_video_parser(),
};
for (size_t index = 0U; index < (sizeof(results) / sizeof(results[0])); ++index) {
+174
View File
@@ -0,0 +1,174 @@
`timescale 1ns / 1ps
`include "gpio_define.svh"
module gpio_tb;
localparam int PIN_NUM = 4;
localparam logic [31:0] USER_BASE = 32'h1000_0000;
localparam logic [31:0] ADMIN_BASE = 32'h1001_4000;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
ribp_if ribp ();
gpio_if #(PIN_NUM) gpio ();
user_gpio_if #(PIN_NUM) user_gpio ();
logic [31:0] read_data;
always #5 clk_i = ~clk_i;
ribp_gpio #(
.PIN_NUM (PIN_NUM),
.USER_BASE_ADDR (USER_BASE),
.ADMIN_BASE_ADDR(ADMIN_BASE),
.HAS_INPUT_CMOS (1'b1),
.HAS_PULL_UP (1'b1),
.HAS_PULL_DOWN (1'b1)
) dut (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.ribp (ribp),
.gpio (gpio),
.user_gpio(user_gpio)
);
task automatic write_register(input logic [31:0] address, input logic [31:0] data,
input logic expected_error);
begin
@(negedge clk_i);
ribp.addr = address;
ribp.wdata = data;
ribp.wstrb = 4'hF;
ribp.valid = 1'b1;
@(posedge clk_i);
#1;
if (!ribp.ready || (ribp.resp_err !== expected_error)) begin
$fatal(1, "write response mismatch at %08x", address);
end
@(negedge clk_i);
ribp.valid = 1'b0;
@(posedge clk_i);
#1;
end
endtask
task automatic read_register(input logic [31:0] address, output logic [31:0] data,
input logic expected_error);
begin
@(negedge clk_i);
ribp.addr = address;
ribp.wdata = '0;
ribp.wstrb = '0;
ribp.valid = 1'b1;
@(posedge clk_i);
#1;
if (!ribp.ready || (ribp.resp_err !== expected_error)) begin
$fatal(1, "read response mismatch at %08x", address);
end
data = ribp.rdata;
@(negedge clk_i);
ribp.valid = 1'b0;
@(posedge clk_i);
#1;
end
endtask
initial begin
ribp.valid = 1'b0;
ribp.addr = '0;
ribp.wdata = '0;
ribp.wstrb = '0;
gpio.di_i = '0;
gpio.alt0_do_i = '0;
gpio.alt0_oe_i = '0;
gpio.alt1_do_i = '0;
gpio.alt1_oe_i = '0;
user_gpio.do_o = '0;
user_gpio.oe_o = '0;
repeat (3) @(posedge clk_i);
rst_n_i = 1'b1;
repeat (3) @(posedge clk_i);
read_register(ADMIN_BASE + `RIBP_GPIO_CAPABILITY, read_data, 1'b0);
if (read_data !== 32'h007F_4204) $fatal(1, "capability register mismatch");
read_register(ADMIN_BASE + `RIBP_GPIO_PAD_CAPABILITY, read_data, 1'b0);
if (read_data !== 32'h0000_0007) $fatal(1, "pad capability register mismatch");
read_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_OUTPUT_ENABLE, read_data, 1'b0);
if (read_data !== 32'd0) $fatal(1, "output enable reset value mismatch");
write_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_OUT_SET, 32'h1, 1'b0);
write_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_OE_SET, 32'h1, 1'b0);
if ({gpio.oe_o[0], gpio.do_o[0]} !== 2'b11) $fatal(1, "software output failed");
write_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_OUT_TOGGLE, 32'h1, 1'b0);
if (gpio.do_o[0] !== 1'b0) $fatal(1, "atomic output toggle failed");
read_register(ADMIN_BASE + 32'h2, read_data, 1'b1);
write_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_USER_ACCESS_MASK, 32'h2, 1'b0);
write_register(USER_BASE + `RIBP_GPIO_USER_OUT_SET, 32'h6, 1'b0);
read_register(USER_BASE + `RIBP_GPIO_USER_DATA_OUT, read_data, 1'b0);
if (read_data !== 32'h2) $fatal(1, "user access mask did not isolate output data");
user_gpio.do_o[2] = 1'b1;
user_gpio.oe_o[2] = 1'b1;
@(negedge clk_i);
ribp.addr = ADMIN_BASE + `RIBP_GPIO_ADMIN_USER_SELECT;
ribp.wdata = 32'h4;
ribp.wstrb = 4'hF;
ribp.valid = 1'b1;
@(posedge clk_i);
#1;
if (gpio.oe_o[2] !== 1'b0) $fatal(1, "user handoff did not force high impedance");
@(negedge clk_i);
ribp.valid = 1'b0;
@(posedge clk_i);
#1;
if ({gpio.oe_o[2], gpio.do_o[2]} !== 2'b11) $fatal(1, "user output was not selected");
write_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_OE_SET, 32'h2, 1'b0);
write_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_OPEN_DRAIN, 32'h2, 1'b0);
write_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_OUT_SET, 32'h2, 1'b0);
if ({gpio.oe_o[1], gpio.do_o[1]} !== 2'b00) $fatal(1, "open-drain release failed");
write_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_OUT_CLEAR, 32'h2, 1'b0);
if ({gpio.oe_o[1], gpio.do_o[1]} !== 2'b10) $fatal(1, "open-drain low drive failed");
write_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_PULL_UP, 32'h8, 1'b0);
write_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_PULL_DOWN, 32'h8, 1'b1);
write_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_INTR_RISE_ENABLE, 32'h1, 1'b0);
write_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_INTR_ENABLE, 32'h1, 1'b0);
gpio.di_i[0] = 1'b1;
repeat (5) @(posedge clk_i);
read_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_INTR_STATE, read_data, 1'b0);
if ((read_data & 32'h1) == 0) $fatal(1, "rising edge interrupt was not latched");
if (!gpio.irq_o) $fatal(1, "combined GPIO interrupt was not asserted");
write_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_INTR_STATE, 32'h1, 1'b0);
if (gpio.irq_o) $fatal(1, "W1C did not clear the interrupt");
write_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_INTR_TEST, 32'h1, 1'b0);
if (!gpio.irq_o) $fatal(1, "software interrupt test failed");
write_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_INTR_STATE, 32'h1, 1'b0);
write_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_INTR_RISE_ENABLE, 32'h9, 1'b0);
write_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_INTR_HIGH_ENABLE, 32'h8, 1'b1);
write_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_FILTER_DIV, 32'h0, 1'b0);
write_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_FILTER_COUNT, 32'h4, 1'b0);
write_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_FILTER_ENABLE, 32'h8, 1'b0);
gpio.di_i[3] = 1'b1;
@(posedge clk_i);
gpio.di_i[3] = 1'b0;
repeat (5) @(posedge clk_i);
read_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_DATA_IN, read_data, 1'b0);
if ((read_data & 32'h8) != 0) $fatal(1, "input filter accepted a short pulse");
gpio.di_i[3] = 1'b1;
repeat (8) @(posedge clk_i);
read_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_DATA_IN, read_data, 1'b0);
if ((read_data & 32'h8) == 0) $fatal(1, "input filter rejected a stable input");
write_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_CONFIG_LOCK, 32'h1, 1'b0);
write_register(ADMIN_BASE + `RIBP_GPIO_ADMIN_OE_CLEAR, 32'h1, 1'b1);
if (gpio.oe_o[0] !== 1'b1) $fatal(1, "configuration lock did not protect OE");
$display("GPIO V2 directed test passed");
$finish;
end
endmodule
-127
View File
@@ -1,127 +0,0 @@
`timescale 1ns / 1ps
module gpio_user_mux_tb;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
ribp_if rib ();
gpio_if gpio ();
user_gpio_if user_gpio ();
logic [31:0] read_data;
always #5 clk_i = ~clk_i;
ribp_gpio dut (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.ribp (rib),
.gpio (gpio),
.user_gpio(user_gpio)
);
task automatic write_register(input logic [7:0] address, input logic [31:0] data);
begin
@(negedge clk_i);
rib.addr = {24'd0, address};
rib.wdata = data;
rib.wstrb = 4'hF;
rib.valid = 1'b1;
@(posedge clk_i);
#1;
@(negedge clk_i);
rib.valid = 1'b0;
@(posedge clk_i);
#1;
end
endtask
task automatic read_register(input logic [7:0] address, output logic [31:0] data);
begin
@(negedge clk_i);
rib.addr = {24'd0, address};
rib.wdata = '0;
rib.wstrb = '0;
rib.valid = 1'b1;
@(posedge clk_i);
#1;
data = rib.rdata;
@(negedge clk_i);
rib.valid = 1'b0;
@(posedge clk_i);
#1;
end
endtask
task automatic select_user_gpio(input logic [31:0] selection);
begin
@(negedge clk_i);
rib.addr = 32'h0000_0030;
rib.wdata = selection;
rib.wstrb = 4'hF;
rib.valid = 1'b1;
@(posedge clk_i);
#1;
if (gpio.oe_o[0] !== 1'b0) $fatal(1, "GPIO handoff did not drive high impedance");
@(negedge clk_i);
rib.valid = 1'b0;
@(posedge clk_i);
#1;
end
endtask
initial begin
rib.valid = 1'b0;
rib.addr = '0;
rib.wdata = '0;
rib.wstrb = '0;
gpio.di_i = '0;
gpio.alt0_do_i = '0;
gpio.alt0_oe_i = '0;
gpio.alt1_do_i = '0;
gpio.alt1_oe_i = '0;
user_gpio.do_o = '0;
user_gpio.oe_o = '0;
repeat (2) @(posedge clk_i);
rst_n_i = 1'b1;
repeat (2) @(posedge clk_i);
write_register(8'h00, 32'h0000_0001);
write_register(8'h10, 32'h0000_0001);
if ({gpio.oe_o[0], gpio.do_o[0]} !== 2'b11) $fatal(1, "rib software GPIO is not active");
user_gpio.do_o[0] = 1'b0;
user_gpio.oe_o[0] = 1'b1;
select_user_gpio(32'h0000_0001);
if ({gpio.oe_o[0], gpio.do_o[0]} !== 2'b10) $fatal(1, "user GPIO was not selected");
read_register(8'h30, read_data);
if (read_data !== 32'h0000_0001) $fatal(1, "USER_SEL readback is incorrect");
read_register(8'h38, read_data);
if (read_data !== 32'h0000_0001) $fatal(1, "USER_STATUS is not active after handoff");
gpio.di_i[0] = 1'b1;
#1;
if (user_gpio.di_i[0] !== 1'b1) $fatal(1, "user GPIO input path is disconnected");
write_register(8'h34, 32'h0000_0001);
read_register(8'h34, read_data);
if (read_data !== 32'h0000_0001) $fatal(1, "USER_LOCK is not write-one-set");
write_register(8'h30, '0);
if ({gpio.oe_o[0], gpio.do_o[0]} !== 2'b10) $fatal(1, "USER_LOCK did not preserve ownership");
read_register(8'h30, read_data);
if (read_data !== 32'h0000_0001) $fatal(1, "locked USER_SEL changed");
gpio.alt0_do_i[1] = 1'b1;
gpio.alt0_oe_i[1] = 1'b1;
write_register(8'h28, 32'h0000_0002);
if ({gpio.oe_o[1], gpio.do_o[1]} !== 2'b11) $fatal(1, "alternate GPIO mode regressed");
write_register(8'h04, 32'h0000_0001);
write_register(8'h08, 32'h0000_0001);
write_register(8'h0C, 32'h0000_0001);
if ({gpio.cs_o[0], gpio.pu_o[0], gpio.pd_o[0]} !== 3'b111)
$fatal(1, "rib GPIO electrical controls changed under user ownership");
$display("GPIO user ownership test passed");
$finish;
end
endmodule
+1
View File
@@ -140,6 +140,7 @@ module ribp_topology_tb;
rst_n_i = 1'b1;
expect_route(`SOC_ADDR_RIBP_GPIO_BASE, 1, 32'h1111_1111);
expect_route(`SOC_ADDR_RIBP_GPIO_ADMIN_BASE, 1, 32'h1111_1111);
expect_route(`SOC_ADDR_PSRAM_BASE, 4, 32'h4444_4444);
expect_route(`SOC_ADDR_FLASH_BASE, 9, 32'h9999_9999);
expect_route(`SOC_ADDR_RIBP_I2C1_BASE, 17, 32'h1234_5678);
+65
View File
@@ -0,0 +1,65 @@
"""Directed RTL tests for the GPIO V2 register and pad-control contract."""
from __future__ import annotations
import shutil
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def test_gpio_v2_register_pad_interrupt_and_filter_contract(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
sv2v = shutil.which("sv2v")
vvp = shutil.which("vvp")
if iverilog is None or sv2v is None or vvp is None:
return
peripheral = ROOT / "rtl/ip/ribp/peripheral"
common = ROOT / "rtl/managed/clusterip/common/rtl"
source_list = tmp_path / "gpio.fl"
source_list.write_text(
"\n".join(
[
"+define+SV_ASSRT_DISABLE",
f"+incdir+{peripheral}",
f"+incdir+{common}",
str(common / "interface/ribp_if.sv"),
str(common / "utils/register.sv"),
str(common / "clkrst/counter.sv"),
str(common / "cdc/cdc_sync.sv"),
str(common / "utils/edge_det.sv"),
str(peripheral / "gpio_if.sv"),
str(peripheral / "user_gpio_if.sv"),
str(peripheral / "gpio_core.sv"),
str(peripheral / "gpio_reg.sv"),
str(peripheral / "ribp_gpio.sv"),
str(ROOT / "tests/rtl/gpio_tb.sv"),
"",
]
),
encoding="utf-8",
)
converted = tmp_path / "gpio_tb.v"
subprocess.run(
[
sys.executable,
str(ROOT / "rtl/mini/script/convt_sv2v.py"),
"-f",
str(source_list),
"--output",
str(converted),
],
check=True,
)
simulation = tmp_path / "gpio_tb"
subprocess.run(
[iverilog, "-g2012", "-s", "gpio_tb", "-o", str(simulation), str(converted)],
check=True,
)
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
assert "GPIO V2 directed test passed" in result.stdout
-58
View File
@@ -1,58 +0,0 @@
"""RTL tests for user-IP ownership of the rib GPIO pad bank."""
from __future__ import annotations
import shutil
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def test_user_ip_can_own_rib_gpio_pads_with_a_safe_handoff(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
sv2v = shutil.which("sv2v")
vvp = shutil.which("vvp")
if iverilog is None or sv2v is None or vvp is None:
return
source_list = tmp_path / "gpio_user_mux.fl"
source_list.write_text(
"\n".join(
[
"+define+SV_ASSRT_DISABLE",
f"+incdir+{ROOT / 'rtl/ip/ribp/peripheral'}",
f"+incdir+{ROOT / 'rtl/managed/clusterip/common/rtl'}",
str(ROOT / "rtl/managed/clusterip/common/rtl/interface/ribp_if.sv"),
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"),
str(ROOT / "rtl/managed/clusterip/common/rtl/cdc/cdc_sync.sv"),
str(ROOT / "rtl/managed/clusterip/common/rtl/utils/edge_det.sv"),
str(ROOT / "rtl/ip/ribp/peripheral/gpio.sv"),
str(ROOT / "tests/rtl/gpio_user_mux_tb.sv"),
"",
]
),
encoding="utf-8",
)
converted = tmp_path / "gpio_user_mux_tb.v"
subprocess.run(
[
sys.executable,
str(ROOT / "rtl/mini/script/convt_sv2v.py"),
"-f",
str(source_list),
"--output",
str(converted),
],
check=True,
)
simulation = tmp_path / "gpio_user_mux_tb"
subprocess.run(
[iverilog, "-g2012", "-s", "gpio_user_mux_tb", "-o", str(simulation), str(converted)],
check=True,
)
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
assert "GPIO user ownership test passed" in result.stdout
+13
View File
@@ -58,6 +58,8 @@ def test_generated_artifacts_share_the_capacity_baseline(tmp_path: Path) -> None
assert "RS_SOC_PSRAM_SIZE UINT32_C(0x00800000)" in header
assert "RS_SOC_SDRAM_SIZE UINT32_C(0x02000000)" in header
assert "RS_SOC_SPISD_SIZE UINT32_C(0x40000000)" in header
assert "#ifdef __ASSEMBLER__" in header
assert "#define UINT32_C(value) value" in header
assert "RS_SOC_RIBP_SDIO_BASE" not in header
assert "RS_SOC_NMI_" not in header
assert "RS_SOC_OPIPSRAM_BASE" not in header
@@ -83,6 +85,17 @@ def test_user_ip_is_always_emitted_for_the_fixed_platform(tmp_path: Path) -> Non
assert "RS_SOC_APB_USER_IP_BASE" in header
def test_bootstrap_assembly_uses_the_generated_gpio_admin_base() -> None:
for source in (ROOT / "crt/arch/riscv/startup.S", ROOT / "app/asm/hello.s"):
text = source.read_text(encoding="utf-8")
assert '#include "retrosoc/generated/memory_map.h"' in text
assert "RS_SOC_RIBP_GPIO_ADMIN_BASE + 0x34" in text
assert "RS_SOC_RIBP_GPIO_ADMIN_BASE + 0x38" in text
assert "RS_SOC_RIBP_PSRAM_BASE + 0x08" in text
assert "0x10000028" not in text
assert "0x1000002c" not in text
def test_map_validation_rejects_overlaps(tmp_path: Path) -> None:
document = json.loads(MEMORY_MAP.read_text(encoding="utf-8"))
document["regions"][1]["base"] = document["regions"][0]["base"]
+11 -8
View File
@@ -203,7 +203,7 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non
rib2apb_filelist = tmp_path / "rib2apb.fl"
sysctrl_filelist = tmp_path / "sysctrl.fl"
pll_rcu_filelist = tmp_path / "pll_rcu.fl"
gpio_user_filelist = tmp_path / "gpio_user.fl"
gpio_filelist = tmp_path / "gpio.fl"
ws2812_filelist = tmp_path / "ws2812.fl"
clint_filelist = tmp_path / "clint.fl"
assert generate_formal_filelist("bus", bus_filelist, memory_map, topology, user_extensions)
@@ -220,7 +220,7 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non
"pll_rcu", pll_rcu_filelist, memory_map, topology, user_extensions
)
assert generate_formal_filelist(
"gpio_user", gpio_user_filelist, memory_map, topology, user_extensions
"gpio", gpio_filelist, memory_map, topology, user_extensions
)
assert generate_formal_filelist(
"ws2812", ws2812_filelist, memory_map, topology, user_extensions
@@ -234,7 +234,7 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non
rib2apb = parse_filelists([rib2apb_filelist], require_files=False)
sysctrl = parse_filelists([sysctrl_filelist], require_files=False)
pll_rcu = parse_filelists([pll_rcu_filelist], require_files=False)
gpio_user = parse_filelists([gpio_user_filelist], require_files=False)
gpio = parse_filelists([gpio_filelist], require_files=False)
ws2812 = parse_filelists([ws2812_filelist], require_files=False)
clint = parse_filelists([clint_filelist], require_files=False)
assert "+define+SV_ASSRT_DISABLE" in bus.defines
@@ -253,9 +253,12 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non
assert ROOT / "rtl/mini/top/rcu.sv" in pll_rcu.files
assert ROOT / "rtl/managed/clusterip/common/rtl/cdc/cdc_2phase.sv" in pll_rcu.files
assert ROOT / "rtl/managed/clusterip/common/rtl/clkrst/rst_sync.sv" in pll_rcu.files
assert ROOT / "rtl/ip/ribp/peripheral/gpio.sv" in gpio_user.files
assert ROOT / "rtl/managed/clusterip/common/rtl/cdc/cdc_sync.sv" in gpio_user.files
assert ROOT / "rtl/mini/formal/gpio_user_formal.sv" in gpio_user.files
assert ROOT / "rtl/ip/ribp/peripheral/gpio_core.sv" in gpio.files
assert ROOT / "rtl/ip/ribp/peripheral/gpio_reg.sv" in gpio.files
assert ROOT / "rtl/ip/ribp/peripheral/ribp_gpio.sv" in gpio.files
assert ROOT / "rtl/ip/ribp/peripheral/user_gpio_if.sv" in gpio.files
assert ROOT / "rtl/managed/clusterip/common/rtl/cdc/cdc_sync.sv" in gpio.files
assert ROOT / "rtl/mini/formal/gpio_formal.sv" in gpio.files
assert ROOT / "rtl/ip/ribp/serial/ribp_ws2812.sv" in ws2812.files
assert ROOT / "rtl/managed/clusterip/common/rtl/utils/fifo.sv" in ws2812.files
assert ROOT / "rtl/mini/formal/ws2812_formal.sv" in ws2812.files
@@ -306,7 +309,7 @@ def test_fatfs_release_script_uses_the_locked_archive_contract() -> None:
def test_formal_result_summary_requires_every_passing_step(tmp_path: Path) -> None:
proofs = ("bus", "rib2apb", "sysctrl", "pll_rcu", "gpio_user", "ws2812")
proofs = ("bus", "rib2apb", "sysctrl", "pll_rcu", "gpio", "ws2812")
for proof in proofs:
directory = tmp_path / proof
directory.mkdir()
@@ -334,7 +337,7 @@ def test_formal_result_summary_requires_every_passing_step(tmp_path: Path) -> No
"--proof",
f"pll_rcu={tmp_path / 'pll_rcu'}",
"--proof",
f"gpio_user={tmp_path / 'gpio_user'}",
f"gpio={tmp_path / 'gpio'}",
"--proof",
f"ws2812={tmp_path / 'ws2812'}",
)
+6 -4
View File
@@ -97,11 +97,12 @@ def test_topology_generates_complete_rib_apb_and_gpio_bindings(tmp_path: Path) -
assert ".apb_rib(u_apb_rib_if)" in bus_fabric
assert ".rib(u_rib_if)" in bus_fabric
assert "`define SOC_IRQ_VECTOR_WIDTH 32" in irq_config
assert "`define SOC_USER_IRQ_MASK 32'h0003FFFC" in irq_config
assert "`define SOC_IRQ_RIBP_WIDTH 11" in irq_config
assert "`define SOC_USER_IRQ_MASK 32'h0007FFFC" in irq_config
assert "`define SOC_IRQ_RIBP_WIDTH 12" in irq_config
assert "`define SOC_IRQ_APB_WIDTH 7" in irq_config
assert "assign irq_o[0] = u_clint_if.software_irq_o[0];" in rib_irq
assert "assign irq_o[10] = ws2812.irq_o;" in rib_irq
assert "assign irq_o[11] = gpio.irq_o;" in rib_irq
assert "assign irq_o[5] = u_tmr_if.irq_o;" in apb_irq
assert "s_irq[16] = s_apb_irq[6];" in irq_wiring
assert "irq_i[31] == 1'b0" in irq_sva
@@ -122,7 +123,7 @@ def test_topology_preserves_default_irq_compatibility_mapping() -> None:
)
for interrupt in document["interrupts"]
]
assert mappings[:18] == [
assert mappings[:19] == [
("clint_software", "ribp", 0, 0, "u_clint_if.software_irq_o[0]"),
("clint_timer", "ribp", 1, 1, "u_clint_if.timer_irq_o[0]"),
("uart0", "ribp", 2, 2, "uart.irq_o"),
@@ -141,8 +142,9 @@ def test_topology_preserves_default_irq_compatibility_mapping() -> None:
("advanced_timer", "apb", 5, 15, "u_tmr_if.irq_o"),
("reserved", "apb", 6, 16, "1'b0"),
("ws2812", "ribp", 10, 17, "ws2812.irq_o"),
("gpio", "ribp", 11, 18, "gpio.irq_o"),
]
assert all(mapping[3] >= 18 for mapping in mappings[18:])
assert all(mapping[3] >= 19 for mapping in mappings[19:])
def test_topology_always_adds_the_user_apb_target(tmp_path: Path) -> None: