mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-11-01 12:14:56 +02:00
206 lines
8.1 KiB
Python
Executable File
206 lines
8.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""vidstream_check.py -- did the row-strip blit put every source row on screen?
|
|
|
|
Task 5's pixel check. The Lua side (harness/mame/vidstream.lua, VT1200_MODE=draw)
|
|
injects ONE strip frame into the payload's slot, waits for the payload to accept
|
|
and blit it, then dumps three files:
|
|
|
|
vram-draw.bin the screen as memory: 1024 rows of 256 bytes from bit 0x04000000
|
|
frame-draw.bin the bytes actually poked into the slot (16-byte header + payload)
|
|
draw-status.txt key=value lines (RX_OK, RX_BAD, OS_TOUCH, SENTINEL, TIMEOUT, ...)
|
|
|
|
A run whose wait for the payload expired writes TIMEOUT=1, and that is reported
|
|
as a timeout -- not as a screen full of wrong pixels, which would blame the
|
|
blit for a frame the payload never accepted (ruling R15). The canary word is
|
|
also asserted (0xA5C3), so a payload that stopped arming it is a failure rather
|
|
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.
|
|
|
|
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
|
|
screen. The pattern itself is checked too, because a frame whose rows are all
|
|
identical would let a blit that always read row 0 pass.
|
|
|
|
usage:
|
|
vidstream_check.py [--vram PATH] [--frame PATH] [--status PATH]
|
|
[--rows 128] [--stretch 8]
|
|
|
|
Exit status 0 only if every check passes.
|
|
"""
|
|
import argparse
|
|
import os
|
|
import sys
|
|
|
|
HEADER_SIZE = 16
|
|
ROW_BYTES = 160 # 1280 pixels, 1 bpp
|
|
VRAM_PITCH = 256 # bytes per screen row (2048 bits)
|
|
SCREEN_ROWS = 1024
|
|
ZERO_ROW_MOD = 3 # rows with r % 3 == 2 are the all-zero rows
|
|
ZERO_ROW_REM = 2
|
|
FILL_BYTE = 0xF0 # rows that are not zero are 0xF0F0 words -> 0xF0 bytes
|
|
CANARY = 0xA5C3 # SENT_VALUE in vidstream.c, at row 1023 / column 1280
|
|
|
|
failures = 0
|
|
|
|
|
|
def check(cond, what):
|
|
global failures
|
|
if not cond:
|
|
failures += 1
|
|
print("FAIL: " + what)
|
|
return cond
|
|
|
|
|
|
def read_file(path):
|
|
"""The whole file, or None with a diagnosable line instead of a traceback."""
|
|
try:
|
|
with open(path, "rb") as f:
|
|
return f.read()
|
|
except OSError as e:
|
|
print("vidstream_check: cannot read %s: %s" % (path, e))
|
|
return None
|
|
|
|
|
|
def read_status(path):
|
|
st = {}
|
|
try:
|
|
with open(path, "r", encoding="ascii", errors="replace") as f:
|
|
for line in f:
|
|
if "=" in line:
|
|
k, v = line.strip().split("=", 1)
|
|
try:
|
|
st[k] = int(v, 0)
|
|
except ValueError:
|
|
st[k] = v
|
|
except OSError as e:
|
|
print("vidstream_check: cannot read %s: %s" % (path, e))
|
|
return None
|
|
return st
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
d = os.environ.get("VID_DUMP_DIR", "/tmp/opencode")
|
|
ap.add_argument("--vram", default=os.path.join(d, "vram-draw.bin"))
|
|
ap.add_argument("--frame", default=os.path.join(d, "frame-draw.bin"))
|
|
ap.add_argument("--status", default=os.path.join(d, "draw-status.txt"))
|
|
ap.add_argument("--rows", type=int, default=128)
|
|
ap.add_argument("--stretch", type=int, default=8)
|
|
args = ap.parse_args()
|
|
|
|
vram = read_file(args.vram)
|
|
frame = read_file(args.frame)
|
|
status = read_status(args.status)
|
|
if vram is None or frame is None or status is None:
|
|
return 2
|
|
|
|
print("vidstream_check: vram %s (%d B) frame %s (%d B) status %s"
|
|
% (args.vram, len(vram), args.frame, len(frame), args.status))
|
|
|
|
# --- a timed-out wait is not a pixel verdict (ruling R15) -------------
|
|
if status.get("TIMEOUT", 0) != 0:
|
|
print("FAIL: timed out waiting for the frame (RX_OK=%r, "
|
|
"RX_DGRAM=%r: the payload did not finish with the frame)"
|
|
% (status.get("RX_OK"), status.get("RX_DGRAM")))
|
|
print("vidstream_check: FAIL (timeout)")
|
|
return 1
|
|
|
|
# --- the frame that was delivered ------------------------------------
|
|
rows, stretch = args.rows, args.stretch
|
|
if not check(stretch > 0, "stretch must be positive, got %d" % stretch):
|
|
return 1
|
|
if not check(len(frame) == HEADER_SIZE + rows * ROW_BYTES,
|
|
"frame is %d bytes, expected 16 + %d*%d = %d"
|
|
% (len(frame), rows, ROW_BYTES, HEADER_SIZE + rows * ROW_BYTES)):
|
|
return 1
|
|
if not check(frame[0:2] == b"VS" and frame[2] == 1 and frame[3] == 1,
|
|
"delivered frame is not a version-1 KIND_STRIP frame"):
|
|
return 1
|
|
check(frame[8] == rows, "header rows=%d, expected %d" % (frame[8], rows))
|
|
check(frame[9] == stretch, "header stretch=%d, expected %d"
|
|
% (frame[9], stretch))
|
|
plen = frame[6] | (frame[7] << 8)
|
|
check(plen == rows * ROW_BYTES,
|
|
"header payload_len=%d, expected %d" % (plen, rows * ROW_BYTES))
|
|
check(rows * stretch == SCREEN_ROWS,
|
|
"rows*stretch = %d, the screen has %d rows" % (rows * stretch,
|
|
SCREEN_ROWS))
|
|
|
|
payload = frame[HEADER_SIZE:]
|
|
|
|
# --- the pattern is distinctive per row class -------------------------
|
|
zero_rows = 0
|
|
for r in range(rows):
|
|
row = payload[r * ROW_BYTES:(r + 1) * ROW_BYTES]
|
|
if r % ZERO_ROW_MOD == ZERO_ROW_REM:
|
|
zero_rows += 1
|
|
check(row == bytes(ROW_BYTES),
|
|
"source row %d should be all zero" % r)
|
|
else:
|
|
check(row == bytes([FILL_BYTE]) * ROW_BYTES,
|
|
"source row %d should be 0x%02X bytes" % (r, FILL_BYTE))
|
|
print("vidstream_check: source %d rows x %d B, stretch %d, %d zero rows"
|
|
% (rows, ROW_BYTES, stretch, zero_rows))
|
|
|
|
# --- the screen ------------------------------------------------------
|
|
if not check(len(vram) == SCREEN_ROWS * VRAM_PITCH,
|
|
"vram dump is %d B, expected %d"
|
|
% (len(vram), SCREEN_ROWS * VRAM_PITCH)):
|
|
return 1
|
|
|
|
matched = 0
|
|
bad = []
|
|
for y in range(SCREEN_ROWS):
|
|
r = y // stretch
|
|
want = payload[r * ROW_BYTES:(r + 1) * ROW_BYTES]
|
|
got = vram[y * VRAM_PITCH:y * VRAM_PITCH + ROW_BYTES]
|
|
if got == want:
|
|
matched += 1
|
|
else:
|
|
bad.append((y, r, want, got))
|
|
for y, r, want, got in bad[:8]:
|
|
print("vidstream_check: screen row %d (source row %d): "
|
|
"expected %s, got %s"
|
|
% (y, r, want[:16].hex(), got[:16].hex()))
|
|
|
|
print("vidstream_check: %d/%d screen rows match" % (matched, SCREEN_ROWS))
|
|
check(matched == SCREEN_ROWS,
|
|
"%d screen rows do not carry their source row"
|
|
% (SCREEN_ROWS - matched))
|
|
|
|
# --- the payload's own record ---------------------------------------
|
|
rx_ok = status.get("RX_OK")
|
|
rx_bad = status.get("RX_BAD")
|
|
os_touch = status.get("OS_TOUCH")
|
|
canary = status.get("SENTINEL")
|
|
check(rx_ok == 1, "RX_OK=%r, expected 1 accepted frame" % (rx_ok,))
|
|
check(rx_bad == 0, "RX_BAD=%r, expected 0 rejected frames" % (rx_bad,))
|
|
check(os_touch == 0,
|
|
"OS_TOUCH=%r: something else wrote to the sentinel corner of VRAM"
|
|
% (os_touch,))
|
|
# A missing canary (a payload that never armed it, or a status file from
|
|
# an older payload) must fail loudly: 0 was the firmware's RAM pattern, so
|
|
# "not written" and "correct" would otherwise look alike.
|
|
check(canary == CANARY,
|
|
"canary word is %r, expected 0x%04X (SENT_VALUE in vidstream.c)"
|
|
% (canary, CANARY))
|
|
print("vidstream_check: RX_OK=%r RX_BAD=%r OS_TOUCH=%r canary=%s"
|
|
% (rx_ok, rx_bad, os_touch,
|
|
("0x%04X" % canary) if isinstance(canary, int) else repr(canary)))
|
|
|
|
if failures:
|
|
print("vidstream_check: FAIL (%d checks)" % failures)
|
|
return 1
|
|
print("vidstream_check: PASS")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|