harness/mame/NETWORK_REPORT.md: how the VT1200 firmware drives the LANCE, from ROM disassembly and MAME traces. 50 sections, each claim tied to a bit address, a trace count or a quoted source line. Covers the driver device struct (0x23CBF0), init (0x3F9D4BC0), descriptor/DMA encoding, the live handler (0x3F9D5B40) with per-status-bit dispatch, the RX poll (0x3F9D62B0), ring arming (0x3F9D36C0), TX completion (0x3F9D8AF0) and the single transmit path every protocol uses (0x3F6084E0 -> 0x3F9DC890 -> 0x3F9D4BC0), plus the IP protocol dispatch (0x3F6270F0), ICMP (343-count chain), TCP state tables and live TIME_WAIT, the ARP responder (0x3F6119A0) and the socket 4-tuple demux confirmed by a memory dump. It also records what was withdrawn: five successive explanations for the real-silicon CSR0=0x1BB / TMD1=0x8338 result, and the descriptor-length encoding bug found by comparing Linux 7990.c and NetBSD am7990.c (length must be 0xF000 | -len; MAME masks the difference in get_buf_length). harness/udpstream: the LANCE loopback/takeover payload and the variants used to test the init/kick orderings in MAME (bare TDMD, STOP before INIT, double INIT, long wait with CSR0 sampling, IDON clear, corrected descriptor length). build-lance.sh takes LANCE_SRC/LANCE_OUT so the variants build from one script.
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