From 8f831eaf33ca31a7db270eed2cda781034d4d2f2 Mon Sep 17 00:00:00 2001 From: Arti Zirk Date: Sat, 31 Oct 2026 20:53:51 +0200 Subject: [PATCH] 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. --- README.md | 17 ++-- harness/badapple/README.md | 16 ++-- harness/badapple/demo.sh | 5 +- harness/c-hello/README.md | 8 +- harness/c-hello/build.sh | 3 +- harness/c-hello/hello.c | 3 +- harness/hello-world/README.md | 6 ++ harness/mame/README.md | 87 +++++++++++++------- harness/mame/RENDER_REPORT.md | 82 ++++++++++++++----- harness/mame/vidstream_check.py | 6 +- harness/mame/vt1200.cpp | 140 ++++++++++++++++++++------------ harness/udpstream/vidstream.c | 8 +- 12 files changed, 253 insertions(+), 128 deletions(-) diff --git a/README.md b/README.md index f96e80a..2b98b9f 100644 --- a/README.md +++ b/README.md @@ -262,19 +262,23 @@ Messages, Ethernet/IP/LAT, a Memory bar, and "All Diagnostics Passed". The keyboard and mouse drivers initialise and speak the real protocol; the LANCE is initialised and the TM shows the real hardware address `08-00-2B-16-4B-5A`. Evidence frames: `harness/mame/skip_frame3000.png`, -`duart2_default_tm_frame3000.png`, `tablet_cursor_*.png`. +`duart2_default_tm_frame3000.png`, `tablet_cursor_*.png` (VR150 captures; the +VR319 default renders the same UI at the real 1280-px placement — see +`harness/mame/RENDER_REPORT.md` §8). ### Regression baseline `progress.lua` at 200 frames, **no env**: ``` -PC=3FDCE400 samples=198 (3 distinct PCs) +PC=3FDCE400 samples=199 (2 distinct PCs) ``` -It is a change detector, not a specification. It has moved twice, both -deliberately; `harness/mame/README.md` records why, including the -`VT1200_IO_PATH_STUB=1` value (`samples=53`, 75 PCs) as a separate baseline. +It is a change detector, not a specification. It has moved three times, all +deliberately; `harness/mame/README.md` records why, including the monitor-default +move (VR319 is the real unit's mode) and the `VT1200_IO_PATH_STUB=1` values +(`samples=97` at the VR319 default, `samples=53` with `VT1200_MONITOR=VR150`) as +separate baselines. ### Deliberate divergences @@ -284,8 +288,7 @@ deliberately; `harness/mame/README.md` records why, including the | `VT1200_SKIP_LEVEL1` | off | DUART1 IP3 high → skip all level-1 tests | leave unset | | `VT1200_IO_PATH_STUB` | off | 10 bytes after the level-0 `0xE1` write | leave unset | -The full env-knob table (21 knobs, down from 33) is in -`harness/mame/README.md`. +The full env-knob list is in `harness/mame/README.md`. --- diff --git a/harness/badapple/README.md b/harness/badapple/README.md index f0c0cc1..b7b62a6 100644 --- a/harness/badapple/README.md +++ b/harness/badapple/README.md @@ -72,14 +72,16 @@ selects `taptun`; the tap is `tap-mess--0` in bridge `vt1200`, `192.168.99.1/24`): ```sh -VT1200_VRAM_REAL=1 harness/mame/run.sh net +harness/mame/run.sh net ``` -`VT1200_VRAM_REAL=1` renders the framebuffer at the **real unit's** geometry -(2048-bit / 256-byte rows, 1280 px). Without it the window uses the driver's -derived 128-byte pitch and the tile picture is sheared — the divergence is -documented in `harness/mame/RENDER_REPORT.md` §8, and it is a property of the -renderer, not of the demo. +The harness now models the real unit's **VR319** monitor, so the firmware itself +uses the **1280-px / 2048-bit (256-byte) row layout** the tile payload targets — +and the Terminal Manager before it renders correctly at the same geometry. No +`VT1200_VRAM_REAL` is needed (that knob forced the pitch without changing the +firmware's layout, which is what used to shear the OS screen). To reproduce the +old derived 1024-px layout, run with `VT1200_MONITOR=VR150`; the tile picture +will then be sheared, as before. See `harness/mame/RENDER_REPORT.md` §8. Terminal 2 — the demo: @@ -143,5 +145,5 @@ path; for a live run the screen is the evidence. | `demo.sh: ... did not answer within 180s` | the terminal is not on the tap, or `run.sh net` was started without the LANCE interface selected in its cfg | | poke reads back a mismatch | the image is being loaded over a stale link, or the payload was built for a different load address | | `DICT_BYTES` stuck below 32768 | datagrams lost before the payload saw them — check the tap and the sender's interface | -| picture sheared in MAME | the window is rendering at the derived 128-byte pitch; use `VT1200_VRAM_REAL=1` | +| picture sheared in MAME | `VT1200_MONITOR=VR150` is selected (the old derived 1024-px layout); leave the default VR319 | | `jump` seems to hang the service | expected: the payload never returns, so rdebug is gone until the machine is rebooted | diff --git a/harness/badapple/demo.sh b/harness/badapple/demo.sh index cfd9b95..9c25216 100755 --- a/harness/badapple/demo.sh +++ b/harness/badapple/demo.sh @@ -3,8 +3,9 @@ # deliver the payload over rdebug, then send the clip with the host sender. # # The terminal itself is started separately -- `harness/mame/run.sh net` for the -# emulator (add VT1200_VRAM_REAL=1 to see the picture at the real unit's -# framebuffer layout), or a real VT1200 on the network. This script only talks +# emulator (its default VR319 monitor already gives the real unit's 1280-px / +# 256-byte framebuffer layout; `VT1200_MONITOR=VR150` reproduces the old derived +# 1024-px view), or a real VT1200 on the network. This script only talks # to it, so one instance serves one demo: the jump hands the payload the # remote-debug process and it never returns. # diff --git a/harness/c-hello/README.md b/harness/c-hello/README.md index b200ad1..7eceea7 100644 --- a/harness/c-hello/README.md +++ b/harness/c-hello/README.md @@ -92,12 +92,14 @@ Success = loader status 0, `entry=0x018A17E0`, markers `48454C4C`/`444F4E45` the image must not need static initializers. The font and order tables are filled in `build_font()` at runtime. -## Geometry is not portable between the real unit and MAME +## Geometry is not portable between the real unit and MAME's VR150 mode The two framebuffers differ, and the payload carries the difference as -`ROW_BYTES`: +`ROW_BYTES`. **The harness now defaults to the real unit's VR319 monitor**, so +MAME's default geometry is the real one; `VT1200_MONITOR=VR150` restores the old +MAME layout (and needs the `build.sh emu` image). -| | real unit (measured) | MAME | +| | real unit (measured) = MAME VR319 (default) | MAME VR150 | |---|---|---| | base | bit `0x04000000` | bit `0x04000000` | | row pitch | **256 bytes** (`ROW_BYTES 256`) | 128 bytes (`VMEM_PITCH_BITS`) | diff --git a/harness/c-hello/build.sh b/harness/c-hello/build.sh index ea3a678..352292d 100755 --- a/harness/c-hello/build.sh +++ b/harness/c-hello/build.sh @@ -24,7 +24,8 @@ DOSBOX_CONF="$ROOT/harness/sdk-scratch/dosbox-bench.conf" TEXT=018A0000 BSS=01A00000 -# `build.sh emu` builds a 128-byte-stride variant for MAME's 1024x1024 canvas. +# `build.sh emu` builds a 128-byte-stride variant for the harness's VR150 mode +# (VT1200_MONITOR=VR150); the default VR319 layout matches the real unit. MODE=${1:-real} NAME=hello if [ "$MODE" = "emu" ]; then NAME=hello-emu; fi diff --git a/harness/c-hello/hello.c b/harness/c-hello/hello.c index e8aef05..40dfc63 100644 --- a/harness/c-hello/hello.c +++ b/harness/c-hello/hello.c @@ -4,7 +4,8 @@ * Writes the word HELLO straight into video RAM, no X11 involved: * * framebuffer bit 0x04000000, 256-byte row pitch on the real unit - * (the emulator uses 128; this image is linked for the real one) + * (the harness now defaults to the same VR319 layout; + * only VT1200_MONITOR=VR150 uses 128) * pixels 1 bpp, LSB-first within each byte * text area black 256x72 box at x=40 y=800, white 5x7 glyphs scaled 8x * diff --git a/harness/hello-world/README.md b/harness/hello-world/README.md index 03434d5..1c5cf22 100644 --- a/harness/hello-world/README.md +++ b/harness/hello-world/README.md @@ -16,6 +16,12 @@ short version: * both are observed from inside the emulator (RAM readback) and from a frame dump (white block at screen x 256..511, y 136..199). +> **Pitch note.** `hello.asm` writes 1024-bit rows (the old derived layout). +> The harness now defaults to the real unit's **VR319** 2048-bit layout, so add +> `VT1200_MONITOR=VR150` to the run below to see the block where this README +> says it is; at the VR319 default the rendered block is sheared because the +> payload, not the firmware, chose the old pitch. + ## Build ```sh diff --git a/harness/mame/README.md b/harness/mame/README.md index 29e3592..c4b24c7 100644 --- a/harness/mame/README.md +++ b/harness/mame/README.md @@ -109,10 +109,13 @@ to be a six-variable incantation; it is now the default, and a bare * Video RAM at bit `0x04000000` — required for the level-1 VIDEO MEMORY test. * The board-window handshake model (two-tick busy bit). -* MAME's `SCN2681` DUART at **DUART1** (`0x0640000`, host/printer), with input - port **IP4–IP6 held high** — that is how the driver reads the VR150 monitor - switch (`0x70`). The level-1 host-port transmit is **ISR-driven**, so a real - DUART is required here, not a convenience. +* MAME's `SCN2681` DUART at **DUART1** (`0x0640000`, host/printer), whose input + port **IP4–IP6** carries the monitor-selection switch. The default is + **VR319 (`0x60`) — the real unit's mode** (see `dwt/notes/REAL_UNIT.md`), so + the firmware itself programs a **1280×1024 display at a 256-byte (2048-bit) + row pitch** and `CONVDP = 0x14`. `VT1200_MONITOR=VR150|VR262|VR315` selects + another position for comparison. The level-1 host-port transmit is + **ISR-driven**, so a real DUART is required here, not a convenience. * MAME's `SCN2681` at **DUART2** (`0x0620000`, keyboard/mouse). This one is a *faithfulness* fix, not a convenience — the real board has one. Without it the board write-back latch answers a read of DUART2's **write-only** IMR with its @@ -126,7 +129,11 @@ to be a six-variable incantation; it is now the default, and a bare `FD`/`T` because that is the protocol, not to satisfy a test. See `INPUT_REPORT.md` and `TABLET_REPORT.md`. * A mono palette (pens 0/1 = black/white) and an active-pixel scanline renderer - that reads VRAM. See `RENDER_REPORT.md`. + that reads VRAM at the pitch the modelled monitor implies: 2048-bit rows / + 1280 px for VR319 (the default), 1024-bit rows / 1024 px for VR150. The + destination span always comes from the display controller's own + `HEBLNK..HSBLNK`, so the active picture sits at `x = 336` in MAME's + 1696-wide VR319 raster, not at `x = 0`. See `RENDER_REPORT.md`. * The **LANCE Ethernet controller** — MAME's `am79c90` (AM79C90) at the firmware's two register windows (**bit `0x5900000` = RAP, `0x5800000` = RDP**), with its bus-master DMA bound (LANCE byte `B` → main-RAM word `B >> 1`) and its @@ -204,8 +211,10 @@ over to the terminal's own mouse cursor, and the V2.2 box appears — title bar, Messages, Ethernet/IP/LAT, a **Memory bar**, and "All Diagnostics Passed". Confirmed in the GUI and from frame dumps: `skip_frame3000.png` (the weave) and -`duart2_default_tm_frame3000.png` (the Terminal Manager). The real terminal shows -the same sequence — see `reference/vt1200-boot-video/`. +`duart2_default_tm_frame3000.png` (the Terminal Manager). Those two files are +**VR150** captures; at the VR319 default the same Terminal Manager renders at the +real 1280-px placement (`VT1200_DUMP_FRAMES=600`, see `RENDER_REPORT.md` §8). The +real terminal shows the same sequence — see `reference/vt1200-boot-video/`. The OS's keyboard and mouse drivers **initialise and speak the real protocol**: it programs DUART2 (`MR1A=0x13`, `MR2A=0x07`, `CSRA/CSRB=0x99`, `ACR=0x60`, @@ -269,12 +278,13 @@ Terminal Manager usable. It is a **value-aware inverse switch**: the default lives in the code, not in a wrapper script setting `VT1200_TABLET`, so `VT1200_MOUSE=1` (or `VT1200_MOUSE=0` for the tablet) always flips it either way. -## Environment knobs (22; down from 33) +## Environment knobs (23; down from 33) Almost all are opt-in and off by default — there is no longer a "working incantation" to remember, because everything the firmware needs to run is now the -default. The one exception is that the **tablet is on by default**; `VT1200_MOUSE` -is the switch that restores the faithful mouse (see the divergences above). +default. The two exceptions are the **tablet** (on by default; `VT1200_MOUSE` +restores the faithful mouse) and the **VR319 monitor** (on by default because +that is the real unit; `VT1200_MONITOR` selects VR150 and friends). Instrumentation: `VT1200_DUARTLOG`, `VT1200_LANCELOG` (every LANCE RAP/RDP and address-ROM access with the PC), `VT1200_LANCE_DMA_LOG` (every LANCE bus-master @@ -283,10 +293,13 @@ the descriptor write-back are read directly), `VT1200_BOARD_LOG`, `VT1200_BOARD_ `VT1200_IRQLOG`, `VT1200_LOG_UNMAPPED`, `VT1200_DPYLOG` (+`_FRAMES`), `VT1200_DUMP_FRAME` (+`_FRAMES`), `VT1200_DUMP_RAM` (+`_FRAMES`), `VT1200_DUMP_VRAM` (+`_FRAMES`), `VT1200_DUMP_DIR`, `VT1200_NO_RENDER`, -`VT1200_VRAM_REAL` (draw the framebuffer at the **real unit's** 2048-bit / -256-byte row pitch, 1280 px wide, instead of the derived 1024-bit / 128-byte -pitch — the view for content written for the hardware, e.g. the tile stream; -off by default, see `RENDER_REPORT.md`), +`VT1200_MONITOR` (monitor-selection switch: `VR319` default / `VR150` / `VR262` +/ `VR315`, or a raw position 0–7; it also picks the renderer's row pitch. +Positions other than 7/5/3/6 are the firmware's "Undefined monitor switch +setting" branch), +`VT1200_VRAM_REAL` (force the 2048-bit / 256-byte / 1280-px renderer pitch +regardless of `VT1200_MONITOR`; only meaningful when the firmware itself is +writing that layout, which the VR319 default does), `VT1200_INPUT_LOG` (per-byte keyboard/mouse/tablet TX/RX trace, with emulated time). Behaviour switches: `VT1200_SKIP_LEVEL1`, `VT1200_MOUSE` (put the VSXXX-AA mouse (relative) back on DUART2-B; the VSXXX-AB tablet is the default — @@ -299,10 +312,10 @@ described above — not a keyboard model, off by default). `progress.lua` at 200 frames, **no env**: ``` -PC=3FDCE400 samples=198 (3 distinct PCs) +PC=3FDCE400 samples=199 (2 distinct PCs) ``` -This has moved **twice**, both deliberately: +This has moved **three** times, all deliberately: 1. `samples=199` (1 PC) → `samples=53` (78 PCs): the working configuration became the default, so a bare run stopped wedging at the hardware-ready gate and began @@ -312,6 +325,15 @@ This has moved **twice**, both deliberately: by the write-back latch's phantom `RXRDY`, so the run parks in the diagnostics menu. That looks like a regression and is not one — it is the removal of a false pass. +3. `samples=198` (3 PCs) → `samples=199` (2 PCs): the monitor switch became + **VR319**, the real unit's mode, instead of the old VR150. The run still parks + in the same level-0 diagnostics menu (verified with frame dumps at 200 and + 600); only the number of frames that land on each of the menu's two + instructions changed, because the VR319 raster (1696×1063 total, 1280×1024 + active) runs at a different emulated frame rate than VR150 (1280×901, + 1024×864 active). `VT1200_MONITOR=VR150` reproduces the old + `samples=198 (3 PCs)` line exactly, which is the control that shows the move + is geometry, not a behaviour change. **The tablet-default change did NOT move this baseline** (198 → 198), and that is expected, not an oversight. The tablet changes what DUART2-B carries on the wire @@ -321,17 +343,22 @@ it then frames 5-byte absolute reports instead of 3-byte relative ones — but a has never addressed DUART2-B at all: the `T`/`R` exchange happens only in the OS phase, which the no-env run does not reach. The only DUART2-B traffic in the window is the device's unsolicited power-up self-test, which nothing clocks in. -The OS-phase render is unchanged too: with `VT1200_SKIP_LEVEL1=1` the frame-3000 +The OS-phase render is unchanged too: with `VT1200_SKIP_LEVEL1=1` the frame-600 dump is **pixel-identical** for the tablet and for `VT1200_MOUSE=1` -(`magick compare -metric AE` = 0), and both reproduce the committed -`tablet_cursor_*.png` frames exactly. +(`magick compare -metric AE` = 0, same md5). That was re-measured at the VR319 +default; the committed `tablet_cursor_*.png` frames are VR150 evidence and are +reproduced with `VT1200_MONITOR=VR150`. **The no-env value is unchanged by `VT1200_IO_PATH_STUB`** — stub off is the -default, so the `samples=198 (3 PCs)` line above still holds. The stub is a separate -observed configuration: +default, so the `samples=199 (2 PCs)` line above still holds. The stub is a separate +observed configuration. Its value depends on the monitor, because frame count is +not emulated time; both are recorded here: ``` -# VT1200_IO_PATH_STUB=1, progress.lua at 200 frames +# VT1200_IO_PATH_STUB=1, progress.lua at 200 frames, VR319 (default) +PC=3FDCE400 samples=97 (69 distinct PCs) + +# VT1200_IO_PATH_STUB=1 VT1200_MONITOR=VR150, progress.lua at 200 frames PC=3FDCE400 samples=53 (75 distinct PCs) ``` @@ -340,10 +367,11 @@ satisfied, the run leaves the level-0 wait and executes level-0/level-1 work. It is **not** parked at `HOST PORT - 15`: the console transcript continues (`LEVEL1/2 TESTING **`, `LEVEL1/2 ERROR -> HOST PORT - 15`, `LEVEL1/2 ERROR -> PRINTER PORT - 16`, `TYPE 'GO' TO CONTINUE`), and the run ends -in a DUART1 counter-ready **interrupt storm** (from frame ~2839; PC in the DUART1 -INT1 ISR, polling ISR reg 5 = `0x08`). See `LEVEL1_CONTINUE_REPORT.md`. It is -recorded as a *different* baseline because it is opt-in; the default must stay -`samples=198`. +in a DUART1 counter-ready **interrupt storm** (at VR150, from frame ~2839; PC in +the DUART1 INT1 ISR, polling ISR reg 5 = `0x08`). See +`LEVEL1_CONTINUE_REPORT.md`. It is recorded as a *different* baseline because it +is opt-in; the default must stay `samples=199` (`samples=53` with +`VT1200_MONITOR=VR150`). It is a change detector, not a specification. If it moves without you meaning it to, find out why. @@ -447,6 +475,7 @@ would reach a real terminal. ```sh M=/home/arti/code/k-space/mame0289/vt1200 +VT1200_MONITOR=VR150 \ HELLO_END=150 VT1200_DUMP_FRAME=145 VT1200_DUMP_RAM=145 \ VT1200_DUMP_DIR=harness/hello-world/evidence \ $M vt1200 -rompath harness/mame/roms -video none -sound none -nothrottle \ @@ -458,5 +487,7 @@ The payload writes `0xDEAD`, `0xBEEF` and a 1024-iteration counter at bit `0x01810000`, and fills a 256x64 block in video RAM at row 100, column 64. The script reads every value back and prints `ok`/`WRONG`; a frame dump shows the block at screen x 256..511, y 136..199. `HELLO_AT=9999` is the control run -(nothing loads, markers stay zero). See `HELLO_WORLD_REPORT.md` and -`harness/hello-world/README.md`. +(nothing loads, markers stay zero). `VT1200_MONITOR=VR150` is required here: +the payload writes 1024-bit rows and the frame numbers are the old VR150 +timeline; the VR319 default would shear the block and boot at a different frame +cadence. See `HELLO_WORLD_REPORT.md` and `harness/hello-world/README.md`. diff --git a/harness/mame/RENDER_REPORT.md b/harness/mame/RENDER_REPORT.md index 73357a0..c561d8a 100644 --- a/harness/mame/RENDER_REPORT.md +++ b/harness/mame/RENDER_REPORT.md @@ -4,6 +4,12 @@ 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 @@ -158,28 +164,62 @@ absent, so the code just blanks the line (as it did before). the whole `0x04000000..0x1fffffff` mapping; the framebuffer itself is the first ~128 KiB. -## 8. The other layout: `VT1200_VRAM_REAL=1` +## 8. The real layout: VR319 is the default -Everything above describes the **derived** geometry: 1024-bit (128-byte) rows, -1024 active pixels, drawn at x 192..1215. It is what the firmware's own blitter -uses (`pc=3FDF9A00` advances the destination by `0x40` words per glyph row), and -what makes the Level-1 error text legible. +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 **real unit** stores 1280-pixel rows at a 256-byte pitch — first 160 bytes -the picture, the remaining 96 a `0x55` mask — as `harness/c-hello/README.md` -records from measurement ("a payload writing at `row*2048` bits lands on -scanline `row`"), and as `dwt/screenshot.py --real` (width 1280, stride 256) -decodes from live VRAM. +The measurements that settle it (probe of the TMS34010 display registers, same +run, after boot): -A payload can target either. The tile stream targets the real unit -(`vram_tile.asm: DPTCH = 0x800` = 2048 bits), so under the derived renderer it -is sheared by a quarter-row per scanline and wrapped. `VT1200_VRAM_REAL=1` -renders the same VRAM at 2048-bit pitch and 1280 px from x=0 instead. Both modes -read the bits LSB-first, which is the TMS34010 pixel order and the order the -tile pipeline now packs (`vidsrc.Rows.pack_row`); an MSB-first packer is -self-consistent with an MSB-first checker and mirrors every eight pixels on a +| 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. - -The switch changes only the bitmap, never CPU state, so the regression baseline -(`progress.lua`, `PC=3FDCE400 samples=198`) is unaffected. Default is off: the -real machine runs level 1, and the derived geometry is what that text needs. diff --git a/harness/mame/vidstream_check.py b/harness/mame/vidstream_check.py index fe99b34..db9de14 100755 --- a/harness/mame/vidstream_check.py +++ b/harness/mame/vidstream_check.py @@ -18,9 +18,9 @@ than a silently missing check. This script is the independent reading of the geometry, not a copy of the code under test. The frame is 1280 pixels wide, 1 bpp, 160 bytes a row; the screen row pitch is 2048 bits = 256 bytes (the real unit's, measured in -harness/c-hello/README.md -- MAME's own renderer uses a different pitch and is -NOT what this test checks). A source row r stretched by S must appear, byte for -byte, on screen rows r*S .. r*S+S-1. +harness/c-hello/README.md -- and the harness default now that it models VR319; +`VT1200_MONITOR=VR150` would use the old 128-byte renderer). A source row r +stretched by S must appear, byte for byte, on screen rows r*S .. r*S+S-1. The comparison uses the bytes that were actually delivered to the slot, not a re-implementation of the pattern: whatever the sender wrote is what must land on diff --git a/harness/mame/vt1200.cpp b/harness/mame/vt1200.cpp index 5c4e99b..bdf3324 100644 --- a/harness/mame/vt1200.cpp +++ b/harness/mame/vt1200.cpp @@ -24,9 +24,12 @@ * The four undecoded board windows, driving the handshake register model with a two-tick busy bit (required to leave early POST). * MAME's SCN2681 at DUART1 (0x0640000 = host/printer), with its input - port IP4-IP6 held high -- which is how the driver reads the VR150 - monitor switch (value 0x70). The level-1 host-port transmit is - ISR-driven, so a real DUART is required. + port IP4-IP6 driven from the modelled monitor-selection switch. The + default is VR319 (0x60) -- the real unit's mode (dwt/notes/REAL_UNIT.md) + -- which makes the firmware program the 1280x1024 display with a + 256-byte (2048-bit) row pitch. VT1200_MONITOR selects VR150 (0x70), + VR262 (0x50) or VR315 (0x30) instead. The level-1 host-port transmit + is ISR-driven, so a real DUART is required. * The 1.75 MiB firmware ROM (physical byte base 0x07e40000 = bit 0x3f200000), mirrored into the three upper 1 GiB quadrants so that the reset-vector page at logical bit 0xffffffe0 resolves to the last @@ -121,6 +124,16 @@ VT1200_DUMP_VRAM_FRAMES. VT1200_DUMP_DIR= directory for the dumps (default ".") VT1200_NO_RENDER=1 blank the screen instead of painting it + VT1200_MONITOR= monitor-selection switch: VR319 (default, + 0x60), VR150 (0x70), VR262 (0x50) or VR315 + (0x30). Also sets the renderer's row pitch: + VR319 is 2048 bits / 1280 px, the others + use the derived 1024-bit / 1024 px. + VT1200_VRAM_REAL=1 force the 2048-bit / 1280 px renderer pitch + regardless of VT1200_MONITOR. Only + meaningful when the emulated firmware is + itself writing that layout (VR319 does). + Kept for the pre-VR319 workflows. Behaviour switch (deliberately diverges from the real terminal): @@ -630,7 +643,6 @@ static const u32 BOARD_BASE[4] = { VT1200_WIN0, VT1200_WIN1, VT1200_WIN2, VT1200 // sheared and unreadable. See RENDER_REPORT.md. static const u32 VRAM_BASE_BITS = 0x04000000; // where the framebuffer lives static const int VMEM_PITCH_BITS = 1024; // 0x40 words = 128 bytes -static const int VMEM_WIDTH = 1024; // visible pixels per row static const int VMEM_HEIGHT = 1024; // rows in the framebuffer class vt1200_state : public driver_device @@ -713,6 +725,13 @@ private: required_device m_duart2; // 0x0620000: keyboard(A)/mouse(B) bool m_duart1_int2 = false; // set by VT1200_SKIP_LEVEL1 + // Monitor-selection switch position 0-7, read from DUART1 IP4-IP6. The + // real unit reports VR319 (dwt/notes/REAL_UNIT.md), which the firmware's + // table tests as 0x60 (position 6); 0x70=VR150, 0x50=VR262, 0x30=VR315, + // anything else is "Undefined monitor switch setting". VT1200_MONITOR + // selects another position for comparison. + int m_monitor_pos = 6; // 0x60 = VR319 + // The real devices on DUART2. Keyboard on channel A, pointing device on // channel B, as the board has them. Wired bit-by-bit to the SCN2681's // serial lines. Channel B carries the VSXXX-AB tablet BY DEFAULT, or the @@ -802,13 +821,12 @@ private: // times -- the instrument for telling "progressing" from "stuck": if two // samples minutes apart are byte-identical, nothing is being drawn. bool m_render = true; - // VT1200_VRAM_REAL=1: render the framebuffer at the REAL unit's geometry - // (2048-bit/256-byte row pitch, 1280 px wide) instead of the derived - // 1024-bit/128-byte pitch MAME's own screen geometry implies. Off by - // default: the Level-1 error text is only legible at 128 bytes (see - // RENDER_REPORT.md), so this is for looking at content written by a payload - // that targets the real machine -- e.g. the tile stream, whose - // vram_tile.asm sets DPTCH=0x800 (2048 bits). The bit order is LSB-first in + // m_render_real: render the framebuffer at the REAL unit's geometry + // (2048-bit/256-byte row pitch) instead of the derived 1024-bit/128-byte + // pitch. It is set automatically from the modelled monitor switch: VR319 + // (the real unit) is 1280 px/2048 bits, VR150 and the other modes use the + // derived pitch. VT1200_VRAM_REAL=1 forces the real geometry regardless + // (the pre-VR319 behaviour of this knob). The bit order is LSB-first in // both modes, which is the TMS34010 pixel order and matches // dwt/screenshot.py's decode of live VRAM. bool m_render_real = false; @@ -886,6 +904,20 @@ static std::vector parse_frame_list(const char *s) void vt1200_state::machine_start() { + // Monitor-selection switch (DUART1 IP4-IP6, value bits 4-6). The switch is + // read only during power-up and selects the display format: + // 7 (0x70) VR150, 5 (0x50) VR262, 3 (0x30) VR315, 6 (0x60) VR319. + // The real unit reports VR319 (dwt/notes/REAL_UNIT.md), which is the + // default; VT1200_MONITOR accepts a name or a raw 0-7 position. + if (const char *m = getenv("VT1200_MONITOR")) + { + if (!strcmp(m, "VR150")) m_monitor_pos = 7; + else if (!strcmp(m, "VR262")) m_monitor_pos = 5; + else if (!strcmp(m, "VR315")) m_monitor_pos = 3; + else if (!strcmp(m, "VR319")) m_monitor_pos = 6; + else m_monitor_pos = int(strtol(m, nullptr, 0)) & 7; + } + if (const char *list = getenv("VT1200_BOARD_MAP")) { std::string s(list), item; @@ -922,6 +954,10 @@ void vt1200_state::machine_start() // Video: render by default; optionally dump a frame as PPM. if (getenv("VT1200_NO_RENDER")) m_render = false; + // The renderer follows the firmware's own screen format. VR319 (the real + // unit, 0x60) is 1280 px at a 256-byte/2048-bit row pitch; the other monitor + // modes use the derived 128-byte pitch this renderer was built against. + m_render_real = (m_monitor_pos == 6); if (getenv("VT1200_VRAM_REAL")) m_render_real = true; if (const char *f = getenv("VT1200_DUMP_FRAME")) @@ -989,12 +1025,12 @@ void vt1200_state::machine_start() // With the real DUART in place, board-level signals that used to be faked // by the window model come from the DUART's input port (read register 0x0D: // IP0-IP6, bit 7 always set). The monitor-selection switch is IP bits 4-6; - // driving them high selects the VR150 CRT (0x70), matching the value the - // firmware looks for. IP2 stays low (the value for the blit-status test at - // bit 0x3F204AA0). - m_duart1->ip4_w(1); - m_duart1->ip5_w(1); - m_duart1->ip6_w(1); + // the real unit reports VR319, i.e. position 6 (0x60), and the firmware + // selects its 1280x1024 / 256-byte-stride display format from that. IP2 + // stays low (the value for the blit-status test at bit 0x3F204AA0). + m_duart1->ip4_w(m_monitor_pos & 1); + m_duart1->ip5_w((m_monitor_pos >> 1) & 1); + m_duart1->ip6_w((m_monitor_pos >> 2) & 1); // VT1200_SKIP_LEVEL1=1 drives DUART1 IP3 high, which makes the boot gate at // bit 0x3F2042D0 take the OS cold-start branch (0x3F5788C0) and skip the @@ -1015,7 +1051,8 @@ void vt1200_state::machine_start() if (getenv("VT1200_IO_PATH_STUB")) m_iopath_stub = true; - logerror("vt1200: board model=handshake busy_ticks=2 switch=duart-ip4/5/6 log=%s render=%d dump=%ld\n", + logerror("vt1200: board model=handshake busy_ticks=2 switch=duart-ip4/5/6 (monitor pos %d = 0x%02X, render pitch %d) log=%s render=%d dump=%ld\n", + m_monitor_pos, m_monitor_pos << 4, m_render_real ? 2048 : 1024, m_log ? "on" : "off", m_render ? 1 : 0, m_dump_frame); logerror("vt1200: duart1=on (SCN2681 @0640000 host/printer), duart2=on (SCN2681 @0620000 kbd/mouse), skip_level1=%s\n", getenv("VT1200_SKIP_LEVEL1") ? "on (IP3 high, IRQ->INT2)" : "off (IP3 low, IRQ->INT1)"); @@ -1474,29 +1511,27 @@ void vt1200_state::log_lance_dma(char kind, u32 addr, u16 value, u16 mask) // Copy one active scanline out of the video RAM framebuffer. // // The TMS34010 screen-refresh address is reconstructed by the device into -// `params`, but the firmware's timing registers (HEBLNK/HSBLNK/VTOTAL) are not -// initialised to the real machine's values, so the geometry MAME derives from -// them (a 1280x901 screen, active x 192..1215) does not describe the memory -// layout. The measured memory layout does: +// `params`. Two memory layouts are in use, selected by the monitor switch: // -// * base bit 0x04000000 (the mapped video RAM) -// * pitch 1024 bits (0x40 words / 128 bytes) per row — taken from the -// firmware's own blitter, which advances its destination by -// 0x40 words per glyph row (pc=3FDF9A00 in VRAM write logs) -// * bit order LSB-first within each 16-bit word — the TMS34010 pixel order -// * active x the device's heblnk..hsblnk span is 1024 pixels, exactly the -// framebuffer width, so screen x-heblnk maps to framebuffer x +// * VR319 (the real unit, 0x60) pitch 2048 bits (0x800 / 256 bytes), +// 1280 active px. The firmware programs +// HEBLNK=0x15/HSBLNK=0x65 (16-px units: +// 336..1616 in MAME's 1696-wide raster), +// CONVSP/CONVDP=0x14 (= 2048 bits) and +// VEBLNK=0x24 (36 -> 1024 rows). +// * VR150 (0x70, the pre-VR319 default) pitch 1024 bits (0x40 / 128 bytes), +// 1024 active px (HEBLNK=12/HSBLNK=76). // -// The screen line maps 1:1 to a framebuffer row, offset by the vertical +// In both modes the destination span is the display controller's own +// heblnk..hsblnk, and the source pitch is the only thing that differs. The +// bit order is LSB-first within each 16-bit word (the TMS34010 pixel order), +// and the screen line maps 1:1 to a framebuffer row, offset by the vertical // blanking start (params->veblnk). // -// VT1200_VRAM_REAL=1 switches to the REAL unit's layout instead: pitch 2048 -// bits (0x800 / 256 bytes), 1280 px wide, drawn from x=0. It exists because a -// payload can write for the real machine and then look sheared here -- the tile -// stream's vram_tile.asm sets DPTCH=0x800 and dwt/screenshot.py --real reads -// stride 256 at 1280 wide. With the switch off nothing about the geometry -// changes: this is a view of the same VRAM at the other documented pitch, not a -// different machine model. +// VT1200_MONITOR chooses the monitor (and therefore the pitch); VR319 is the +// default because that is the real unit's mode (dwt/notes/REAL_UNIT.md). +// VT1200_VRAM_REAL=1 still forces the 2048-bit layout regardless of monitor, +// for compatibility with the old knob. void vt1200_state::render_active_line(bitmap_ind16 &bitmap, int scanline, const tms340x0_device::display_params *params) { @@ -1512,30 +1547,33 @@ void vt1200_state::render_active_line(bitmap_ind16 &bitmap, int scanline, return; const u16 *const vram = m_vram.target(); - // Two geometries, one per documented layout (harness/c-hello/README.md, + // Two geometries, one per monitor mode (harness/c-hello/README.md, // "Geometry is not portable between the real unit and MAME"): // - // derived (default) pitch 1024 bits, 1024 px, x = heblnk + fx - // real (VT1200_VRAM_REAL=1) pitch 2048 bits (0x800, 256 bytes), 1280 px, - // x = fx -- the real raster is 1280 wide, so there is - // no blanking offset to add + // VR319 (default, the real unit) pitch 2048 bits (0x800, 256 bytes), + // 1280 active px, x = heblnk + fx + // VR150 (VT1200_MONITOR=VR150) pitch 1024 bits, 1024 active px, + // x = heblnk + fx // // The real numbers are the ones a payload targeting the hardware writes: // vram_tile.asm sets DPTCH=0x800 and dwt/screenshot.py --real reads stride - // 256 / width 1280. Revert note: delete the m_render_real branch and the - // environment read and this file returns to the measured 128-byte renderer - // byte for byte; nothing else depends on it. + // 256 / width 1280. + // + // The pitch is the only pitch-dependent number. The destination span + // always comes from the display controller's own blanking registers: in + // VR319 mode the firmware programs HEBLNK=0x15/HSBLNK=0x65 (16-px units), + // i.e. 1280 active pixels starting at screen x=336 in MAME's 1696-wide + // raster. Drawing the framebuffer at x=0 would shift the whole OS screen + // left by 336 px and leave the right quarter black -- which is what made + // the "real layout" look corrupted before. const u32 pitch = m_render_real ? 2048u : u32(VMEM_PITCH_BITS); - const int width = m_render_real ? 1280 : VMEM_WIDTH; - const int x0 = m_render_real ? 0 : (int)params->heblnk; - const int x1 = m_render_real ? bitmap.width() : (int)params->hsblnk; + const int x0 = (int)params->heblnk; + const int x1 = std::min((int)params->hsblnk, bitmap.width()); const u32 row_bit = (u32)fby * pitch; - for (int fx = 0; fx < width; fx++) + for (int fx = 0; x0 + fx < x1; fx++) { const int x = x0 + fx; - if (x >= x1 || x >= bitmap.width()) - break; const u32 bit = row_bit + (u32)fx; // Address-map units are bits, so a 16-bit word is 16 units. bitmap.pix(scanline, x) = (vram[bit >> 4] >> (bit & 15)) & 1; diff --git a/harness/udpstream/vidstream.c b/harness/udpstream/vidstream.c index 830eca4..c1ecae5 100644 --- a/harness/udpstream/vidstream.c +++ b/harness/udpstream/vidstream.c @@ -70,10 +70,10 @@ * fits with one descriptor to spare. * * Screen geometry. VRAM is at bit 0x04000000 and a screen row is 0x800 bits - * (256 bytes), the real unit's pitch (harness/c-hello/README.md). MAME's own - * renderer decodes the same memory at 128 bytes a row; that difference is a - * property of the harness, so the draw test compares VRAM as memory, not the - * rendered picture. + * (256 bytes), the real unit's pitch (harness/c-hello/README.md). The harness + * now models the real unit's VR319 monitor by default, so MAME's renderer uses + * the same 256-byte pitch; the draw test still compares VRAM as memory, not the + * rendered picture, because that is what the payload actually writes. * * OS repaint canary (spec 8.9). The blit covers screen rows 0..1023 and * columns 0..1279 only. One 16-bit sentinel word sits in the unused right