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.
This commit is contained in:
arti committed 2026-10-31 20:53:51 +02:00
1 parent eabc3051be
commit 8f831eaf33
12 files changed
+253 -128

No files matched your search

+10 -7
View File
@@ -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`.
---
+9 -7
View File
@@ -72,14 +72,16 @@ selects `taptun`; the tap is `tap-mess-<uid>-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 |
+3 -2
View File
@@ -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.
#
+5 -3
View File
@@ -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`) |
+2 -1
View File
@@ -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
+2 -1
View File
@@ -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
*
+6
View File
@@ -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
+59 -28
View File
@@ -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`.
+61 -21
View File
@@ -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.
+3 -3
View File
@@ -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
+89 -51
View File
@@ -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=<dir> directory for the dumps (default ".")
VT1200_NO_RENDER=1 blank the screen instead of painting it
VT1200_MONITOR=<name|0-7> 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<scn2681_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<long> 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>((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;
+4 -4
View File
@@ -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