#!/usr/bin/env python3 """Assemble one tests//program.asm and generate its golden (Expected outputs).""" from __future__ import annotations import argparse import os from pathlib import Path import shutil import struct import subprocess from rv32_model import RV32Model, read_words, write_words REPO_ROOT = Path(__file__).resolve().parents[1] def find_assembler(explicit: str | None) -> str: candidates = [ explicit, os.environ.get("RISCV_AS"), REPO_ROOT / "scripts" / "riscv-none-elf-as", "riscv-none-elf-as", "riscv32-unknown-elf-as", "riscv64-unknown-elf-as", ] for candidate in candidates: if not candidate: continue candidate = str(candidate) resolved = shutil.which(candidate) if resolved: return resolved path = Path(candidate) try: if path.is_file() and os.access(path, os.X_OK): return str(path) except OSError: continue raise RuntimeError( "RISC-V GNU assembler not found; " "or set RISCV_AS=/path/to/riscv-*-as" ) def elf_text(path: Path) -> bytes: image = path.read_bytes() if image[:4] != b"\x7fELF" or image[4] != 1 or image[5] != 1: raise RuntimeError(f"{path} is not a little-endian ELF32 object") section_offset = struct.unpack_from(" None: parser = argparse.ArgumentParser() parser.add_argument("test_dir", type=Path) parser.add_argument("--assembler") parser.add_argument("--max-instructions", type=int, default=10000) args = parser.parse_args() test_dir = args.test_dir.resolve() source = test_dir / "program.asm" if not source.is_file(): raise RuntimeError(f"missing required {source}") assembler = find_assembler(args.assembler) obj = test_dir / "program.o" subprocess.run( [assembler, "-march=rv32i", "-mabi=ilp32", "-mno-relax", str(source), "-o", str(obj)], check=True, ) text = elf_text(obj) if len(text) % 4: raise RuntimeError(".text size is not a multiple of four bytes") program = [int.from_bytes(text[i:i + 4], "little") for i in range(0, len(text), 4)] program_path = test_dir / "program.hex" write_words(program_path, program) data_path = test_dir / "data.hex" initial_data = read_words(data_path if data_path.exists() else None) model = RV32Model(program, initial_data) count = model.run(args.max_instructions) write_words(test_dir / "expected_regs.hex", model.regs) write_words(test_dir / "expected_data.hex", model.data_words()) print(f"Built {test_dir.name}: {len(program)} words, halted after {count} instructions") if __name__ == "__main__": main()