feat: add management debug reset and JTAG integration for Hazard3

- Implemented  module to handle debug reset requests, ensuring proper synchronization with the AHB-Lite bridge.
- Created  to integrate JTAG-DTM and Debug Module, facilitating abstract command exposure.
- Updated  and  modules to include JTAG interface signals.
- Enhanced the development environment with scripts for running debug sessions using OpenOCD and GDB.
- Added tests for management debug reset functionality and JTAG integration, ensuring correct behavior under various scenarios.
- Updated SDC generation to include JTAG clock domain and port requirements.
- Modified various scripts to support new debug features and ensure compatibility with the Hazard3 architecture.
This commit is contained in:
maksyuki committed 2026-08-05 22:30:11 +08:00
1 parent e73126912d
commit 6cff6102a4
55 files changed
+1544 -227

No files matched your search

+73
View File
@@ -0,0 +1,73 @@
`timescale 1ns / 1ps
module mgmt_debug_reset_tb;
logic s_clk;
logic s_rst_n;
logic s_reset_req;
logic s_bridge_idle;
logic s_core_rst_n;
logic s_reset_done;
always #5 s_clk = ~s_clk;
mgmt_debug_reset u_mgmt_debug_reset (
.clk_i (s_clk),
.rst_n_i (s_rst_n),
.reset_req_i (s_reset_req),
.bridge_idle_i(s_bridge_idle),
.core_rst_n_o (s_core_rst_n),
.reset_done_o (s_reset_done)
);
initial begin
s_clk = 1'b0;
s_rst_n = 1'b0;
s_reset_req = 1'b0;
s_bridge_idle = 1'b1;
repeat (2) @(posedge s_clk);
s_rst_n = 1'b1;
repeat (4) @(posedge s_clk);
#1;
assert (s_core_rst_n && s_reset_done)
else $fatal(1, "initial reset release failed");
s_bridge_idle = 1'b0;
s_reset_req = 1'b1;
@(posedge s_clk);
s_reset_req = 1'b0;
repeat (2) @(posedge s_clk);
#1;
assert (s_core_rst_n && !s_reset_done)
else $fatal(1, "request did not wait for idle");
s_bridge_idle = 1'b1;
@(posedge s_clk);
#1;
assert (!s_core_rst_n && !s_reset_done)
else $fatal(1, "core reset did not assert");
repeat (4) @(posedge s_clk);
#1;
assert (s_core_rst_n && s_reset_done)
else $fatal(1, "core reset did not release");
s_reset_req = 1'b1;
repeat (2) @(posedge s_clk);
#1;
assert (!s_core_rst_n && !s_reset_done)
else $fatal(1, "held reset did not assert");
repeat (3) @(posedge s_clk);
#1;
assert (!s_core_rst_n && !s_reset_done)
else $fatal(1, "held reset released too early");
s_reset_req = 1'b0;
repeat (4) @(posedge s_clk);
#1;
assert (s_core_rst_n && s_reset_done)
else $fatal(1, "held reset did not release");
$display("management debug reset test passed");
$finish;
end
endmodule
+5
View File
@@ -36,8 +36,13 @@ def test_clock_reset_domain_inventory_matches_the_rcu() -> None:
"external",
"system",
"audio",
"jtag",
"dvp",
}
assert {
(crossing["name"], crossing["source"], crossing["destination"])
for crossing in document["crossings"]
} >= {("jtag_dmi", "jtag", "system")}
def test_clock_reset_domain_inventory_rejects_unknown_domain_and_instance(tmp_path: Path) -> None:
+42
View File
@@ -0,0 +1,42 @@
"""Directed reset-sequencing test for the management Hazard3 debug path."""
from __future__ import annotations
import shutil
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def test_management_debug_reset_waits_for_the_ahbl_bridge(tmp_path: Path) -> None:
iverilog = shutil.which("iverilog")
vvp = shutil.which("vvp")
if iverilog is None or vvp is None:
return
simulation = tmp_path / "mgmt_debug_reset_tb"
sources = (
ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv",
ROOT / "rtl/managed/clusterip/common/rtl/clkrst/rst_sync.sv",
ROOT / "rtl/mini/top/mgmt_debug_reset.sv",
ROOT / "tests/rtl/mgmt_debug_reset_tb.sv",
)
subprocess.run(
[
iverilog,
"-g2012",
"-DSV_ASSRT_DISABLE",
f"-I{ROOT / 'rtl/managed/clusterip/common/rtl'}",
"-s",
"mgmt_debug_reset_tb",
"-o",
str(simulation),
*(str(source) for source in sources),
],
check=True,
)
result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True)
assert "management debug reset test passed" in result.stdout
+6
View File
@@ -57,6 +57,9 @@ def test_pin_map_generates_asic_and_platform_bindings(tmp_path: Path) -> None:
assert "inout extclk_i_pad," in ports
assert "input xi_i_pad," in ports
assert "inout sdram_dq15_io_pad" in ports
assert "inout jtag_tck_i_pad" in ports
assert "inout jtag_trst_n_i_pad" in ports
assert "output jtag_tdo_o_pad" in ports
assert "u_gpio_31_io_pad" in pads
assert "u_sdram_dq15_io_pad" in pads
assert ".gpio_30_io_pad(gpio_io30)" in fpga
@@ -65,6 +68,9 @@ def test_pin_map_generates_asic_and_platform_bindings(tmp_path: Path) -> None:
assert testbench.count(".gpio_24_io_pad(") == 1
assert ".sdram_clk_o_pad(s_sdram_clk)" in verilator
assert ".sdram_dq15_io_pad(s_sdram_dq[15])" in verilator
assert ".jtag_tck_i_pad(s_jtag_tck)" in verilator
assert ".jtag_tdo_o_pad(s_jtag_tdo)" in verilator
assert ".jtag_tck_i_pad(s_jtag_tck)" in testbench
assert "user_gpio_" not in ports
assert "user_gpio_" not in pads
assert "user_gpio_" not in testbench
+49
View File
@@ -129,6 +129,21 @@ def test_generate_all_is_stable_and_expands_paths(tmp_path: Path) -> None:
}.issubset({path.name for path in generated})
def test_filelist_round_trips_verilog_define_values(tmp_path: Path) -> None:
generate_all(tmp_path, ["+define+SOC_JTAG_IDCODE=32'hDEADBEEF"])
assert (tmp_path / "def.fl").read_text(encoding="utf-8") == (
"+define+SOC_JTAG_IDCODE=32'hDEADBEEF\n"
)
parsed = parse_filelists([tmp_path / "def.fl"])
output = tmp_path / "round-trip.fl"
write_filelist(output, parsed)
assert output.read_text(encoding="utf-8") == (
"+define+SOC_JTAG_IDCODE=32'hDEADBEEF\n"
)
assert parse_filelists([output]) == parsed
def test_source_export_selects_the_requested_management_core(tmp_path: Path) -> None:
module_path = ROOT / "syn/tools/export_soc_sources.py"
spec = importlib.util.spec_from_file_location("retrosoc_source_export", module_path)
@@ -153,6 +168,8 @@ def test_source_export_selects_the_requested_management_core(tmp_path: Path) ->
have_sram_if=False,
have_sram_macro=False,
have_sva=False,
have_debug="YES" if core == "HAZARD3" else "NO",
jtag_idcode="DEADBEEF",
dynamic_core_filelist=dynamic_core,
dynamic_ip_filelist=dynamic_ip,
)
@@ -167,6 +184,8 @@ def test_source_export_selects_the_requested_management_core(tmp_path: Path) ->
)
assert f"+define+CORE_{core}" in filelist.defines
assert "+define+SOC_JTAG_IDCODE=-559038737" in filelist.defines
assert ("+define+HAVE_DEBUG" in filelist.defines) == (core == "HAZARD3")
assert any(path.name == source_name for path in filelist.files)
@@ -605,6 +624,7 @@ def test_development_environment_contract_is_lock_pinned(tmp_path: Path) -> None
stamp = stamp_data(ROOT, cache, DEFAULT_TOOLS, lock, lock_path)
assert stamp["tools"]["verilator"] == lock["toolchains"]["ubuntu-22.04"]["verilator"]["version"]
assert stamp["tools"]["openocd"] == lock["toolchains"]["ubuntu-22.04"]["openocd"]["version"]
assert set(stamp["python_requirements"]) == {"requirements/build.txt", "requirements/ci.txt"}
activation = render_activation(cache, [cache / "venv/bin", cache / "toolchains/verilator/bin"])
assert "export RETROSOC_DEVELOPMENT_CACHE=" in activation
@@ -673,6 +693,35 @@ def test_management_core_selection_is_limited_to_hazard3_and_picorv32() -> None:
assert ".RESET_VECTOR (`SOC_CPU_RESET_ADDR)" in wrapper
def test_hazard3_debug_flow_is_locked_and_uses_remote_bitbang() -> None:
lock = load_lock(ROOT / "config/dependencies.lock.json")
makefile = (ROOT / "Makefile").read_text(encoding="utf-8")
wrapper = (ROOT / "rtl/mini/top/mgmt_core_wrapper.sv").read_text(encoding="utf-8")
debug_wrapper = (ROOT / "rtl/mini/top/mgmt_debug_wrapper.sv").read_text(encoding="utf-8")
verilator_makefile = (ROOT / "rtl/mini/mk/verilator.mk").read_text(encoding="utf-8")
driver = (ROOT / "scripts/run_debug_session.py").read_text(encoding="utf-8")
openocd = (ROOT / "rtl/mini/dv/verilator/openocd/retrosoc_hazard3.cfg").read_text(
encoding="utf-8"
)
assert "HAVE_DEBUG ?= YES" in makefile
assert "JTAG_IDCODE ?= DEADBEEF" in makefile
assert "if [ $$value -gt 2147483647 ]" in makefile
assert "HAVE_DEBUG=YES requires CORE=HAZARD3" in makefile
assert ".MULDIV_UNROLL (2)" in wrapper
assert ".BRANCH_PREDICTOR (1)" in wrapper
assert ".BREAKPOINT_TRIGGERS(2)" in wrapper
assert ".HAVE_SBA(0)" in debug_wrapper
assert "mgmt_debug_reset u_mgmt_debug_reset" in debug_wrapper
assert "--timeout $(SOC_SIM_TIME)" in verilator_makefile
assert "DEBUG_GDB_PASS" in driver
assert "break *0x30000008" in driver
assert "adapter driver remote_bitbang" in openocd
assert "-expected-id 0xdeadbeef" in openocd
assert lock["toolchains"]["ubuntu-22.04"]["openocd"]["version"] == "0.12.0-1"
assert any(profile == "configs/ci/ihp130-debug.mk" for profile, _ in PR_COMMANDS)
def test_benchmark_profile_uses_functional_sram_and_reserved_data() -> None:
makefile = (ROOT / "Makefile").read_text(encoding="utf-8")
profile = (ROOT / "configs/benchmark/ihp130-hazard3.mk").read_text(encoding="utf-8")
+3
View File
@@ -47,8 +47,11 @@ def test_core_sdc_covers_current_clock_domains(tmp_path: Path) -> None:
assert "create_clock -name clk_external -period 13.888888889" in sdc
assert "create_generated_clock -name clk_system" in sdc
assert "create_clock -name clk_audio -period 54.253472222" in sdc
assert "create_clock -name clk_jtag -period 100" in sdc
assert "create_clock -name clk_dvp -period 41.666666667" in sdc
assert "get_pins -quiet" in sdc
assert "get_ports -quiet" in sdc
assert "set clk_jtag_port [require_ports \"clock jtag\" {jtag_tck_i_pad}]" in sdc
assert "u_retrosoc.u_ip_ribp_wrapper.u_rib_dvp.u_dvp_pclk_clk_buf/clk_o" in sdc
assert "-group [get_clocks {clk_external clk_system}]" in sdc
assert "set_clock_transition 0.1 [get_clocks {clk_dvp}]" in sdc