mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
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:
55 files changed
+1544
-227
No files matched your search
@@ -23,6 +23,13 @@ into `meta/performance.json`. The parser requires the terminal
|
||||
`PERF_BENCHMARK_PASS` marker and deliberately does not alter the common
|
||||
simulation success-marker policy.
|
||||
|
||||
`run_debug_session.py` is the local Hazard3 debug acceptance driver. It starts
|
||||
the Verilator remote-bitbang endpoint, the lock-pinned OpenOCD binary, and
|
||||
RISC-V GDB, then records their logs and a structured result. Invoke it through
|
||||
`make CONFIG=configs/ci/ihp130-debug.mk SIMU=VERILATOR debug-sim`; see
|
||||
[`../docs/hazard3-debug.md`](../docs/hazard3-debug.md) for its scope and
|
||||
limitations.
|
||||
|
||||
Update or add tests in [`../tests`](../tests) for script behavior. Run
|
||||
`ruff check .` and `python3 -m pytest -q`; build-flow changes also require the
|
||||
relevant dry-run or regression profile from [`AGENTS.md`](../AGENTS.md).
|
||||
@@ -11,7 +11,7 @@ from typing import Any
|
||||
|
||||
|
||||
RESET_PRIMITIVES = {"rst_sync"}
|
||||
CDC_PRIMITIVES = {"async_fifo", "cdc_2phase", "cdc_sync"}
|
||||
CDC_PRIMITIVES = {"async_fifo", "cdc_2phase", "cdc_sync", "hazard3_apb_async_bridge"}
|
||||
IDENTIFIER_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*$")
|
||||
HIERARCHICAL_PIN_RE = re.compile(
|
||||
r"[A-Za-z_][A-Za-z0-9_.]*(?:/[A-Za-z_][A-Za-z0-9_.]*)+$"
|
||||
@@ -95,7 +95,13 @@ def validate(document_path: Path, root: Path) -> None:
|
||||
sta_sources[name] = None
|
||||
else:
|
||||
sta_sources[name] = require_identifier(source_domain, f"{field}.sta.source_domain")
|
||||
require_hierarchical_pin(sta.get("pin"), f"{field}.sta.pin")
|
||||
object_type = sta.get("object_type", "pin")
|
||||
if object_type not in {"pin", "port"}:
|
||||
raise ValueError(f"{field}.sta.object_type must be pin or port")
|
||||
if object_type == "pin":
|
||||
require_hierarchical_pin(sta.get("pin"), f"{field}.sta.pin")
|
||||
elif source_port is None:
|
||||
raise ValueError(f"{field}.sta port object requires source_port")
|
||||
require_positive_number(sta.get("period_ns"), f"{field}.sta.period_ns")
|
||||
require_identifier(sta.get("async_group"), f"{field}.sta.async_group")
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ DEFAULT_TOOLS = (
|
||||
"sby",
|
||||
"bitwuzla",
|
||||
"opensta",
|
||||
"openocd",
|
||||
"riscv_gnu",
|
||||
)
|
||||
TOOL_EXECUTABLES = {
|
||||
@@ -43,7 +44,8 @@ TOOL_EXECUTABLES = {
|
||||
"sby": ("sby",),
|
||||
"bitwuzla": ("bitwuzla",),
|
||||
"opensta": ("sta",),
|
||||
"riscv_gnu": ("riscv32-unknown-elf-gcc",),
|
||||
"openocd": ("openocd",),
|
||||
"riscv_gnu": ("riscv32-unknown-elf-gcc", "riscv32-unknown-elf-gdb"),
|
||||
}
|
||||
PYTHON_IMPORTS = ("pytest", "ruff", "yamllint")
|
||||
PYTHON_EXECUTABLES = ("mbake",)
|
||||
|
||||
@@ -24,6 +24,7 @@ def parse_args() -> argparse.Namespace:
|
||||
parser.add_argument("--sta", required=True)
|
||||
parser.add_argument("--pdk", required=True)
|
||||
parser.add_argument("--formal", choices=("YES", "NO"), default="NO")
|
||||
parser.add_argument("--debug", choices=("YES", "NO"), default="NO")
|
||||
parser.add_argument("--lock", type=Path, default=DEFAULT_LOCK)
|
||||
parser.add_argument("--format", choices=("text", "json"), default="text")
|
||||
parser.add_argument("--output", type=Path)
|
||||
@@ -66,6 +67,8 @@ def main() -> int:
|
||||
tools.append("sta")
|
||||
if args.formal == "YES":
|
||||
tools.extend(("sby", "yosys", "yosys-smtbmc", "sv2v", "bitwuzla"))
|
||||
if args.debug == "YES":
|
||||
tools.extend(("openocd", "riscv32-unknown-elf-gdb"))
|
||||
|
||||
source_names = ["mpw", "hazard3", "cluster_common", "third_party_ip"]
|
||||
pdk_names = {
|
||||
|
||||
@@ -29,6 +29,7 @@ PR_COMMANDS = (
|
||||
("configs/ci/ihp130.mk", ("firmware",)),
|
||||
("configs/ci/ihp130-shell.mk", ("firmware",)),
|
||||
("configs/ci/ihp130.mk", ("SIMU=VERILATOR", "HAVE_SVA=YES", "firmware", "sim")),
|
||||
("configs/ci/ihp130-debug.mk", ("SIMU=VERILATOR", "debug-sim")),
|
||||
("configs/ci/ihp130.mk", ("SIMU=IVERILOG", "RTL_SIM_TIMEOUT=5200000", "sim-asm")),
|
||||
("configs/ci/ihp130.mk", ("SYNTH=YOSYS", "synth")),
|
||||
(
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the local Verilator, OpenOCD, and GDB Hazard3 debug acceptance flow."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
from scripts.setup_helpers import atomic_write # noqa: E402
|
||||
|
||||
|
||||
SUCCESS_MARKER = "DEBUG_GDB_PASS"
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--emulator", required=True, type=Path)
|
||||
parser.add_argument("--image", required=True, type=Path)
|
||||
parser.add_argument("--elf", required=True, type=Path)
|
||||
parser.add_argument("--openocd", default="openocd")
|
||||
parser.add_argument("--gdb", default="riscv32-unknown-elf-gdb")
|
||||
parser.add_argument("--openocd-config", required=True, type=Path)
|
||||
parser.add_argument("--log-dir", required=True, type=Path)
|
||||
parser.add_argument("--result", required=True, type=Path)
|
||||
parser.add_argument("--timeout", type=float, default=120.0)
|
||||
parser.add_argument("--sim-time", type=int, default=180)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def reserve_port() -> int:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as listener:
|
||||
listener.bind(("127.0.0.1", 0))
|
||||
return int(listener.getsockname()[1])
|
||||
|
||||
|
||||
def wait_for_port(port: int, timeout: float) -> None:
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
with socket.create_connection(("127.0.0.1", port), timeout=0.2):
|
||||
return
|
||||
except OSError:
|
||||
time.sleep(0.1)
|
||||
raise TimeoutError(f"timed out waiting for 127.0.0.1:{port}")
|
||||
|
||||
|
||||
def stop(process: subprocess.Popen[object] | None) -> None:
|
||||
if process is None or process.poll() is not None:
|
||||
return
|
||||
process.terminate()
|
||||
try:
|
||||
process.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
process.kill()
|
||||
process.wait()
|
||||
|
||||
|
||||
def gdb_commands(elf: Path, gdb_port: int) -> str:
|
||||
return "\n".join(
|
||||
(
|
||||
"set confirm off",
|
||||
"set pagination off",
|
||||
"set remotetimeout 30",
|
||||
"set architecture riscv:rv32",
|
||||
f"file {elf}",
|
||||
f"target extended-remote 127.0.0.1:{gdb_port}",
|
||||
"p/x $pc",
|
||||
"set {unsigned int}0x3001ff00 = 0x5a5aa5a5",
|
||||
"x/w 0x3001ff00",
|
||||
"load",
|
||||
"break *0x30000008",
|
||||
"continue",
|
||||
"p/x $pc",
|
||||
"stepi",
|
||||
"p/x $pc",
|
||||
f'printf "{SUCCESS_MARKER}\\n"',
|
||||
"detach",
|
||||
"quit",
|
||||
"",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
start = time.monotonic()
|
||||
started_at = datetime.now(timezone.utc)
|
||||
log_dir = args.log_dir.resolve()
|
||||
log_dir.mkdir(parents=True, exist_ok=True)
|
||||
args.result.parent.mkdir(parents=True, exist_ok=True)
|
||||
emulator_log = log_dir / "emulator.log"
|
||||
openocd_log = log_dir / "openocd.log"
|
||||
gdb_log = log_dir / "gdb.log"
|
||||
gdb_script = log_dir / "debug.gdb"
|
||||
jtag_port = reserve_port()
|
||||
gdb_port = reserve_port()
|
||||
emulator: subprocess.Popen[object] | None = None
|
||||
openocd: subprocess.Popen[object] | None = None
|
||||
error: str | None = None
|
||||
status = "failed"
|
||||
|
||||
try:
|
||||
gdb_script.write_text(gdb_commands(args.elf.resolve(), gdb_port), encoding="utf-8")
|
||||
with emulator_log.open("w", encoding="utf-8") as emulator_stream:
|
||||
emulator = subprocess.Popen(
|
||||
[
|
||||
str(args.emulator.resolve()),
|
||||
"--image",
|
||||
str(args.image.resolve()),
|
||||
"--sim-time",
|
||||
str(args.sim_time),
|
||||
"--jtag-port",
|
||||
str(jtag_port),
|
||||
],
|
||||
stdout=emulator_stream,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
)
|
||||
wait_for_port(jtag_port, args.timeout)
|
||||
|
||||
with openocd_log.open("w", encoding="utf-8") as openocd_stream:
|
||||
openocd = subprocess.Popen(
|
||||
[
|
||||
args.openocd,
|
||||
"-c",
|
||||
f"set jtag_port {jtag_port}",
|
||||
"-f",
|
||||
str(args.openocd_config.resolve()),
|
||||
"-c",
|
||||
f"gdb_port {gdb_port}",
|
||||
"-c",
|
||||
"tcl_port disabled",
|
||||
"-c",
|
||||
"telnet_port disabled",
|
||||
],
|
||||
stdout=openocd_stream,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
)
|
||||
wait_for_port(gdb_port, args.timeout)
|
||||
with gdb_log.open("w", encoding="utf-8") as gdb_stream:
|
||||
result = subprocess.run(
|
||||
[args.gdb, "--batch", "--nx", "--quiet", "--command", str(gdb_script)],
|
||||
stdout=gdb_stream,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
timeout=args.timeout,
|
||||
check=False,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"GDB exited with status {result.returncode}")
|
||||
if SUCCESS_MARKER not in gdb_log.read_text(encoding="utf-8"):
|
||||
raise RuntimeError("GDB session did not print the success marker")
|
||||
status = "passed"
|
||||
except (OSError, RuntimeError, TimeoutError, subprocess.SubprocessError) as exception:
|
||||
error = str(exception)
|
||||
finally:
|
||||
stop(openocd)
|
||||
stop(emulator)
|
||||
|
||||
report = {
|
||||
"schema_version": 1,
|
||||
"tool": "verilator-openocd-gdb",
|
||||
"started_at": started_at.isoformat(),
|
||||
"finished_at": datetime.now(timezone.utc).isoformat(),
|
||||
"duration_seconds": round(time.monotonic() - start, 3),
|
||||
"status": status,
|
||||
"success_marker": SUCCESS_MARKER,
|
||||
"success_marker_seen": status == "passed",
|
||||
"logs": {
|
||||
"emulator": str(emulator_log),
|
||||
"openocd": str(openocd_log),
|
||||
"gdb": str(gdb_log),
|
||||
"gdb_script": str(gdb_script),
|
||||
},
|
||||
}
|
||||
if error is not None:
|
||||
report["error"] = error
|
||||
atomic_write(args.result, json.dumps(report, indent=2, sort_keys=True) + "\n")
|
||||
if error is not None:
|
||||
print(f"debug session failed: {error}")
|
||||
return 1
|
||||
print(SUCCESS_MARKER)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in new issue
Block a user