Files
thejayman77 ec82764bef Initial commit: retroDE_ps2 — first-of-its-kind PS2 GS FPGA core (DE25-Nano / Agilex 5)
RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression
(272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps,
and all dump-derived textures/traces) is excluded via .gitignore and stays local.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 20:10:50 -04:00

93 lines
3.0 KiB
Python
Executable File

#!/usr/bin/env python3
# retroDE_ps2 — bin_to_hex
#
# Convert a raw 32-bit binary image (e.g. a PS2 BIOS dump) to the text
# hex format iverilog's $readmemh expects: one 32-bit word per line,
# hex (no 0x prefix), newline-terminated. iverilog reads the first line
# into memory index 0 and advances by one per line.
#
# Typical usage:
# python3 sim/golden/bin_to_hex.py path/to/ps2_bios.bin > /tmp/bios.hex
# make -C sim tb_iop_core_bios_smoke BIOS=/tmp/bios.hex
#
# Endianness:
# PS2 BIOS images on disk are little-endian byte order, which matches
# how the core will see each 32-bit word once it's fetched from the
# bios_rom_stub backing memory. We emit words using `<I` (little-
# endian unsigned 32-bit) to preserve that.
#
# Image size:
# Inputs shorter than the BIOS ROM are padded to the stub's
# DEFAULT_SIZE_WORDS (1 Mi words = 4 MiB) so $readmemh fills the whole
# array and leaves no stale values. Inputs longer than that are
# rejected — they're either the wrong file or would silently trample
# something.
#
# See sim/README.md "Real-BIOS iteration" section for the full workflow.
import argparse
import struct
import sys
DEFAULT_SIZE_WORDS = 1 << 20 # 4 MiB / 4 bytes
def main() -> int:
parser = argparse.ArgumentParser(
description="Convert a binary BIOS image to hex-text for $readmemh.")
parser.add_argument("input", help="input .bin file")
parser.add_argument(
"-o", "--output",
help="output .hex path (default: stdout)")
parser.add_argument(
"--words", type=int, default=DEFAULT_SIZE_WORDS,
help=f"total words to emit (default {DEFAULT_SIZE_WORDS}, the "
f"bios_rom_stub default depth)")
parser.add_argument(
"--pad", default="00000000",
help="pad word used when input is shorter than --words "
"(default 00000000 = SLL $0,$0,0 = NOP)")
args = parser.parse_args()
with open(args.input, "rb") as fh:
raw = fh.read()
if len(raw) % 4 != 0:
print(
f"warning: input length {len(raw)} not a multiple of 4; "
"trailing bytes dropped",
file=sys.stderr)
raw = raw[: len(raw) - (len(raw) % 4)]
words = list(struct.iter_unpack("<I", raw))
n_input = len(words)
if n_input > args.words:
print(
f"error: input has {n_input} words but only {args.words} "
"will be emitted; refusing to truncate",
file=sys.stderr)
return 2
out_fh = open(args.output, "w") if args.output else sys.stdout
try:
for (w,) in words:
out_fh.write(f"{w:08x}\n")
pad_word = int(args.pad, 16)
for _ in range(args.words - n_input):
out_fh.write(f"{pad_word:08x}\n")
finally:
if args.output:
out_fh.close()
print(
f"[bin_to_hex] wrote {args.words} words "
f"({n_input} from input, {args.words - n_input} pad) "
f"to {args.output or 'stdout'}",
file=sys.stderr)
return 0
if __name__ == "__main__":
sys.exit(main())