mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-06 14:55:14 +00:00
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:
53 files changed
+2512
-1200
No files matched your search
+57
-64
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"]
|
||||
|
||||
@@ -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'}",
|
||||
)
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user