Files
vt1200/harness/mame/RENDER_REPORT.md
T
SDD 571850e564 harness: render the TMS34010 active scanlines from video RAM
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.
2026-10-16 12:42:33 +03:00

7.1 KiB
Raw Blame History

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, &params);   // 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 y to framebuffer row y - params->veblnk and screen x heblnk..hsblnk to framebuffer x 0..1023.
  • Reads each pixel from the video-RAM share: bit address 0x04000000 + row*1024 + x, i.e. word index (row*64) + (x>>4) and bit x&15 of that 16-bit word — LSB-first within the word.
  • VT1200_NO_RENDER=1 restores 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 reconstructed rowaddr advances 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_LOG flags enlarging it as a separate experiment).
  • The video-RAM dump for a single frame is ~58 MB because the share covers the whole 0x04000000..0x1fffffff mapping; the framebuffer itself is the first ~128 KiB.