587 lines
23 KiB
Bash
Executable file
587 lines
23 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
|
# See LICENSE.md for license information.
|
|
# SPDX-License-Identifier: MIT
|
|
#
|
|
# verify-x68000-serial-mouse.sh - the X68000's RS-232C port and mouse at once.
|
|
#
|
|
# Both live on the one Z8530 SCC: RS-232C on channel A, where JoeyLib's serial
|
|
# add-on owns the receive interrupt (src/x68000/jlX68kSccRxIsr.s), and the mouse
|
|
# on channel B, where the IOCS ROM owns the receive interrupt and solicits a
|
|
# packet every other Timer C tick. The chip has ONE register pointer for both
|
|
# channels, so a select-then-access register sequence split by the other
|
|
# side's interrupt goes to the wrong register, and the interrupt-under-service
|
|
# reset it swallows wedges the chip's interrupt chain. The serial and mouse
|
|
# gates each run one side alone; this one runs both together, hard, for long
|
|
# enough to matter.
|
|
#
|
|
# Boots SERMOUSE.X (examples/sermouse) under the patched MAME with SCC channel
|
|
# A on a null_modem socket and every SCC channel at the real chip's 3-byte
|
|
# receive FIFO (JOEY_Z80SCC_RX_FIFO=3), so an interrupt held off too long loses
|
|
# bytes here as it would on the machine. Then, all at once:
|
|
# serial the guest streams byte k = streamByte(k, 0xA5) and checks the
|
|
# peer's stream, byte k = streamByte(k, 0x00), in order; the host
|
|
# peer (tests/gate/serialStreamPeer.c) checks the guest's and
|
|
# answers byte for byte. The guest also raises DTR 16 times a loop,
|
|
# the add-on's own select-then-write of channel A's WR5, so that
|
|
# sequence runs under both channels' interrupts all the time.
|
|
# mouse the Lua visits the four stage corners, pushing both axes into the
|
|
# corner EVERY frame -- a packet on channel B at every solicitation,
|
|
# while the pointer stays pinned where the clamp puts it -- checks
|
|
# the position there, then presses a button and checks it, then
|
|
# releases it (checked at the next corner). Clamp edges only: MAME's
|
|
# relative-axis injection is timing-sensitive, so interior positions
|
|
# are not testable (see verify-x68000-mouse.sh). Two left clicks a
|
|
# pass also prove the ROM's software keyboard stays off (hal.c
|
|
# X68K_SKEY_OFF): the second would bring it up and eat the next
|
|
# right click.
|
|
# Timeline: PASSES corner passes with the traffic flowing, every check also
|
|
# requiring both stream counts to have moved and no bad byte; SPACE pauses the
|
|
# guest's stream, the peer follows, and once quiet the guest's counts must
|
|
# hold still and equal a quiet snapshot of the peer's EXACTLY -- every byte
|
|
# sent either way arrived, in order; one more pass in the quiet; SPACE resumes
|
|
# and both counts must move again (the receive interrupt still fires at the
|
|
# end, the program still runs).
|
|
#
|
|
# MAME 0.264 needs one correction here, made in the Lua and not in MAME. Its
|
|
# Z8530 (src/devices/machine/z80scc.cpp) answers an interrupt acknowledge
|
|
# wrongly when BOTH channels have one pending: z80daisy_irq_ack puts the
|
|
# highest-priority source in service but returns m_chanB->m_rr2, the vector of
|
|
# whichever source was triggered LAST. The CPU then runs the other channel's
|
|
# handler, whose interrupt-under-service reset clears the source the chip
|
|
# acknowledged, and that channel's byte is left in its FIFO with nothing
|
|
# pending: the channel runs a byte behind until its FIFO overruns (serial
|
|
# loses bytes) or a stale byte starts a mouse packet (pointer and buttons go
|
|
# wrong). Measured before the correction: both, within seconds. Silicon
|
|
# returns the vector of the source it acknowledges, so a tap on the CPU's
|
|
# level-5 acknowledge rewrites the vector to that source's, and the verdict
|
|
# line reports how many acknowledges it corrected (iackFixed). Only this gate
|
|
# keeps both channels busy at once, so no other gate needs it.
|
|
#
|
|
# The guest reports through two verification rows at the bottom of the stage
|
|
# (one nibble per pixel, high nibble first), read from the text plane:
|
|
# row 198 3C3C, status, received(8), bad, sent(8), short writes, first bad(8)
|
|
# row 199 5A5A, present/left/right/0, x, y (examples/keys' mouse row)
|
|
# The serial row changes at most every 4 frames, so a read counts only when
|
|
# two consecutive frames agree (a present caught half done cannot).
|
|
#
|
|
# PASS: every Lua check right, the peer's stream PASS, at least MIN_BYTES each
|
|
# way, and the guest's quiet counts among the peer's quiet snapshots.
|
|
#
|
|
# Variants, built here into the work directory from examples/sermouse:
|
|
# X68K_SERMOUSE_BAUD=19200|38400 the line rate (default 9600, MAME's
|
|
# null_modem default); RSDRV.SYS, which the
|
|
# boot disk loads, is what reaches past 9600
|
|
# X68K_SERMOUSE_DENSE=n stage lines 0 .. n-1 change palette on
|
|
# every line (default 0: one palette), so
|
|
# the SCB raster interrupt holds IPL 6 for
|
|
# the whole run every frame while the
|
|
# streams and the mouse run
|
|
# scripts/verify-x68000-serial-scb.sh runs the dense combinations this port
|
|
# guarantees; see "Serial under a dense SCB" in claudeDocs/x68000_port.md.
|
|
#
|
|
# Requires: ./install.sh install x68000 -- the patched MAME
|
|
# (installer/patches/mame: 0002 for the FIFO depth, 0004 for the
|
|
# RS-232C slot and the mouse) and the Human68k system disk.
|
|
#
|
|
# bash scripts/verify-x68000-serial-mouse.sh
|
|
set -uo pipefail
|
|
|
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
source "$repo/scripts/lib/joeyEnv.sh"
|
|
joeyNeed MAME mame
|
|
joeyNeed X68K_DIR x68kFirmware
|
|
joeyNeedFile "$X68K_DIR/roms/x68000/cgrom.dat" x68kCgrom
|
|
source "$repo/scripts/lib/x68kDisk.sh"
|
|
PORT="${X68K_SERMOUSE_PORT:-6801}"
|
|
WALL="${X68K_SERMOUSE_WALL:-600}"
|
|
PASSES="${X68K_SERMOUSE_PASSES:-3}"
|
|
MIN_BYTES="${X68K_SERMOUSE_MIN_BYTES:-16384}"
|
|
BAUD="${X68K_SERMOUSE_BAUD:-9600}"
|
|
DENSE="${X68K_SERMOUSE_DENSE:-0}"
|
|
PROG="$repo/build/x68000/bin/SERMOUSE.X"
|
|
FIFO_DEPTH=3
|
|
DEFAULT_BAUD=9600
|
|
# The peer outlives MAME by this much, so it always sees the link close, and
|
|
# is given this long to report once MAME is gone.
|
|
PEER_MARGIN=30
|
|
PEER_GRACE=10
|
|
|
|
[ -f "$X68K_SYSTEM_DISK" ] || { echo "verify-x68000-serial-mouse: missing $X68K_SYSTEM_DISK" >&2; exit 2; }
|
|
[ -f "$PROG" ] || { echo "verify-x68000-serial-mouse: build SERMOUSE.X first (make -f make/x68000.mk)" >&2; exit 2; }
|
|
|
|
work=$(mktemp -d -t joey-x68sermouse.XXXXXX)
|
|
peer=""
|
|
mame=""
|
|
mameChild=""
|
|
cleanup() {
|
|
joeyStop "$mameChild" "$mame" "$peer"
|
|
rm -rf "$work"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
# A variant is examples/sermouse built against the same library with its
|
|
# line rate and SCB set (see the header).
|
|
if [ "$BAUD" != "$DEFAULT_BAUD" ] || [ "$DENSE" != 0 ]; then
|
|
joeyNeed X68K_ROOT toolchain:x68000
|
|
"$X68K_ROOT/bin/m68k-xelf-gcc" -O2 -m68000 -fomit-frame-pointer \
|
|
-DJOEYLIB_PLATFORM_X68000 -DLINE_BAUD="${BAUD}u" -DDENSE_SCB_LINES="${DENSE}u" \
|
|
-I"$repo/include" \
|
|
"$repo/examples/sermouse/sermouse.c" \
|
|
"$repo/build/x68000/lib/libjoey.a" -lm \
|
|
-o "$work/SERMOUSE.X" || exit 1
|
|
PROG="$work/SERMOUSE.X"
|
|
fi
|
|
x68kBootDisk "$work/boot.xdf" 'A:\SERMOUSE.X' || exit 1
|
|
x68kXdf add "$work/boot.xdf" "$PROG" SERMOUSE.X >/dev/null || exit 1
|
|
|
|
{
|
|
printf 'local PASSES = %d\n' "$PASSES"
|
|
printf 'local MIN_BYTES = %d\n' "$MIN_BYTES"
|
|
printf 'local BAUD = %d\n' "$BAUD"
|
|
printf 'local DENSE = %d\n' "$DENSE"
|
|
cat <<'LUA'
|
|
local cpu = manager.machine.devices[":maincpu"]
|
|
local mem = cpu.spaces["program"]
|
|
local nat = manager.machine.natkeyboard
|
|
local scc = manager.machine.devices[":scc"]
|
|
local sccA = manager.machine.devices[":scc:cha"]
|
|
local frame = 0
|
|
local t0 = 0
|
|
local biased = false
|
|
local done = false
|
|
local checks = 0
|
|
local fails = {}
|
|
local steps = {}
|
|
local pending = nil
|
|
local lastRaw = nil
|
|
local prev = nil
|
|
local quiet = nil
|
|
local pushX = 0
|
|
local pushY = 0
|
|
local axisValX = 0
|
|
local axisValY = 0
|
|
|
|
-- Stage geometry on the text plane (src/x68000/hal.c X68K_PRESENT_TEXT): four
|
|
-- bit planes 128 KB apart at 0xE00000, 128 bytes per row, leftmost pixel in
|
|
-- bit 7, the stage centered at (96, 156).
|
|
local TEXT_BASE = 0xE00000
|
|
local PLANES = 4
|
|
local PLANE_BYTES = 0x20000
|
|
local ROW_BYTES = 128
|
|
local ORIGIN_X = 96
|
|
local ORIGIN_Y = 156
|
|
-- The verification rows (examples/sermouse/sermouse.c): 16-bit words and
|
|
-- 32-bit longs, one nibble per pixel, laid end to end from the sentinel.
|
|
local WORD = 4
|
|
local LONG = 8
|
|
local SERIAL_ROW = 198
|
|
local SERIAL_SENTINEL = 0x3C3C
|
|
local SERIAL_NIBBLES = 4 * WORD + 3 * LONG
|
|
local MOUSE_ROW = 199
|
|
local MOUSE_SENTINEL = 0x5A5A
|
|
local MOUSE_NIBBLES = 4 * WORD
|
|
local STAGE_RIGHT = 319
|
|
local STAGE_BOTTOM = 199
|
|
local STATUS_OPEN = 0x0001
|
|
local STATUS_PAUSED = 0x0002
|
|
local NO_BAD_INDEX = 0xFFFFFFFF
|
|
|
|
-- Mouse drive: the x68k mouse's X/Y are 12-bit absolute counters it diffs
|
|
-- into delta packets; biased mid-range so a pass (net zero per axis, at most
|
|
-- two phases one way) never wraps them.
|
|
local AXIS_BIAS = 2048
|
|
local PUSH_STEP = 6 -- counter units per frame, per axis
|
|
local PUSH_FRAMES = 90 -- 540 units per phase: over the 319-pixel stage with room to spare
|
|
local BUTTON_FRAMES = 40
|
|
local CORNER_FRAMES = PUSH_FRAMES + BUTTON_FRAMES
|
|
local PASS_FRAMES = 4 * CORNER_FRAMES
|
|
local QUIET_SETTLE = 120 -- frames after SPACE before the line is read as quiet
|
|
local QUIET_HOLD = 60 -- and again this much later: it must not move
|
|
local RESUME_FRAMES = 120
|
|
local BOOT_BIAS_FRAME = 300
|
|
local SENTINEL_FIRST = 600
|
|
local SENTINEL_EVERY = 50
|
|
local SETTLE_FRAMES = 60
|
|
local STEADY_LIMIT = 60 -- frames a serial read may take to see two agreeing frames
|
|
local BOOT_LIMIT = 30000
|
|
|
|
-- The acknowledge-vector correction (see the script header). MAME keeps the
|
|
-- chip's six interrupt sources in m_int_state, highest priority first,
|
|
-- channel A's three then channel B's, each INT (pending) and/or IEO (in
|
|
-- service); m_int_source holds each one's type, which with the channel is the
|
|
-- 3-bit status the vector carries in bits 3-1 (WR9 status-low, as the X68000
|
|
-- runs it). The 68000 fetches the level-5 vector at 0xFFFFFA in cpu space.
|
|
local IACK5 = 0xFFFFFA
|
|
local SCC_SOURCES = 6
|
|
local SCC_A_SOURCES = 3
|
|
local DAISY_IEO = 0x02
|
|
local WR9_VIS = 0x01
|
|
local WR9_NV_SHSL = 0x12 -- no vector / status high: vectors this does not model
|
|
local STATUS_CHANNEL_A = 0x04
|
|
local STATUS_TYPE_MASK = 0x03
|
|
local VECTOR_STATUS_KEEP = 0xF1
|
|
local VECTOR_BYTE = 0xFF
|
|
local intState = emu.item(scc.items["0/m_int_state"])
|
|
local intSource = emu.item(scc.items["0/m_int_source"])
|
|
local wr9 = emu.item(scc.items["0/m_wr9"])
|
|
local wr2 = emu.item(sccA.items["0/m_wr2"])
|
|
local iackFixed = 0
|
|
|
|
-- The text plane holds two pages, and the port flips between them with the
|
|
-- text Y scroll (CRTC R11, which reads back): rows are counted from the page
|
|
-- on screen.
|
|
local function shownRow()
|
|
return mem:read_u16(0xE80016) & 0x3FF
|
|
end
|
|
|
|
local function textPix(px, y)
|
|
local sx = ORIGIN_X + px
|
|
local addr = TEXT_BASE + (ORIGIN_Y + shownRow() + y) * ROW_BYTES + (sx >> 3)
|
|
local bit = 7 - (sx & 7)
|
|
local v = 0
|
|
for p = 0, PLANES - 1 do
|
|
v = v | (((mem:read_u8(addr + p * PLANE_BYTES) >> bit) & 1) << p)
|
|
end
|
|
return v
|
|
end
|
|
|
|
local function rowNibbles(y, count)
|
|
local t = {}
|
|
for i = 0, count - 1 do
|
|
t[#t + 1] = textPix(i, y)
|
|
end
|
|
return t
|
|
end
|
|
|
|
-- A reader over a row's nibbles: each call takes the next field, high nibble
|
|
-- first.
|
|
local function fields(t)
|
|
local pos = 0
|
|
return function(count)
|
|
local v = 0
|
|
for i = pos + 1, pos + count do
|
|
v = (v << 4) | t[i]
|
|
end
|
|
pos = pos + count
|
|
return v
|
|
end
|
|
end
|
|
|
|
local function parseSerial(t)
|
|
local take = fields(t)
|
|
local s = {}
|
|
s.sentinel = take(WORD) == SERIAL_SENTINEL
|
|
s.status = take(WORD)
|
|
s.rx = take(LONG)
|
|
s.bad = take(WORD)
|
|
s.tx = take(LONG)
|
|
s.short = take(WORD)
|
|
s.firstBad = take(LONG)
|
|
return s
|
|
end
|
|
|
|
-- The flags word is one nibble per predicate: present, left, right, then 0.
|
|
local function readMouse()
|
|
local take = fields(rowNibbles(MOUSE_ROW, MOUSE_NIBBLES))
|
|
local m = {}
|
|
m.sentinel = take(WORD) == MOUSE_SENTINEL
|
|
m.present = take(1)
|
|
m.left = take(1)
|
|
m.right = take(1)
|
|
m.zero = take(1)
|
|
m.x = take(WORD)
|
|
m.y = take(WORD)
|
|
return m
|
|
end
|
|
|
|
local ports = manager.machine.ioport.ports
|
|
local axisX, axisY, btnLeft, btnRight
|
|
local portX = ports[":mouse_port:x68k:X"]
|
|
local portY = ports[":mouse_port:x68k:Y"]
|
|
local portB = ports[":mouse_port:x68k:BTN"]
|
|
if portX then for _, f in pairs(portX.fields) do axisX = f end end
|
|
if portY then for _, f in pairs(portY.fields) do axisY = f end end
|
|
if portB then
|
|
for _, f in pairs(portB.fields) do
|
|
if f.mask == 2 then btnLeft = f end
|
|
if f.mask == 1 then btnRight = f end
|
|
end
|
|
end
|
|
|
|
-- After MAME has acknowledged: the source now in service is the one the
|
|
-- vector must name. A global, because a collected tap handle removes the tap.
|
|
iackTap = cpu.spaces["cpu_space"]:install_read_tap(IACK5, IACK5 + 1, "iackVector", function(offset, data, mask)
|
|
local w = wr9:read(0)
|
|
if (w & WR9_VIS) == 0 or (w & WR9_NV_SHSL) ~= 0 then
|
|
return nil
|
|
end
|
|
for i = 0, SCC_SOURCES - 1 do
|
|
if (intState:read(i) & DAISY_IEO) ~= 0 then
|
|
local status = (intSource:read(i) & STATUS_TYPE_MASK) | (i < SCC_A_SOURCES and STATUS_CHANNEL_A or 0)
|
|
local vector = (wr2:read(0) & VECTOR_STATUS_KEEP) | (status << 1)
|
|
if vector == (data & VECTOR_BYTE) then
|
|
return nil
|
|
end
|
|
iackFixed = iackFixed + 1
|
|
return (data & ~VECTOR_BYTE) | vector
|
|
end
|
|
end
|
|
return nil
|
|
end)
|
|
|
|
local function finish(verdict)
|
|
done = true
|
|
local s = prev or {}
|
|
io.write(string.format("VERIFY-X68K-SERMOUSE %s baud=%d dense=%d checks=%d fails=%d rx=%d tx=%d quiet=%s iackFixed=%d%s\n",
|
|
verdict, BAUD, DENSE, checks, #fails, s.rx or 0, s.tx or 0,
|
|
quiet and string.format("%d:%d", quiet.rx, quiet.tx) or "none", iackFixed,
|
|
#fails > 0 and (" detail=" .. table.concat(fails, ",")) or ""))
|
|
io.flush()
|
|
manager.machine:exit()
|
|
end
|
|
|
|
if not (axisX and axisY and btnLeft and btnRight) then
|
|
done = true
|
|
io.write("VERIFY-X68K-SERMOUSE error: mouse ioports not found\n")
|
|
io.flush()
|
|
manager.machine:exit()
|
|
end
|
|
|
|
-- One check; the first failure is the verdict, so the run stops there.
|
|
local function expect(name, ok, detail)
|
|
if done then return end
|
|
checks = checks + 1
|
|
if not ok then
|
|
fails[#fails + 1] = name .. (detail and ("(" .. detail .. ")") or "")
|
|
finish("FAIL")
|
|
end
|
|
end
|
|
|
|
local function expectMouse(step, x, y, l, r)
|
|
local m = readMouse()
|
|
expect(step .. ":sentinel", m.sentinel)
|
|
expect(step .. ":present", m.present == 1, tostring(m.present))
|
|
expect(step .. ":xy", m.x == x and m.y == y, string.format("%d,%d want %d,%d", m.x, m.y, x, y))
|
|
expect(step .. ":buttons", m.left == l and m.right == r and m.zero == 0,
|
|
string.format("%d%d%d want %d%d0", m.left, m.right, m.zero, l, r))
|
|
end
|
|
|
|
-- A steady serial read: fn runs with the row once two consecutive frames
|
|
-- agree on it.
|
|
local function whenSteady(name, fn)
|
|
pending = { name = name, fn = fn, since = frame }
|
|
lastRaw = nil
|
|
end
|
|
|
|
local function serialHealthy(step, s)
|
|
expect(step .. ":sentinel", s.sentinel)
|
|
expect(step .. ":open", (s.status & STATUS_OPEN) ~= 0, string.format("status %04X", s.status))
|
|
expect(step .. ":bad", s.bad == 0 and s.firstBad == NO_BAD_INDEX, string.format("%d first %d", s.bad, s.firstBad))
|
|
expect(step .. ":short", s.short == 0, tostring(s.short))
|
|
end
|
|
|
|
-- Traffic flowing: both counts moved since the last read.
|
|
local function expectMoving(step)
|
|
whenSteady(step, function(s)
|
|
serialHealthy(step, s)
|
|
expect(step .. ":paused", (s.status & STATUS_PAUSED) == 0)
|
|
if prev then
|
|
expect(step .. ":rxMoved", s.rx > prev.rx, string.format("%d -> %d", prev.rx, s.rx))
|
|
expect(step .. ":txMoved", s.tx > prev.tx, string.format("%d -> %d", prev.tx, s.tx))
|
|
end
|
|
prev = s
|
|
end)
|
|
end
|
|
|
|
-- Line quiet: both counts exactly where the pause left them.
|
|
local function expectFrozen(step)
|
|
whenSteady(step, function(s)
|
|
serialHealthy(step, s)
|
|
expect(step .. ":paused", (s.status & STATUS_PAUSED) ~= 0)
|
|
if quiet == nil then
|
|
quiet = s
|
|
expect(step .. ":rxMin", s.rx >= MIN_BYTES, tostring(s.rx))
|
|
expect(step .. ":txMin", s.tx >= MIN_BYTES, tostring(s.tx))
|
|
else
|
|
expect(step .. ":rxFrozen", s.rx == quiet.rx, string.format("%d -> %d", quiet.rx, s.rx))
|
|
expect(step .. ":txFrozen", s.tx == quiet.tx, string.format("%d -> %d", quiet.tx, s.tx))
|
|
end
|
|
prev = s
|
|
end)
|
|
end
|
|
|
|
local function at(t, fn)
|
|
steps[#steps + 1] = { at = t, run = fn }
|
|
end
|
|
|
|
-- Corners in visiting order: where the clamp pins the pointer, the push
|
|
-- direction that pins it there, and the button pressed once it is.
|
|
local corners = {
|
|
{ name = "topLeft", x = 0, y = 0, dx = -1, dy = -1, button = "left" },
|
|
{ name = "topRight", x = STAGE_RIGHT, y = 0, dx = 1, dy = -1, button = "right" },
|
|
{ name = "bottomRight", x = STAGE_RIGHT, y = STAGE_BOTTOM, dx = 1, dy = 1, button = "left" },
|
|
{ name = "bottomLeft", x = 0, y = STAGE_BOTTOM, dx = -1, dy = 1, button = "right" },
|
|
}
|
|
|
|
-- One pass over the corners from frame `base`; `serialCheck` runs beside
|
|
-- each position check.
|
|
local function pass(base, label, serialCheck)
|
|
for i, c in ipairs(corners) do
|
|
local t = base + (i - 1) * CORNER_FRAMES
|
|
local step = label .. ":" .. c.name
|
|
local button = c.button == "left" and btnLeft or btnRight
|
|
local l = c.button == "left" and 1 or 0
|
|
at(t, function() pushX = c.dx; pushY = c.dy end)
|
|
at(t + PUSH_FRAMES, function()
|
|
expectMouse(step, c.x, c.y, 0, 0)
|
|
serialCheck(step)
|
|
button:set_value(1)
|
|
end)
|
|
at(t + CORNER_FRAMES - 1, function()
|
|
expectMouse(step .. "Button", c.x, c.y, l, 1 - l)
|
|
button:clear_value()
|
|
end)
|
|
end
|
|
end
|
|
|
|
local t = 0
|
|
for p = 1, PASSES do
|
|
pass(t, "busy" .. p, expectMoving)
|
|
t = t + PASS_FRAMES
|
|
end
|
|
at(t, function() nat:post(" ") end)
|
|
at(t + QUIET_SETTLE, function() expectFrozen("pause") end)
|
|
at(t + QUIET_SETTLE + QUIET_HOLD, function() expectFrozen("pauseHold") end)
|
|
t = t + QUIET_SETTLE + QUIET_HOLD + 1
|
|
pass(t, "quiet", expectFrozen)
|
|
t = t + PASS_FRAMES
|
|
at(t, function() nat:post(" ") end)
|
|
at(t + RESUME_FRAMES, function()
|
|
prev = quiet
|
|
expectMoving("resumed")
|
|
end)
|
|
local finalFrame = t + RESUME_FRAMES
|
|
|
|
emu.register_frame_done(function()
|
|
frame = frame + 1
|
|
if done then return end
|
|
if not biased and frame == BOOT_BIAS_FRAME then
|
|
biased = true
|
|
axisValX = AXIS_BIAS
|
|
axisValY = AXIS_BIAS
|
|
axisX:set_value(axisValX)
|
|
axisY:set_value(axisValY)
|
|
end
|
|
if t0 == 0 then
|
|
if frame > SENTINEL_FIRST and frame % SENTINEL_EVERY == 0 then
|
|
local s = parseSerial(rowNibbles(SERIAL_ROW, SERIAL_NIBBLES))
|
|
if s.sentinel and readMouse().sentinel then
|
|
t0 = frame + SETTLE_FRAMES
|
|
end
|
|
end
|
|
if frame > BOOT_LIMIT then
|
|
done = true
|
|
io.write("VERIFY-X68K-SERMOUSE timeout: SERMOUSE never came up\n")
|
|
io.flush()
|
|
manager.machine:exit()
|
|
end
|
|
return
|
|
end
|
|
if pushX ~= 0 or pushY ~= 0 then
|
|
axisValX = axisValX + pushX * PUSH_STEP
|
|
axisValY = axisValY + pushY * PUSH_STEP
|
|
axisX:set_value(axisValX)
|
|
axisY:set_value(axisValY)
|
|
end
|
|
for _, s in ipairs(steps) do
|
|
if frame == t0 + s.at and not done then s.run() end
|
|
end
|
|
if pending and not done then
|
|
local raw = table.concat(rowNibbles(SERIAL_ROW, SERIAL_NIBBLES), ",")
|
|
if raw == lastRaw then
|
|
local p = pending
|
|
pending = nil
|
|
p.fn(parseSerial(rowNibbles(SERIAL_ROW, SERIAL_NIBBLES)))
|
|
elseif frame - pending.since > STEADY_LIMIT then
|
|
expect(pending.name .. ":steady", false)
|
|
else
|
|
lastRaw = raw
|
|
end
|
|
end
|
|
if not done and not pending and frame > t0 + finalFrame then
|
|
finish(#fails == 0 and "PASS" or "FAIL")
|
|
end
|
|
end)
|
|
LUA
|
|
} > "$work/sermouse.lua"
|
|
|
|
peerTool=$(joeyTool serialStreamPeer) || exit 1
|
|
"$peerTool" "$PORT" "$((WALL + PEER_MARGIN))" "$MIN_BYTES" \
|
|
> "$work/peer.out" 2>&1 &
|
|
peer=$!
|
|
# The peer must be listening before MAME connects out to it.
|
|
for _ in $(seq 1 50); do
|
|
grep -q '^STREAM listening' "$work/peer.out" 2>/dev/null && break
|
|
sleep 0.1
|
|
done
|
|
|
|
# From the work dir, so MAME's cfg/ and nvram/ land there. Under timeout so a
|
|
# hung run cannot outlive WALL whatever happens to this script; mameChild is
|
|
# MAME itself, which cleanup stops first (stopping only the timeout process
|
|
# could leave MAME running).
|
|
cd "$work" || exit 1
|
|
if [ "$BAUD" != "$DEFAULT_BAUD" ]; then
|
|
mkdir -p "$work/cfg"
|
|
"$repo/scripts/lib/mameNullModemCfg.sh" x68000 :rs232c:null_modem "$BAUD" \
|
|
> "$work/cfg/x68000.cfg" || exit 2
|
|
fi
|
|
JOEY_Z80SCC_RX_FIFO=$FIFO_DEPTH timeout -s KILL "$WALL" "$MAME" x68000 -bios ipl10 \
|
|
-rompath "$X68K_DIR/roms" -flop1 "$work/boot.xdf" \
|
|
-rs232c null_modem -bitb "socket.127.0.0.1:$PORT" \
|
|
-video none -sound none -nothrottle \
|
|
-autoboot_script "$work/sermouse.lua" </dev/null > "$work/mame.out" 2>&1 &
|
|
mame=$!
|
|
sleep 1
|
|
mameChild=$(pgrep -P "$mame" | head -1)
|
|
|
|
# The Lua ends the run: after its last check, or at its first failure.
|
|
wait "$mame"
|
|
mame=""
|
|
mameChild=""
|
|
for _ in $(seq 1 "$PEER_GRACE"); do
|
|
joeyAlive "$peer" || break
|
|
sleep 1
|
|
done
|
|
joeyStop "$peer"
|
|
peer=""
|
|
|
|
lua=$(grep -E '^VERIFY-X68K-SERMOUSE ' "$work/mame.out" | tail -1)
|
|
stream=$(grep -E '^STREAM (PASS|FAIL)' "$work/peer.out" | tail -1)
|
|
echo "${lua:-VERIFY-X68K-SERMOUSE: no verdict from the Lua}"
|
|
echo "${stream:-STREAM: no verdict from the peer}"
|
|
|
|
if ! grep -q "RX FIFO depth $FIFO_DEPTH" "$work/mame.out"; then
|
|
echo "verify-x68000-serial-mouse: SKIP - this MAME does not take JOEY_Z80SCC_RX_FIFO (reinstall: ./install.sh install mame)" >&2
|
|
exit 2
|
|
fi
|
|
fail=0
|
|
if ! echo "$lua" | grep -q '^VERIFY-X68K-SERMOUSE PASS .* fails=0 '; then
|
|
echo "verify-x68000-serial-mouse: FAIL - guest or mouse check (Lua line above)" >&2
|
|
tail -5 "$work/mame.out" >&2
|
|
fail=1
|
|
fi
|
|
if ! echo "$stream" | grep -q '^STREAM PASS '; then
|
|
echo "verify-x68000-serial-mouse: FAIL - the guest's stream to the host (peer line above)" >&2
|
|
fail=1
|
|
fi
|
|
guestQuiet=$(echo "$lua" | sed -n 's/.* quiet=\([0-9]*:[0-9]*\).*/\1/p')
|
|
peerQuiet=$(echo "$stream" | sed -n 's/.* quiet=\([0-9:,]*\) .*/\1/p')
|
|
if [ -z "$guestQuiet" ] || [[ ",$peerQuiet," != *",$guestQuiet,"* ]]; then
|
|
echo "verify-x68000-serial-mouse: FAIL - quiet counts differ: guest received:sent $guestQuiet, peer sent:received $peerQuiet" >&2
|
|
fail=1
|
|
fi
|
|
if [ "$fail" -ne 0 ]; then
|
|
exit 1
|
|
fi
|
|
echo "verify-x68000-serial-mouse: PASS at $BAUD baud, $DENSE dense-SCB lines (both streams byte-exact and complete at the pause, mouse corners and buttons right throughout, receive interrupt alive at the end)"
|