forked from arti/vt1200
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).
This commit is contained in:
1 parent
487bb1789a
commit
45110270a7
6 files changed
+670
-18
No files matched your search
@@ -4,17 +4,24 @@
|
||||
# harness/mame/run-vidstream.sh # build, then mode alloc
|
||||
# BENCH=0 harness/mame/run-vidstream.sh # use the existing .bin
|
||||
# VT1200_MODE=rx harness/mame/run-vidstream.sh # alloc + the receive path
|
||||
# VT1200_MODE=draw harness/mame/run-vidstream.sh # alloc + rx + the blit
|
||||
# STREAM_POOL_BYTES=0x7FFF000 harness/mame/run-vidstream.sh # force fallback
|
||||
#
|
||||
# Mode rx injects one valid frame and one corrupted frame with the debugger and
|
||||
# asserts RX_OK == 1 && RX_BAD == 1 (see vidstream.lua and the task-4 report).
|
||||
#
|
||||
# Mode draw injects one 128-row strip frame (stretch 8 = 1024 screen rows), lets
|
||||
# the payload blit it, and dumps the screen and the delivered frame; this script
|
||||
# then runs harness/mame/vidstream_check.py, which compares every screen row
|
||||
# against its source row. The pixel verdict is the checker's, not the Lua's.
|
||||
#
|
||||
# The run uses VT1200_SKIP_LEVEL1=1: the payload's heap is only initialised in
|
||||
# the OS phase. In the diagnostics menu the free counter (0x1E8BA0) and the
|
||||
# free-list head (0x1E8AC0) both read 0, so the malloc success path cannot be
|
||||
# tested there. The skip is a documented divergence (harness/mame/README.md).
|
||||
#
|
||||
# Exit status: 0 only if vidstream.lua printed "VID: RESULT PASS".
|
||||
# Exit status: 0 only if vidstream.lua printed "VID: RESULT PASS" and, in draw
|
||||
# mode, vidstream_check.py passed too.
|
||||
set -eu
|
||||
|
||||
HERE=$(cd "$(dirname "$0")" && pwd)
|
||||
@@ -57,6 +64,7 @@ set +e
|
||||
VT1200_SKIP_LEVEL1=1 \
|
||||
VT1200_MODE="$MODE" \
|
||||
VID_PAYLOAD="$VID_PAYLOAD" \
|
||||
VID_DUMP_DIR="$LOGDIR" \
|
||||
VID_POOL_BYTES="$POOL_BYTES" \
|
||||
VID_STATIC_PTR="$STATIC_PTR" \
|
||||
VID_ENTRY_PTR="$ENTRY_PTR" \
|
||||
@@ -79,4 +87,20 @@ if ! grep -q '^VID: RESULT PASS' "$LOG"; then
|
||||
echo "run-vidstream.sh: check failed (log $LOG)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The pixel comparison is a separate, independent reading of the geometry
|
||||
# (vidstream_check.py); a transport pass with the wrong picture is a failure.
|
||||
if [ "$MODE" = "draw" ]; then
|
||||
set +e
|
||||
python3 "$HERE/vidstream_check.py" \
|
||||
--vram "$LOGDIR/vram-draw.bin" \
|
||||
--frame "$LOGDIR/frame-draw.bin" \
|
||||
--status "$LOGDIR/draw-status.txt"
|
||||
STATUS=$?
|
||||
set -e
|
||||
if [ "$STATUS" -ne 0 ]; then
|
||||
echo "run-vidstream.sh: pixel check failed (log $LOG)" >&2
|
||||
exit "$STATUS"
|
||||
fi
|
||||
fi
|
||||
exit 0
|
||||
+206
-7
@@ -25,6 +25,15 @@
|
||||
-- is the accepted frame's payload_len, that RX_SLOT names slot A, and that the
|
||||
-- payload cleared both ready flags.
|
||||
--
|
||||
-- Mode "draw" (Task 5) is the pixel check. It injects ONE valid 128-row strip
|
||||
-- frame (stretch 8 -- exactly 1024 screen rows, ruling R3) whose rows are a
|
||||
-- known pattern, waits for the payload to accept and blit it, then dumps the
|
||||
-- screen (1024 rows x 256 bytes from bit 0x04000000), the frame bytes it poked,
|
||||
-- and the payload's counters. The comparison is harness/mame/vidstream_check.py's
|
||||
-- job: this Lua does not compare pixels, so a blit that paints the wrong rows
|
||||
-- cannot be blessed here. No blit exists in the payload before Task 5, which is
|
||||
-- why this mode fails on a Task 4 tree.
|
||||
--
|
||||
-- Why injection and not a real Ethernet frame: harness/mame/vt1200.cpp never
|
||||
-- creates a device_network_interface, so the emulated LANCE is not attached to
|
||||
-- the host at all (ruling R11). Wiring it would be a MAME change, which
|
||||
@@ -38,9 +47,14 @@
|
||||
-- including the RX_* cells below, is in harness/udpstream/frame_parse.h.
|
||||
--
|
||||
-- Env:
|
||||
-- VT1200_MODE alloc (default) or rx
|
||||
-- VT1200_MODE alloc (default), rx or draw
|
||||
-- VID_AT load/entry frame (default 2400, the OS phase)
|
||||
-- VID_END inspection/exit frame (default VID_AT + 60)
|
||||
-- VID_DUMP_DIR where draw mode writes vram-draw.bin / frame-draw.bin /
|
||||
-- draw-status.txt (default /tmp/opencode)
|
||||
-- VID_SENTINEL_CLOBBER=1 draw mode only: overwrite the VRAM canary word
|
||||
-- before the frame is announced, so the payload must count
|
||||
-- an OS touch. The negative control for OS_TOUCH.
|
||||
-- VID_PAYLOAD raw .text image (default harness/udpstream/build/vidstream.bin)
|
||||
-- VID_ORIGIN payload load (poke) bit address (default 0x018C0000)
|
||||
-- VID_ENTRY_PTR _start bit address from vidstream.env (default VID_ORIGIN)
|
||||
@@ -92,6 +106,9 @@ local RX_LEN_A = MARK_PAGE + 0x6c * 8
|
||||
local RX_READY_B = MARK_PAGE + 0x70 * 8
|
||||
local RX_LEN_B = MARK_PAGE + 0x74 * 8
|
||||
|
||||
-- Task 5: the OS-repaint canary (frame_parse.h, ruling R12).
|
||||
local OS_TOUCH = MARK_PAGE + 0x44 * 8
|
||||
|
||||
-- Must match vidstream.c.
|
||||
local SLOT_BYTES = 24576
|
||||
|
||||
@@ -104,7 +121,23 @@ local RX_ROW_BYTES = 160
|
||||
local RX_PLEN = RX_ROWS * RX_ROW_BYTES
|
||||
local RX_FRAME = 16 + RX_PLEN
|
||||
|
||||
if MODE ~= "alloc" and MODE ~= "rx" then
|
||||
-- Draw mode: 128 rows x 8 screen rows = 1024, the whole screen (ruling R3).
|
||||
-- 20,480 B of payload + 16 B header fits the 24,576 B slot.
|
||||
local DRAW_ROWS = 128
|
||||
local DRAW_STRETCH = 8
|
||||
local DRAW_PLEN = DRAW_ROWS * RX_ROW_BYTES
|
||||
local DRAW_FRAME = 16 + DRAW_PLEN
|
||||
|
||||
-- Screen memory: bit 0x04000000, 256-byte row pitch (the real unit's, see
|
||||
-- harness/c-hello/README.md; MAME's own renderer uses a different pitch and is
|
||||
-- not what this check reads).
|
||||
local VRAM_BASE = 0x04000000
|
||||
local VRAM_ROW_BITS = 0x800
|
||||
local VRAM_ROWS = 1024
|
||||
local VRAM_ROW_BYTES = 256
|
||||
local DUMP_DIR = os.getenv("VID_DUMP_DIR") or "/tmp/opencode"
|
||||
|
||||
if MODE ~= "alloc" and MODE ~= "rx" and MODE ~= "draw" then
|
||||
print("VID ERROR: mode '" .. MODE .. "' is not implemented")
|
||||
machine:exit()
|
||||
return
|
||||
@@ -129,7 +162,9 @@ local function crc_add(crc, bytes, first, count)
|
||||
end
|
||||
|
||||
-- A complete frame, header and payload, laid out as frame.py writes it.
|
||||
local function make_frame(kind, frame_no, rows, stretch, plen)
|
||||
-- `fill`, when given, computes payload byte i; draw mode needs a per-row
|
||||
-- pattern, the rx mode keeps the arbitrary (i*7+3) filler.
|
||||
local function make_frame(kind, frame_no, rows, stretch, plen, fill)
|
||||
local b = {}
|
||||
b[0] = 0x56 -- 'V'
|
||||
b[1] = 0x53 -- 'S'
|
||||
@@ -144,7 +179,11 @@ local function make_frame(kind, frame_no, rows, stretch, plen)
|
||||
b[10] = 0
|
||||
b[11] = 0
|
||||
for i = 0, plen - 1 do
|
||||
b[16 + i] = (i * 7 + 3) & 0xFF
|
||||
if fill then
|
||||
b[16 + i] = fill(i)
|
||||
else
|
||||
b[16 + i] = (i * 7 + 3) & 0xFF
|
||||
end
|
||||
end
|
||||
local crc = crc_add(0xFFFF, b, 0, 12)
|
||||
crc = crc_add(crc, b, 16, plen)
|
||||
@@ -223,13 +262,31 @@ local function in_payload(pc)
|
||||
end
|
||||
|
||||
local function enter_direct(reason)
|
||||
dbg:command("bpclear " .. hex(ENTRY_PC))
|
||||
dbg:command("bpclear") -- clears ALL: see disarm_entry()
|
||||
cpu.state['PC'].value = ENTRY_PTR
|
||||
direct = true
|
||||
print(string.format("VID: frame %d: %s; entered payload directly at %s",
|
||||
frames, reason, hex(ENTRY_PTR)))
|
||||
end
|
||||
|
||||
-- Disarm the idle-loop breakpoint and say so.
|
||||
--
|
||||
-- `bpclear <address>` does NOT clear by address: MAME's command takes a
|
||||
-- BREAKPOINT INDEX (debugcmd.cpp, execute_bpclear -> "Invalid breakpoint
|
||||
-- number"). Task 3/4 passed the address, so the breakpoint stayed armed the
|
||||
-- whole run. It did not matter while the payload never yielded -- but as soon
|
||||
-- as a frame burst masked interrupts long enough for the OS to reach its idle
|
||||
-- loop, the stale action fired again and re-entered _start (Task 5 measured a
|
||||
-- second malloc and RX_OK back at 0). This script owns the debugger session
|
||||
-- and sets exactly one breakpoint, so "clear all" is the safe form.
|
||||
local function disarm_entry()
|
||||
dbg:command("bpclear")
|
||||
dbg:command("bplist")
|
||||
local last = "?"
|
||||
pcall(function() last = tostring(dbg.consolelog[#dbg.consolelog]) end)
|
||||
print("VID: entry breakpoint disarmed; breakpoint list: " .. last)
|
||||
end
|
||||
|
||||
-- Fill slot A with a valid frame and slot B with the same frame carrying one
|
||||
-- flipped payload byte, then raise both ready flags. The payload has already
|
||||
-- published the slot addresses by the time this runs; a zero there means the
|
||||
@@ -261,6 +318,127 @@ local function inject()
|
||||
return true
|
||||
end
|
||||
|
||||
-- Draw mode: one valid 128-row frame into slot A. Row r is the 16-bit word
|
||||
-- 0xF0F0 repeated (160 bytes of 0xF0), except every third row (r % 3 == 2),
|
||||
-- which is all zero -- two row classes, so a blit that always read the same
|
||||
-- source row cannot pass. The frame bytes are kept for the checker, which
|
||||
-- compares against what was actually delivered rather than re-deriving it.
|
||||
local draw_at = nil
|
||||
local draw_bytes = nil
|
||||
|
||||
local function draw_row_byte(i)
|
||||
if (i // RX_ROW_BYTES) % 3 == 2 then
|
||||
return 0x00
|
||||
end
|
||||
return 0xF0
|
||||
end
|
||||
|
||||
local function inject_draw()
|
||||
local a = prog:read_u32(RX_SLOT_A)
|
||||
|
||||
if a == 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
local f = make_frame(1, 0x0051, DRAW_ROWS, DRAW_STRETCH, DRAW_PLEN,
|
||||
draw_row_byte)
|
||||
poke(a, f, DRAW_FRAME)
|
||||
draw_bytes = f
|
||||
prog:write_u32(RX_LEN_A, DRAW_FRAME)
|
||||
-- Negative control for the OS-repaint canary: clobber the sentinel word
|
||||
-- (last screen row, column 1280) just before the payload is told a frame
|
||||
-- is ready. The first blit must then count an OS touch. Off by default;
|
||||
-- see VID_SENTINEL_CLOBBER in the env list.
|
||||
if os.getenv("VID_SENTINEL_CLOBBER") then
|
||||
prog:write_u16(VRAM_BASE + (VRAM_ROWS - 1) * VRAM_ROW_BITS + 1280,
|
||||
0x1234)
|
||||
print(string.format("VID: frame %d: sentinel clobbered to 1234 (canary control)",
|
||||
frames))
|
||||
end
|
||||
prog:write_u32(RX_READY_A, 1)
|
||||
print(string.format("VID: frame %d: injected slot A (%s, %d B, valid, %d rows x %d stretch)",
|
||||
frames, hex(a), DRAW_FRAME, DRAW_ROWS, DRAW_STRETCH))
|
||||
local okb, badb = verify(a, f, DRAW_FRAME)
|
||||
print(string.format("VID: frame %d: draw frame readback %s (%d bad)",
|
||||
frames, okb and "OK" or "FAILED", badb))
|
||||
return true
|
||||
end
|
||||
|
||||
-- Read the screen as memory: 1024 rows x 256 bytes from bit 0x04000000.
|
||||
-- read_u16 is used rather than read_u32 so the byte order (a TMS34010 word is
|
||||
-- little-endian on the wire, low byte first) is explicit rather than assumed.
|
||||
local function read_vram()
|
||||
local rows = {}
|
||||
|
||||
for y = 0, VRAM_ROWS - 1 do
|
||||
local off = VRAM_BASE + y * VRAM_ROW_BITS
|
||||
local chars = {}
|
||||
for w = 0, VRAM_ROW_BYTES // 2 - 1 do
|
||||
local v = prog:read_u16(off + w * 16)
|
||||
chars[#chars + 1] = string.char(v & 0xFF, (v >> 8) & 0xFF)
|
||||
end
|
||||
rows[#rows + 1] = table.concat(chars)
|
||||
end
|
||||
return table.concat(rows)
|
||||
end
|
||||
|
||||
local function write_file(path, s)
|
||||
local f, err = io.open(path, "wb")
|
||||
if not f then
|
||||
print("VID ERROR: cannot write " .. path .. ": " .. tostring(err))
|
||||
return false
|
||||
end
|
||||
f:write(s)
|
||||
f:close()
|
||||
return true
|
||||
end
|
||||
|
||||
-- Dump the screen and the payload's record for vidstream_check.py, then end
|
||||
-- the run. The pass/fail for pixels belongs to the checker; here we only
|
||||
-- report what the payload itself says (one accepted frame, no rejects, no
|
||||
-- repaint by the OS).
|
||||
local function dump_draw()
|
||||
local vram = read_vram()
|
||||
local chars = {}
|
||||
for i = 0, DRAW_FRAME - 1 do
|
||||
chars[#chars + 1] = string.char(draw_bytes[i])
|
||||
end
|
||||
local frame = table.concat(chars)
|
||||
local okv = write_file(DUMP_DIR .. "/vram-draw.bin", vram)
|
||||
local okf = write_file(DUMP_DIR .. "/frame-draw.bin", frame)
|
||||
local rx_ok = prog:read_u32(RX_OK)
|
||||
local rx_bad = prog:read_u32(RX_BAD)
|
||||
local os_touch = prog:read_u32(OS_TOUCH)
|
||||
local ready = prog:read_u32(RX_READY_A)
|
||||
local alive = prog:read_u32(VID_ALIVE)
|
||||
local ptr = prog:read_u32(POOL_PTR)
|
||||
local slota = prog:read_u32(RX_SLOT_A)
|
||||
local pcv = cpu.state['PC'].value
|
||||
local sent = prog:read_u16(VRAM_BASE + (VRAM_ROWS - 1) * VRAM_ROW_BITS
|
||||
+ 1280)
|
||||
local ok = okv and okf
|
||||
|
||||
print(string.format("VID: heartbeat %d, PC=%s (%s)", alive, hex(pcv),
|
||||
in_payload(pcv) and "inside the payload" or "OUTSIDE the payload"))
|
||||
print(string.format("VID: POOL_PTR=%s RX_SLOT_A=%s (an unchanged pointer means "
|
||||
.. "the payload did not re-enter main)", hex(ptr), hex(slota)))
|
||||
|
||||
write_file(DUMP_DIR .. "/draw-status.txt", string.format(
|
||||
"RX_OK=%d\nRX_BAD=%d\nOS_TOUCH=%d\nRX_READY_A=%d\nSENTINEL=%d\n",
|
||||
rx_ok, rx_bad, os_touch, ready, sent))
|
||||
|
||||
print(string.format("VID: frame %d: screen dumped to %s/vram-draw.bin (%d B), frame to %s/frame-draw.bin (%d B)",
|
||||
frames, DUMP_DIR, #vram, DUMP_DIR, DRAW_FRAME))
|
||||
print(string.format("VID: RX_OK=%d RX_BAD=%d OS_TOUCH=%d RX_READY_A=%d SENTINEL=%04X",
|
||||
rx_ok, rx_bad, os_touch, ready, sent))
|
||||
|
||||
if rx_ok ~= 1 or rx_bad ~= 0 or ready ~= 0 or os_touch ~= 0 then
|
||||
ok = false
|
||||
end
|
||||
print(ok and "VID: RESULT PASS" or "VID: RESULT FAIL")
|
||||
machine:exit()
|
||||
end
|
||||
|
||||
local function inspect_pool()
|
||||
local ptr = prog:read_u32(POOL_PTR)
|
||||
local freeb = prog:read_u32(POOL_FREEB)
|
||||
@@ -418,7 +596,7 @@ emu.register_frame_done(function()
|
||||
|
||||
if loaded and not entered and in_payload(pcv) then
|
||||
entered = true
|
||||
dbg:command("bpclear " .. hex(ENTRY_PC))
|
||||
disarm_entry()
|
||||
alive0 = prog:read_u32(VID_ALIVE)
|
||||
print(string.format("VID: payload entered by frame %d (PC=%s, heartbeat=%d)",
|
||||
frames, hex(pcv), alive0))
|
||||
@@ -439,11 +617,32 @@ emu.register_frame_done(function()
|
||||
end
|
||||
end
|
||||
|
||||
-- Draw mode: inject once, then wait for the payload to consume the slot
|
||||
-- (it clears RX_READY_A) and give it a few frames of slack before reading
|
||||
-- the screen back. The timeout is generous on purpose: fp_parse() runs a
|
||||
-- BITWISE CRC, and a 20 KB frame costs it ~40 emulated frames (measured),
|
||||
-- so an unlucky 10-frame wait would read the screen before the blit.
|
||||
-- A payload that never takes the frame still gets its screen dumped, so
|
||||
-- the checker reports the truth instead of the run hanging.
|
||||
if entered and MODE == "draw" and draw_at == nil then
|
||||
if inject_draw() then
|
||||
draw_at = frames
|
||||
end
|
||||
end
|
||||
|
||||
if MODE == "draw" and draw_at ~= nil and frames > draw_at + 1 then
|
||||
if (prog:read_u32(RX_READY_A) == 0 and frames >= draw_at + 5)
|
||||
or frames >= draw_at + 300 then
|
||||
dump_draw()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if entered and frames % 10 == 0 and frames < STOP_AT then
|
||||
alive_last = prog:read_u32(VID_ALIVE)
|
||||
end
|
||||
|
||||
if frames >= STOP_AT then
|
||||
if frames >= STOP_AT and not (MODE == "draw" and draw_at ~= nil) then
|
||||
if not entered then
|
||||
print("VID: payload never entered -> FAIL")
|
||||
print("VID: RESULT FAIL")
|
||||
|
||||
Executable
+182
@@ -0,0 +1,182 @@
|
||||
#!/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, ...)
|
||||
|
||||
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
|
||||
|
||||
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))
|
||||
|
||||
# --- 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")
|
||||
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,))
|
||||
print("vidstream_check: RX_OK=%r RX_BAD=%r OS_TOUCH=%r"
|
||||
% (rx_ok, rx_bad, os_touch))
|
||||
|
||||
if failures:
|
||||
print("vidstream_check: FAIL (%d checks)" % failures)
|
||||
return 1
|
||||
print("vidstream_check: PASS")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -42,6 +42,13 @@
|
||||
* RX_BAD +0xA0 0x018101A0 frames rejected by fp_parse
|
||||
* RX_LAST_LEN +0xC0 0x018101C0 payload_len of the last accepted
|
||||
* RX_SLOT +0xE0 0x018101E0 slot of the last accepted (0=A)
|
||||
* OS_TOUCH +0x220 0x01810220 times the VRAM canary was not ours
|
||||
*
|
||||
* OS_TOUCH is Task 5's OS-repaint canary (design spec 8.9): the blit leaves one
|
||||
* 16-bit sentinel word in the unused right edge of the last screen row, and
|
||||
* OS_TOUCH counts how often that word was not what the payload left there. It
|
||||
* is at cell 0x44 -- free between the RX_* counters and the injection cells
|
||||
* below (ruling R12).
|
||||
*
|
||||
* Debugger injection interface (Task 4; the harness plays the receive path):
|
||||
*
|
||||
@@ -97,6 +104,8 @@
|
||||
#define RX_LAST_LEN MARK_CELL(0x38)
|
||||
#define RX_SLOT MARK_CELL(0x3c)
|
||||
|
||||
#define OS_TOUCH MARK_CELL(0x44)
|
||||
|
||||
#define RX_SLOT_A MARK_CELL(0x60)
|
||||
#define RX_SLOT_B MARK_CELL(0x64)
|
||||
#define RX_READY_A MARK_CELL(0x68)
|
||||
|
||||
+129
-10
@@ -2,10 +2,12 @@
|
||||
* vidstream.c -- terminal side of the VT1200 video stream.
|
||||
*
|
||||
* Task 3 claimed the working set with ONE malloc at startup and published
|
||||
* where it landed. Task 4 adds the receive side: two 24 KB frame slots at the
|
||||
* where it landed. Task 4 added the receive side: two 24 KB frame slots at the
|
||||
* head of that pool, and a poll loop that hands each completed slot to
|
||||
* fp_parse() (frame_parse.c, shared with the host unit test) and counts what
|
||||
* it accepted. Task 5 replaces the accept path's placeholder with the blit.
|
||||
* it accepted. Task 5 blits an accepted strip frame to the screen: one
|
||||
* PIXBLT B,XY per source row, repeated down the row `stretch` times by
|
||||
* SPTCH=0 (vram_b.asm).
|
||||
*
|
||||
* Pool layout inside the single allocation (POOL_BYTES = 249856 = 244 KB):
|
||||
*
|
||||
@@ -17,9 +19,24 @@
|
||||
*
|
||||
* A slot holds one complete frame -- 16-byte header and payload -- exactly as
|
||||
* it arrives on the wire, so the bytes fp_parse() validates are the bytes the
|
||||
* blit will read. Neither slot is ever split across a ring wrap: the two ring
|
||||
* blit reads. Neither slot is ever split across a ring wrap: the two ring
|
||||
* entries' descriptors are re-armed per slot at frame start.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
* edge of the last screen row (row 1023, column 1280), outside every pixel the
|
||||
* blit writes. If something else -- the resident OS -- paints over us it also
|
||||
* disturbs that corner; OS_TOUCH counts how often that was seen. This
|
||||
* decoder has no partial update: an accepted frame repaints the whole screen
|
||||
* anyway, so detecting the touch and repainting are the same act here, and
|
||||
* OS_TOUCH is the record.
|
||||
*
|
||||
* Marker layout: see the table in frame_parse.h. Markers are 32-bit cells on
|
||||
* the byte-aligned page at bit 0x01810000, 0x20 bits (4 bytes) apart. The
|
||||
* design plan names them 0x01810020, 0x01810024, ... but the TMS34010 is
|
||||
@@ -28,7 +45,9 @@
|
||||
*
|
||||
* marker named 0x018100NN -> bit 0x01810000 + NN * 8
|
||||
*
|
||||
* (POOL_PTR = 0x01810100, ...). vidstream.lua uses the same rule.
|
||||
* (POOL_PTR = 0x01810100, ...). Task 5's OS_TOUCH is 0x01810220 (ruling R12),
|
||||
* i.e. cell 0x44 -- free between the RX_* counters and the injection cells.
|
||||
* vidstream.lua uses the same rule.
|
||||
*
|
||||
* K&R C only: no prototypes, no '//', declarations before statements.
|
||||
*/
|
||||
@@ -41,6 +60,22 @@
|
||||
|
||||
#define STATIC_BYTES 249856 /* the designed working set, 244 KB */
|
||||
|
||||
/* Screen memory and the strip geometry (see the header comment). */
|
||||
#define VRAM_BASE 0x04000000 /* video RAM, bit address */
|
||||
#define VRAM_ROW 0x800 /* 2048 bits = 256 bytes a screen row */
|
||||
#define SCREEN_ROWS 1024 /* rows in the framebuffer */
|
||||
#define ROW_PIXELS 1280 /* a strip row is 1280 pixels */
|
||||
#define ROW_BYTES 160 /* 1280 pixels, 1 bpp */
|
||||
|
||||
/*
|
||||
* The sentinel word: last screen row, first column past the 1280 the blit
|
||||
* writes. Outside the blitted area by construction, inside mapped VRAM (the
|
||||
* next 256-byte row would be bit 0x06000000, which the driver maps as the NVR,
|
||||
* not as video memory).
|
||||
*/
|
||||
#define SENT_ADDR (VRAM_BASE + (SCREEN_ROWS - 1) * VRAM_ROW + ROW_PIXELS)
|
||||
#define SENT_VALUE 0xA5C3
|
||||
|
||||
/*
|
||||
* The two frame slots. SLOT_BYTES is the plan's 24 KB: a 128-row strip frame
|
||||
* (128 x 160 B + 16 B header = 20,496 B = 15 packets) fits, and so does any
|
||||
@@ -62,11 +97,17 @@ unsigned int stack_cushion[1024];
|
||||
|
||||
/*
|
||||
* VRAM.ASM's externals. vram.asm is linked into every payload in this
|
||||
* directory; the C side owns the values. Task 5 fills the blit_* words.
|
||||
* directory; the C side owns the values. vram_b.asm (PIXBLT B,XY) shares
|
||||
* blit_src/blit_daddr/blit_dydx and ignores blit_sptch, because its SPTCH is
|
||||
* always 0 -- that is what makes one source row fill `stretch` screen rows.
|
||||
*/
|
||||
unsigned int vram_pattern;
|
||||
unsigned int blit_src, blit_sptch, blit_daddr, blit_dydx;
|
||||
|
||||
extern void vram_blit_b_(); /* vram_b.asm: PIXBLT B,XY, SPTCH = 0 */
|
||||
extern void vid_dint_(); /* vram_b.asm: DINT */
|
||||
extern void vid_eint_(); /* vram_b.asm: EINT */
|
||||
|
||||
/*
|
||||
* The firmware's free-space counter is a 32-bit count IN BITS at
|
||||
* 0x1E8BA0 (dwt/notes/HEAP.md). POOL_FREE reports bytes, because the
|
||||
@@ -77,6 +118,79 @@ static unsigned int heap_bytes()
|
||||
return *(unsigned int *)HEAP_FREE / 8;
|
||||
}
|
||||
|
||||
/*
|
||||
* blit_row(src, daddr_xy, dydx)
|
||||
*
|
||||
* One PIXBLT B,XY. The three values are published where vram_b.asm reads
|
||||
* them (see the note there on the calling form; the assembly entry point takes
|
||||
* no arguments). `src` is the packed 1-bpp source row as a bit address,
|
||||
* `daddr_xy` is the screen position as (y << 16) | x, and `dydx` is the
|
||||
* rectangle as (screen rows << 16) | pixels. The primitive always sets
|
||||
* SPTCH = 0, so the "screen rows" dimension reads the same source row that
|
||||
* many times: that is the vertical stretch.
|
||||
*/
|
||||
static void blit_row(src, daddr_xy, dydx)
|
||||
unsigned int src;
|
||||
unsigned int daddr_xy;
|
||||
unsigned int dydx;
|
||||
{
|
||||
blit_src = src;
|
||||
blit_daddr = daddr_xy;
|
||||
blit_dydx = dydx;
|
||||
vram_blit_b_();
|
||||
}
|
||||
|
||||
/*
|
||||
* paint_frame(slot, h)
|
||||
*
|
||||
* Draw one accepted strip frame: source row r goes down screen rows
|
||||
* r*stretch .. r*stretch+stretch-1. A frame the screen cannot hold is not
|
||||
* drawn: zero rows or zero stretch, more than SCREEN_ROWS screen rows, or a
|
||||
* payload shorter than the rows it claims (fp_parse() has already proved that
|
||||
* payload_len bytes arrived, so that bound is real). A refused frame never
|
||||
* touches VRAM, so a malformed header cannot paint garbage over a good one.
|
||||
*
|
||||
* The whole burst runs with interrupts masked (spec 8.9) so the resident OS
|
||||
* cannot repaint between two of our source rows and tear the picture.
|
||||
*/
|
||||
static void paint_frame(slot, h)
|
||||
unsigned int slot;
|
||||
fp_header *h;
|
||||
{
|
||||
unsigned int r, rows, stretch, src, sent;
|
||||
|
||||
rows = h->rows;
|
||||
stretch = h->stretch;
|
||||
if (h->kind != FP_KIND_STRIP || rows == 0 || stretch == 0) {
|
||||
return;
|
||||
}
|
||||
if (rows * stretch > SCREEN_ROWS) {
|
||||
return;
|
||||
}
|
||||
if (rows * ROW_BYTES > h->payload_len) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* The canary word lives outside every pixel this frame writes. If it
|
||||
* is not ours any more, something else painted VRAM: count it and put
|
||||
* it back. There is no partial update here -- this frame repaints the
|
||||
* whole screen anyway -- so OS_TOUCH is the record, not a trigger. */
|
||||
sent = (unsigned int)*(unsigned short *)SENT_ADDR;
|
||||
if (sent != SENT_VALUE) {
|
||||
*(unsigned int *)OS_TOUCH = *(unsigned int *)OS_TOUCH + 1;
|
||||
*(unsigned short *)SENT_ADDR = SENT_VALUE;
|
||||
}
|
||||
|
||||
vid_dint_();
|
||||
src = slot + h->payload_off * 8;
|
||||
for (r = 0; r < rows; r++) {
|
||||
blit_row(src + r * ROW_PIXELS,
|
||||
(r * stretch) << 16,
|
||||
(stretch << 16) | ROW_PIXELS);
|
||||
}
|
||||
vid_eint_();
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle one slot if the receive path has raised its ready flag.
|
||||
*
|
||||
@@ -125,11 +239,10 @@ unsigned int slot;
|
||||
*(unsigned int *)RX_OK = *(unsigned int *)RX_OK + 1;
|
||||
*(unsigned int *)RX_LAST_LEN = h.payload_len;
|
||||
*(unsigned int *)RX_SLOT = slot;
|
||||
/* Task 5 blits the accepted frame from here: h.payload_off
|
||||
* bytes into the slot, h.rows rows of 160 bytes (1280 pixels,
|
||||
* 1 bpp, MSB first), h.stretch scanlines each. Nothing is
|
||||
* drawn for a rejected frame, so a corrupt frame can never
|
||||
* reach VRAM. */
|
||||
/* Only an accepted frame reaches VRAM: the bytes just validated
|
||||
* are the bytes the blit reads, and a corrupt frame can never
|
||||
* paint over a good one. */
|
||||
paint_frame(base, &h);
|
||||
} else {
|
||||
*(unsigned int *)RX_BAD = *(unsigned int *)RX_BAD + 1;
|
||||
}
|
||||
@@ -161,6 +274,12 @@ int main()
|
||||
*(unsigned int *)RX_LEN_A = 0;
|
||||
*(unsigned int *)RX_READY_B = 0;
|
||||
*(unsigned int *)RX_LEN_B = 0;
|
||||
*(unsigned int *)OS_TOUCH = 0;
|
||||
|
||||
/* Arm the VRAM canary before any frame is accepted, so a repaint
|
||||
* between now and the first blit is counted rather than mistaken for
|
||||
* our own last frame. */
|
||||
*(unsigned short *)SENT_ADDR = SENT_VALUE;
|
||||
|
||||
before = heap_bytes();
|
||||
ptr = malloc_(POOL_BYTES); /* malloc_ takes BYTES, returns BITS */
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
; vram_b.asm -- PIXBLT B,XY: a packed 1-bpp RAM row straight to the screen.
|
||||
;
|
||||
; Task 5's blit primitive. PIXBLT's B source form reads the source as packed
|
||||
; 1-bit pixels from a LINEAR bit address and writes an XY destination, which is
|
||||
; exactly the strip decoder's shape: a 1280-pixel row (160 bytes) in the slot,
|
||||
; painted down `stretch` screen rows.
|
||||
;
|
||||
; The vertical stretch is one instruction, not a loop over screen rows: SPTCH
|
||||
; = 0 makes every source row read the SAME source address, so a PIXBLT whose
|
||||
; DYDX names `stretch` rows paints the one row `stretch` times.
|
||||
;
|
||||
; DESCRIPTION-register assignments for PIXBLT B,XY (TMS34010 User's Guide,
|
||||
; instruction reference; vram.asm already uses the same values for L,XY):
|
||||
; SADDR = B0 source bit address (1 bpp, packed)
|
||||
; SPTCH = B1 source pitch in bits; 0 = do not advance the source
|
||||
; DADDR = B2 destination XY: (y << 16) | x
|
||||
; DPTCH = B3 destination pitch in bits (0x800 = 256-byte screen row)
|
||||
; OFFSET = B4 VRAM base bit address
|
||||
; DYDX = B7 dimensions in XY form: (rows << 16) | columns
|
||||
;
|
||||
; COLOR0 = B8 pixel value for a clear source bit (0 = black)
|
||||
; COLOR1 = B9 pixel value for a set source bit (0xFFFF = white)
|
||||
;
|
||||
; B8/B9 are NOT I/O registers: COLOR0 and COLOR1 are implied B-register
|
||||
; operands of every graphics instruction (vram.asm's FILL passes its pattern in
|
||||
; B9 for the same reason). They are explicit here because a PIXBLT B expands
|
||||
; a packed 1-bpp source into pixel values, so whatever the resident OS left in
|
||||
; B9 becomes the colour of every set pixel -- measured: the first draw run
|
||||
; painted every row it blitted black because nothing had set B9 (the padding
|
||||
; past column 1280, which the blit does not write, kept the OS's old bytes).
|
||||
;
|
||||
; PSIZE (0C0000150h) 1 one bit per destination pixel
|
||||
; CONVSP (0C0000130h) 14h 0x800-bit pitch code (1 << (~0x14 & 0x1F) = 2048)
|
||||
; CONVDP (0C0000140h) 14h same, for the XY destination
|
||||
; PMASK (0C0000160h) 0 every plane writable
|
||||
; CONTROL (0C00000B0h) raster op 0 (replace), CD/RM/RR preserved
|
||||
;
|
||||
; Entry points. The TMS34010 C compiler passes arguments in its own frame-
|
||||
; pointer scheme, and no hand-written assembly in this repository has ever
|
||||
; DECODED that frame: the ROM thunks in entry.asm forward the caller's stack
|
||||
; frame through `calla` without touching it, and the two array primitives in
|
||||
; vram.asm take their inputs from published words in .bss with a no-argument
|
||||
; entry (called by lance_cmd.c, measured on the unit). This primitive follows
|
||||
; the vram.asm form, so the three interface values named in the plan live in
|
||||
; blit_src / blit_daddr / blit_dydx (blit_sptch only exists for vram.asm and is
|
||||
; ignored: this primitive always sets SPTCH = 0). The C side wraps them in
|
||||
; blit_row() so the call reads as blit_row(src, daddr_xy, dydx).
|
||||
;
|
||||
; All inputs are reloaded on every call: PIXBLT leaves SADDR/DADDR advanced, so
|
||||
; a caller that "saves" them saves a half-consumed rectangle.
|
||||
;
|
||||
; _vid_dint_ / _vid_eint_ -- interrupts off/on. The C compiler cannot emit
|
||||
; DINT/EINT, and spec section 8.9 wants the OS masked for the whole burst so a
|
||||
; resident repaint cannot tear the picture.
|
||||
|
||||
.ref _blit_src
|
||||
.ref _blit_daddr
|
||||
.ref _blit_dydx
|
||||
.text
|
||||
.globl _vram_blit_b_
|
||||
_vram_blit_b_:
|
||||
setf 32, 0, 1
|
||||
move b0, -*sp, 1
|
||||
move b1, -*sp, 1
|
||||
move b2, -*sp, 1
|
||||
move b3, -*sp, 1
|
||||
move b4, -*sp, 1
|
||||
move b7, -*sp, 1
|
||||
move b8, -*sp, 1
|
||||
move b9, -*sp, 1
|
||||
|
||||
move @_blit_src, b0, 1 ; SADDR: packed 1-bpp source row
|
||||
clr b1 ; SPTCH = 0: every row is the same source row
|
||||
move @_blit_daddr, b2, 1 ; DADDR (XY)
|
||||
movi 800h, b3 ; DPTCH = 2048 bits = 256-byte row pitch
|
||||
movi 04000000h, b4 ; OFFSET = VRAM base
|
||||
move @_blit_dydx, b7, 1 ; DYDX (rows, columns)
|
||||
clr b8 ; COLOR0 = 0 (black)
|
||||
movi 0FFFFh, b9 ; COLOR1 = all planes set (white)
|
||||
|
||||
; --- pixel format ------------------------------------------------------
|
||||
movi 1, a1
|
||||
move a1, @0C0000150h, 0 ; PSIZE = 1
|
||||
movi 14h, a1
|
||||
move a1, @0C0000130h, 0 ; CONVSP = pitch code for 0x800 bits
|
||||
move a1, @0C0000140h, 0 ; CONVDP
|
||||
clr a1
|
||||
move a1, @0C0000160h, 0 ; PMASK = 0 (all planes writable)
|
||||
move a1, @0C0000170h, 0 ; upper half, for forward compatibility
|
||||
|
||||
; --- graphics operation: replace, no transparency, no window ----------
|
||||
move @0C00000B0h, a1, 0 ; CONTROL
|
||||
andi 0801Fh, a1 ; keep CD (15) and RM/RR (2-4) only
|
||||
move a1, @0C00000B0h, 0
|
||||
move @0C00000B0h, a1, 0 ; read back: I/O writes are pipelined
|
||||
|
||||
pixblt b, xy
|
||||
|
||||
move *sp+, b9, 1
|
||||
move *sp+, b8, 1
|
||||
move *sp+, b7, 1
|
||||
move *sp+, b4, 1
|
||||
move *sp+, b3, 1
|
||||
move *sp+, b2, 1
|
||||
move *sp+, b1, 1
|
||||
move *sp+, b0, 1
|
||||
rets
|
||||
|
||||
.globl _vid_dint_
|
||||
_vid_dint_:
|
||||
dint
|
||||
rets
|
||||
|
||||
.globl _vid_eint_
|
||||
_vid_eint_:
|
||||
eint
|
||||
rets
|
||||
|
||||
.end
|
||||
Reference in new issue
Block a user