Files
retroSoC/scripts/run_debug_session.py
T
maksyuki 6cff6102a4 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.
2026-08-05 22:30:11 +08:00

197 lines
6.6 KiB
Python

#!/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())