Commit Graph
222 Commits
Author SHA1 Message Date
arti 650ec9a0cd spec: Bad Apple at 60 fps exact — raw tagged tiles, no dictionary 2026-10-31 21:54:48 +02:00
arti d6aa6f46c1 docs(badapple): how the clip data is prepared, with the measured costs
The setup section said 'a clip cache' without saying where a clip comes from,
what it costs, or how to tell the result is sane.  Adds a Data preparation
section: the public sources (and why an upscale is not better), the frame
extraction (3,292 frames, 489 MB, ~12 s, LSB-first), the cache build (~2 min
47 s, four files, 6.1 MB), and the make_tiles summary to compare against --
including that a saturated 256-entry dictionary and 24 dB PSNR are expected, not
a failure.
2026-10-31 21:27:09 +02:00
arti 8f831eaf33 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.
2026-10-31 20:53:51 +02:00
arti eabc3051be feat(badapple): the demo as a tool -- rdebug delivery, clip builder, one command
A new folder so the demo can be reproduced instead of re-derived: the delivery
and the clip packing were living in throwaway scripts under /tmp, and the only
written route to a real clip was an ffmpeg recipe that packs MSB first -- the
order that mirrors every eight pixels on a display.

  harness/badapple/rdebug_load.py    image -> terminal RAM over the firmware's
                                     own remote-debug service (TCP 771), then
                                     jump to the entry from vidstream.env
  harness/badapple/clip_from_video.py video -> LSB-first 1-bpp frames for
                                     make_tiles.py --clip
  harness/badapple/demo.sh           ICMP gate, load, send; one command
  harness/badapple/README.md         setup, run in MAME, run on real hardware,
                                     what success looks like, failure modes

The loader records the two protocol facts that cost time to find: a 512-byte
poke chunk (1024 is mis-parsed as a following opcode: "Invalid command
0xA5A5A5A5"), and that nothing can be probed after the jump, so the image is
read back BEFORE jumping and the run is observed from the machine afterwards.

Tests cover what a wrong assumption would silently corrupt -- chunk arithmetic,
the linkage the entry point is read from, and pixel 0 landing on bit 0 -- and
the tool was exercised end to end against MAME: demo.sh loaded the payload and
the terminal reported DICT_BYTES=32768, DICT_BAD=0, RX_OK=28, TILES_DROP=0
while the sender saw frames_rx=129, drops=0.

153 tests pass (7 new).
2026-10-31 20:20:35 +02:00
arti f7490d2e09 feat(opencode): add tms34010 agent with the whole User's Guide preloaded
Subagent/primary agent whose system prompt inlines docs/TMS34010-users-guide.txt
(SPVU001A, 1.75 MB, ~361k tokens) via {file:} substitution, so whole-book
questions -- cross-chapter synthesis, complete opcode lists, encoding and
timing tables -- are answered from the text in one pass.

Read-only (edit and subagent denied) and keeps read/shell, so it can render a
page with pdftoppm when the OCR text layer is mangled: section 12 prints XY as
XV and checkmarks as 'J'.
2026-10-31 20:03:06 +02:00
arti 9af7b4fb8d fix(rx): release descriptors individually instead of flipping the whole ring
The receive loop re-armed all 16 descriptors into the other slot after every
completed frame.  During a burst that re-pointed descriptors the chip had
ALREADY filled but the consumer had not read, so those frames were lost without
a trace: the LANCE DMA log shows all 23 dictionary datagrams written by the
chip, 1.132 ms apart (the MTU/bandwidth poll cadence), while the payload's own
counters accounted for 4 (DICT_BYTES=1448, dict_bad=3).  Nothing was dropped by
MAME; the ring was being rewritten under the frames it held.

rx_switch()/rx_armed are gone.  A descriptor is held (rx_hold) while its
datagram belongs to the frame being assembled, and rx_release() hands each one
back -- into the other slot -- only once that frame has been parsed or
abandoned.  Nothing the chip delivered is ever re-pointed unread.

Measured, same zero-gap 23-datagram dictionary upload:
  before                      dgram=4   DICT_BYTES=1448   dict_bad=3
  release fix, 16 descriptors dgram=18  DICT_BYTES=24616  dict_bad=0
  plus a 32-descriptor ring   dgram=23  DICT_BYTES=32768  dict_bad=0

The ring is now 32 descriptors because 23 datagrams is the burst the protocol
actually defines (a whole 32 KB dictionary at MAX_BODY 1448), and 16 could not
hold it while the consumer copied.  SLOT_BYTES grows to 49152 to hold the ring;
POOL_BYTES does NOT grow -- the 160 KB "framebuffer reserve" was unused slack
(the decoder paints straight from the fragments), so the extra 24,576 B per slot
comes out of it and the LANCE block keeps its fixed 0x3C000 tail.

RX_OK is now published after rx_release(), not inside the dispatch.  It is the
"frame done" signal a reader polls, and it used to become visible while
descriptors were still being rewritten: ring mode caught a descriptor holding
slot B's high byte with slot A's low word.  A reader that sees RX_OK move now
sees a painted picture and a consistent ring.

vidstream.lua's ring mode follows: it asserts the invariant that replaced the
flip -- the consumed descriptors move to the other slot, the untouched ones stay
-- instead of "all 16 in slot B".

Evidence: 146 unit tests pass; modes rx, draw, ring, tiles, delta and replay all
PASS with their pixel checkers (draw 1024/1024 rows, tiles and replay via
tile_check).  End to end over rdebug with the STOCK sender (no pacing): the
dictionary burst completes (DICT_READY=1, DICT_BYTES=32768, dict_bad=0) and
RX_OK=100 frames paint, TILES_DROP=0.
2026-10-31 19:45:34 +02:00
arti 5af949b338 fix(tiles): pack LSB first, the terminal's scanout order; render the real layout
The tile pipeline packed every row MSB first: pixel i -> bit 7 - i % 8. The
encoder and tile_check.py shared that convention, so every MAME replay check
passed while a display showed each group of eight pixels mirrored.

Evidence, from the real clip's own replay dump (vram-replay-200.bin, frame 200
of Bad Apple, 1,228,800 pixels):

  decoded MSB first   1,215 mismatched  <- what the pipeline produced
  decoded LSB first   3,113 mismatched  <- the mirrored picture, a sawtooth

and both the real unit and MAME decode LSB first: dwt/screenshot.py --real
("LSB-first bits", measured on live VRAM) and vt1200.cpp's
(vram[bit >> 4] >> (bit & 15)) & 1.  A zoomed edge of that dump showed the
comb of triangles; after this change the same edge is a smooth curve.

vidsrc.Rows.pack_row now sets bit i % 8 of byte i // 8.  The test that pins it
was written first and watched fail: it decodes the packed row with the
hardware's rule, which no MSB-first packer can pass.  The two older tests that
asserted MSB first were rewritten -- they were asserting the bug -- and the
synthetic clip now packs through a bit-reversal table, so the committed cache
is regenerated (PSNR 29.47 -> 31.30 dB).  The layout claims in tilecost.py,
vram_tile.asm and make_tiles.load_clip are corrected so the clip format states
the order instead of leaving it to whoever writes the frames.

Separately, MAME's renderer reads 128-byte rows while a payload targeting the
real unit writes 256-byte rows (vram_tile.asm DPTCH=0x800; c-hello/README.md:
"a payload writing at row*2048 bits lands on scanline row").  VT1200_VRAM_REAL=1
now draws the framebuffer at 2048 bits / 1280 px from x=0.  Off by default: the
Level-1 error text is only legible at the derived 128-byte pitch, and that is
what the faithful boot needs.  The branch only touches the bitmap, so the
regression baseline is unmoved (PROG_FRAMES=200 -> PC=3FDCE400, samples=198,
3 distinct PCs), and the default frame-3000 render of the Terminal Manager is
unchanged.

Verified end to end on the 3,292-frame Bad Apple clip: RX_OK=3196, TILES_DROP=0,
3292/3292 frames dumped and matched by tile_check.py, and the dumps decode to
1,379 / 4,125 mismatched pixels of 1,228,800 (quantisation) under the hardware
rule versus 3,101 / 9,793 under MSB.  MAME's own frame dumps with
VT1200_VRAM_REAL=1 show the clip unsheared.

Revert note for the driver: delete the m_render_real branch and its getenv and
vt1200.cpp returns to the measured 128-byte renderer byte for byte.  No second
reader reviewed that citation; it is quoted in the comment for the next one.
2026-10-31 18:06:14 +02:00
opencode.energia.arti.ee 173f3bebda plan: record the outstanding unit/soak work as the only remainder 2026-10-31 08:56:33 +00:00
opencode.energia.arti.ee 10bdd1a73c spec: delta wire form is grouped; status cadence has out-of-cadence reports 2026-10-31 03:37:21 +00:00
opencode.energia.arti.ee 75f4d35733 docs: delta wire form and status cadence; require four status frames 2026-10-31 03:37:16 +00:00
opencode.energia.arti.ee a84f5f2811 fix(tiles): early status on dictionary failure, decode-time field, chunk bound 2026-10-31 03:35:14 +00:00
opencode.energia.arti.ee b995c2caaa docs: correct the tile-stream wire overhead and test counts 2026-10-31 03:11:22 +00:00
opencode.energia.arti.ee 79e27680c9 feat(tiles): bad apple tile stream end to end 2026-10-31 03:06:04 +00:00
opencode.energia.arti.ee b331b90c39 test(tiles): multi-datagram delta case and stricter block diff 2026-10-31 02:41:04 +00:00
opencode.energia.arti.ee 8f8454461d feat(tiles): delta frames and periodic keyframes 2026-10-31 02:38:40 +00:00
opencode.energia.arti.ee 2fa232ba56 feat(tiles): 32x32 PIXBLT B,XY tile blit and full-frame paint 2026-10-31 02:20:50 +00:00
opencode.energia.arti.ee 0b726fda3a feat(tiles): dictionary storage and kind dispatch in the payload
The payload assembles KIND_DICT_CHUNK frames into a 32 KB dictionary at
POOL_PTR + 2 * SLOT_BYTES and learns the tile kinds:

* frame_parse.h gains FP_KIND_TILES_FULL (2), FP_KIND_TILES_DELTA (3) and
  FP_KIND_DICT_CHUNK (4), exactly frame.py's KINDS table, plus the marker
  cells DICT_READY/DICT_BYTES/DICT_BAD and TILES_DROP.  A host test parses
  the header and compares it with KINDS so the two cannot drift apart.
* dict_chunk() enforces an ascending, contiguous offset (no gap, overlap,
  duplicate or reorder; a mid-entry offset only as a continuation), a chunk
  that does not overrun the last entry, and no chunk after DICT_READY.  A
  refused chunk leaves the prefix alone.  Readiness is exactly 32768 bytes
  (256 whole entries), so a short upload never becomes ready.
* The dispatcher routes STRIP to the blit, DICT_CHUNK to the assembler, and
  refuses TILES_FULL/TILES_DELTA without painting, counting them in
  TILES_DROP (Task 4 adds the tile blit).
* A compile-time assertion refuses a POOL_BYTES that cannot hold the
  dictionary; the 1987 preprocessor does not fold macro arithmetic in #if,
  so it is a defined array whose negative length fails the build.
* VT1200_MODE=dict injects both tile kinds before the dictionary, 23
  contiguous chunks with a duplicate offset, a gap and an overrunning tail
  mixed in, and reads the stored 32 KB back byte for byte.
2026-10-31 01:55:06 +00:00
opencode.energia.arti.ee 84349d6da3 feat(tiles): chunked dictionary upload protocol 2026-10-31 01:35:22 +00:00
opencode.energia.arti.ee d3b2e59be6 spec: dictionary entry stride is 1024 bits, not 128 2026-10-31 01:33:42 +00:00
opencode.energia.arti.ee a6e4ac44ad feat(tiles): deterministic dictionary builder and quantiser 2026-10-31 01:32:02 +00:00
opencode.energia.arti.ee cd4dbe2317 plan: tile codec (scheme C) — dictionary, tile blit, deltas, bad apple 2026-10-31 01:28:05 +00:00
opencode.energia.arti.ee 22c539bf74 fix(stream): ring ownership window, FCS-error accounting, unit loader procedure 2026-10-31 01:25:14 +00:00
opencode.energia.arti.ee 9917c5659a fix(stream): delivery metric normalised to the status frame counter 2026-10-31 01:08:54 +00:00
opencode.energia.arti.ee 49a943583c feat(stream): real rx ring over chained descriptors; unit measurement script 2026-10-31 01:03:59 +00:00
opencode.energia.arti.ee 7eb7b61234 plan: task 7 carries the real rx ring and the unit measurement 2026-10-31 00:31:12 +00:00
opencode.energia.arti.ee 53b1580abd feat(stream): paced sender and terminal status frames 2026-10-31 00:26:26 +00:00
opencode.energia.arti.ee 5cd708460a plan: align the remaining CRC text with R14 (header-only) 2026-10-31 00:01:57 +00:00
opencode.energia.arti.ee 16148d0d24 fix(stream): header-only frame CRC; draw-mode evidence guards
Ruling R14: the frame CRC now covers header bytes 0..11 only.  A
payload-wide CRC cost the terminal ~139 instructions per payload byte
(the parser, not the wire or the blitter, was the frame-rate ceiling),
while every datagram already carries an Ethernet FCS the LANCE checks
and the length bound catches truncation.  frame.py, fp_parse and the
harness's own frame signer follow the new rule; a corrupted payload
byte is now ACCEPTED and a corrupted header byte rejected, stated
explicitly in both test suites, and the C golden vectors were
regenerated from frame.py.

Measured after: RX_READY_A write -> first RX_OK read is 1 emulated
frame for a 20,480-byte frame (84 before), which is the frame hook's
resolution.

Ruling R15: draw mode deletes its dump files before the run (wrapper
and Lua) so a failed run cannot be judged on the previous screen; a
wait that hits its cap reports "timed out waiting for the frame" and
the checker reports it as a timeout, not as pixel mismatches; the
checker asserts the VRAM canary word is 0xA5C3, so a payload that
stopped arming it fails instead of passing quietly.

rx mode now corrupts a header byte in slot B, because a payload flip is
accepted by design under R14.
2026-10-30 23:59:20 +00:00
opencode.energia.arti.ee 5e9b2e9c57 spec+plan: header-only frame CRC (the body is covered by the Ethernet FCS) 2026-10-30 23:50:35 +00:00
opencode.energia.arti.ee 45110270a7 feat(stream): row-strip blit with SPTCH=0 vertical stretch
One PIXBLT B,XY per source row paints the whole screen: DYDX names
`stretch` rows and SPTCH = 0 makes each of them read the same packed
source row, so the vertical stretch costs no extra CPU work.

vram_b.asm carries the primitive (PSIZE=1, CONVSP/CONVDP=0x14,
PMASK=0, OFFSET=0x04000000, DPTCH=0x800, raster op 0) plus DINT/EINT
thunks; vidstream.c masks interrupts for the burst (spec 8.9), refuses
geometry the screen cannot hold, and counts an OS repaint through a
sentinel word in the unused right edge of the last screen row
(OS_TOUCH, 0x01810220).

MAME mode draw injects a 128-row frame (stretch 8 = 1024 screen rows,
ruling R3) and dumps the screen; vidstream_check.py compares all 1024
rows against the bytes actually delivered.  RED: 0/1024 rows matched.
GREEN: 1024/1024, RX_OK=1, OS_TOUCH=0.

Fixes a latent harness bug found on the way: `bpclear <address>` takes
a breakpoint INDEX, so the idle-loop breakpoint was never cleared; once
a burst masked interrupts long enough, its stale action fired again and
re-entered main (a second malloc, counters back to 0).
2026-10-30 23:43:02 +00:00
opencode.energia.arti.ee 487bb1789a feat(stream): frame parser shared by host tests and payload 2026-10-30 23:16:48 +00:00
opencode.energia.arti.ee 213083f2e0 plan: task 4 verified by host unit tests (the driver has no network interface) 2026-10-30 22:52:46 +00:00
opencode.energia.arti.ee 18b496146b fix(stream): absolute payload path; pool must be word-aligned and below the payload 2026-10-30 22:47:02 +00:00
opencode.energia.arti.ee 4f6b0bf1a6 feat(stream): payload skeleton with one startup malloc 2026-10-30 22:40:23 +00:00
opencode.energia.arti.ee 1a68630534 feat(stream): 1-bpp row packing and strip frame encoder 2026-10-30 22:21:24 +00:00
opencode.energia.arti.ee 4122ebe616 plan: task 2 must pass the strip payload into the CRC 2026-10-30 22:18:37 +00:00
opencode.energia.arti.ee 11375a1323 fix(stream): seal_header rejects over-length input; payload_len must match payload 2026-10-30 22:17:48 +00:00
opencode.energia.arti.ee 16a52bb853 feat(stream): frame header and CRC16 shared by both ends 2026-10-30 22:15:18 +00:00
opencode.energia.arti.ee be1e8b28a7 gitignore: mame scratch dirs written into the repo root (cfg/, nvram/, roms/) 2026-10-30 21:59:42 +00:00
opencode.energia.arti.ee f29ee5db2d plan: streaming transport + row strips (M1/M2), tasks 1-7 2026-10-30 21:58:14 +00:00
opencode.energia.arti.ee eba6165a19 spec: VT1200 ethernet video streaming (transport + tile codec), rev 3 2026-10-30 21:57:30 +00:00
arti 1d8656ca10 udpstream: PIXBLT L,XY blit primitive (vram.asm) and a 256 KB .bss
vram_blit_ copies a 1-bpp rectangle from RAM to VRAM.  The manual has no L,L
variant, so the destination is XY and the hardware forms the address:
  B0=SADDR  B1=SPTCH  B2=DADDR(XY)  B3=DPTCH=0x800  B4=OFFSET=0x04000000
  B7=DYDX   PSIZE=1  PMASK=0  CONVSP=CONVDP=0x14  CONTROL raster-op=0
SPTCH is a parameter because 0 makes every row read the same source address,
which is how one expanded row can be stamped eight times down the screen.

The .bss region in the linker command grows from 0x40000 to 0x200000 address
units (32 KB -> 256 KB) so a 1280x1024 1-bpp frame buffer (163840 B) fits; the
previous .coff had been built from sources that were not yet committed.

bench-sweep.sh gains its executable bit.
2026-10-30 17:44:27 +02:00
arti 636b3d6985 udpstream: the CPU store path costs ~14 us/store regardless of destination
Measured on the unit: byte stores to DRAM 13.92 us, to VRAM 13.93 us -- the
same, so the display arbiter is not involved.  In MAME 16-bit stores halve the
time exactly (863 ms vs 1588 ms per frame), i.e. the cost is per store
instruction, not per byte.

Against the graphics engine (FILL L 0.40 us/word, PIXBLT L,XY 0.62 us/word)
the CPU is 25-35x slower per word.  So frames must never be built with CPU
stores: the LANCE can chain descriptors so a 160 KB frame lands contiguously
in a pre-armed buffer with no CPU copy, and one PIXBLT L,XY puts it on screen.

Budget: full frame ~5 fps (129 ms wire + 60 ms blit), 640x512 ~21 fps,
320x240 ~80 fps.  Report section 75; cmd 10 (PIXBLT from RAM) and cmd 11
(16-bit store control) are the benchmarks.
2026-10-30 17:42:22 +02:00
arti 9b3f02cf02 udpstream: DRAM benchmark writes to a .bss buffer, not a guessed address
cmd 9's first version wrote to 0x01A10000 (bits), chosen as "above MARK, below
.bss".  On the unit that was not usable RAM: no reply in 120 s and the machine
stopped answering ping and rdebug -- one power cycle.

The target is now `static unsigned char drambuf[512]`, placed by the linker
inside .bss, which the payload already relies on for its labels and strings.
Loop and store count are unchanged, so the per-store figure stays comparable.

Report 74.5 records it.
2026-10-30 17:39:27 +02:00
arti fb095eb778 udpstream: alternating-frame flash (30.7 fps sustained) + DRAM store benchmark + ROM cross-check
cmd 8 alternates 0xFFFF/0x0000 per frame so the rate is visible: 300 frames
in 9.775 s on the unit, 32.6 ms each, 30.69 fps sustained.

cmd 9 is the same byte-store loop aimed at a DRAM buffer, to measure the CPU's
store rate without the VRAM arbiter and size a frame-expansion stage.
vtcmd.py --dram selects it.

Report 74.3 records the independent Ghidra pass over the ROM: the firmware
sets CONVSP/CONVDP=0x14 (pitch 0x800 bits = 256 B), PSIZE=1, PMASK=0 and
OFFSET=0x04000000 for XY addressing -- the same values vram.asm uses -- and
fills with FILL_L chunked at 0x7FC0 pixels because DYDX is 15 bits per axis.
There is no VBLT and no wait-state/arbitration register in the ROM, so the
12 us/byte CPU store cost is inherent to this board, not a setting.
2026-10-30 17:34:47 +02:00
arti d99a9cdb6c udpstream: hardware FILL L makes full-screen writes 70x faster (30.6 fps on the unit)
Byte stores to VRAM measure 2284 ms per 1280x1024 frame on the real unit
(0.44 fps) -- about a scanline period per byte, i.e. almost no access through
the arbiter.  vram.asm now drives the GSP's array engine instead: FILL L with
DADDR/DPTCH/DYDX/COLOR1 (B2/B3/B7/B9), PSIZE=1, PMASK=0 and PPOP=0, per the
TMS34010 User's Guide's implied-operand table, with CONTROL read-modify-written
so CD/RM/RR survive.

Measured: 32.7 ms/frame, 30.6 fps on the unit; 27.1 ms in MAME (74x over byte
stores there, 70x on hardware).  2,508,224 destination words/s = 0.40 us/word.

Consequence for video: a full uncompressed frame is wire-limited at 8 fps
(163840 B at 1.27 MB/s); a small source expanded on the device is limited by
the ~33 ms transfer, i.e. ~30 fps full screen if the expansion uses PIXBLT.

build-lance.sh assembles vram.asm and links VRAM.obj; MARK moved to 0x01A00000
because .text had come within 146 bytes of it.
2026-10-30 17:32:59 +02:00
arti 1571328a6b NETWORK_REPORT: osboot 3 of 3 on the unit; scripted hand-back is 7 s load + 40 s restart 2026-10-30 17:20:28 +02:00
arti ed185279ca udpstream: the payload announces itself; test-osboot.sh has no sleep
Loading the COFF over rdebug pokes thousands of words one at a time, so how
long the payload takes to appear varies by tens of seconds -- a fixed sleep is
either wasteful or too short.  The payload now broadcasts three frames the
moment its ring is armed: ethertype 0x88B6, 'K' 'S', opcode 0xFF, magic
"LANC".  `vtcmd.py wait` listens for it; measured at 8.7 s in MAME.

Two details found by measurement:

  * The announce must use TX slot 0, because the chip transmits only from its
    ring position -- arming slot 7 while the position is 0 never sends
    anything (the first attempt did exactly that and was silently lost).
  * After the announce the reply cursor advances to slot 1 to match the chip's
    new position, so replies do not collide with the announce descriptor.
    txfull then stays 0 across consecutive replies; before this it read 1.

test-osboot.sh ties the hand-back together with no sleep: listen, load, wait
for the announce, stats, osboot, wait for :771.  Report sections 73.3/73.4.
2026-10-30 17:19:28 +02:00
arti f74bd7ae05 NETWORK_REPORT: osboot works on the unit -- 40 s, Terminal Manager back
osboot at 17:11:21, rdebug back at 17:12:01 (40 s), ping OK, and the VRAM
screenshot shows the Terminal Manager with a live display, the right MAC/IP/
LAT name and the clean-boot banner.

Same machine, same payload as the two failed reset-vector attempts: reboot
0/2 (POST, never returns), osboot 1/1 (OS cold start, 40 s).  The display
returns before the network -- the operator could see the screen at 32 s while
port 771 was still closed.

Recorded as 1 of 1, not as reliable: section 72.3 already lists three
conclusions drawn from single observations, so a second run is needed before
this is the standard hand-back.
2026-10-30 17:12:41 +02:00
arti baffb8292e udpstream: osboot -- jump to the OS cold start (0x3F5788C0), the path that quietens the LANCE
The reset vector is the wrong entry: it starts POST, which re-programs the
board window from a state a payload has already changed.  The firmware has a
second entry, 0x3F5788C0, where the boot gate at 0x3F2042D0 branches when it
skips level 1.  It begins with DINT, CSR0=STOP on the LANCE, and a board-window
re-program -- exactly what a takeover breaks.

MAME, from the LANCE access log: the payload's last access and the OS's first
are 130 us apart, then pc=3F578940 writes CSR0=0x0004 (STOP) and pc=3FDDCAE0
(the OS driver) re-initialises the chip, after which the OS keeps servicing it.

cmd 6 / vtcmd.py osboot.  Unit run pending; the report section is 73.
2026-10-30 17:10:45 +02:00