mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-06 14:55:14 +00:00
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:
24 files changed
+664
-182
No files matched your search
@@ -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
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -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]:
|
||||
|
||||
Reference in new issue
Block a user