Files
vt1200/harness/mame/hello.lua
T
SDD 1608a68cdb hello-world: the TMS34010 SDK assembles code that runs on the emulated VT1200
An 82-byte payload written in GSPA assembly, linked at bit 01800000h and
loaded into main RAM by harness/mame/hello.lua, executes on the CPU: it
writes DEAD/BEEF and a 1024-iteration counter into RAM and fills a 256x64
block in video RAM, which appears in the frame dump at x 256..511,
y 136..199.  Entry is a debugger breakpoint on the level-0 menu idle loop,
not a delivery mechanism.  Includes the build script, a COFF .text
extractor (gsprom's hex addresses wrap at 64 KB), the run log, RAM excerpt,
frame crops including a no-load control, and HELLO_WORLD_REPORT.md.
2026-10-18 01:20:38 +03:00

153 lines
5.5 KiB
Lua

-- hello.lua -- load the hello-world payload into RAM and enter it.
--
-- MECHANISM: this is DEBUGGER ENTRY, not a delivery mechanism. The firmware
-- boots normally to the level-0 diagnostics menu; at frame HELLO_AT this script
-- writes the payload bytes into main RAM and arms a breakpoint on the menu's
-- idle loop (3FDCE400). The breakpoint action sets PC to the payload's bit
-- address and resumes. Nothing is patched on disk; the ROM is untouched.
--
-- Evidence printed from inside the emulator:
-- * payload bytes read back from RAM equal the linked binary
-- * the two magic words and the loop counter written by the payload
-- * the video-RAM block the payload wrote
--
-- <mame> vt1200 -rompath harness/mame/roms -video none -sound none -nothrottle \
-- -debug -debugger none \
-- -autoboot_script harness/mame/hello.lua -autoboot_delay 0
--
-- Env:
-- HELLO_AT frame at which to load and enter (default 120)
-- HELLO_END frame at which to inspect and exit (default 150)
-- HELLO_PAYLOAD linked payload (default harness/hello-world/payload.bin)
-- HELLO_ORIGIN payload bit address (default 01800000)
local function hex(v, w) return string.format("%0" .. (w or 8) .. "X", v) end
local machine = manager.machine
local cpu = machine.devices[':maincpu']
local dbg = machine.debugger
local prog = cpu.spaces['program']
if not dbg then
print("HELLO ERROR: debugger not enabled; pass -debug")
machine:exit()
return
end
local AT = tonumber(os.getenv("HELLO_AT") or "120")
local STOP_AT = tonumber(os.getenv("HELLO_END") or "150")
local ORIGIN = tonumber(os.getenv("HELLO_ORIGIN") or "0x01800000")
local PATH = os.getenv("HELLO_PAYLOAD") or "harness/hello-world/payload.bin"
local MAGIC1 = 0x01810000
local MAGIC2 = 0x01810010
local COUNT = 0x01810020
local VRAM_ROW = 0x04019040 -- 04000000h + 100*1024 + 64
local IDLE_PC = 0x3FDCE400 -- level-0 menu idle loop (dsjs a9, itself)
local payload = nil
do
local f, err = io.open(PATH, "rb")
if not f then
print("HELLO ERROR: cannot open " .. PATH .. ": " .. tostring(err))
machine:exit()
return
end
payload = f:read("*a")
f:close()
print(string.format("HELLO: payload %s bytes=%d", PATH, #payload))
end
local frames = 0
local loaded = false
local entered = false
local hist = {}
local function load_payload()
local n = math.floor(#payload / 2)
local words = {}
for i = 0, n - 1 do
local lo = payload:byte(i * 2 + 1)
local hi = payload:byte(i * 2 + 2)
words[i] = lo | (hi << 8)
prog:write_u16(ORIGIN + i * 16, words[i])
end
-- Read back and compare: this is the check that the load is placed at the
-- right bit address with the right word order.
local bad = 0
for i = 0, n - 1 do
if prog:read_u16(ORIGIN + i * 16) ~= words[i] then
bad = bad + 1
if bad <= 4 then
print(string.format("HELLO MISMATCH word %d at bit %s: wrote %04X read %04X",
i, hex(ORIGIN + i * 16), words[i], prog:read_u16(ORIGIN + i * 16)))
end
end
end
print(string.format("HELLO: loaded %d words at bit %s; readback %s (%d bad)",
n, hex(ORIGIN), bad == 0 and "OK" or "FAILED", bad))
-- Entry is a breakpoint action: the firmware reaches IDLE_PC every frame.
-- (The action only assigns PC and resumes; the payload proves itself.)
dbg:command("bpset " .. hex(IDLE_PC) .. ",,{ pc = " .. hex(ORIGIN) .. " ; g }")
end
local function inspect()
local m1 = prog:read_u16(MAGIC1)
local m2 = prog:read_u16(MAGIC2)
local ct = prog:read_u16(COUNT)
print(string.format("HELLO: MAGIC1@%s = %04X (%s)", hex(MAGIC1), m1,
m1 == 0xDEAD and "DEAD ok" or "WRONG"))
print(string.format("HELLO: MAGIC2@%s = %04X (%s)", hex(MAGIC2), m2,
m2 == 0xBEEF and "BEEF ok" or "WRONG"))
print(string.format("HELLO: COUNT @%s = %04X (%s)", hex(COUNT), ct,
ct == 0x0400 and "1024 stores ok" or "WRONG"))
-- The 16 words of row 100, then the same columns of row 163 (last row).
local function rowdump(bitaddr, tag)
local s = {}
for i = 0, 15 do
s[#s + 1] = string.format("%04X", prog:read_u16(bitaddr + i * 16))
end
print(string.format("HELLO: VRAM %s @%s: %s", tag, hex(bitaddr), table.concat(s, " ")))
end
rowdump(VRAM_ROW, "row100 ")
rowdump(VRAM_ROW + 63 * 1024, "row163 ")
-- Every word of the 64x16 region must be FFFF: no holes, no short rows.
local filled, holes = 0, 0
for r = 0, 63 do
for c = 0, 15 do
local v = prog:read_u16(VRAM_ROW + r * 1024 + c * 16)
if v == 0xFFFF then filled = filled + 1 else holes = holes + 1 end
end
end
print(string.format("HELLO: VRAM block %d/1024 words are FFFF, %d holes", filled, holes))
-- One word either side of the block must be untouched by us.
print(string.format("HELLO: VRAM row100 word before start = %04X, after end = %04X",
prog:read_u16(VRAM_ROW - 16), prog:read_u16(VRAM_ROW + 16 * 16)))
print(string.format("HELLO: PC at inspection = %s (payload spin expected)", hex(cpu.state['PC'].value)))
end
emu.register_frame_done(function()
frames = frames + 1
local pcv = cpu.state['PC'].value
hist[pcv] = (hist[pcv] or 0) + 1
if frames == AT and not loaded then
loaded = true
load_payload()
end
if loaded and not entered and prog:read_u16(MAGIC1) == 0xDEAD then
entered = true
print(string.format("HELLO: payload has run by frame %d (PC=%s)",
frames, hex(cpu.state['PC'].value)))
end
if frames >= STOP_AT then
print(string.format("--- frame %d, %d distinct PCs sampled ---", frames,
(function() local n = 0 for _ in pairs(hist) do n = n + 1 end return n end)()))
inspect()
machine:exit()
end
end, "vt1200-hello")
dbg.visible_cpu = cpu
dbg.execution_state = 'run'