feat: implement user IP integration with demo and validation tests

This commit is contained in:
maksyuki committed 2026-08-21 09:18:20 +08:00
1 parent db9282aaf6
commit 805735e20d
23 files changed
+454 -89

No files matched your search

+6
View File
@@ -87,6 +87,12 @@ Application-specific public headers belong below their own component include
directory, such as `board/include/retrosoc/board/` or
`media/include/retrosoc/media/`.
The shell's user-IP integration follows the same boundary under
`network/userip`: its public demo entry point is namespaced below
`include/retrosoc/network/`, while the slot 1 timer and slot 2 GPIO register
tables remain private to the component. Generic slot selection and checked
APB-window access come from `<retrosoc/hal/user_ip.h>`.
DMA users must select an explicit SDK channel. UART0 owns channel 0, I2C0
channel 1, I2C1 channel 2, media/benchmark clients serialize on bulk channel
3, and crypto reserves channels 4/5. See [DMA MVP](../docs/ip/dma.md) before
+27
View File
@@ -1,3 +1,4 @@
#include <archinfo_regs.h>
#include <retrosoc/core/archinfo.h>
#include <retrosoc/core/soc.h>
#include <retrosoc/hal/clint.h>
@@ -8,6 +9,7 @@
#include <retrosoc/hal/sdram.h>
#include <retrosoc/hal/timer.h>
#include <retrosoc/hal/uart.h>
#include <retrosoc/hal/user_ip.h>
#include <retrosoc/lib/printf.h>
#include <retrosoc/service/test.h>
@@ -19,6 +21,27 @@ static bool rs_ci_smoke_archinfo_v2(void) {
(rs_archinfo_read_device_id(device_id) == RS_ENOTSUP) && (rs_rtc_probe() == RS_OK);
}
static bool rs_ci_smoke_user_ip(void) {
rs_status_t status;
uint32_t identifier = 0U;
status = rs_user_ip_probe(0U, &identifier);
if ((status != RS_OK) || (identifier != ARCHINFO_COMPONENT_ID_VALUE)) {
printf("ci_smoke: user IP 0 status %d identifier 0x%08x\n", (int)status,
(unsigned int)identifier);
return false;
}
for (uint32_t ip_id = 1U; ip_id <= RS_SOC_USER_IP_COUNT; ++ip_id) {
status = rs_user_ip_probe((uint8_t)ip_id, &identifier);
if ((status != RS_OK) || (identifier != ip_id)) {
printf("ci_smoke: user IP %u status %d identifier 0x%08x\n", (unsigned int)ip_id,
(int)status, (unsigned int)identifier);
return false;
}
}
return rs_user_ip_select(0U) == RS_OK;
}
static bool rs_ci_smoke_rng_v2(void) {
const rs_rng_config_t config = {
.fifo_watermark = 1U,
@@ -265,6 +288,10 @@ int main(void) {
rs_test_finish(RS_TEST_FAILED, 1U);
}
printf("ci_smoke: archinfo passed\n");
if (!rs_ci_smoke_user_ip()) {
rs_test_finish(RS_TEST_FAILED, 11U);
}
printf("ci_smoke: user IP passed\n");
if (!rs_ci_smoke_rng_v2()) {
rs_test_finish(RS_TEST_FAILED, 8U);
}
@@ -0,0 +1,10 @@
#ifndef RETROSOC_NETWORK_USER_IP_DEMO_H
#define RETROSOC_NETWORK_USER_IP_DEMO_H
#include <stdint.h>
#include <retrosoc/core/status.h>
rs_status_t rs_user_ip_demo_run(uint8_t ip_id);
#endif
+123
View File
@@ -0,0 +1,123 @@
#include <stdint.h>
#include <archinfo.h>
#include <archinfo_regs.h>
#include <retrosoc/core/soc.h>
#include <retrosoc/hal/timer.h>
#include <retrosoc/hal/user_ip.h>
#include <retrosoc/lib/printf.h>
#include <retrosoc/network/user_ip_demo.h>
#include "user_ip_regs.h"
#define RS_USER_IP_FIXED_SLOT UINT8_C(0)
#define RS_USER_TIMER_DEFAULT_DIVIDER UINT32_C(2)
#define RS_USER_GPIO_DEMO_OUTPUTS UINT32_C(0x0000FFFE)
static rs_status_t rs_user_ip_expect_identifier(uint8_t ip_id, uint32_t expected) {
uint32_t identifier;
rs_status_t status;
status = rs_user_ip_probe(ip_id, &identifier);
if (status != RS_OK) {
return status;
}
return identifier == expected ? RS_OK : RS_EFORMAT;
}
static rs_status_t rs_user_ip_archinfo_demo(void) {
archinfo_snapshot_t info;
rs_status_t status;
status = rs_user_ip_expect_identifier(RS_USER_IP_FIXED_SLOT, ARCHINFO_COMPONENT_ID_VALUE);
if (status != RS_OK) {
return status;
}
if (archinfo_read((uintptr_t)RS_SOC_APB4_USER_IP_BASE, &info) != ARCHINFO_STATUS_OK) {
return RS_EIO;
}
printf("[FIXED IP] ARCHINFO ABI V2\n");
printf("[ARCHINFO COMPONENT] %x\n", info.component_id);
printf("[ARCHINFO SOC/REV] %x/%x\n", info.soc_id, info.soc_revision);
printf("[ARCHINFO VERSION/CAP] %x/%x\n", info.ip_version, info.capability);
return RS_OK;
}
static rs_status_t rs_user_ip_timer_demo(void) {
uint32_t divider;
uint32_t count;
rs_status_t status;
status = rs_user_ip_expect_identifier(RS_USER_TIMER_SLOT, (uint32_t)RS_USER_TIMER_SLOT);
if (status != RS_OK) {
return status;
}
if ((rs_user_ip_write(RS_USER_TIMER_REG_DIV, RS_USER_TIMER_DEFAULT_DIVIDER) != RS_OK) ||
(rs_user_ip_write(RS_USER_TIMER_REG_COUNT, UINT32_C(0)) != RS_OK) ||
(rs_user_ip_read(RS_USER_TIMER_REG_DIV, &divider) != RS_OK)) {
return RS_EIO;
}
printf("[USER IP %u] user timer\n", (uint32_t)RS_USER_TIMER_SLOT);
printf("divider: %x\n", divider);
for (uint32_t index = 0U; index < 6U; ++index) {
if (rs_user_ip_read(RS_USER_TIMER_REG_COUNT, &count) != RS_OK) {
return RS_EIO;
}
printf("[%u] count: %u\n", index, count);
}
return RS_OK;
}
static rs_status_t rs_user_ip_gpio_demo(void) {
uint32_t data_input;
uint32_t data_output;
uint32_t readback;
rs_status_t status;
status = rs_user_ip_expect_identifier(RS_USER_GPIO_SLOT, (uint32_t)RS_USER_GPIO_SLOT);
if (status != RS_OK) {
return status;
}
if ((rs_user_ip_write(RS_USER_GPIO_REG_OE, RS_USER_GPIO_DEMO_OUTPUTS) != RS_OK) ||
(rs_user_ip_write(RS_USER_GPIO_REG_DATA_OUT, UINT32_C(0)) != RS_OK)) {
return RS_EIO;
}
printf("[USER IP %u] user GPIO\n", (uint32_t)RS_USER_GPIO_SLOT);
data_output = UINT32_C(0);
for (uint32_t index = 0U; index < 3U; ++index) {
if (rs_timer_delay_ms(RS_TIMER_0, 1U, RS_TIMER_DELAY_TIMEOUT) != RS_OK) {
return RS_ETIMEOUT;
}
data_output ^= RS_USER_GPIO_DEMO_OUTPUTS;
if ((rs_user_ip_write(RS_USER_GPIO_REG_DATA_OUT, data_output) != RS_OK) ||
(rs_user_ip_read(RS_USER_GPIO_REG_DATA_OUT, &readback) != RS_OK) ||
(readback != data_output)) {
return RS_EIO;
}
printf("[%u] data out: %x\n", index, readback);
}
for (uint32_t index = 0U; index < 6U; ++index) {
if (rs_user_ip_read(RS_USER_GPIO_REG_DATA_IN, &data_input) != RS_OK) {
return RS_EIO;
}
printf("[%u] data in: %x\n", index, data_input);
}
return RS_OK;
}
rs_status_t rs_user_ip_demo_run(uint8_t ip_id) {
switch (ip_id) {
case RS_USER_IP_FIXED_SLOT:
return rs_user_ip_archinfo_demo();
case RS_USER_TIMER_SLOT:
return rs_user_ip_timer_demo();
case RS_USER_GPIO_SLOT:
return rs_user_ip_gpio_demo();
default:
return RS_ENOTSUP;
}
}
+17
View File
@@ -0,0 +1,17 @@
#ifndef RETROSOC_NETWORK_USER_IP_REGS_H
#define RETROSOC_NETWORK_USER_IP_REGS_H
#include <stdint.h>
#define RS_USER_TIMER_SLOT UINT8_C(1)
#define RS_USER_TIMER_REG_ID UINT32_C(0x000)
#define RS_USER_TIMER_REG_DIV UINT32_C(0x004)
#define RS_USER_TIMER_REG_COUNT UINT32_C(0x008)
#define RS_USER_GPIO_SLOT UINT8_C(2)
#define RS_USER_GPIO_REG_ID UINT32_C(0x000)
#define RS_USER_GPIO_REG_OE UINT32_C(0x004)
#define RS_USER_GPIO_REG_DATA_OUT UINT32_C(0x008)
#define RS_USER_GPIO_REG_DATA_IN UINT32_C(0x00C)
#endif
-50
View File
@@ -1,50 +0,0 @@
#include <retrosoc/core/soc.h>
#include <retrosoc/lib/printf.h>
#include <retrosoc/hal/timer.h>
#include <userip.h>
void userip_main(int argc, char **argv) {
// user custom area HACK:
(void)argv;
if (argc == 0) {
printf("[FIXED IP] archinfo test\n");
printf("[ARCHINFO SYS] %x\n", reg_user_ip_reg0);
printf("[ARCHINFO IDL] %x\n", reg_user_ip_reg1);
printf("[ARCHINFO IDH] %x\n", reg_user_ip_reg2);
} else if (argc == 1) {
reg_user_ip_reg1 = 2;
printf("div val: %x\n", reg_user_ip_reg1);
reg_user_ip_reg2 = 0;
for (int i = 0; i < 6; ++i)
printf("tim val: %d\n", reg_user_ip_reg2);
} else if (argc == 2) {
printf("[reg0] %x\n", reg_user_ip_reg0);
reg_user_ip_reg1 = (uint32_t)0xfffe;
printf("do val: %x\n", reg_user_ip_reg5);
for (int i = 0; i < 3; ++i) {
if (rs_timer_delay_ms(RS_TIMER_0, 1U, RS_TIMER_DELAY_TIMEOUT) != RS_OK) {
printf("timer delay failed\n");
return;
}
reg_user_ip_reg5 = ~reg_user_ip_reg5;
printf("[%d]do val: %x\n", i, reg_user_ip_reg5);
}
printf("cs val: %x\n", reg_user_ip_reg2);
reg_user_ip_reg2 = (uint32_t)0xffff;
printf("cs val: %x\n", reg_user_ip_reg2);
printf("pu val: %x\n", reg_user_ip_reg3);
reg_user_ip_reg3 = (uint32_t)0xffff;
printf("pu val: %x\n", reg_user_ip_reg3);
printf("pd val: %x\n", reg_user_ip_reg4);
reg_user_ip_reg4 = (uint32_t)0xffff;
printf("pd val: %x\n", reg_user_ip_reg4);
reg_user_ip_reg1 = (uint32_t)0xfffe;
for (int i = 0; i < 6; ++i) {
printf("[%d]di val: %x\n", i, reg_user_ip_reg6);
}
}
}
-5
View File
@@ -1,5 +0,0 @@
#ifndef USERIP_H__
#define USERIP_H__
void userip_main(int argc, char **argv);
#endif
+2 -2
View File
@@ -1,2 +1,2 @@
APP_SRCS += $(ROOT_PATH)/app/network/userip/userip.c
APP_INC_DIRS += $(ROOT_PATH)/app/network/userip
APP_SRCS += $(ROOT_PATH)/app/network/userip/src/user_ip_demo.c
APP_INC_DIRS += $(ROOT_PATH)/app/network/userip/include
+6
View File
@@ -46,6 +46,12 @@ clock, user-core, performance, and test-service APIs use this HAL; direct
`reg_sysctrl_*` register macros are not public SDK interfaces. See the
[SystemCtrl contract](../docs/ip/sysctrl.md).
`<retrosoc/hal/user_ip.h>` owns selection, identification, and validated
32-bit access to the fixed 4 KiB user-IP window. The generic HAL does not
assign semantics to extension-specific registers; each integrated user IP
keeps its register ABI and driver in its owning application component. See the
[User IP software contract](../docs/ip/user-ip.md).
`<retrosoc/hal/dma.h>` provides the channel-aware direct-mode DMA API. UART0,
I2C0, I2C1, and bulk/media clients have deterministic channel assignments; see
the [DMA MVP contract](../docs/ip/dma.md). The current SDK intentionally
-8
View File
@@ -25,12 +25,4 @@
#define TF_CARD_OFFST RS_SOC_SPISD_SIZE
#define RS_SOC_REG32(base, offset) (*(volatile uint32_t *)(uintptr_t)((base) + (offset)))
#define reg_user_ip_reg0 RS_SOC_REG32(RS_SOC_APB4_USER_IP_BASE, UINT32_C(0x00))
#define reg_user_ip_reg1 RS_SOC_REG32(RS_SOC_APB4_USER_IP_BASE, UINT32_C(0x04))
#define reg_user_ip_reg2 RS_SOC_REG32(RS_SOC_APB4_USER_IP_BASE, UINT32_C(0x08))
#define reg_user_ip_reg3 RS_SOC_REG32(RS_SOC_APB4_USER_IP_BASE, UINT32_C(0x0C))
#define reg_user_ip_reg4 RS_SOC_REG32(RS_SOC_APB4_USER_IP_BASE, UINT32_C(0x10))
#define reg_user_ip_reg5 RS_SOC_REG32(RS_SOC_APB4_USER_IP_BASE, UINT32_C(0x14))
#define reg_user_ip_reg6 RS_SOC_REG32(RS_SOC_APB4_USER_IP_BASE, UINT32_C(0x18))
#endif
+14
View File
@@ -0,0 +1,14 @@
#ifndef RETROSOC_HAL_USER_IP_H
#define RETROSOC_HAL_USER_IP_H
#include <stdint.h>
#include <retrosoc/core/status.h>
rs_status_t rs_user_ip_get_selected(uint8_t *ip_id);
rs_status_t rs_user_ip_select(uint8_t ip_id);
rs_status_t rs_user_ip_probe(uint8_t ip_id, uint32_t *identifier);
rs_status_t rs_user_ip_read(uint32_t offset, uint32_t *value);
rs_status_t rs_user_ip_write(uint32_t offset, uint32_t value);
#endif
+3
View File
@@ -147,6 +147,9 @@ rs_status_t rs_sysctrl_get_ip_select(uint8_t *ip_id) {
}
rs_status_t rs_sysctrl_set_ip_select(uint8_t ip_id) {
if ((uint32_t)ip_id > RS_SOC_USER_IP_COUNT) {
return RS_EINVAL;
}
*rs_sysctrl_register(RS_SYSCTRL_IPSEL_OFFSET) = (uint32_t)ip_id;
return RS_OK;
}
+56
View File
@@ -0,0 +1,56 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <retrosoc/core/soc.h>
#include <retrosoc/hal/sysctrl.h>
#include <retrosoc/hal/user_ip.h>
#define RS_USER_IP_IDENTIFICATION_OFFSET UINT32_C(0x000)
#define RS_USER_IP_REGISTER_SIZE UINT32_C(4)
static bool rs_user_ip_offset_valid(uint32_t offset) {
return ((offset & (RS_USER_IP_REGISTER_SIZE - UINT32_C(1))) == 0U) &&
(offset <= (RS_SOC_APB4_USER_IP_SIZE - RS_USER_IP_REGISTER_SIZE));
}
static volatile uint32_t *rs_user_ip_register(uint32_t offset) {
return (volatile uint32_t *)(uintptr_t)(RS_SOC_APB4_USER_IP_BASE + offset);
}
rs_status_t rs_user_ip_get_selected(uint8_t *ip_id) {
return rs_sysctrl_get_ip_select(ip_id);
}
rs_status_t rs_user_ip_select(uint8_t ip_id) {
return rs_sysctrl_set_ip_select(ip_id);
}
rs_status_t rs_user_ip_probe(uint8_t ip_id, uint32_t *identifier) {
rs_status_t status;
if (identifier == NULL) {
return RS_EINVAL;
}
status = rs_user_ip_select(ip_id);
if (status != RS_OK) {
return status;
}
return rs_user_ip_read(RS_USER_IP_IDENTIFICATION_OFFSET, identifier);
}
rs_status_t rs_user_ip_read(uint32_t offset, uint32_t *value) {
if ((value == NULL) || !rs_user_ip_offset_valid(offset)) {
return RS_EINVAL;
}
*value = *rs_user_ip_register(offset);
return RS_OK;
}
rs_status_t rs_user_ip_write(uint32_t offset, uint32_t value) {
if (!rs_user_ip_offset_valid(offset)) {
return RS_EINVAL;
}
*rs_user_ip_register(offset) = value;
return RS_OK;
}
+2 -1
View File
@@ -149,7 +149,8 @@ void rs_app_info(void) {
(void *)(uintptr_t)RS_SOC_APB4_WDG_BASE);
printf(" 1 x CRC @%p\n",
(void *)(uintptr_t)RS_SOC_APB4_CRC_BASE);
printf(" 1 x USER_IP(4KiB) @%p\n", (void *)&reg_user_ip_reg0);
printf(" 1 x USER_IP(4KiB) @%p\n",
(void *)(uintptr_t)RS_SOC_APB4_USER_IP_BASE);
printf("\n");
printf("#############################################################\n");
printf("#############################################################\n");
+13 -14
View File
@@ -5,14 +5,14 @@
#include <retrosoc/lib/string.h>
#include <retrosoc/service/shell.h>
#include <retrosoc/hal/lcd.h>
#include <retrosoc/hal/sysctrl.h>
#include <retrosoc/hal/timer.h>
#include <retrosoc/hal/user_ip.h>
#include <ff.h>
#include <retrosoc/media/video_player.h>
#include <retrosoc/network/user_ip_demo.h>
#include <core_portme.h>
// #include <lvgl.h>
// #include <lv_port_disp.h>
#include <userip.h>
static char sh_argv_buf[RS_SHELL_MAX_ARGC][RS_SHELL_MAX_COMMAND_LENGTH + 1U];
static char *sh_argv[RS_SHELL_MAX_ARGC + 1U];
@@ -686,6 +686,7 @@ static void rs_shell_app_coremark_cmd(int argc, char **argv) {
static void rs_shell_app_userip_cmd(int argc, char **argv) {
uint8_t current_user_id;
rs_status_t status;
if (argc != 1 && argc != 2) {
printf("[userip] cmd param error\n");
@@ -693,28 +694,26 @@ static void rs_shell_app_userip_cmd(int argc, char **argv) {
}
if (argc == 1) {
if (rs_sysctrl_get_ip_select(&current_user_id) != RS_OK) {
if (rs_user_ip_get_selected(&current_user_id) != RS_OK) {
printf("user ip selection read failed\n");
return;
}
printf("current user ip id: %03d\n", current_user_id);
userip_main(0, NULL);
} else if (argc == 2) {
int32_t user_id;
if ((rs_strtoi32(argv[1], &user_id) == RS_OK) && (user_id >= 0) && (user_id <= 255)) {
printf("switch to user ip id to [%03d...]\n", user_id);
if (rs_sysctrl_set_ip_select((uint8_t)user_id) != RS_OK) {
printf("user ip selection write failed\n");
return;
}
userip_main((int)user_id, NULL); // HACK:
} else {
printf("error use id: %d\n", user_id);
if ((rs_strtoi32(argv[1], &user_id) != RS_OK) || (user_id < 0) || (user_id > UINT8_MAX)) {
printf("invalid user ip id: %s\n", argv[1]);
return;
}
current_user_id = (uint8_t)user_id;
printf("switch to user ip id [%03d]\n", user_id);
}
status = rs_user_ip_demo_run(current_user_id);
if (status != RS_OK) {
printf("user ip %u test failed: %d\n", (uint32_t)current_user_id, status);
}
// userip: list all info/current id
}
void rs_shell_launch(void) {
+2
View File
@@ -41,6 +41,8 @@ the root README and subsystem guides.
counting modes, interrupt, debug-freeze, HAL, and verification contracts.
- [ip/sysctrl.md](ip/sysctrl.md) defines the SystemCtrl register ABI, user-core
lifecycle, PLL/fault/performance/RTC/test contracts, HAL, and verification.
- [ip/user-ip.md](ip/user-ip.md) defines the selectable 4 KiB user-IP window,
generic HAL, extension-owned register ABI, and verification contract.
- [ip/clint.md](ip/clint.md) defines the standard CLINT register map, fixed
timebase, RV32 access rules, interrupt behavior, and verification contract.
- [ip/gpio.md](ip/gpio.md) defines the GPIO dual-window ABI, pad ownership,
+3 -1
View File
@@ -1,10 +1,12 @@
# IP Documentation
This directory owns architecture, register ABI, software contract, and
verification documentation for self-owned retroSoC peripheral IP.
verification documentation for self-owned retroSoC peripheral IP and platform
extension interfaces.
- [timer.md](timer.md) defines the dual APB4 general timer.
- [sysctrl.md](sysctrl.md) defines the APB4 SystemCtrl register ABI, control-plane contracts, HAL, and verification.
- [user-ip.md](user-ip.md) defines the selectable user-IP window and software ownership boundary.
- [ws2812.md](ws2812.md) defines the WS2812 transmitter.
- [clint.md](clint.md) defines the management-hart software and timer interrupt block.
- [gpio.md](gpio.md) defines the dual-window GPIO controller.
+60
View File
@@ -0,0 +1,60 @@
# User IP Software Contract
## Scope
The Mini SoC exposes one 4 KiB APB4 user-IP window at
`RS_SOC_APB4_USER_IP_BASE`. SystemCtrl selects which implementation responds to
that window. Slot 0 is the fixed ARCHINFO demonstration target; generated user
extensions occupy contiguous slots beginning at 1.
The extension map in `rtl/mini/integration/user_extensions.json` is the source
of truth for implemented slots. Its generator emits `RS_SOC_USER_IP_COUNT`,
which is also the highest valid slot because extension slots are required to
be contiguous from 1.
## SDK Boundary
`<retrosoc/hal/user_ip.h>` provides the generic software interface:
- `rs_user_ip_get_selected()` reads the active slot.
- `rs_user_ip_select()` validates and selects an implemented slot.
- `rs_user_ip_probe()` selects a slot and reads its identification word at
offset `0x000`.
- `rs_user_ip_read()` and `rs_user_ip_write()` perform aligned 32-bit accesses
within the 4 KiB window.
The HAL rejects null outputs, unaligned offsets, out-of-window offsets, and
unimplemented slot numbers before accessing the user-IP window. Callers must
serialize selection changes with accesses; the bare-metal HAL does not provide
locking.
The word at offset `0x000` is interpreted by the selected implementation. Slot
0 returns the ARCHINFO component ID. User extension templates reserve the same
offset for the slot ID and require it to remain readable.
## Extension-Owned ABI
The generic SDK does not define registers beyond the identification word.
Register meanings belong to each integrated user IP and must not be added to
`<retrosoc/core/soc.h>`.
The current shell integration keeps its private definitions below
`app/network/userip`:
| Slot | Target | Register ABI |
| --- | --- | --- |
| 0 | fixed ARCHINFO | Managed ClusterIP ARCHINFO V2 ABI |
| 1 | managed timer target | `ID 0x000`, `DIV 0x004`, `COUNT 0x008` |
| 2 | managed GPIO target | `ID 0x000`, `OE 0x004`, `DATA_OUT 0x008`, `DATA_IN 0x00C` |
Replacing an extension target requires updating its application driver and the
register-parity test in the same change. Generic SDK consumers remain
unchanged.
## Verification
Host tests cover argument, alignment, range, and slot validation without
performing MMIO. `tests/test_user_ip_register_parity.py` compares the private C
register definitions with the integrated managed RTL and extension manifest.
The `ci_smoke` firmware probes slot 0 and every generated extension slot in
simulation, then restores slot 0.
+2 -1
View File
@@ -113,6 +113,7 @@ CRT_SRCS := $(ROOT_PATH)/crt/arch/riscv/startup.S \
$(ROOT_PATH)/crt/src/hal/spisd_math.c \
$(ROOT_PATH)/crt/src/hal/spisd.c \
$(ROOT_PATH)/crt/src/hal/xpi.c \
$(ROOT_PATH)/crt/src/hal/user_ip.c \
$(ROOT_PATH)/crt/src/hal/user_core.c
ifeq ($(HAVE_CSR),YES)
@@ -212,4 +213,4 @@ firmware: $(FIRMWARE_ELF)
firmware asm: | manifest
.PHONY: FORCE_VERSION
.PHONY: FORCE_VERSION
+1
View File
@@ -32,6 +32,7 @@ TEST_SOURCES = (
"crt/src/hal/i2s_math.c",
"crt/src/hal/dma_math.c",
"crt/src/hal/sysctrl.c",
"crt/src/hal/user_ip.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",
+3
View File
@@ -13,6 +13,9 @@ tooling.
- `test_crypto.py` runs directed AES, SHA-2, RSA/Montgomery, streaming, and
APB4 simulations; `test_crypto_register_parity.py` checks the handwritten
RTL/C register ABI and `test_dma.py` covers the crypto DMA endpoints.
- `test_user_ip_register_parity.py` keeps the integrated slot 1 timer and slot
2 GPIO register offsets synchronized with their application-owned C
definitions and checks their extension-manifest slot assignments.
Add a host C test when changing deterministic runtime or media logic; add a
Python test when changing scripts or build policy. Run:
+28 -7
View File
@@ -17,6 +17,7 @@
#include <retrosoc/hal/spisd.h>
#include <retrosoc/hal/timer.h>
#include <retrosoc/hal/uart.h>
#include <retrosoc/hal/user_ip.h>
#include <retrosoc/hal/ws2812.h>
#include <retrosoc/lib/printf.h>
#include <retrosoc/lib/stdlib.h>
@@ -698,6 +699,26 @@ static int test_dma_config_validation(void) {
return 0;
}
static int test_user_ip_validation(void) {
uint32_t value;
if ((rs_user_ip_get_selected(NULL) != RS_EINVAL) ||
(rs_user_ip_select(UINT8_MAX) != RS_EINVAL) ||
(rs_user_ip_probe(UINT8_MAX, &value) != RS_EINVAL) ||
(rs_user_ip_probe(0U, NULL) != RS_EINVAL)) {
return 1;
}
if ((rs_user_ip_read(0U, NULL) != RS_EINVAL) || (rs_user_ip_read(1U, &value) != RS_EINVAL) ||
(rs_user_ip_read(UINT32_C(0x1000), &value) != RS_EINVAL)) {
return 2;
}
if ((rs_user_ip_write(2U, 0U) != RS_EINVAL) ||
(rs_user_ip_write(UINT32_C(0x1000), 0U) != RS_EINVAL)) {
return 3;
}
return 0;
}
static int test_ps2_decoders(void) {
ps2_keyboard_decoder_t keyboard;
ps2_mouse_decoder_t mouse;
@@ -767,13 +788,13 @@ 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_psram_helpers(), test_sdram_helpers(), test_uart_helpers(),
test_i2s_helpers(), test_i2c_helpers(), test_sdio_helpers(),
test_spisd_helpers(), test_gpio_helpers(), test_dma_config_validation(),
test_opipsram_helpers(), test_ps2_decoders(), test_wav_parser(),
test_video_parser(),
test_string_helpers(), test_formatter(), test_compiler_helpers(),
test_wait_helper(), test_ws2812_helpers(), test_timer_helpers(),
test_psram_helpers(), test_sdram_helpers(), test_uart_helpers(),
test_i2s_helpers(), test_i2c_helpers(), test_sdio_helpers(),
test_spisd_helpers(), test_gpio_helpers(), test_dma_config_validation(),
test_opipsram_helpers(), test_user_ip_validation(), test_ps2_decoders(),
test_wav_parser(), test_video_parser(),
};
for (size_t index = 0U; index < (sizeof(results) / sizeof(results[0])); ++index) {
+76
View File
@@ -0,0 +1,76 @@
"""Keep the integrated user-IP RTL and application register ABI synchronized."""
from __future__ import annotations
import json
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
C_DEFINE = ROOT / "app/network/userip/src/user_ip_regs.h"
EXTENSIONS = ROOT / "rtl/mini/integration/user_extensions.json"
SOC_HEADER = ROOT / "crt/include/retrosoc/core/soc.h"
RTL_DEFINES = {
1: ROOT / "rtl/managed/mpw/ip/username1/user_ip_design.sv",
2: ROOT / "rtl/managed/mpw/ip/username2/user_ip_design.sv",
}
def _rtl_values(path: Path) -> dict[str, int]:
values: dict[str, int] = {}
pattern = re.compile(r"^\s*localparam\s+(\w+)\s*=\s*8'h([0-9a-fA-F]+);(?:\s*//.*)?$")
for line in path.read_text(encoding="utf-8").splitlines():
match = pattern.match(line)
if match is not None:
name, value = match.groups()
values[name] = int(value, 16)
return values
def _c_values() -> dict[str, int]:
values: dict[str, int] = {}
pattern = re.compile(r"^#define\s+(\w+)\s+UINT(?:8|32)_C\((0x[0-9a-fA-F]+|\d+)\)$")
for line in C_DEFINE.read_text(encoding="utf-8").splitlines():
match = pattern.match(line)
if match is not None:
name, value = match.groups()
values[name] = int(value, 0)
return values
def test_user_ip_register_offsets_match_integrated_rtl() -> None:
c = _c_values()
mappings = {
1: {
"USER_IP_APB_ID": "RS_USER_TIMER_REG_ID",
"USER_IP_APB_DIV": "RS_USER_TIMER_REG_DIV",
"USER_IP_APB_CNT": "RS_USER_TIMER_REG_COUNT",
},
2: {
"USER_IP_APB_ID": "RS_USER_GPIO_REG_ID",
"USER_IP_APB_OE": "RS_USER_GPIO_REG_OE",
"USER_IP_APB_DO": "RS_USER_GPIO_REG_DATA_OUT",
"USER_IP_APB_DI": "RS_USER_GPIO_REG_DATA_IN",
},
}
for slot, mapping in mappings.items():
rtl = _rtl_values(RTL_DEFINES[slot])
for rtl_name, c_name in mapping.items():
assert rtl[rtl_name] == c[c_name], f"slot {slot}: {rtl_name} != {c_name}"
def test_user_ip_software_slots_match_extension_manifest() -> None:
c = _c_values()
document = json.loads(EXTENSIONS.read_text(encoding="utf-8"))
targets = {(target["slot"], target["module"]) for target in document["ip_targets"]}
assert targets == {
(c["RS_USER_TIMER_SLOT"], "mpw_i1"),
(c["RS_USER_GPIO_SLOT"], "mpw_i2"),
}
def test_legacy_user_ip_register_macros_are_retired() -> None:
assert "reg_user_ip_reg" not in SOC_HEADER.read_text(encoding="utf-8")