mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
feat: implement user IP integration with demo and validation tests
This commit is contained in:
23 files changed
+454
-89
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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, ÷r) != 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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
#ifndef USERIP_H__
|
||||
#define USERIP_H__
|
||||
|
||||
void userip_main(int argc, char **argv);
|
||||
#endif
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 *)®_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
@@ -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(¤t_user_id) != RS_OK) {
|
||||
if (rs_user_ip_get_selected(¤t_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) {
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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")
|
||||
Reference in new issue
Block a user