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