#!/usr/bin/env python3 """ run_ucsim.py — прогнать тестовый .ihx под ucsim_z80 и разобрать результат. Как это устроено (см. crt0_ucsim.s и tcheck.h): тестовый бинарь кладёт итог в структуру tc_result в ОЗУ и заканчивается инструкцией halt. Мы находим адрес _tc_result в карте линковки (.noi), гоняем ucsim, дампим структуру и печатаем отчёт. Через simif сознательно не идём: номера его команд меняются от версии к версии, а halt + dump работают одинаково везде. Код возврата: 0 — все проверки прошли, 1 — есть провалы или сбой прогона. """ import re import subprocess import sys from pathlib import Path TC_MAGIC = 0x5A5A HDR = 8 # magic, total, failed, logn — по 2 байта LOGSZ = 768 # TC_LOGSZ из tcheck.h BYTES_PER_LINE = 16 # Строка дампа ucsim: "0x8000 5a 5a 07 00 ... ZZ......" # ASCII-хвост тоже может выглядеть как hex, поэтому берём РОВНО столько # токенов, сколько байт положено в строке, а не «сколько нашлось». DUMP_RE = re.compile(r"^0x([0-9A-Fa-f]+)\s+((?:[0-9A-Fa-f]{2}\s+)+)") def find_symbol(noi_path: Path, name: str) -> int: m = re.search(rf"^DEF {re.escape(name)} (0x[0-9A-Fa-f]+)$", noi_path.read_text(), re.M) if not m: raise SystemExit(f"{noi_path.name}: символ {name} не найден " f"(тест не слинкован с tcheck.c?)") return int(m.group(1), 16) def run(ucsim: Path, ihx: Path, start: int, length: int, timeout: int): stop = start + length - 1 script = (f'file "{ihx}"\n' f'reset\n' f'run\n' f'dump 0x{start:04X} 0x{stop:04X} {BYTES_PER_LINE}\n' f'quit\n') try: p = subprocess.run([str(ucsim), "-t", "z80"], input=script, capture_output=True, text=True, timeout=timeout) except subprocess.TimeoutExpired: raise SystemExit(f"ТАЙМАУТ {timeout} с — тест зациклился " f"(нет halt / бесконечный цикл в коде)") return p.stdout def parse_dump(out: str, start: int, length: int) -> bytes: mem = bytearray(length) seen = bytearray(length) for line in out.splitlines(): m = DUMP_RE.match(line.strip()) if not m: continue addr = int(m.group(1), 16) off = addr - start if off < 0 or off >= length: continue want = min(BYTES_PER_LINE, length - off) toks = m.group(2).split()[:want] for i, t in enumerate(toks): mem[off + i] = int(t, 16) seen[off + i] = 1 if not any(seen): raise SystemExit("не удалось разобрать дамп ucsim:\n" + out) return bytes(mem) def main() -> int: if len(sys.argv) < 4: print("usage: run_ucsim.py [timeout]", file=sys.stderr) return 2 ucsim, ihx, noi = (Path(a) for a in sys.argv[1:4]) timeout = int(sys.argv[4]) if len(sys.argv) > 4 else 60 name = ihx.stem addr = find_symbol(noi, "_tc_result") out = run(ucsim, ihx, addr, HDR + LOGSZ, timeout) if "Halted" not in out: print(f"[{name}] ПРОГОН НЕ ЗАВЕРШИЛСЯ штатным halt:\n{out}") return 1 mem = parse_dump(out, addr, HDR + LOGSZ) u16 = lambda o: mem[o] | (mem[o + 1] << 8) magic, total, failed, logn = u16(0), u16(2), u16(4), u16(6) if magic != TC_MAGIC: print(f"[{name}] tc_result не заполнен (magic=0x{magic:04X}) — " f"main() не позвал ни одной проверки?") return 1 if failed: log = mem[HDR:HDR + min(logn, LOGSZ)].decode("utf-8", "replace") print(f"[{name}] ПРОВАЛ: {failed} из {total}") for line in log.rstrip("\n").split("\n"): print(f" {line}") return 1 print(f"[{name}] ok: {total}") return 0 if __name__ == "__main__": sys.exit(main())