MAME's TMS34010 renderer does not read VRAM: tms340x0_ind16 calls the driver's scanline callback for the active region and paints only the blanking regions. Our callback drew nothing, so the active area was never painted and every frame dump was all-black. Add render_active_line(), called from scanline(): it reads the framebuffer out of the video-RAM share at bit 0x04000000 with a measured 1024-bit (0x40-word, 128-byte) row pitch and LSB-first bit order within each 16-bit word. The pitch comes from the firmware's own character blitter (PIXBLT at pc=3FDF9A00 advances its destination by 0x40 words per glyph row); at the 160-byte pitch MAME derived from HEBLNK/HSBLNK/HTOTAL the text is sheared and unreadable. Validated: frame 160 shows the level-1 CP437 character table legibly, frame 600 the level-1 error screen. VT1200_DPYLOG added as an opt-in per-scanline display-params dump. Regression (no VT1200_* env): PC=3FDCE400 samples=199 -- holds. See harness/mame/RENDER_REPORT.md.
DEC VT1200 X Terminal — ROM images
The DEC VT1200 is a monochrome X Window System (X11) terminal (circa 1990). It runs the X server, terminal manager, window manager, and VT emulator entirely from ROM on a Texas Instruments TMS34010 CPU, with 2–4 MB of RAM and no virtual memory.
These are the firmware ROM dumps, split by firmware revision. The confirmed memory map is in MEMORY_MAP.md.
Contents
| Folder | Firmware | ROMs | Part numbers |
|---|---|---|---|
v2.1/ |
V2.1 | 14 x 128 KiB | 23-210E9 .. 23-223E9 |
v2.2/ |
V2.2 | 14 x 128 KiB | 23-274E9 .. 23-287E9 |
Each file is 131072 bytes (128 KiB) and is named by its DEC part number.
Source
Dumped and published by 9track.net (Dave/9track collection):
- Index page: https://9track.net/roms/ (section "DEC VT1200", category Terminal)
- Files: https://9track.net/roms/files/dec/vt1200/
Fetched: 2026-10-15.
Notes
- The bitsavers terminal document archive does not currently host VT1200 ROM
images (checked
/bits/and/pdf/dec/terminal/), so 9track.net is the canonical source for these dumps. - The 9track table does not list the component/location for each ROM; only the part-number-labeled filenames are given.
- Verify integrity with
sha256sum v2.1/* v2.2/*.
Firmware layout (reverse-engineered)
The 14 EPROMs are byte-interleaved 16-bit words, not a flat byte stream. Within each consecutive pair of part numbers, the odd part number holds the low byte and the even part number holds the high byte:
v2.1 pairs: (210,211) (212,213) (214,215) (216,217) (218,219) (220,221) (222,223)
v2.2 pairs: (274,275) (276,277) (278,279) (280,281) (282,283) (284,285) (286,287)
The seven 256 KiB banks are laid out so that file offset == physical byte - 0x07E40000: the highest part-number pair sits at the lowest address.
reconstruct.py writes that 1.75 MiB (0x1C0000) image by default; pass
--part-order for the legacy ascending-part-number ordering.
./reconstruct.py v2.1 # -> vt1200_v21.bin (address order)
./reconstruct.py v2.2 # -> vt1200_v22.bin
python3 -m unittest test_reconstruct -v # layout regression tests
Evidence this interleave is correct: only it produces readable text, e.g.
-DEC-Terminal-Medium-R-Normal--14-100-100-100-C-80-ISO8859-1, WM XIO Error,
packet_size = %d exceeds MAXSIZE_RCVPKT, and SCCS headers such as
$Header: ctype.c,v 1.1 89/10/05 19:40:49 craig Exp $. Getting the interleave
wrong shifts every string by one byte (e.g. DEC becomes EDC).
Disassembly
The CPU is a TI TMS34010. The ROM is loaded at physical byte base
0x07E40000 (bit 0x3F200000); the reset vector (0x3F200040) points at the
power-on code at ROM+8 (DINT; SETF 16,0,0; SETF 0,0,1; CLR A0; …). Each
256 KiB bank begins with an 8-byte header [checksum16][?][AAAA][5555].
MAME's bundled disassembler, which ships a tms34010 backend:
unidasm vt1200_v22.bin -arch tms34010 -basepc 0x7E40000 -count 40
Or smart34010's dis34 (note -b is a bit-address base):
dis34 -a -l -b 3F200000 vt1200_v21.bin
unidasm/dis34 print bit addresses (0x10 per 16-bit instruction), whereas
Ghidra uses byte addresses. tools/vt1200_map.py prints the vectors and
verifies the per-bank header checksums:
python3 tools/vt1200_map.py vt1200_v22.bin
Pointer constants (bit-address → byte-address)
The firmware stores bit addresses; a 32-bit value P refers to physical
byte P >> 3. A byte-addressed tool such as Ghidra cannot resolve those
constants directly. tools/ptr_constants.py scans an address-ordered image
for such constants and resolves their targets:
python3 tools/ptr_constants.py vt1200_v22.bin
python3 tools/ptr_constants.py vt1200_v22.bin --strings
python3 tools/ptr_constants.py vt1200_v22.bin --tsv pointers.tsv
python3 -m unittest test_ptr_constants -v
On v2.2 it finds ~44,700 constants, of which ~3,680 distinct targets are
strings (e.g. 0x004B08 <- 0x3F225840 'LAT: could not allocate XCB for …').
The Ghidra side is implemented in ghidra_scripts/FixPtrConstants.java: it
scans the loaded ROM and adds a DATA reference from each constant to the byte
it points at (P >> 3), leaving the bytes untouched. AssertPtrFix.java is a
headless regression check for it. Already applied to the project:
| Program | references created |
|---|---|
vt1200_v21.bin |
37,187 |
vt1200_v22.bin |
40,290 |
/opt/ghidra/support/analyzeHeadless ghidra vt1200 \
-process -noanalysis \
-scriptPath ghidra_scripts \
-postScript FixPtrConstants.java
Ghidra
Ghidra has no built-in TMS34010 language, so we install the community module
ZPaul2Fresh8/Ghidra_TMS34010:
tools/ghidra-tms34010/— vendored, patched module sourcetools/ghidra-tms34010-local.patch— all local changes vs. upstreamtools/install-ghidra-plugin.sh— installs + compiles it into Ghidra
tools/install-ghidra-plugin.sh # needs sudo; installs into /opt/ghidra
The patch does two things:
- Ghidra 12 schema fixes —
<data_space space="ROM">must be lowercaserom(matching the slaspec), and every<memory_block>needsinitialized="false"or a bit/byte-mapped address. - Removes the Midway arcade content — upstream shipped a Midway board map
(
VRAM/RAM/NVRAM/PALETTE/GFX/ROMblocks plusWATCHDOG,DIPSWITCHES,INPUT_n,midtunit_*and arom:0xFFFFCtrap table) that does not describe the VT1200 and whoseGFX/ROMblocks collided with the loaded image. The cleanedpspeckeeps only the CPU definition.
Project
ghidra/ is a ready-to-open Ghidra project (ghidra/vt1200.gpr) containing the
reconstructed v2.1 and v2.2 images, imported with tms34010:LE:32:default
at byte base 0x07E40000 and disassembled from the reset vector plus a
linear pass over the boot bank.
| Program | Functions | Instructions |
|---|---|---|
vt1200_v21.bin |
8 | 61,401 |
vt1200_v22.bin |
6 | 62,444 |
Helper scripts live in ghidra_scripts/:
VerifyTMS34010.java— locates the loaded block, follows flow from the reset vector, then linearly disassembles the boot bank, and prints regions.StatsTMS34010.java— function/instruction counts.FixPtrConstants.java— converts bit-address pointer constants to references.AssertPtrFix.java— headless regression check for the above.
Caveats:
currentProgram.getImageBase()is not the ROM base. Use the loaded block (rom:07e40000–rom:07ffffff); the helper script does this.- The module's pcode semantics are incomplete, so flow-following cannot
expand far and the analyzers emit non-fatal
ArrayIndexOutOfBoundsExceptions. That is why the boot bank is also disassembled linearly. - Ghidra 12 dropped Jython; its bundled Python scripts need PyGhidra, so the helpers here are Java.
- Data cross-references are materialised by
FixPtrConstants.java(the bit vs byte address issue) — see the pointer-constants section above.
Documentation
docs/ holds the free DEC/TI documents gathered during this work. Each PDF
has a .txt sidecar with the extracted text (handy for grep).
| File | Source |
|---|---|
EK-V1000-SV-002_service-guide.pdf |
filibeto.org/dec/hardware/EK-V1000-SV-002.pdf |
EK-V1000-UG-002_installing-VT1000.pdf |
filibeto.org/dec/hardware/EK-V1000-UG-002.pdf |
EK-V1200-UG-001_installing-VT1200.pdf |
filibeto.org/dec/hardware/EK-V1200-UG-001.pdf |
EK-V1000-RN-005_VT1000-v2.0-release-notes.pdf |
filibeto.org/dec/hardware/EK-V1000-RN-005.pdf |
EK-V1200-RN-001_VT1200-release-notes.pdf |
filibeto.org/dec/hardware/EK-V1200-RN-001.pdf |
EK-V1000-RC-003_installing-optional-memory.pdf |
filibeto.org/dec/hardware/EK-V1000-RC-003.pdf |
EK-V120I-IN-001_installing-DECimage-1200.pdf |
filibeto.org/dec/hardware/EK-V120I-IN-001.pdf |
EK-V1000-IP-001_illustrated-parts-breakdown.pdf |
filibeto.org/dec/hardware/EK-V1000-IP-001.pdf |
DTJ-1991-vol3num4_X-Window-Terminals.pdf |
Digital Technical Journal 3(4), Fall 1991, pp. 26–35 |
TMS34010-users-guide.pdf |
TI TMS34010 User's Guide |
ipb_pages/*.png |
page renders of the IPB (that scan has no text layer) |
reference/ holds the VT1000 v1.2 ROM dump (12 × 27C010) published by bitsavers
(bitsavers.org/pdf/dec/terminal/vt1000/VT1000-v1.2.zip) plus an earlier linear
dis34 disassembly of v2.1 (gzipped).
Notably no online document contains the memory map — the missing artifact is
the MP-02989-01 VT1000/VT1200 System Box Maintenance Print Set (fold-out
schematics). See MEMORY_MAP.md.
Repository layout
vt1200/
├── v2.1/ v2.2/ # original 28 EPROM dumps
├── reconstruct.py # de-interleave a revision -> address-ordered image
├── test_reconstruct.py
├── MEMORY_MAP.md # the confirmed memory map
├── README.md
├── vt1200_v21.bin vt1200_v22.bin
├── docs/ # DEC/TI manuals (PDF + extracted .txt) + ipb_pages/
├── reference/ # VT1000 v1.2 ROM dump, earlier dis34 output
├── ghidra/ # Ghidra project (vt1200.gpr + .rep)
├── ghidra_scripts/ # Verify/Stats/FixPtrConstants/AssertPtrFix .java
├── tools/
│ ├── vt1200_map.py # vectors + bank-header checksums
│ ├── ptr_constants.py # find/resolve bit-address constants
│ ├── smart34010/ # dis34 disassembler source
│ ├── ghidra-tms34010/ # patched processor module source
│ ├── ghidra-tms34010-local.patch
│ └── install-ghidra-plugin.sh
└── test_ptr_constants.py