105 lines
3.0 KiB
Python
Executable File
105 lines
3.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Convert a simple .hex image to a plain .mif-style binary file.
|
|
|
|
Default output format matches build/mem_8kx8b.mif in this repo:
|
|
- one binary word per line
|
|
- no header
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
from pathlib import Path
|
|
|
|
|
|
def parse_tokens(path: Path) -> list[int]:
|
|
values: list[int] = []
|
|
for raw in path.read_text().splitlines():
|
|
line = raw.split("//", 1)[0].split("#", 1)[0].strip()
|
|
if not line:
|
|
continue
|
|
for token in line.replace(",", " ").split():
|
|
if token.lower().startswith("0x"):
|
|
token = token[2:]
|
|
values.append(int(token, 16))
|
|
return values
|
|
|
|
|
|
def pack_words(
|
|
byte_values: list[int], word_bytes: int, little_endian: bool
|
|
) -> list[int]:
|
|
if word_bytes <= 0:
|
|
raise ValueError("word_bytes must be >= 1")
|
|
if word_bytes == 1:
|
|
return byte_values[:]
|
|
|
|
words: list[int] = []
|
|
for i in range(0, len(byte_values), word_bytes):
|
|
chunk = byte_values[i : i + word_bytes]
|
|
if len(chunk) < word_bytes:
|
|
chunk = chunk + [0] * (word_bytes - len(chunk))
|
|
|
|
word = 0
|
|
if little_endian:
|
|
for b_idx, b in enumerate(chunk):
|
|
word |= (b & 0xFF) << (8 * b_idx)
|
|
else:
|
|
for b in chunk:
|
|
word = (word << 8) | (b & 0xFF)
|
|
words.append(word)
|
|
return words
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description="Convert .hex to plain .mif")
|
|
parser.add_argument("input_hex", type=Path, help="Input .hex file")
|
|
parser.add_argument("output_mif", type=Path, help="Output .mif file")
|
|
parser.add_argument(
|
|
"--word-bytes",
|
|
type=int,
|
|
default=1,
|
|
help="Bytes per output word (default: 1)",
|
|
)
|
|
parser.add_argument(
|
|
"--little-endian",
|
|
action="store_true",
|
|
help="Pack bytes little-endian when --word-bytes > 1 (default)",
|
|
)
|
|
parser.add_argument(
|
|
"--big-endian",
|
|
action="store_true",
|
|
help="Pack bytes big-endian when --word-bytes > 1",
|
|
)
|
|
parser.add_argument(
|
|
"--depth",
|
|
type=int,
|
|
default=0,
|
|
help="Optional output depth (pads with zeros up to this many words)",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
if args.little_endian and args.big_endian:
|
|
raise SystemExit("Choose only one of --little-endian or --big-endian")
|
|
|
|
little_endian = True
|
|
if args.big_endian:
|
|
little_endian = False
|
|
|
|
words = pack_words(parse_tokens(args.input_hex), args.word_bytes, little_endian)
|
|
width_bits = args.word_bytes * 8
|
|
max_word = (1 << width_bits) - 1
|
|
|
|
if args.depth > 0 and args.depth < len(words):
|
|
raise SystemExit(
|
|
f"Requested --depth={args.depth} but image has {len(words)} words"
|
|
)
|
|
if args.depth > len(words):
|
|
words.extend([0] * (args.depth - len(words)))
|
|
|
|
lines = [f"{(w & max_word):0{width_bits}b}" for w in words]
|
|
args.output_mif.write_text("\n".join(lines) + ("\n" if lines else ""))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|