Operator documented the physical VDB + companion card: Jaton KY2-JAX-CVGA54PCI (Cirrus CL-GD5434) feeds a 26-pin feature-connector ribbon to an AMD/Lattice MACH130-15JC CPLD (the ISA 0x300-0x31A port decoder + hardwired byte-lane splitter), which drives 3x Brooktree Bt477KPJ80 RAMDACs -> one VGA stream to the color radar + a DB25 fan-out to the 5 mono MFDs. Confirms the RE (3 DACs = 3 palette groups; Bt477 6-bit DAC = the driver's shr al,2). A component tied in parallel to the PC front-panel reset (remote reboot or VDB known-state on reset) is under operator research. Also: RP uses the FULL 640x480 on all 6 displays (so our top-strip decode = a framebuffer READ bug, stride/page, not sparse RP content). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
159 lines
8.8 KiB
Markdown
159 lines
8.8 KiB
Markdown
# The VDB — VWE Video Display/Splitter Board
|
|
|
|
Reverse-engineered from the shared MUNGA_L4 driver (`CODE/RP/MUNGA_L4/
|
|
L4SVGA16.ASM` + `L4VB16.CPP`/`.HPP`) and the pod emulator's HLE
|
|
(`emulator/vpx-device/vpxlog.cpp`). **A physical VDB sample + its companion
|
|
Cirrus Logic SVGA card exist** (operator, 2026-07-10) — see the hardware
|
|
verification checklist at the end. This board is IDENTICAL between BattleTech
|
|
and Red Planet (shared driver); only the game-side display layout differs.
|
|
|
|
## What it is
|
|
|
|
An ISA card that **taps the companion Cirrus Logic SVGA card's pixel output**
|
|
off the VGA feature connector and fans that ONE framebuffer out to the six
|
|
secondary cockpit displays (5 mono MFDs + 1 color radar) via three RAMDAC-like
|
|
palette groups. It is a DUMB splitter: no framebuffer of its own, no
|
|
compute — it re-clocks the SVGA pixel stream and routes byte-lanes through
|
|
palette CLUTs to separate VGA heads. "Adam's port decoder design" (Adam G.,
|
|
VWE hardware) per the driver comments.
|
|
|
|
The game renders cockpit gauges to the Cirrus SVGA in a **640x480x16bpp**
|
|
mode (VBE mode 0x111; `L4GAUGE=640x480x16`). The Division/VPX card is separate
|
|
(the out-the-window 3D). So the SVGA framebuffer the VDB taps = GAUGES ONLY.
|
|
|
|
## Register map (definitive, from L4SVGA16.ASM)
|
|
|
|
I/O 0x300-0x31A. Three palette groups, each a 6-bit VGA-DAC-style CLUT. The
|
|
driver's port table (`L4VB16.CPP` ~3685) passes the group base as **base+2**:
|
|
|
|
```
|
|
Group base(+2) mask read-addr write-addr data
|
|
NativePalette 0x3C6 (the Cirrus's own VGA DAC — 0x3C6/0x3C8/0x3C9)
|
|
SecondaryPalette 0x302 0x302 0x303 0x304 0x305
|
|
AuxiliaryPal1 0x30A 0x30A 0x30B 0x30C 0x30D
|
|
AuxiliaryPal2 0x312 0x312 0x313 0x314 0x315
|
|
|
|
Clock divider: 0x319 write = high-color divider OFF (VWE_HC_OFF)
|
|
0x31A write = high-color divider ON (VWE_HC_ON) (any value)
|
|
```
|
|
|
|
Per-group offsets (VGA-DAC layout): **+0 pixel-mask, +1 read-address,
|
|
+2 write-address, +3 data** (auto-incrementing R,G,B triplets). DAC is
|
|
**6-bit**: `SVGAWriteFullPalette` does `shr al,2` (8→6) on write,
|
|
`SVGAReadFullPalette` does `shl al,2` (6→8) on read. Palette load =
|
|
set write-addr to 0, then `rep outsb` 256*3 bytes. Mask write = one `out`
|
|
to base+0. The game only WRITES the VDB (fire-and-forget, no status/ACK);
|
|
reads return DAC read-back.
|
|
|
|
## How the split works (the dumb part)
|
|
|
|
**High-color clock divider (0x31A on):** in 16bpp mode each pixel is two
|
|
bytes. The divider clocks the LOW byte and HIGH byte of each pixel into
|
|
SEPARATE palette lanes → separate heads. So:
|
|
- **LOW byte (bits 0-7) -> Secondary palette (0x302 group) -> one head**
|
|
- **HIGH byte (bits 8-15) -> Aux1/Aux2 palettes -> the other heads**
|
|
|
|
Each head is a physical VGA output off the splitter; an "octopus" cable fans
|
|
the (up to) three head signals to the six cockpit displays. Decode confirmed
|
|
live for BT: radar = low byte via Secondary; the 5 mono MFDs = high byte via
|
|
Aux1/Aux2 color channels. [[vdb-three-vga-head-decode]]
|
|
|
|
## How the game packs multiple displays into one framebuffer
|
|
|
|
The card is dumb, but the DRIVER is clever about loading the CLUTs so several
|
|
displays share the same pixels. Each cockpit display (`L4GraphicsPort`) has:
|
|
- **`bitMask`** — which framebuffer pixel BITS belong to this display.
|
|
- **`channelEnable`** — which DAC color channel it drives: Red / Green / Blue
|
|
/ AllChannels (+ ...TransparentZero variants that leave index 0 undefined).
|
|
|
|
`BuildSecondaryPalette` (low byte = `bitMask & 0xFF`) and
|
|
`BuildAuxiliaryPalette` (high byte = `(bitMask>>8) & 0xFF`) walk the display's
|
|
bits with a `BitWrangler` and write that display's color/ramp into its channel
|
|
of the shared 256-entry CLUT. So N mono displays coexist in one byte-lane by
|
|
owning disjoint bit groups and different color channels; the CLUT decodes each
|
|
combination back to the right per-display brightness. `BuildAuxiliaryPalette`
|
|
generates a linear brightness RAMP across the display's bit values (mono
|
|
MFD = beam intensity); `BuildSecondaryPalette` copies source colors (color
|
|
radar).
|
|
|
|
## Palette flash + fade (the pixel-mask cycling)
|
|
|
|
`SVGA16::UpdatePalette` (`L4VB16.CPP` ~4196): each palette can FLASH by
|
|
cycling its **pixel-mask** through `mask[]` states at `flashRate` (this is the
|
|
`0x302` mask writes seen cycling e.g. 0x3F/0x7F/0xBF/0xFF — a blink, NOT index
|
|
decode). **Only the Secondary palette is allowed to FADE** (its RGB scaled
|
|
over time). So the pixel-mask register is used as a per-display blink control,
|
|
not (usually) as a decode gate.
|
|
|
|
## Our emulation (vpxlog.cpp)
|
|
|
|
`vdb_write`/`vdb_read` mirror the register map (`VDB_BASE=0x300`,
|
|
`vdb_group_of`/`vdb_group_base`); `vdb_pal[0/1/2]` = Secondary/Aux1/Aux2.
|
|
`pal_draw` reads the live Cirrus framebuffer (`vga.mem.linear` at
|
|
`vga.config.real_start`, 640x480, stride 1280) and for each pixel looks up
|
|
the low byte via pal0 and high byte via pal1/pal2 (one color channel per mono
|
|
head, pentapus split). 6-bit DAC expanded to 8-bit. `VDB_PALDUMP=<prefix>`
|
|
dumps each group's 768-byte CLUT; pixel-mask writes now log their value; masks
|
|
default 0xFF; `VDB_APPLYMASK=1` ANDs the index with the mask (correct DAC
|
|
emulation, default off = BT-identical).
|
|
|
|
## RP vs BT — the OPEN decode problem (2026-07-10)
|
|
|
|
Same board, same driver. Captured live for RP (VDB_PALDUMP): framebuffer
|
|
identical (mode 0x111, 640x480); **Secondary/pal0 loads ALL ZEROS** (BT drives
|
|
it as the dynamic color radar) -> our radar head is black; Aux1=green,
|
|
Aux2=RGB are driven; the RP MFD heads decode as SPARSE fragments in the top
|
|
strip. So under the shared mechanism, RP simply puts NO display in the low
|
|
byte (Secondary empty is legitimate), and its high-byte MFD content is not
|
|
landing where our BT-tuned head/channel split expects.
|
|
|
|
**Operator's steer: the card is dumb, so the fix is glaringly simple** — not
|
|
a driver-logic difference. Leading suspects to check next (empirically, like
|
|
the first BT pass): (a) the framebuffer PAGE/`real_start` — is RP double-
|
|
buffering so we read a stale/wrong SVGA page? (`SVGASetPage` exists); (b) the
|
|
device DUMP path stopped writing win*.bmp for RP (fires in pal_draw) — worth
|
|
finding why, it may reveal the head-render path isn't running the same;
|
|
(c) which bit groups/channels RP's displays actually claim (dump a BT vs RP
|
|
Aux palette side-by-side; the ramp/channel structure shows the bit layout);
|
|
(d) confirm the low/high byte order and the 640x480 vs a smaller gauge region
|
|
(top-strip content hints RP may draw gauges in fewer rows).
|
|
|
|
## Physical hardware (documented from the operator's sample, 2026-07-10)
|
|
|
|
CONFIRMS the reverse-engineering below the chip level:
|
|
|
|
- **Companion video card**: **Jaton KY2-JAX-CVGA54PCI** — a legacy PCI Local
|
|
Bus VGA card, **Cirrus Logic CL-GD5434** chipset. Renders the cockpit gauges
|
|
(640x480x16, VBE 0x111) and feeds the pixel stream to the VDB via a **26-pin
|
|
ribbon off the VGA Feature Connector**.
|
|
- **The splitter brain**: **AMD/Lattice MACH130-15JC** CPLD — EE-CMOS, 64
|
|
macrocells, 15 ns max prop delay, 84-pin PLCC (2nd-sourced by Rochester
|
|
Electronics). This is "Adam's port decoder": it CONSUMES the feature-connector
|
|
video stream AND takes instructions from the **ISA bus** (the 0x300-0x31A
|
|
register file — palette loads, masks, and the 0x319/0x31A high-color clock
|
|
divider). It does the byte-lane split in hardwired logic (same for BT and RP).
|
|
- **The three DACs**: **3x Brooktree Bt477KPJ80** RAMDACs (256-entry, 6-bit/
|
|
channel, 80 MHz) = the three palette groups Secondary/Aux1/Aux2. The Bt477's
|
|
6-bit DAC is exactly why the driver does `shr al,2` (8->6) on palette writes.
|
|
Output routing (confirms the head map): **one Bt477 -> a VGA stream to the
|
|
color RADAR display** (= Secondary/0x302); the **other two Bt477s -> a DB25
|
|
connector that fans out to the 5 monochrome MFDs** (= Aux1/0x30A R,G = 2
|
|
lower MFDs, Aux2/0x312 R,G,B = 3 upper MFDs; 5 mono wires total, one channel
|
|
unused). The DB25 fan-out is the "octopus/pentapus" cable.
|
|
- **Reset-tie component**: a part wired in PARALLEL with the PC's front-panel
|
|
RESET switch — either a remote-reboot path OR a means to force the VDB into
|
|
a known state when the PC is reset (so the CPLD/DACs re-init cleanly).
|
|
OPERATOR RESEARCHING. (Plausible: the CPLD needs a hard reset synchronized
|
|
with the host, since it has no software reset in the 0x300-0x31A map -- only
|
|
clock on/off; a reset tie guarantees the splitter isn't left mid-state.)
|
|
|
|
### Still worth capturing for the archive
|
|
- Photos of each chip + the board silkscreen; the DB25 pinout (which pin ->
|
|
which MFD) and the 26-pin feature-connector ribbon pinout.
|
|
- Whether the MACH130 JEDEC fuse-map is readable (a MACH130 can often be
|
|
read back unless secured) -- that IS the splitter logic, the ultimate
|
|
ground truth, and preservable like the RIO EPROM.
|
|
- The reset-tie circuit trace (operator's follow-up).
|
|
|
|
Related: [[vdb-three-vga-head-decode]], [[tesla-cockpit-emulator-state]].
|