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.
7.1 KiB
VT1200 MAME renderer — the graphical milestone
Status: DONE. The driver now paints the terminal's screen. The level-1 self-test CP437 character table is visible and legible, and the level-1 error screen it writes later is legible too.
1. The defect
MAME's TMS34010 renderer does not read VRAM. tms340x0_ind16
(tms34010.cpp) calls the driver's scanline callback for the active region and
paints only the blanking regions itself:
if (params.enabled)
m_scanline_ind16_cb(screen, bitmap, cliprect.min_y, ¶ms); // active pixels -> driver
...
/* blank out the blank regions */
Our callback (vt1200_state::scanline) drew nothing when m_render was
true — it only blanked when m_render was false. So the active area was never
painted by anyone, and the frame dump was whatever the bitmap started as (all
zero = black on the first frame). The stale comment in screen_update —
"Let the TMS34010 display controller do the rendering" — was the
misconception; the controller delegates.
2. What I implemented
vt1200_state::render_active_line() (called from scanline()), an
active-pixel scanline renderer:
- Blanks the whole scanline first (the device only repaints the H-blanking regions, so stale bitmap data would otherwise show).
- Maps screen line
yto framebuffer rowy - params->veblnkand screen xheblnk..hsblnkto framebuffer x0..1023. - Reads each pixel from the video-RAM share: bit address
0x04000000 + row*1024 + x, i.e. word index(row*64) + (x>>4)and bitx&15of that 16-bit word — LSB-first within the word. VT1200_NO_RENDER=1restores the old blank-everything behaviour.
A new opt-in diagnostic VT1200_DPYLOG=<path> (plus VT1200_DPYLOG_FRAMES)
dumps the TMS34010 display params per scanline; it is how the geometry below
was pinned.
3. Which geometry I trusted, and why
The real-machine facts I was given were 1280×1024, 160 bytes/scanline, 163840-byte framebuffer. I trusted the video-RAM base and the bit/pixel model from those facts, but I did not use 160 bytes/scanline — I used the geometry the firmware itself demonstrates, and I can reconcile the two:
| quantity | value I rendered | source / evidence |
|---|---|---|
| VRAM base | bit 0x04000000 |
the mapping that unblocked the level-1 VIDEO MEMORY test; the reset routine's MOVI 0x4000000,B4 sets it as the blitter bitmap base |
| row pitch | 1024 bits = 128 bytes = 0x40 words | the firmware's own character blitter: the PIXBLT at pc=3FDF9A00 writes glyph rows at word offsets 0x118C, 0x11CC, 0x120C …, i.e. +0x40 words per row |
| bit order | LSB-first in each 16-bit word | the TMS34010 pixel order; only this order makes the text readable |
| active width | 1024 px | MAME derives heblnk..hsblnk = 192..1216, which is exactly 1024 px — the framebuffer width |
| screen line → row | y - veblnk |
1:1; fb_row = scanline - params->veblnk reproduces the dumped VRAM exactly |
The decisive test was rendering the dumped VRAM content both ways:
- pitch 128 bytes, LSB-first → perfect text:
LEVEL1/2 ERROR -> HOST PORT - 15 001EFF01 - pitch 160 bytes → sheared, unreadable gibberish.
So the framebuffer is 128 bytes/line (1024 px). The given "160 bytes/
scanline" is consistent with the total raster width (1280/8 = 160),
while only the 1024 active pixels are stored — and MAME's derived active span
of 1024 px independently agrees. The requested 160-byte number is therefore
not contradicted; it is just the total-raster figure, not the memory pitch.
In short: I trusted the real-machine base and mono/1bpp model, and the
real-machine active width (via MAME's 1024-px blanking span), but the memory
pitch comes from the firmware's own blitter because rendering proves it.
MAME's derived 80×901 geometry is a separate artefact (the timing registers
HEBLNK/HSBLNK/VTOTAL are not initialised to the real monitor's values);
the renderer does not use those numbers to index memory.
4. Frame dumps (before / after)
Regenerate with:
M=/home/arti/code/k-space/mame0289/vt1200
VT1200_VRAM=1 VT1200_BOARD_MODEL=handshake VT1200_BOARD_BUSY_TICKS=2 \
VT1200_BOARD_SWITCH="0640000D:70:70" VT1200_DUART=1 VT1200_DUART_SEL=1 \
VT1200_DUMP_DIR=harness/mame VT1200_DUMP_FRAME=<n> \
$M vt1200 -rompath harness/mame/roms -video none -sound none -nothrottle \
-debug -debugger none -seconds_to_run <n/50+2> -autoboot_delay 0
Before: the three pre-existing frame dumps (vt1200-frame-60.ppm and
friends) were byte-identical all-black — the active pixels were never painted.
They have been regenerated (and the .ppm files are gitignored as generated
artefacts).
After (committed as PNG evidence):
| file | what it shows |
|---|---|
harness/mame/vt1200-frame-160.png |
the full CP437 character table |
harness/mame/vt1200-frame-200.png |
the table being wiped (white block) |
harness/mame/vt1200-frame-600.png |
the level-1 error screen |
The matching .ppm files are written by VT1200_DUMP_FRAME in the same
directory.
5. Is the character table visible and legible? — Yes
vt1200-frame-160.png shows the complete 32×16 CP437 font table, readable
glyph by glyph:
`abcdefghijklmnopqrstuvwxyz{|}~⌂
!"#$%&'()*+,-./0123456789:;<=>?
@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒ
áíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐
...
:¤¤¤·ΔφÃΓ~¤θÑÂφ≡∃ΠΨΩ∴∆Θ...
¬¤Üø>δεφψηιοκλϑνόπρσςƒσξις+→↓
This matches the real machine's boot order (character table first, then a wipe): frame 160 is the table, frame 200 the wipe, frames 90–140 a full-screen test pattern, and frame 600 the later level-1 error screen. The level-1 pie graph is not reached because the firmware is stuck at the (capped, handed-over) HOST PORT gate — as expected.
6. Regression
With no VT1200_* environment variables:
PC=3FDCE400 samples=199
Holds — identical to before the change. The renderer only affects the bitmap;
it never touches CPU state, and with no VT1200_VRAM the video-RAM share is
absent, so the code just blanks the line (as it did before).
7. Unresolved / caveats
- Vertical mapping is empirical. I map framebuffer row
= scanline - veblnk; the device's reconstructedrowaddradvances only every second scanline (DUDATE-derived), which does not describe the memory pitch, so I do not use it. The visible 864 lines show framebuffer rows 0–863; if the real VEBLNK/VSBLNK differ (they are known to be uninitialised) the last rows are simply not dumped. - The screen is still MAME's derived 1280×901, with the 1024×864 image drawn into the active span (x 192..1215). The frame dump therefore has black borders. Reconfiguring the raster to the real geometry is a separate change.
- Main RAM is still 256 KiB — deliberately not touched here (
BRINGUP_LOGflags enlarging it as a separate experiment). - The video-RAM dump for a single frame is ~58 MB because the share covers
the whole
0x04000000..0x1fffffffmapping; the framebuffer itself is the first ~128 KiB.