Files
vt1200/reconstruct.py

114 lines
4.2 KiB
Python
Executable File

#!/usr/bin/env python3
"""Reconstruct the VT1200 X-server image from the raw EPROM dumps.
The VT1200 firmware is held in 14 x 128 KiB (27C010) byte-wide EPROMs per
firmware revision. The EPROMs are wired as 16-bit words: consecutive part
numbers form a pair, and within a pair the ODD-numbered part holds the low
byte and the EVEN-numbered part holds the high byte of the word.
Example for v2.1:
pair (23-210E9, 23-211E9) -> 256 KiB bank, 210E9 = high byte,
211E9 = low byte
There are two possible orderings of the seven 256 KiB banks:
* ``linear`` (default) -- CPU *address* order. The ROM is top-aligned at
physical byte 0x07E40000, and the bank with the *highest* part numbers sits
at the lowest address. In this order file offset == physical byte - base,
which is what a disassembler/emulator wants.
* ``--part-order`` -- legacy ascending part-number order (bank 0 first).
The image is 1.75 MiB (0x1C0000) and can be fed to a TMS34010 disassembler
(e.g. smart34010's ``dis34`` or MAME's ``unidasm``) or loaded into an emulator
at base 0x07E40000. See MEMORY_MAP.md. Note the firmware stores *bit*
addresses (physical byte = bit >> 3).
Usage:
./reconstruct.py v2.2 # -> vt1200_v22.bin (address order)
./reconstruct.py v2.1 out.bin
./reconstruct.py v2.1 --part-order
"""
import re
import os
import glob
import argparse
HERE = os.path.dirname(os.path.abspath(__file__))
# Physical byte address the ROM is mapped at; also the size of one image.
ROM_BASE = 0x07E40000
def load(dirname):
roms = {}
for path in sorted(glob.glob(os.path.join(HERE, dirname, "*.bin"))):
m = re.search(r"(\d+)E9", os.path.basename(path))
if not m:
continue
with open(path, "rb") as fh:
roms[int(m.group(1))] = fh.read()
return roms
def reconstruct(dirname, linear=True):
"""De-interleave the EPROMs of a revision.
Returns (parts, image) where parts is the ascending list of part numbers
and image is the reconstructed ROM. With linear=True the banks are placed
in CPU address order (file offset == physical byte - ROM_BASE).
"""
roms = load(dirname)
parts = sorted(roms)
if len(parts) % 2:
raise SystemExit(f"{dirname}: expected an even number of EPROMs, got {len(parts)}")
banks = []
for i in range(0, len(parts), 2):
even_pn, odd_pn = parts[i], parts[i + 1] # odd part = low byte
low, high = roms[odd_pn], roms[even_pn]
if len(low) != len(high):
raise SystemExit(f"{dirname}: size mismatch between {odd_pn} and {even_pn}")
img = bytearray(len(low) * 2)
img[0::2] = low
img[1::2] = high
banks.append(bytes(img))
if linear:
banks.reverse() # highest part number sits at the lowest address
return parts, b"".join(banks)
def main(argv=None):
ap = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
ap.add_argument("revision", help="ROM directory, e.g. v2.1 or v2.2")
ap.add_argument("output", nargs="?", help="output file (default vt1200_vNN.bin)")
ap.add_argument(
"--linear", dest="linear", action="store_true",
help="emit CPU address order (default): offset == physical byte - 0x07E40000",
)
ap.add_argument(
"--part-order", dest="linear", action="store_false",
help="emit legacy ascending part-number order",
)
ap.set_defaults(linear=True)
args = ap.parse_args(argv)
default = "vt1200_" + args.revision.replace(".", "") + ".bin"
out = args.output or os.path.join(HERE, default)
parts, img = reconstruct(args.revision, linear=args.linear)
with open(out, "wb") as fh:
fh.write(img)
order = "address" if args.linear else "part-number"
strings = len(re.findall(rb"[\x20-\x7e]{16,}", img))
print(f"{args.revision}: {len(parts)} EPROMs ({parts[0]}..{parts[-1]}) "
f"{order} order -> {out} ({len(img)} bytes = 0x{len(img):X}), "
f"{strings} text runs")
if args.linear:
print(f" load base: 0x{ROM_BASE:08X} (bit 0x{ROM_BASE * 8:08X})")
if __name__ == "__main__":
main()