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.
11 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.
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=VR150if 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, ¶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.
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|VR315restores 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 runs0..(HSBLNK-HEBLNK). In VR319 this is screen x 336..1615. Drawing from x=0 (the oldVT1200_VRAM_REALbranch) 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 withVT1200_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 showLEVEL1/2 ERROR -> HOST PORT - 15andLEVEL1/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, andtile_check.pyreports 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.