Files
vt1200/harness/mame/RENDER_REPORT.md
arti 8f831eaf33 fix(video): model the real unit's VR319 monitor
The harness forced the VR150 monitor switch, so the firmware wrote the OS
screen at 1024 px / 128-byte rows.  VT1200_VRAM_REAL then re-read that
framebuffer at the VR319 pitch, which sheared the OS and wrapped the right
quarter of every row.

Model VR319 (switch 0x60) by default -- the real unit's mode -- so the
firmware itself programs 1280x1024 with a 256-byte row pitch (CONVDP=0x14).
The renderer's pitch follows VT1200_MONITOR, and the destination span now
comes from HEBLNK..HSBLNK instead of x=0, placing the active 1280 px at
x=336 in MAME's 1696-wide raster.

Verified: clean Terminal Manager at frame 600, legible faithful diagnostics
menu and level-1 error screens, tile mode PASS with tile_check 1280/1280
tiles, tablet/mouse pixel-identical.  VT1200_MONITOR=VR150 reproduces the
old geometry; the regression baseline moves 198 -> 199 (2 PCs) and is
recorded in harness/mame/README.md.
2026-10-31 20:53:51 +02:00

11 KiB
Raw Permalink 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.

Note (VR319 update). Sections 1–7 record the first, VR150-derived geometry. The real unit is a VR319 and the harness now models that by default, so the firmware writes 2048-bit rows / 1280 px and the renderer follows it. Section 8 is the corrected model; the only thing sections 1–7 still need is VT1200_MONITOR=VR150 if you want to reproduce them.


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.

8. The real layout: VR319 is the default

Sections 1–7 describe the VR150 geometry: 1024-bit (128-byte) rows, 1024 active pixels, drawn at x 192..1215. That was the harness's wrong default. The real unit reports Monitor Type VR319 (dwt/notes/REAL_UNIT.md), and the firmware chooses its display format from that switch. The old VT1200_VRAM_REAL=1 knob then re-read the VR150 framebuffer at the VR319 pitch, which is why it sheared the OS: the firmware had written 128-byte rows, not 256-byte ones.

The measurements that settle it (probe of the TMS34010 display registers, same run, after boot):

monitor (switch) HEBLNK HSBLNK active px CONVDP/CONVSP pitch
VR150 (0x70) 0x0C → 192 0x4C → 1216 1024 0x15 1024 bits / 128 B
VR319 (0x60, real unit) 0x15 → 336 0x65 → 1616 1280 0x14 2048 bits / 256 B

CONVDP is the chip's own destination-pitch register: the firmware sets it to 0x15 in VR150 mode and 0x14 in VR319 mode, so the pitch is not a renderer choice at all — it is what the software tells the blitter and the scanout to use. VEBLNK is 0x24 (36) in both modes; the active rows are 1024.

The fix is therefore in the machine model, not in the renderer's pitch:

  • The monitor switch now defaults to VR319 (0x60), the real unit's mode; VT1200_MONITOR=VR150|VR262|VR315 restores the old geometry for comparison.
  • The renderer's source pitch follows the monitor (2048 bits for VR319, 1024 for the others).
  • The destination span is always the display controller's HEBLNK..HSBLNK, and the source x runs 0..(HSBLNK-HEBLNK). In VR319 this is screen x 336..1615. Drawing from x=0 (the old VT1200_VRAM_REAL branch) put the framebuffer under the device's left blanking, so the device erased the leftmost 336 px and the picture looked shifted/cut.

VT1200_VRAM_REAL=1 is kept as a force-the-2048-pitch knob for the pre-VR319 workflows, but it is no longer needed for the tile stream: with the default VR319 the OS and vram_tile.asm both write 2048-bit rows, and the tile checker passes with no environment (VT1200_MODE=tiles harness/mame/run-vidstream.sh, tile_check: PASS).

Evidence at the new default:

  • VT1200_SKIP_LEVEL1=1, frame 600: the Terminal Manager renders cleanly at 1696×1063 with the 1280-px active area at x=336. Regenerate with VT1200_DUMP_FRAMES=600 VT1200_DUMP_DIR=....
  • No env, frame 600: the level-0 diagnostics menu (VERSION 2.08 - SELECTED TEST TYPE:) is legible and correctly placed in the active area.
  • VT1200_IO_PATH_STUB=1: frames 1000 and 1900 show LEVEL1/2 ERROR -> HOST PORT - 15 and LEVEL1/2 ERROR -> PRINTER PORT - 16 (plus "CONTINUING THE TESTS") legibly — the same screens the report used before, now at 2048-bit pitch.
  • VT1200_MODE=tiles: the 1280×1024 tile test pattern fills the active area, and tile_check.py reports 1280/1280 tiles matching.

Both modes still read the bits LSB-first, which is the TMS34010 pixel order and the order the tile pipeline packs (vidsrc.Rows.pack_row); an MSB-first packer is self-consistent with an MSB-first checker and mirrors every eight pixels on a display.