Files
arti d4a3ccc42c harness: model the battery-backed NVR at bit 0x6000000
The terminal's settings NVR was not modelled: 0x6000000 fell inside the
video-memory mapping (0x04000000..0x1fffffff), so NVR reads returned stale
framebuffer words and writes were lost.  That is the owner's 'User settings
invalid' plus 'settings do not persist' (and the font path is stored here).
Map a persisted NVRAM over the 0x800-word settings block; MAME now saves and
reloads it (nvram/vt1200/nvram), verified write->restart->read.  No MAME-tree
change.
2026-10-16 17:25:04 +03:00

6.7 KiB

The NVR (non-volatile settings RAM) — model gap and fix

Date: 2026-10-16. Firmware vt1200_v22.bin md5 b46a3e442ccb86134d2356bd70d22fd2 (unmodified). No MAME-tree change.

Verdict (short)

  • The terminal's NVRAM/settings live at bit 0x6000000 (physical byte 0xC00000): a 16-bit-wide region whose user-settings block is 0x800 bytes, one byte per 16-bit word (i.e. 0x800 words).
  • Our model did not model it at all. That address lies inside the driver's video-memory mapping (0x04000000..0x1fffffff), so every NVR read returned whatever unrelated VRAM happened to hold and every NVR write went into the volatile VRAM array and was lost on reset/exit. This is the same "write it somewhere that throws the value away" pattern that produced the phantom DUART interrupt and the phantom level-0 pass.
  • Fix: a persisted NVRAM device mapped over the video-memory entry (map(0x6000000, 0x6007fff).ram().share("nvram") + NVRAM(config, "nvram", nvram_device::DEFAULT_ALL_1)). It is saved to / loaded from MAME's nvram directory (~/.mame/nvram/vt1200/nvram, 4096 bytes), so it survives a full restart, not just a reset — mechanically verified below.
  • The font path is a setting in this NVR (service guide, quoted below), so with the region modelled it has somewhere to live. The font data is not here.

What the service guide says

docs/EK-V1000-SV-002_service-guide.txt:

Nonvolatile RAM (NVR) on the system logic board stores the user's customized settings, including group codes, host name or IP address, font paths, and the terminal IP address.

The terminal stores feature settings in nonvolatile RAM (NVR) memory. NVR retains the settings when the terminal is turned off, so the terminal can restore the settings when power is turned on again.

(There is also a level-0/level-1 "NVR Checksum Test", error 14.)

The firmware's side (address, size, format)

Two routines, both already named in reference/memory-space-map.md row 7:

  • nvr_user_settings_validate (bit 0x3F4ABE50, byte 0x07E957CA):
    • MOVI 0x800 / MOVI 0x1a0020 / MOVI 0x6000000 / CALLA io_settings_copy_expand — copies 0x800 bytes from 0x6000000 into RAM 0x1a0020;
    • sums the bytes, compares the sum against a stored value, then checks a 0x55/0xAA magic; on a mismatch it prints User settings invalid. (bit 0x3F4B5FC0) or Old user settings layout. (bit 0x3F4B5EF0).
  • the save routine (bit 0x3F4AC880, byte 0x07E95910): runs the validator, computes a checksum over RAM 0x1a0020, then MOVI 0x800 / MOVI 0x1a0020 / MOVI 0x6000000 / CALLA io_settings_copy_linear — copies 0x800 bytes RAM → 0x6000000, one settings byte per 16-bit word (io_settings_copy_linear: MOVB *A7,A12 then SETF 0x10 and MOVE A12,*A0+, so the word stride is 16 bits).

So 0x800 words is the right size for the settings block; larger reads/writes were never observed (the only absolute operands pointing here are the two above).

The bug in the harness

program_map maps video memory over the whole 0x04000000..0x1fffffff range, and 0x06000000 is inside it. There was no entry for it, and the board windows (0x05800000, 0x05900000, 0x06200000, 0x06400000) and the MAC ROM (0x06800000) do not cover it. So all NVR traffic fell through to the vram share: reads returned stale framebuffer words (which fail the checksum), writes were accepted but evaporated. Nothing wrote a .nv file and nothing was reloaded, which is exactly the owner's "User settings invalid" + "settings do not persist".

The fix

// program_map(), registered AFTER the video-memory mapping so it wins:
map(0x6000000, 0x6007fff).ram().share("nvram");

// machine_config:
NVRAM(config, "nvram", nvram_device::DEFAULT_ALL_1);

nvram_device holds the same-named share and writes it to <nvram_directory>/vt1200/nvram on exit.

Does it persist? — mechanically, yes (verified)

A write through the CPU to NVR word 0x700, then a full MAME restart:

# process 1 (write):
prog:write_u16(0x6007000, 0xBEEF)     -> file ~/.mame/nvram/vt1200/nvram @0xE00 = ef be
# process 2 (fresh run):
prog:read_u16(0x6007000)              -> 0xBEEF

So the region is saved at exit and reloaded at start — settings would survive a restart, not merely a soft reset. (Baselines unaffected; see below.)

What I could not demonstrate, stated plainly

The OS itself writes the NVR during cold-start: in a VT1200_SKIP_LEVEL1=1 boot the whole block goes FF…FF (the power-on default) → 00…00 at frame 144. On a blank NVR that is a zeroed (still-invalid) block, which is why the Terminal Manager still prints User settings invalid. / System defaults loaded. — that is the correct first-boot behaviour, not a bug. I could not drive the Customize → Save Current Settings flow from a headless script, so the firmware's own save → restart → load round trip is not demonstrated here. The owner can settle it in one step: set the font path, Save Current Settings, quit, restart, see whether it comes back. If it does not, the next suspect is the save path's own initialisation of the block (the frame-144 write), not the region's existence.

The font cache is not in the NVR

  • The font path is a setting here (see the service guide quote). The font data is not: 2 KiB cannot hold it, and the firmware never touches 0x6000000 beyond the 0x800-word settings block.
  • There is no disk/FS device anywhere in the address map (reference/memory-space-map.md): the terminal's storage is main RAM + VRAM + this NVR. A downloaded font would live in main RAM.
  • The string Disk full or allocation exceeded is used in exactly one place: the message for TFTP error code 3 in the terminal's tftp_read ERROR-packet handler (bit ~0x3F5EDB30; the error-message table is at file 0x1A7028). So on that path the terminal prints TFTP error 3 rather than generating it from a local storage API. Whether the error 3 arrives from the host's TFTP server or from the terminal's own server needs a packet capture; the code does not settle it. (Note harness/serve.py is being modified by another worker — that may be the relevant server.)

Baselines (unchanged by this fix)

  • progress.lua 200 frames, no env: samples=198 (3 distinct PCs).
  • VT1200_IO_PATH_STUB=1: samples=53 (75 distinct PCs).
  • VT1200_SKIP_LEVEL1=1, frame-3000 dump reproduces duart2_default_tm_frame3000.png (AE = 1826 — the cursor only).

The .nv file is written under the default nvram directory (~/.mame/nvram/vt1200/nvram); nothing of the owner's existed there before this change, and no existing .nv was deleted.