mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
feat: refactor XPI integration and add programming tool
- Updated filelist to include new XPI source files. - Modified IRQ compatibility mapping for XPI in the SOC topology generator. - Adjusted JSON topology configuration to reflect new IRQ signal naming. - Changed software.mk to reference the new XPI HAL source file. - Enhanced APB4 peripheral module to include XPI IRQ signal. - Updated RTL style audit to include new XPI files. - Documented new XPI programming script in README. - Added a new script for programming XPI flash through OpenOCD. - Enhanced XPI IO testbench to validate new LUT engine and isolation behavior. - Created unit tests for the new XPI programming tool and its functionalities.
This commit is contained in:
47 files changed
+5166
-2736
No files matched your search
@@ -32,6 +32,11 @@ RISC-V GDB, then records their logs and a structured result. Invoke it through
|
||||
[`../docs/hazard3-debug.md`](../docs/hazard3-debug.md) for its scope and
|
||||
limitations.
|
||||
|
||||
`program_xpi_flash.py` drives the SRAM-resident XPI loader through an existing
|
||||
OpenOCD GDB endpoint. It validates and splits a BIN at 4 KiB sector boundaries,
|
||||
generates a reviewable GDB script, and performs erase/program only when
|
||||
`--execute` is explicit. See [`../docs/ip/xpi.md`](../docs/ip/xpi.md).
|
||||
|
||||
`check_rtl_style.py` applies the ownership-aware RTL style rules from
|
||||
[`../rtl/rtl_style_manifest.json`](../rtl/rtl_style_manifest.json). The CI
|
||||
target checks changed self-owned RTL for positional module connections, legacy
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Program NOR through the SRAM XPI loader and an existing OpenOCD GDB server."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import tempfile
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
FLASH_SIZE = 0x0100_0000
|
||||
SECTOR_SIZE = 4096
|
||||
SUCCESS_MARKER = "XPI_FLASH_PROGRAM_PASS"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FlashChunk:
|
||||
address: int
|
||||
data: bytes
|
||||
|
||||
|
||||
def split_image(data: bytes, address: int) -> list[FlashChunk]:
|
||||
"""Split a payload so every loader call stays within one erase sector."""
|
||||
if not data:
|
||||
raise ValueError("image is empty")
|
||||
if address < 0 or address >= FLASH_SIZE or len(data) > FLASH_SIZE - address:
|
||||
raise ValueError("image range is outside the 16 MiB NOR device")
|
||||
|
||||
chunks: list[FlashChunk] = []
|
||||
offset = 0
|
||||
while offset < len(data):
|
||||
chunk_address = address + offset
|
||||
sector_remaining = SECTOR_SIZE - (chunk_address % SECTOR_SIZE)
|
||||
chunk_data = data[offset : offset + sector_remaining]
|
||||
chunks.append(FlashChunk(chunk_address, chunk_data))
|
||||
offset += len(chunk_data)
|
||||
return chunks
|
||||
|
||||
|
||||
def parse_symbol_address(nm_output: str, symbol: str) -> int:
|
||||
for line in nm_output.splitlines():
|
||||
fields = line.split()
|
||||
if len(fields) >= 3 and fields[-1] == symbol:
|
||||
return int(fields[0], 16)
|
||||
raise ValueError(f"loader symbol not found: {symbol}")
|
||||
|
||||
|
||||
def build_gdb_commands(
|
||||
loader: Path, target: str, staging_address: int, chunks: list[tuple[FlashChunk, Path]]
|
||||
) -> str:
|
||||
lines = [
|
||||
"set confirm off",
|
||||
"set pagination off",
|
||||
"set remotetimeout 60",
|
||||
"set architecture riscv:rv32",
|
||||
f"file {loader.resolve()}",
|
||||
f"target extended-remote {target}",
|
||||
"monitor halt",
|
||||
"load",
|
||||
"break main",
|
||||
"continue",
|
||||
"set $result = (int)rs_xpi_flash_probe()",
|
||||
'if $result != 0',
|
||||
' printf "XPI flash probe failed: %d\\n", $result',
|
||||
" quit 1",
|
||||
"end",
|
||||
]
|
||||
for chunk, chunk_path in chunks:
|
||||
lines.extend(
|
||||
(
|
||||
f"restore {chunk_path.resolve()} binary 0x{staging_address:08x}",
|
||||
f"set $result = (int)rs_xpi_flash_update(0x{chunk.address:08x}, {len(chunk.data)})",
|
||||
"if $result != 0",
|
||||
(
|
||||
f' printf "XPI flash update failed at 0x{chunk.address:08x}: '
|
||||
'%d\\n", $result'
|
||||
),
|
||||
" quit 1",
|
||||
"end",
|
||||
)
|
||||
)
|
||||
lines.extend((f'printf "{SUCCESS_MARKER}\\n"', "detach", "quit", ""))
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--loader", required=True, type=Path, help="SRAM loader ELF")
|
||||
parser.add_argument("--image", required=True, type=Path, help="raw binary image")
|
||||
parser.add_argument("--address", type=lambda value: int(value, 0), default=0)
|
||||
parser.add_argument("--target", default="127.0.0.1:3333", help="OpenOCD GDB endpoint")
|
||||
parser.add_argument("--gdb", default="riscv32-unknown-elf-gdb")
|
||||
parser.add_argument("--nm", default="riscv32-unknown-elf-nm")
|
||||
parser.add_argument("--gdb-script", type=Path, help="keep the generated GDB script")
|
||||
parser.add_argument("--result", type=Path, help="write a machine-readable result")
|
||||
parser.add_argument(
|
||||
"--execute", action="store_true", help="perform the destructive erase/program operation"
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def run(args: argparse.Namespace) -> int:
|
||||
if not args.execute and args.gdb_script is None:
|
||||
raise ValueError("--gdb-script is required when --execute is not specified")
|
||||
|
||||
loader = args.loader.resolve()
|
||||
image = args.image.resolve()
|
||||
if not loader.is_file():
|
||||
raise FileNotFoundError(f"loader ELF not found: {loader}")
|
||||
if not image.is_file():
|
||||
raise FileNotFoundError(f"image not found: {image}")
|
||||
|
||||
chunks = split_image(image.read_bytes(), args.address)
|
||||
nm_result = subprocess.run(
|
||||
[args.nm, "-g", "--defined-only", str(loader)],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
staging_address = parse_symbol_address(nm_result.stdout, "rs_xpi_flash_staging")
|
||||
|
||||
if args.gdb_script is not None:
|
||||
work_dir = args.gdb_script.resolve().with_suffix(".chunks")
|
||||
work_dir.mkdir(parents=True, exist_ok=True)
|
||||
temporary = None
|
||||
else:
|
||||
temporary = tempfile.TemporaryDirectory(prefix="retrosoc-xpi-flash-")
|
||||
work_dir = Path(temporary.name)
|
||||
|
||||
chunk_files: list[tuple[FlashChunk, Path]] = []
|
||||
for index, chunk in enumerate(chunks):
|
||||
chunk_path = work_dir / f"chunk-{index:04d}.bin"
|
||||
chunk_path.write_bytes(chunk.data)
|
||||
chunk_files.append((chunk, chunk_path))
|
||||
commands = build_gdb_commands(loader, args.target, staging_address, chunk_files)
|
||||
script_path = args.gdb_script.resolve() if args.gdb_script is not None else work_dir / "flash.gdb"
|
||||
script_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
script_path.write_text(commands, encoding="utf-8")
|
||||
|
||||
return_code = 0
|
||||
if args.execute:
|
||||
result = subprocess.run(
|
||||
[args.gdb, "--batch", "--nx", "--quiet", "--command", str(script_path)],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
print(result.stdout, end="")
|
||||
if result.stderr:
|
||||
print(result.stderr, end="")
|
||||
return_code = result.returncode
|
||||
if return_code == 0 and SUCCESS_MARKER not in result.stdout:
|
||||
return_code = 1
|
||||
else:
|
||||
print(f"generated {script_path}; pass --execute to program the device")
|
||||
|
||||
if args.result is not None:
|
||||
args.result.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.result.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"tool": "xpi-jtag-flash",
|
||||
"passed": return_code == 0,
|
||||
"executed": args.execute,
|
||||
"address": args.address,
|
||||
"bytes": image.stat().st_size,
|
||||
"sectors": len(chunks),
|
||||
"loader": str(loader),
|
||||
"image": str(image),
|
||||
"gdb_script": str(script_path),
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
+ "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
if temporary is not None:
|
||||
temporary.cleanup()
|
||||
return return_code
|
||||
|
||||
|
||||
def main() -> int:
|
||||
return run(parse_args())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in new issue
Block a user