feat: refactor GPIO Interface and Migrate Legacy Code

- Updated the GPIO interface in multiple modules to use the new user_gpio_if structure instead of the legacy gpio_if.
- Modified the ip_apb_wrapper, ip_nmi_wrapper, user_ip_design_template, user_ip_top, and user_ip_wrapper to reflect the new GPIO interface.
- Increased the USER_GPIO_NUM from 16 to 32 in mdd_config.svh to accommodate more GPIOs.
- Removed legacy GPIO handling from retrosoc and retrosoc_asic modules.
- Implemented a migration script in generate_mpw.py to automatically update legacy GPIO ports in source files.
- Added tests for the new GPIO user interface and ensured proper functionality through gpio_user_mux_tb and gpio_user_mux.py.
- Created new makefiles for IP MDD configurations to support the updated GPIO structure.
- Updated regression tests to include new configurations and ensure compatibility with the changes.
This commit is contained in:
maksyuki committed 2026-07-22 09:58:40 +08:00
1 parent 921f8caa64
commit b5b5492659
24 files changed
+664 -182

No files matched your search

+2
View File
@@ -47,6 +47,8 @@ application, linker layout, and optional features as one reproducible configurat
| --- | --- | --- | --- |
| [`configs/ci/hazard3-rv32im-ihp130.mk`](configs/ci/hazard3-rv32im-ihp130.mk) | HAZARD3 / RV32IM | `bringup` | Pull-request open-source regression: firmware, Verilator, Icarus, Yosys, Icarus netlist simulation, and OpenSTA. |
| [`configs/ci/hazard3-rv32im-ihp130-shell.mk`](configs/ci/hazard3-rv32im-ihp130-shell.mk) | HAZARD3 / RV32IM | `shell` | Pull-request firmware build with CSR support enabled. |
| [`configs/ci/hazard3-rv32im-ihp130-ip-mdd.mk`](configs/ci/hazard3-rv32im-ihp130-ip-mdd.mk) | HAZARD3 / RV32IM + MDD IP | `bringup` | Pull-request firmware and Verilator integration coverage for the user-IP GPIO ownership path. |
| [`configs/ci/hazard3-rv32im-ihp130-ip-mdd-shell.mk`](configs/ci/hazard3-rv32im-ihp130-ip-mdd-shell.mk) | HAZARD3 / RV32IM + MDD IP | `shell` | Pull-request firmware compilation coverage for the user-IP software command. |
| [`configs/ci/mdd-rv32im-ihp130.mk`](configs/ci/mdd-rv32im-ihp130.mk) | MDD / RV32IM | `bringup` | Pull-request firmware and Verilator regression. |
| [`configs/nightly/picorv32-rv32im-ihp130.mk`](configs/nightly/picorv32-rv32im-ihp130.mk) | PicoRV32 / RV32IM | `bringup` | Nightly firmware, Verilator, and Icarus regression. |
| [`configs/cluster/hazard3-ics55.mk`](configs/cluster/hazard3-ics55.mk) | HAZARD3 / RV32IM | `bringup` | Cluster-only configuration; requires the site PDK and licensed-tool environment. |
@@ -0,0 +1,12 @@
SOC := MINI
CORE := HAZARD3
IP := MDD
PDK := IHP130
HAVE_PLL := NO
HAVE_SRAM_IF := NO
HAVE_SRAM_MACRO := NO
HAVE_SVA := NO
ISA := RV32IM
HAVE_CSR := YES
APP := shell
LINK_TYPE := ld2_psram
@@ -0,0 +1,12 @@
SOC := MINI
CORE := HAZARD3
IP := MDD
PDK := IHP130
HAVE_PLL := NO
HAVE_SRAM_IF := NO
HAVE_SRAM_MACRO := NO
HAVE_SVA := NO
ISA := RV32IM
HAVE_CSR := NO
APP := bringup
LINK_TYPE := ld2_psram
+3
View File
@@ -49,6 +49,9 @@
#define reg_gpio_istat RS_SOC_REG32(RS_SOC_NMI_GPIO_BASE, UINT32_C(0x24))
#define reg_gpio_iofcfg RS_SOC_REG32(RS_SOC_NMI_GPIO_BASE, UINT32_C(0x28))
#define reg_gpio_pinmux RS_SOC_REG32(RS_SOC_NMI_GPIO_BASE, UINT32_C(0x2C))
#define reg_gpio_user_sel RS_SOC_REG32(RS_SOC_NMI_GPIO_BASE, UINT32_C(0x30))
#define reg_gpio_user_lock RS_SOC_REG32(RS_SOC_NMI_GPIO_BASE, UINT32_C(0x34))
#define reg_gpio_user_status RS_SOC_REG32(RS_SOC_NMI_GPIO_BASE, UINT32_C(0x38))
// uart0
#define reg_uart0_clkdiv RS_SOC_REG32(RS_SOC_NMI_UART0_BASE, UINT32_C(0x00))
#define reg_uart0_data RS_SOC_REG32(RS_SOC_NMI_UART0_BASE, UINT32_C(0x04))
+22
View File
@@ -25,3 +25,25 @@ unsupported features, and unapproved connection syntax.
SoC wrappers use the existing clusterIP interfaces for protocol boundaries.
`apb4_if_bridge` only adapts `apb4_pure_if` to `apb4_if`, and `gpio_pad_bridge`
only exposes the pad-side subset of `gpio_if`; neither module contains state.
## User IP GPIO ownership
The IP=MDD configuration does not add dedicated user GPIO pads. A user IP can
instead drive any of the 32 native GPIO pads through `user_gpio_if`. Software
selects an owner per pin with the native GPIO `USER_SEL` register at offset
`0x30`; clear selects the existing software/alternate GPIO path and set selects
the user IP. `USER_LOCK` at `0x34` is write-one-set and prevents a selected
owner bit from changing until reset. `USER_STATUS` at `0x38` reports the user
IP pins that are actively connected.
On every accepted ownership change, the pad output enable is forced low for one
full system clock. The native GPIO block retains `CS`, `PU`, and `PD` control
in all modes. User IPs provide only output data, output enable, and sampled pad
input. Configure the target output data and enable before writing `USER_SEL`,
then program `USER_LOCK` after the handoff when the assignment is permanent.
User-IP RTL must declare its GPIO port as `user_gpio_if.user_ip gpio`; the only
signals available through that port are `do_o`, `oe_o`, and `di_i`. The MPW
generator migrates the locked legacy examples in its isolated build output, so
they cannot drive native GPIO electrical controls. New IP submissions must use
the `user_gpio_if` declaration directly.
@@ -0,0 +1,77 @@
{
"entries": [
{
"count": 1,
"example": "UNSIGNED:$ROOT/rtl/ip/native/storage/xpi.sv:<line>: Comparison is constant due to unsigned arithmetic",
"signature": "010491867f065cec"
},
{
"count": 4,
"example": "WIDTHEXPAND:$ROOT/rtl/ip/native/peripheral/sysctrl.sv:<line>: Operator EQ expects 32 bits on the LHS, but LHS's SEL generates 8 bits.",
"signature": "3bba21d5150bdc2e"
},
{
"count": 1,
"example": "UNOPTFLAT:$ROOT/rtl/ip/native/storage/xpi.sv:<line>: Signal unoptimizable: Circular combinational logic: 'retrosoc_top.u_retrosoc_asic.u_xpi_if.io_di_i'",
"signature": "411d9ab46fef9d7d"
},
{
"count": 1,
"example": "UNSIGNED:$ROOT/rtl/mini/top/ip_nmi_wrapper.sv:<line>: Comparison is constant due to unsigned arithmetic",
"signature": "7350cffccb478d64"
},
{
"count": 1,
"example": "SPECIFYIGN:$ROOT/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_sram/verilog/RM_IHPSG13_1P_1024x64_c2_bm_bist.v:<line>: Ignoring unsupported: specify block construct",
"signature": "7a759d6e07651fd8"
},
{
"count": 2,
"example": "CMPCONST:$ROOT/rtl/ip/native/storage/spisd_write.sv:<line>: Comparison is constant due to limited range",
"signature": "813090adc9f1e8a8"
},
{
"count": 1,
"example": "WIDTHEXPAND:$ROOT/rtl/ip/native/peripheral/sysctrl.sv:<line>: Operator CASE expects 32 bits on the Case Item, but Case Item's CONST '8'h0' generates 8 bits.",
"signature": "8ad9b6921bd232e7"
},
{
"count": 1,
"example": "UNSIGNED:$ROOT/rtl/mini/top/bus.sv:<line>: Comparison is constant due to unsigned arithmetic",
"signature": "9f53ff454a8648a4"
},
{
"count": 1,
"example": "WIDTHEXPAND:$ROOT/rtl/ip/native/peripheral/sysctrl.sv:<line>: Operator CASE expects 32 bits on the Case Item, but Case Item's CONST '8'h4' generates 8 bits.",
"signature": "be2cb5333a881eca"
},
{
"count": 1,
"example": "WIDTHEXPAND:$ROOT/rtl/ip/native/peripheral/sysctrl.sv:<line>: Operator CASE expects 32 bits on the Case expression, but Case expression's SEL generates 8 bits.",
"signature": "c43a29f4c975e9c1"
},
{
"count": 68,
"example": "SPECIFYIGN:$ROOT/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_stdcell/verilog/sg13g2_stdcell.v:<line>: Ignoring unsupported: specify block construct",
"signature": "c54331448f63ac73"
},
{
"count": 1,
"example": "WIDTHTRUNC:$ROOT/rtl/ip/native/storage/spisd_read.sv:<line>: Operator ASSIGNDLY expects 8 bits on the Assign RHS, but Assign RHS's REPLICATE generates 9 bits.",
"signature": "c963ef155f9a032c"
},
{
"count": 3,
"example": "WIDTHTRUNC:$ROOT/rtl/ip/native/storage/spisd_write.sv:<line>: Bit extraction of var[7:0] requires 3 bit index, not 4 bits.",
"signature": "ef9f314dab2ac31d"
},
{
"count": 11,
"example": "SPECIFYIGN:$ROOT/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_io/verilog/sg13g2_io.v:<line>: Ignoring unsupported: specify block construct",
"signature": "faa191e2dbf4a242"
}
],
"profile": "hazard3-rv32im-ihp130-ip-mdd",
"schema_version": 1,
"tool": "verilator"
}
+101 -28
View File
@@ -31,18 +31,20 @@
`include "gpio_define.svh"
interface gpio_if ();
logic [`NMI_GPIO_NUM-1:0] oe_o;
logic [`NMI_GPIO_NUM-1:0] cs_o;
logic [`NMI_GPIO_NUM-1:0] pu_o;
logic [`NMI_GPIO_NUM-1:0] pd_o;
logic [`NMI_GPIO_NUM-1:0] do_o;
logic [`NMI_GPIO_NUM-1:0] di_i;
logic [`NMI_GPIO_NUM-1:0] alt0_do_i;
logic [`NMI_GPIO_NUM-1:0] alt0_oe_i;
logic [`NMI_GPIO_NUM-1:0] alt1_do_i;
logic [`NMI_GPIO_NUM-1:0] alt1_oe_i;
logic irq_o;
interface gpio_if #(
parameter int DATA_WIDTH = `NMI_GPIO_NUM
) ();
logic [DATA_WIDTH-1:0] oe_o;
logic [DATA_WIDTH-1:0] cs_o;
logic [DATA_WIDTH-1:0] pu_o;
logic [DATA_WIDTH-1:0] pd_o;
logic [DATA_WIDTH-1:0] do_o;
logic [DATA_WIDTH-1:0] di_i;
logic [DATA_WIDTH-1:0] alt0_do_i;
logic [DATA_WIDTH-1:0] alt0_oe_i;
logic [DATA_WIDTH-1:0] alt1_do_i;
logic [DATA_WIDTH-1:0] alt1_oe_i;
logic irq_o;
modport dut(
output oe_o,
@@ -63,12 +65,24 @@ interface gpio_if ();
modport soc_pad(output oe_o, output cs_o, output pu_o, output pd_o, output do_o, input di_i);
endinterface
interface user_gpio_if #(
parameter int DATA_WIDTH = `NMI_GPIO_NUM
) ();
logic [DATA_WIDTH-1:0] do_o;
logic [DATA_WIDTH-1:0] oe_o;
logic [DATA_WIDTH-1:0] di_i;
modport user_ip(output do_o, output oe_o, input di_i);
modport padctrl(input do_o, input oe_o, output di_i);
endinterface
module nmi_gpio (
// verilog_format: off
input logic clk_i,
input logic rst_n_i,
nmi_if.slave nmi,
gpio_if.dut gpio
gpio_if.dut gpio,
user_gpio_if.padctrl user_gpio
// verilog_format: on
);
@@ -101,6 +115,14 @@ module nmi_gpio (
logic [`NMI_GPIO_NUM-1:0] s_gpio_iofcfg_d, s_gpio_iofcfg_q;
logic s_gpio_pinmux_en;
logic [`NMI_GPIO_NUM-1:0] s_gpio_pinmux_d, s_gpio_pinmux_q;
`ifdef IP_MDD
logic s_gpio_user_sel_en;
logic [`NMI_GPIO_NUM-1:0] s_gpio_user_sel_d, s_gpio_user_sel_q;
logic s_gpio_user_lock_en;
logic [`NMI_GPIO_NUM-1:0] s_gpio_user_lock_d, s_gpio_user_lock_q;
logic [`NMI_GPIO_NUM-1:0] s_gpio_user_handoff_d, s_gpio_user_handoff_q;
logic [`NMI_GPIO_NUM-1:0] s_gpio_user_status;
`endif
// irq
logic [`NMI_GPIO_NUM-1:0] s_gpio_di_re, s_gpio_di_fe, s_gpio_irq;
logic [`NMI_GPIO_NUM-1:0] s_irq_rise, s_irq_fall;
@@ -109,6 +131,7 @@ module nmi_gpio (
logic s_irq_trg, s_irq_stat;
// alt
logic [`NMI_GPIO_NUM-1:0] s_gpio_alt_out, s_gpio_alt_oe;
logic [`NMI_GPIO_NUM-1:0] s_gpio_native_out, s_gpio_native_oe;
// shake
assign s_nmi_wr_hdshk = nmi.valid && (~s_nmi_ready_q) && (|nmi.wstrb);
@@ -124,9 +147,22 @@ module nmi_gpio (
assign s_gpio_alt_out[i] = s_gpio_pinmux_q[i] ? gpio.alt1_do_i[i] : gpio.alt0_do_i[i];
end
for (genvar i = 0; i < `NMI_GPIO_NUM; i++) begin : IOF_PINMUX_BLOCK
assign gpio.oe_o[i] = s_gpio_iofcfg_q[i] ? s_gpio_alt_oe[i] : s_gpio_oe_q[i];
assign gpio.do_o[i] = s_gpio_iofcfg_q[i] ? s_gpio_alt_out[i] : s_gpio_do_q[i];
assign s_gpio_native_oe[i] = s_gpio_iofcfg_q[i] ? s_gpio_alt_oe[i] : s_gpio_oe_q[i];
assign s_gpio_native_out[i] = s_gpio_iofcfg_q[i] ? s_gpio_alt_out[i] : s_gpio_do_q[i];
end
`ifdef IP_MDD
assign user_gpio.di_i = gpio.di_i;
for (genvar i = 0; i < `NMI_GPIO_NUM; i++) begin : USER_GPIO_MUX_BLOCK
assign gpio.oe_o[i] = s_gpio_user_handoff_q[i]
? 1'b0
: (s_gpio_user_sel_q[i] ? user_gpio.oe_o[i] : s_gpio_native_oe[i]);
assign gpio.do_o[i] = s_gpio_user_sel_q[i] ? user_gpio.do_o[i] : s_gpio_native_out[i];
end
`else
assign gpio.oe_o = s_gpio_native_oe;
assign gpio.do_o = s_gpio_native_out;
assign user_gpio.di_i = '0;
`endif
assign s_irq_stat = |s_gpio_istat_q;
assign gpio.irq_o = s_irq_stat;
@@ -284,6 +320,38 @@ module nmi_gpio (
s_gpio_pinmux_q
);
`ifdef IP_MDD
assign s_gpio_user_sel_en = s_nmi_wr_hdshk && nmi.addr[7:0] == `NMI_GPIO_USER_SEL;
assign s_gpio_user_sel_d = (s_gpio_user_sel_q & s_gpio_user_lock_q) |
(nmi.wdata[`NMI_GPIO_NUM-1:0] & ~s_gpio_user_lock_q);
dffer #(`NMI_GPIO_NUM) u_gpio_user_sel_dffer (
clk_i,
rst_n_i,
s_gpio_user_sel_en,
s_gpio_user_sel_d,
s_gpio_user_sel_q
);
assign s_gpio_user_lock_en = s_nmi_wr_hdshk && nmi.addr[7:0] == `NMI_GPIO_USER_LOCK;
assign s_gpio_user_lock_d = s_gpio_user_lock_q | nmi.wdata[`NMI_GPIO_NUM-1:0];
dffer #(`NMI_GPIO_NUM) u_gpio_user_lock_dffer (
clk_i,
rst_n_i,
s_gpio_user_lock_en,
s_gpio_user_lock_d,
s_gpio_user_lock_q
);
assign s_gpio_user_handoff_d = s_gpio_user_sel_en ? (s_gpio_user_sel_d ^ s_gpio_user_sel_q) : '0;
dffr #(`NMI_GPIO_NUM) u_gpio_user_handoff_dffr (
clk_i,
rst_n_i,
s_gpio_user_handoff_d,
s_gpio_user_handoff_q
);
assign s_gpio_user_status = s_gpio_user_sel_q & ~s_gpio_user_handoff_q;
`endif
// nmi resp
assign s_nmi_ready_d = nmi.valid && (~s_nmi_ready_q);
@@ -298,19 +366,24 @@ module nmi_gpio (
always_comb begin
s_nmi_rdata_d = s_nmi_rdata_q;
unique case (nmi.addr[7:0])
`NMI_GPIO_OE: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_oe_q};
`NMI_GPIO_CS: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_cs_q};
`NMI_GPIO_PU: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_pu_q};
`NMI_GPIO_PD: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_pd_q};
`NMI_GPIO_DO: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_do_q};
`NMI_GPIO_DI: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_di};
`NMI_GPIO_IEN: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_ien_q};
`NMI_GPIO_ITYPE0: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_itype0_q};
`NMI_GPIO_ITYPE1: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_itype1_q};
`NMI_GPIO_ISTAT: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_istat_q};
`NMI_GPIO_IOFCFG: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_iofcfg_q};
`NMI_GPIO_PINMUX: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_pinmux_q};
default: s_nmi_rdata_d = s_nmi_rdata_q;
`NMI_GPIO_OE: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_oe_q};
`NMI_GPIO_CS: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_cs_q};
`NMI_GPIO_PU: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_pu_q};
`NMI_GPIO_PD: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_pd_q};
`NMI_GPIO_DO: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_do_q};
`NMI_GPIO_DI: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_di};
`NMI_GPIO_IEN: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_ien_q};
`NMI_GPIO_ITYPE0: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_itype0_q};
`NMI_GPIO_ITYPE1: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_itype1_q};
`NMI_GPIO_ISTAT: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_istat_q};
`NMI_GPIO_IOFCFG: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_iofcfg_q};
`NMI_GPIO_PINMUX: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_pinmux_q};
`ifdef IP_MDD
`NMI_GPIO_USER_SEL: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_user_sel_q};
`NMI_GPIO_USER_LOCK: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_user_lock_q};
`NMI_GPIO_USER_STATUS: s_nmi_rdata_d = {{(32 - `NMI_GPIO_NUM) {1'b0}}, s_gpio_user_status};
`endif
default: s_nmi_rdata_d = s_nmi_rdata_q;
endcase
end
dffer #(32) u_nmi_rdata_dffer (
+3
View File
@@ -25,5 +25,8 @@
`define NMI_GPIO_ISTAT 8'h24 // ro
`define NMI_GPIO_IOFCFG 8'h28 // rw
`define NMI_GPIO_PINMUX 8'h2c // rw
`define NMI_GPIO_USER_SEL 8'h30 // rw
`define NMI_GPIO_USER_LOCK 8'h34 // rw1s
`define NMI_GPIO_USER_STATUS 8'h38 // ro
// verilog_format: on
`endif
+4 -17
View File
@@ -30,8 +30,6 @@ module retrosoc_tb;
`endif
reg [ 4:0] r_core_sel;
wire [ 4:0] s_core_sel;
wire s_user_gpio_0_io;
reg r_user_gpio_0_io;
wire s_uart0_tx;
// for handle x-prop issue
wire s_uart0_rx = 1'b1;
@@ -84,11 +82,10 @@ module retrosoc_tb;
always #(1000 / AUD_CPU_FREQ / 2) r_aud_clk = (r_aud_clk === 1'b0);
// connect inout pad
assign s_ext_clk = r_ext_clk;
assign s_aud_clk = r_aud_clk;
assign s_rst_n = r_rst_n;
assign s_core_sel = r_core_sel;
assign s_user_gpio_0_io = r_user_gpio_0_io;
assign s_ext_clk = r_ext_clk;
assign s_aud_clk = r_aud_clk;
assign s_rst_n = r_rst_n;
assign s_core_sel = r_core_sel;
retrosoc_asic u_retrosoc_asic (
`include "retrosoc_asic_tb_bindings.svh"
@@ -241,16 +238,6 @@ module retrosoc_tb;
end
end
initial begin : USER_GPIO_BLOCK
integer i;
#1000;
r_user_gpio_0_io = 1'b0;
while (1) begin
#150000;
r_user_gpio_0_io = ~r_user_gpio_0_io;
end
end
initial begin
if ($test$plusargs("wave_YES")) begin
$display("== generate simulation wave ==");
-10
View File
@@ -33,15 +33,6 @@
"count": 5,
"signal": "s_core_sel[{index}]"
},
{
"kind": "full",
"direction": "inout",
"feature": "IP_MDD",
"prefix": "user_gpio_",
"suffix": "_io_pad",
"count": 16,
"interface": "u_user_gpio_if"
},
{
"kind": "full",
"direction": "inout",
@@ -247,7 +238,6 @@
"core_sel_2_i_pad": "s_core_sel[2]",
"core_sel_3_i_pad": "s_core_sel[3]",
"core_sel_4_i_pad": "s_core_sel[4]",
"user_gpio_0_io_pad": "s_user_gpio_0_io",
"gpio_0_io_pad": "s_gpio_0_io",
"gpio_1_io_pad": "s_gpio_1_io",
"gpio_7_io_pad": "s_i2c0_scl_io",
+2 -2
View File
@@ -27,7 +27,7 @@ module ip_apb_wrapper (
ps2_if.dut ps2,
`ifdef IP_MDD
input logic [`USER_IPSEL_WIDTH-1:0] ip_sel_i,
gpio_if.dut gpio,
user_gpio_if.user_ip user_gpio,
`endif
output logic [ 6:0] irq_o
// verilog_format: on
@@ -157,7 +157,7 @@ module ip_apb_wrapper (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.sel_i (ip_sel_i),
.gpio (gpio),
.gpio (user_gpio),
.apb (u_user_ip_apb_if)
);
`endif
+6 -4
View File
@@ -20,6 +20,7 @@ module ip_nmi_wrapper (
// natv if
nmi_if.slave nmi,
gpio_if.dut gpio,
user_gpio_if.padctrl user_gpio,
uart_if.dut uart,
psram_if.dut psram,
spi_if.dut spisd,
@@ -282,10 +283,11 @@ module ip_nmi_wrapper (
nmi_gpio u_nmi_gpio (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.nmi (u_gpio_nmi_if),
.gpio (gpio)
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.nmi (u_gpio_nmi_if),
.gpio (gpio),
.user_gpio(user_gpio)
);
+1 -1
View File
@@ -14,6 +14,6 @@
`define USER_CORESEL_WIDTH 5
`define USER_IPSEL_WIDTH 8
`define USER_GPIO_NUM 16
`define USER_GPIO_NUM 32
`endif
+8 -4
View File
@@ -22,9 +22,6 @@ module retrosoc (
`ifdef CORE_MDD
input logic [`USER_CORESEL_WIDTH-1:0] core_sel_i,
`endif
`ifdef IP_MDD
gpio_if.dut user_gpio,
`endif
`ifdef HAVE_SRAM_IF
ram_if.master ram,
`endif
@@ -42,6 +39,7 @@ module retrosoc (
nmi_if u_dma_nmi_if ();
nmi_if u_nmi_nmi_if ();
nmi_if u_apb_nmi_if ();
user_gpio_if u_user_gpio_if ();
// ip interface
gpio_if u_gpio_if ();
psram_if u_psram_if ();
@@ -73,6 +71,11 @@ module retrosoc (
.outer(gpio)
);
`ifndef IP_MDD
assign u_user_gpio_if.do_o = '0;
assign u_user_gpio_if.oe_o = '0;
`endif
`ifdef CORE_MDD
assign u_sysctrl_if.core_sel_i = core_sel_i;
`else
@@ -395,6 +398,7 @@ module retrosoc (
.rst_aud_n_i (rst_aud_n_i),
.nmi (u_nmi_nmi_if),
.gpio (u_gpio_if),
.user_gpio (u_user_gpio_if),
.uart (uart0),
.psram (u_psram_if),
.spisd (u_spisd_if),
@@ -430,7 +434,7 @@ module retrosoc (
.ps2 (u_ps2_if),
`ifdef IP_MDD
.ip_sel_i (u_sysctrl_if.ip_sel_o),
.gpio (user_gpio),
.user_gpio (u_user_gpio_if),
`endif
.irq_o (s_apb_irq)
);
-7
View File
@@ -29,10 +29,6 @@ module retrosoc_asic (
logic [`USER_CORESEL_WIDTH-1:0] s_core_sel;
`endif
`ifdef IP_MDD
gpio_if #(`USER_GPIO_NUM) u_user_gpio_if ();
`endif
`ifdef HAVE_SRAM_IF
ram_if u_ram_if ();
`endif
@@ -76,9 +72,6 @@ rcu u_rcu (
`ifdef CORE_MDD
.core_sel_i (s_core_sel),
`endif
`ifdef IP_MDD
.user_gpio (u_user_gpio_if),
`endif
`ifdef HAVE_SRAM_IF
.ram (u_ram_if),
`endif
+5 -8
View File
@@ -25,7 +25,7 @@ module user_ip_design_template #(
// verilog_format: off
input logic clk_i,
input logic rst_n_i,
gpio_if.dut gpio,
user_gpio_if.user_ip gpio,
apb4_if.slave apb
// verilog_format: on
);
@@ -67,13 +67,10 @@ module user_ip_design_template #(
// ========== USER CUSTOM AREA ==============
// NOTE: If needed, define io logic here.
// `gpio_oe` is active high, meaning gpio is
// output when `gpio_oe[x]` = 1'b1.
assign gpio.gpio_oe = '1;
assign gpio.gpio_cs = '1; // 1: CMOS 0: SCHMI
assign gpio.gpio_pu = '0;
assign gpio.gpio_pd = '0;
assign gpio.gpio_out = '0;
// `oe_o` is active high, meaning GPIO is output when `oe_o[x]` is 1'b1.
// Pad electrical controls remain owned by the native GPIO controller.
assign gpio.oe_o = '0;
assign gpio.do_o = '0;
// ========== USER CUSTOM AREA END ==========
+68 -83
View File
@@ -16,15 +16,15 @@ module user_ip_top (
input logic clk_i,
input logic rst_n_i,
input logic [`USER_IPSEL_WIDTH-1:0] sel_i,
gpio_if.dut gpio,
user_gpio_if.user_ip gpio,
apb4_if.slave apb
// verilog_format: on
);
// ====== GENERATED BY SCRIPT ======
// verilog_format: off
gpio_if u_user_1_gpio_if();
gpio_if u_user_2_gpio_if();
user_gpio_if #(`USER_GPIO_NUM) u_user_1_gpio_if();
user_gpio_if #(`USER_GPIO_NUM) u_user_2_gpio_if();
apb4_if u_user_1_apb_if(clk_i, rst_n_i);
apb4_if u_user_2_apb_if(clk_i, rst_n_i);
@@ -32,103 +32,88 @@ module user_ip_top (
// verilog_format: on
always_comb begin
// gpio
gpio.gpio_out = '0;
gpio.gpio_oe = '0;
gpio.gpio_cs = '0;
gpio.gpio_pu = '0;
gpio.gpio_pd = '0;
u_user_1_gpio_if.gpio_in = '0;
u_user_2_gpio_if.gpio_in = '0;
gpio.do_o = '0;
gpio.oe_o = '0;
// apb
apb.pready = '0;
apb.prdata = '0;
apb.pslverr = '0;
u_user_1_apb_if.paddr = '0;
u_user_1_apb_if.pprot = '0;
u_user_1_apb_if.psel = '0;
u_user_1_apb_if.penable = '0;
u_user_1_apb_if.pwrite = '0;
u_user_1_apb_if.pwdata = '0;
u_user_1_apb_if.pstrb = '0;
u_user_2_apb_if.paddr = '0;
u_user_2_apb_if.pprot = '0;
u_user_2_apb_if.psel = '0;
u_user_2_apb_if.penable = '0;
u_user_2_apb_if.pwrite = '0;
u_user_2_apb_if.pwdata = '0;
u_user_2_apb_if.pstrb = '0;
apb.pready = '0;
apb.prdata = '0;
apb.pslverr = '0;
u_user_1_apb_if.paddr = '0;
u_user_1_apb_if.pprot = '0;
u_user_1_apb_if.psel = '0;
u_user_1_apb_if.penable = '0;
u_user_1_apb_if.pwrite = '0;
u_user_1_apb_if.pwdata = '0;
u_user_1_apb_if.pstrb = '0;
u_user_2_apb_if.paddr = '0;
u_user_2_apb_if.pprot = '0;
u_user_2_apb_if.psel = '0;
u_user_2_apb_if.penable = '0;
u_user_2_apb_if.pwrite = '0;
u_user_2_apb_if.pwdata = '0;
u_user_2_apb_if.pstrb = '0;
unique case (sel_i)
8'd1: begin
// gpio
gpio.gpio_out = u_user_1_gpio_if.gpio_out;
gpio.gpio_oe = u_user_1_gpio_if.gpio_oe;
gpio.gpio_cs = u_user_1_gpio_if.gpio_cs;
gpio.gpio_pu = u_user_1_gpio_if.gpio_pu;
gpio.gpio_pd = u_user_1_gpio_if.gpio_pd;
u_user_1_gpio_if.gpio_in = gpio.gpio_in;
gpio.do_o = u_user_1_gpio_if.do_o;
gpio.oe_o = u_user_1_gpio_if.oe_o;
// apb
apb.pready = u_user_1_apb_if.pready;
apb.prdata = u_user_1_apb_if.prdata;
apb.pslverr = u_user_1_apb_if.pslverr;
u_user_1_apb_if.paddr = apb.paddr;
u_user_1_apb_if.pprot = apb.pprot;
u_user_1_apb_if.psel = apb.psel;
u_user_1_apb_if.penable = apb.penable;
u_user_1_apb_if.pwrite = apb.pwrite;
u_user_1_apb_if.pwdata = apb.pwdata;
u_user_1_apb_if.pstrb = apb.pstrb;
apb.pready = u_user_1_apb_if.pready;
apb.prdata = u_user_1_apb_if.prdata;
apb.pslverr = u_user_1_apb_if.pslverr;
u_user_1_apb_if.paddr = apb.paddr;
u_user_1_apb_if.pprot = apb.pprot;
u_user_1_apb_if.psel = apb.psel;
u_user_1_apb_if.penable = apb.penable;
u_user_1_apb_if.pwrite = apb.pwrite;
u_user_1_apb_if.pwdata = apb.pwdata;
u_user_1_apb_if.pstrb = apb.pstrb;
end
8'd2: begin
// gpio
gpio.gpio_out = u_user_2_gpio_if.gpio_out;
gpio.gpio_oe = u_user_2_gpio_if.gpio_oe;
gpio.gpio_cs = u_user_2_gpio_if.gpio_cs;
gpio.gpio_pu = u_user_2_gpio_if.gpio_pu;
gpio.gpio_pd = u_user_2_gpio_if.gpio_pd;
u_user_2_gpio_if.gpio_in = gpio.gpio_in;
gpio.do_o = u_user_2_gpio_if.do_o;
gpio.oe_o = u_user_2_gpio_if.oe_o;
// apb
apb.pready = u_user_2_apb_if.pready;
apb.prdata = u_user_2_apb_if.prdata;
apb.pslverr = u_user_2_apb_if.pslverr;
u_user_2_apb_if.paddr = apb.paddr;
u_user_2_apb_if.pprot = apb.pprot;
u_user_2_apb_if.psel = apb.psel;
u_user_2_apb_if.penable = apb.penable;
u_user_2_apb_if.pwrite = apb.pwrite;
u_user_2_apb_if.pwdata = apb.pwdata;
u_user_2_apb_if.pstrb = apb.pstrb;
apb.pready = u_user_2_apb_if.pready;
apb.prdata = u_user_2_apb_if.prdata;
apb.pslverr = u_user_2_apb_if.pslverr;
u_user_2_apb_if.paddr = apb.paddr;
u_user_2_apb_if.pprot = apb.pprot;
u_user_2_apb_if.psel = apb.psel;
u_user_2_apb_if.penable = apb.penable;
u_user_2_apb_if.pwrite = apb.pwrite;
u_user_2_apb_if.pwdata = apb.pwdata;
u_user_2_apb_if.pstrb = apb.pstrb;
end
default: begin
// gpio
gpio.gpio_out = '0;
gpio.gpio_oe = '0;
gpio.gpio_cs = '0;
gpio.gpio_pu = '0;
gpio.gpio_pd = '0;
u_user_1_gpio_if.gpio_in = '0;
u_user_2_gpio_if.gpio_in = '0;
gpio.do_o = '0;
gpio.oe_o = '0;
// apb
apb.pready = '0;
apb.prdata = '0;
apb.pslverr = '0;
u_user_1_apb_if.paddr = '0;
u_user_1_apb_if.pprot = '0;
u_user_1_apb_if.psel = '0;
u_user_1_apb_if.penable = '0;
u_user_1_apb_if.pwrite = '0;
u_user_1_apb_if.pwdata = '0;
u_user_1_apb_if.pstrb = '0;
u_user_2_apb_if.paddr = '0;
u_user_2_apb_if.pprot = '0;
u_user_2_apb_if.psel = '0;
u_user_2_apb_if.penable = '0;
u_user_2_apb_if.pwrite = '0;
u_user_2_apb_if.pwdata = '0;
u_user_2_apb_if.pstrb = '0;
apb.pready = '0;
apb.prdata = '0;
apb.pslverr = '0;
u_user_1_apb_if.paddr = '0;
u_user_1_apb_if.pprot = '0;
u_user_1_apb_if.psel = '0;
u_user_1_apb_if.penable = '0;
u_user_1_apb_if.pwrite = '0;
u_user_1_apb_if.pwdata = '0;
u_user_1_apb_if.pstrb = '0;
u_user_2_apb_if.paddr = '0;
u_user_2_apb_if.pprot = '0;
u_user_2_apb_if.psel = '0;
u_user_2_apb_if.penable = '0;
u_user_2_apb_if.pwrite = '0;
u_user_2_apb_if.pwdata = '0;
u_user_2_apb_if.pstrb = '0;
end
endcase
end
assign u_user_1_gpio_if.di_i = gpio.di_i;
assign u_user_2_gpio_if.di_i = gpio.di_i;
user_ip_design_username1 #(1) u_user_ip_design_username1 (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
+1 -1
View File
@@ -15,7 +15,7 @@ module user_ip_wrapper (
input logic clk_i,
input logic rst_n_i,
input logic [`USER_IPSEL_WIDTH-1:0] sel_i,
gpio_if.dut gpio,
user_gpio_if.user_ip gpio,
apb4_if.slave apb
// verilog_format: on
);
+62 -5
View File
@@ -3,14 +3,18 @@
from __future__ import annotations
import argparse
import errno
import fcntl
import hashlib
import os
import re
import shutil
import subprocess
import tempfile
import time
from contextlib import contextmanager
from pathlib import Path
from typing import TextIO
from typing import Iterator, TextIO
def tree_digest(root: Path) -> str:
@@ -49,6 +53,58 @@ def rewrite_filelists(root: Path, replacements: tuple[tuple[Path, Path], ...]) -
filelist.write_text(content, encoding="utf-8")
def migrate_user_gpio_interfaces(root: Path) -> list[Path]:
"""Migrate legacy MPW user-IP GPIO ports in isolated build sources."""
legacy_port = re.compile(r"\bgpio_if\.dut\s+gpio\b")
legacy_pad_control = re.compile(
r"^[ \t]*assign[ \t]+gpio\.gpio_(?:cs|pu|pd)[ \t]*=[^;]*;[^\n]*\n",
flags=re.MULTILINE,
)
migrated: list[Path] = []
for source in sorted(root.rglob("*.sv")):
content = source.read_text(encoding="utf-8")
if legacy_port.search(content) is None:
continue
content, port_count = legacy_port.subn("user_gpio_if.user_ip gpio", content)
if port_count != 1:
raise ValueError(f"expected one legacy GPIO port in {source}")
content = legacy_pad_control.sub("", content)
for legacy_name, interface_name in (
("gpio.gpio_oe", "gpio.oe_o"),
("gpio.gpio_out", "gpio.do_o"),
("gpio.gpio_in", "gpio.di_i"),
):
content = content.replace(legacy_name, interface_name)
if "gpio.gpio_" in content:
raise ValueError(f"unsupported legacy GPIO signal in {source}")
source.write_text(content, encoding="utf-8")
migrated.append(source)
return migrated
def remove_tree(path: Path) -> None:
"""Remove an MPW workspace, tolerating transient NFS directory entries."""
for attempt in range(3):
try:
shutil.rmtree(path)
return
except OSError as error:
if error.errno != errno.ENOTEMPTY or attempt == 2:
raise
time.sleep(0.1)
@contextmanager
def temporary_workspace(parent: Path, prefix: str) -> Iterator[Path]:
workspace = Path(tempfile.mkdtemp(prefix=prefix, dir=parent))
try:
yield workspace
finally:
if workspace.exists():
remove_tree(workspace)
def prepare_legacy_mpw_workspace(root: Path, source: Path, workspace: Path) -> Path:
legacy_mpw = workspace / "rtl" / "mini" / "mpw"
legacy_mpw.mkdir(parents=True, exist_ok=True)
@@ -75,11 +131,10 @@ def generate(args: argparse.Namespace) -> None:
log_path = output.parent / f"{output.name}-generation.log"
output.parent.mkdir(parents=True, exist_ok=True)
with log_path.open("w", encoding="utf-8") as log, tempfile.TemporaryDirectory(
prefix=f".{output.name}.", dir=output.parent
) as temp:
with log_path.open("w", encoding="utf-8") as log, temporary_workspace(
output.parent, f".{output.name}."
) as workspace:
log.write(f"simulator={args.simu} core={args.core} ip={args.ip}\n")
workspace = Path(temp)
candidate = workspace / "output"
candidate.mkdir()
if not needs_core and not needs_ip:
@@ -100,6 +155,8 @@ def generate(args: argparse.Namespace) -> None:
run(workspace, log, mpw / "ip.py")
run(workspace, log, mpw / "info.py", "IP")
shutil.copytree(shared / "ip", candidate / "ip")
migrated = migrate_user_gpio_interfaces(candidate / "ip")
log.write(f"migrated user GPIO interfaces: {len(migrated)}\n")
if needs_core:
run(workspace, log, mpw / "core.py", args.simu)
run(workspace, log, mpw / "info.py", "CORE")
+38 -4
View File
@@ -15,25 +15,59 @@ sys.path.insert(0, str(ROOT))
from scripts.check_c_warnings import self_owned_warnings # noqa: E402
MDD_VERILATOR_SIM_TIME = 180
PR_COMMANDS = (
("configs/ci/hazard3-rv32im-ihp130.mk", ("firmware",)),
("configs/ci/hazard3-rv32im-ihp130-shell.mk", ("firmware",)),
("configs/ci/hazard3-rv32im-ihp130-ip-mdd-shell.mk", ("firmware",)),
(
"configs/ci/hazard3-rv32im-ihp130-ip-mdd.mk",
(
"SIMU=VERILATOR",
f"SOC_SIM_TIME={MDD_VERILATOR_SIM_TIME}",
"firmware",
"sim",
),
),
(
"configs/ci/mdd-rv32im-ihp130.mk",
("SIMU=VERILATOR", "SOC_SIM_TIME=120", "firmware", "sim"),
(
"SIMU=VERILATOR",
f"SOC_SIM_TIME={MDD_VERILATOR_SIM_TIME}",
"firmware",
"sim",
),
),
("configs/ci/hazard3-rv32im-ihp130.mk", ("SIMU=VERILATOR", "sim")),
("configs/ci/hazard3-rv32im-ihp130.mk", ("SIMU=IVERILOG", "RTL_SIM_TIMEOUT=5200000", "sim-asm")),
(
"configs/ci/hazard3-rv32im-ihp130.mk",
("SIMU=IVERILOG", "RTL_SIM_TIMEOUT=5200000", "sim-asm"),
),
("configs/ci/hazard3-rv32im-ihp130.mk", ("SYNTH=YOSYS", "synth")),
("configs/ci/hazard3-rv32im-ihp130.mk", ("SIMU=IVERILOG", "SIM_FIRMWARE_NAME=retrosoc_asm", "SIM_SUCCESS_MARKER=Mem wr/rd test success", "RTL_SIM_TIMEOUT=5200000", "netsim")),
(
"configs/ci/hazard3-rv32im-ihp130.mk",
(
"SIMU=IVERILOG",
"SIM_FIRMWARE_NAME=retrosoc_asm",
"SIM_SUCCESS_MARKER=Mem wr/rd test success",
"RTL_SIM_TIMEOUT=5200000",
"netsim",
),
),
("configs/ci/hazard3-rv32im-ihp130.mk", ("STA=OPENSTA", "sta")),
)
NIGHTLY_COMMANDS = PR_COMMANDS + (
("configs/nightly/picorv32-rv32im-ihp130.mk", ("SIMU=VERILATOR", "firmware", "sim")),
("configs/nightly/picorv32-rv32im-ihp130.mk", ("SIMU=IVERILOG", "RTL_SIM_TIMEOUT=5200000", "sim-asm")),
(
"configs/nightly/picorv32-rv32im-ihp130.mk",
("SIMU=IVERILOG", "RTL_SIM_TIMEOUT=5200000", "sim-asm"),
),
)
PR_PROFILES = (
"configs/ci/hazard3-rv32im-ihp130.mk",
"configs/ci/hazard3-rv32im-ihp130-ip-mdd.mk",
"configs/ci/mdd-rv32im-ihp130.mk",
)
NIGHTLY_PROFILES = PR_PROFILES + ("configs/nightly/picorv32-rv32im-ihp130.mk",)
+127
View File
@@ -0,0 +1,127 @@
`timescale 1ns / 1ps
module gpio_user_mux_tb;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
nmi_if nmi ();
gpio_if gpio ();
user_gpio_if user_gpio ();
logic [31:0] read_data;
always #5 clk_i = ~clk_i;
nmi_gpio dut (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.nmi (nmi),
.gpio (gpio),
.user_gpio(user_gpio)
);
task automatic write_register(input logic [7:0] address, input logic [31:0] data);
begin
@(negedge clk_i);
nmi.addr = {24'd0, address};
nmi.wdata = data;
nmi.wstrb = 4'hF;
nmi.valid = 1'b1;
@(posedge clk_i);
#1;
@(negedge clk_i);
nmi.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);
nmi.addr = {24'd0, address};
nmi.wdata = '0;
nmi.wstrb = '0;
nmi.valid = 1'b1;
@(posedge clk_i);
#1;
data = nmi.rdata;
@(negedge clk_i);
nmi.valid = 1'b0;
@(posedge clk_i);
#1;
end
endtask
task automatic select_user_gpio(input logic [31:0] selection);
begin
@(negedge clk_i);
nmi.addr = 32'h0000_0030;
nmi.wdata = selection;
nmi.wstrb = 4'hF;
nmi.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);
nmi.valid = 1'b0;
@(posedge clk_i);
#1;
end
endtask
initial begin
nmi.valid = 1'b0;
nmi.addr = '0;
nmi.wdata = '0;
nmi.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, "native 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, "native GPIO electrical controls changed under user ownership");
$display("GPIO user ownership test passed");
$finish;
end
endmodule
+59
View File
@@ -0,0 +1,59 @@
"""RTL tests for user-IP ownership of the native 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_native_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",
"+define+IP_MDD",
f"+incdir+{ROOT / 'rtl/ip/native/peripheral'}",
f"+incdir+{ROOT / 'rtl/managed/clusterip/common/rtl'}",
str(ROOT / "rtl/managed/clusterip/common/rtl/interface/nmi_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/native/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
+3
View File
@@ -60,6 +60,9 @@ def test_pin_map_generates_asic_and_platform_bindings(tmp_path: Path) -> None:
assert ".gpio_15_io_pad()" in fpga
assert ".gpio_24_io_pad(s_psram_dat1)" in testbench
assert testbench.count(".gpio_24_io_pad(") == 1
assert "user_gpio_" not in ports
assert "user_gpio_" not in pads
assert "user_gpio_" not in testbench
def test_pin_map_rejects_duplicate_pads(tmp_path: Path) -> None:
+48 -8
View File
@@ -25,8 +25,9 @@ from scripts.analyze_warnings import normalize # noqa: E402
from scripts.check_c_warnings import self_owned_warnings # noqa: E402
from scripts.check_format import format_files # noqa: E402
from scripts.dependency_lock import LockError, load_lock # noqa: E402
from scripts.generate_mpw import migrate_user_gpio_interfaces # noqa: E402
from scripts.install_toolchain import safe_extract # noqa: E402
from scripts.regress import regression_environment # noqa: E402
from scripts.regress import MDD_VERILATOR_SIM_TIME, PR_COMMANDS, regression_environment # noqa: E402
from scripts.setup_helpers import download_file, ensure_git_repo # noqa: E402
@@ -89,6 +90,33 @@ def test_generate_all_is_stable_and_expands_paths(tmp_path: Path) -> None:
assert str(ROOT / "rtl/managed/clusterip") in cluster
def test_legacy_user_gpio_interface_is_migrated_in_generated_source(tmp_path: Path) -> None:
source = tmp_path / "user_ip_design.sv"
source.write_text(
"""module user_ip_design (gpio_if.dut gpio);
logic value;
assign gpio.gpio_oe = value;
assign gpio.gpio_cs = value;
assign gpio.gpio_pu = value;
assign gpio.gpio_pd = value;
assign gpio.gpio_out = value;
always_comb value = gpio.gpio_in[0];
endmodule
""",
encoding="utf-8",
)
assert migrate_user_gpio_interfaces(tmp_path) == [source]
migrated = source.read_text(encoding="utf-8")
assert "gpio_if.dut" not in migrated
assert "gpio.gpio_" not in migrated
assert "user_gpio_if.user_ip gpio" in migrated
assert "gpio.oe_o" in migrated
assert "gpio.do_o" in migrated
assert "gpio.di_i" in migrated
def test_prepare_norflash_and_missing_firmware(tmp_path: Path) -> None:
models = tmp_path / "models"
models.mkdir()
@@ -270,6 +298,22 @@ def test_regression_runner_uses_one_build_timestamp(monkeypatch) -> None:
)
def test_mdd_regressions_allow_uart_boot_to_complete() -> None:
mdd_profiles = {
"configs/ci/hazard3-rv32im-ihp130-ip-mdd.mk",
"configs/ci/mdd-rv32im-ihp130.mk",
}
expected_values = (
"SIMU=VERILATOR",
f"SOC_SIM_TIME={MDD_VERILATOR_SIM_TIME}",
"firmware",
"sim",
)
assert MDD_VERILATOR_SIM_TIME == 180
assert {profile for profile, values in PR_COMMANDS if values == expected_values} == mdd_profiles
def test_run_flow_writes_structured_result(tmp_path: Path) -> None:
log = tmp_path / "flow.log"
result = tmp_path / "result.json"
@@ -442,8 +486,7 @@ def test_warning_normalization_keeps_ranges_and_removes_variant_hash(tmp_path: P
)
normalized = normalize(tmp_path, "WIDTH", message)
assert normalized == (
"WIDTH:$BUILD/generated/core.sv:<line>: "
"Bit extraction of var[7:0] is too wide"
"WIDTH:$BUILD/generated/core.sv:<line>: Bit extraction of var[7:0] is too wide"
)
@@ -473,8 +516,7 @@ def test_metrics_collection_and_observe_policy(tmp_path: Path) -> None:
report_dir = variant / "syn/yosys/rpt"
report_dir.mkdir(parents=True)
(report_dir / "retrosoc_asic_area.rpt").write_text(
" 42 1.23E+02 retrosoc_asic\n"
"Chip area for top module '\\retrosoc_asic': 123.0\n",
" 42 1.23E+02 retrosoc_asic\nChip area for top module '\\retrosoc_asic': 123.0\n",
encoding="utf-8",
)
(report_dir / "retrosoc_asic_area.json").write_text(
@@ -569,9 +611,7 @@ def test_ci_actions_are_pinned_to_commits() -> None:
assert re.fullmatch(r"[^@]+@[0-9a-f]{40}", value), f"unpinned action in {path}: {value}"
HASH_LOCKED_REQUIREMENT = re.compile(
r"[^\s=]+==[^\s]+(?:\s+--hash=sha256:[0-9a-f]{64})+"
)
HASH_LOCKED_REQUIREMENT = re.compile(r"[^\s=]+==[^\s]+(?:\s+--hash=sha256:[0-9a-f]{64})+")
def hash_locked_requirement_lines(content: str) -> list[str]: