diff --git a/harness/mame/NVRAM_REPORT.md b/harness/mame/NVRAM_REPORT.md new file mode 100644 index 0000000..5772ef3 --- /dev/null +++ b/harness/mame/NVRAM_REPORT.md @@ -0,0 +1,139 @@ +# 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 + +```c++ +// 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 +`/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. diff --git a/harness/mame/README.md b/harness/mame/README.md index 3363aee..4eee4fe 100644 --- a/harness/mame/README.md +++ b/harness/mame/README.md @@ -69,6 +69,7 @@ Reports in this directory: | `TABLET_REPORT.md` | the VSXXX-AB tablet: the self-test device-code ID, the 5-byte absolute report, and absolute host-pointer input | | `LANCE_REPORT.md` | the LANCE Ethernet model: the register file, the 8-byte address ROM and its checksum, the byte↔bit DMA, and the remaining loopback/CRC blocker | | `LANCE_LOOPBACK_REPORT.md` | `C — LANCE TEST`: the TX buffer **does** carry a correct FCS in the passing `DTCR=1` case and deliberately carries **none** in the failing one; the residual mismatch is MAME never setting `RMD1_FRAM` | +| `NVRAM_REPORT.md` | the battery-backed NVR (bit `0x6000000`): previously unmodelled (its address was swallowed by the video-memory mapping), now a persisted `nvram_device`; the font path lives there, the font data does not | | `RENDER_REPORT.md` | the active-pixel scanline renderer, and the geometry it derived | | `DUART_REPORT.md` | the DUARTs: wiring, the ISR-driven transmit, the datasheet-verified model edits | | `BRINGUP_LOG.md` | the whole bring-up narrative, **including withdrawn claims** — read it sceptically | @@ -131,6 +132,10 @@ to be a six-variable incantation; it is now the default, and a bare modelled as the eight bytes the firmware actually reads: `08 00 2b 16 4b 5a c1 86` (MAC + a checksum it validates). See `LANCE_REPORT.md`. +* The **NVR** (battery-backed settings RAM) at **bit `0x6000000`** — a persisted + `nvram_device` over the 0x800-word user-settings block, saved to MAME's nvram + directory. Before this it was unmodelled and its address was swallowed by the + video-memory mapping, so settings never persisted. See `NVRAM_REPORT.md`. A bare run gets through POST and then **fails level 0's I/O data path test**, so it lands in the interactive diagnostics menu (`VERSION 2.08 - SELECTED TEST TYPE:`) diff --git a/harness/mame/vt1200.cpp b/harness/mame/vt1200.cpp index 41b25d1..617a98c 100644 --- a/harness/mame/vt1200.cpp +++ b/harness/mame/vt1200.cpp @@ -195,6 +195,7 @@ #include "emupal.h" #include "machine/am79c90.h" #include "machine/mc68681.h" +#include "machine/nvram.h" #include "screen.h" #include @@ -647,6 +648,7 @@ public: , m_mouse(*this, "mouse") , m_tablet(*this, "tablet") , m_lance(*this, "lance") + , m_nvram(*this, "nvram") , m_vram(*this, "vram") { } @@ -740,6 +742,12 @@ private: // main-RAM word B>>1. See the note there. required_device m_lance; + // Battery-backed NVRAM ("NVR" in the service guide): the OS keeps its + // user settings / font path / IP address here. A plain 16-bit RAM at bit + // 0x6000000 (byte 0xC00000), persisted by MAME's nvram_device through the + // shared region named "nvram". See NVRAM_REPORT.md. + required_device m_nvram; + // Ethernet address ROM: bit 0x6800000 (physical byte 0xD00000). The // firmware reads EIGHT bytes, one per 16-bit step: // movi 0x6800000,a11 / ... / movb *a7(0x0),*a11(0x1b0) / addk 16,a7 @@ -1123,6 +1131,18 @@ void vt1200_state::program_map(address_map &map) // (addk 16,a7), read with movb (low byte of the word -- see lance_rom_r): // six MAC bytes then a two-byte checksum, so eight 16-bit words. map(0x6800000, 0x6800000 + 0x007f).r(FUNC(vt1200_state::lance_rom_r)); + + // Battery-backed NVRAM at bit 0x6000000 (byte 0xC00000). The service guide + // calls it NVR ("stores the user's customized settings, including group + // codes, host name or IP address, font paths, and the terminal IP + // address"). The firmware's validate/save routines (bit 0x3F4ABE50 and + // 0x3F4AC880) copy a 0x800-byte settings block between RAM 0x1a0020 and + // this device, one settings byte per 16-bit word, so 0x800 words covers + // it. Registered AFTER the video-memory mapping -- which spans + // 0x04000000..0x1fffffff and would otherwise swallow this address -- so + // the NVRAM wins. MAME's nvram_device persists it via the "nvram" share + // (see NVRAM_REPORT.md). + map(0x6000000, 0x6007fff).ram().share("nvram"); } // MAME may hand us either an entry-relative offset (0..0xffff) or the absolute // address; BOARD_BASE is 0x10000-aligned, so ORing the low 16 bits is correct @@ -1697,6 +1717,12 @@ void vt1200_state::vt1200(machine_config &config) // whatever MAME's default palette held (reported: red). Two entries suffice. PALETTE(config, m_palette, palette_device::MONOCHROME).set_entries(2); + // Battery-backed NVRAM. DEFAULT_ALL_1 models an erased/blank NVR; the OS + // then reports "User settings invalid" once and writes its defaults, and + // from then on the region is saved to (and reloaded from) MAME's nvram + // directory -- i.e. settings survive a restart, not just a reset. + NVRAM(config, "nvram", nvram_device::DEFAULT_ALL_1); + // The two Signetics SCN2681 DUARTs (same family as the Motorola MC68681: // 2681 = RD/WR strobes, 68681 = R/W + DTACK + interrupt vector). The // VT1200 firmware drives them with the generic 68681 register map, and the