ec82764bef
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>
93 lines
3.0 KiB
Python
Executable File
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())
|