forked from arti/vt1200
feat(termtris): compat sprintf/stdio for the TI r5.01 runtime
This commit is contained in:
1 parent
a8612681ba
commit
a8041818bb
9 files changed
+626
No files matched your search
@@ -72,3 +72,4 @@ tools/tms34010-sdk/1991-07 TIGA Promo Kit and Art Software/
|
||||
|
||||
# termtris build scratch (TI tools + 2ine libs)
|
||||
apps/termtris/work/
|
||||
apps/termtris/build/*.log
|
||||
Binary file not shown.
@@ -0,0 +1,59 @@
|
||||
*****************************************************
|
||||
TMS340 COFF Linker Version 5.01
|
||||
*****************************************************
|
||||
Tue Nov 03 18:50:58 2026
|
||||
|
||||
OUTPUT FILE NAME: <FMT.out>
|
||||
ENTRY POINT SYMBOL: "_start" address: 018a0000
|
||||
|
||||
|
||||
MEMORY CONFIGURATION
|
||||
|
||||
name origin length attributes
|
||||
-------- -------- --------- ----------
|
||||
prog 018a0000 000020000 RWIX
|
||||
ram 01a00000 000040000 RWIX
|
||||
|
||||
|
||||
SECTION ALLOCATION MAP
|
||||
|
||||
output attributes/
|
||||
section page origin length input sections
|
||||
-------- ---- ---------- ---------- ----------------
|
||||
.text 0 018a0000 00001fa0
|
||||
018a0000 00000150 ENTRY.obj (.text)
|
||||
018a0150 00000780 FMT.obj (.text)
|
||||
018a08d0 000015c0 COMPAT.obj (.text)
|
||||
018a1e90 00000110 rts.lib (.text)
|
||||
|
||||
.cinit 0 018a1fa0 00000010
|
||||
018a1fa0 00000010 CINITZ.obj (.cinit)
|
||||
|
||||
.bss 0 01a00000 00008320 UNINITIALIZED
|
||||
01a00000 00008000 ENTRY.obj (.bss)
|
||||
01a08000 00000300 FMT.obj (.bss)
|
||||
01a08300 00000020 COMPAT.obj (.bss)
|
||||
|
||||
.data 0 018a0000 00000000 UNINITIALIZED
|
||||
|
||||
|
||||
GLOBAL SYMBOLS
|
||||
|
||||
address name address name
|
||||
-------- ---- -------- ----
|
||||
01a00000 .bss 018a0000 .text
|
||||
018a0000 .data 018a0000 .data
|
||||
018a0000 .text 018a0000 edata
|
||||
018a08d0 _fflush 018a0000 _start
|
||||
018a0150 _main 018a0150 _main
|
||||
01a08000 _out 018a08d0 _fflush
|
||||
018a10d0 _sprintf 018a10d0 _sprintf
|
||||
018a0000 _start 018a1e90 _strlen
|
||||
01a08300 _stdout 018a1fa0 cinit
|
||||
018a1e90 _strlen 018a1fa0 etext
|
||||
018a1fa0 cinit 01a00000 .bss
|
||||
018a0000 edata 01a08000 _out
|
||||
01a08320 end 01a08300 _stdout
|
||||
018a1fa0 etext 01a08320 end
|
||||
|
||||
[14 symbols]
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* compat.c -- the small libc surface termtris needs on the VT1200.
|
||||
*
|
||||
* The TI r5.01 runtime has no stdio and no sprintf. This implements exactly
|
||||
* the decimal/string formats game.c uses. Arguments are walked manually
|
||||
* because the SDK's va_arg macro does not survive the 1990 compiler's
|
||||
* preprocessor here; the TI ABI lays the varargs contiguously below &fmt.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
|
||||
FILE *stdout;
|
||||
extern int strlen(char *);
|
||||
|
||||
int fflush(FILE *fp)
|
||||
{
|
||||
(void)fp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *fmt_num(char *out, long v, int width, int zero, int left, int unsig)
|
||||
{
|
||||
char tmp[24];
|
||||
int n = 0, neg = 0, pad, i;
|
||||
unsigned long u;
|
||||
|
||||
if(v < 0 && !unsig) {
|
||||
neg = 1;
|
||||
u = (unsigned long)(-v);
|
||||
} else {
|
||||
u = (unsigned long)v;
|
||||
}
|
||||
do {
|
||||
tmp[n++] = (char)('0' + (u % 10UL));
|
||||
u /= 10UL;
|
||||
} while(u);
|
||||
|
||||
pad = width - (n + neg);
|
||||
if(pad < 0) pad = 0;
|
||||
|
||||
if(left) {
|
||||
if(neg) *out++ = '-';
|
||||
while(n) *out++ = tmp[--n];
|
||||
for(i = 0; i < pad; i++) *out++ = ' ';
|
||||
} else if(zero) {
|
||||
if(neg) *out++ = '-';
|
||||
for(i = 0; i < pad; i++) *out++ = '0';
|
||||
while(n) *out++ = tmp[--n];
|
||||
} else {
|
||||
for(i = 0; i < pad; i++) *out++ = ' ';
|
||||
if(neg) *out++ = '-';
|
||||
while(n) *out++ = tmp[--n];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
int sprintf(char *buf, char *fmt, ...)
|
||||
{
|
||||
char *out = buf;
|
||||
char *ap = (char *)&fmt;
|
||||
|
||||
while(*fmt) {
|
||||
int left = 0, zero = 0, width = 0, unsig = 0, islong = 0;
|
||||
|
||||
if(*fmt != '%') {
|
||||
*out++ = *fmt++;
|
||||
continue;
|
||||
}
|
||||
fmt++;
|
||||
if(*fmt == '%') {
|
||||
*out++ = '%';
|
||||
fmt++;
|
||||
continue;
|
||||
}
|
||||
if(*fmt == '-') { left = 1; fmt++; }
|
||||
if(*fmt == '0') { zero = 1; fmt++; }
|
||||
while(*fmt >= '0' && *fmt <= '9') {
|
||||
width = width * 10 + (*fmt - '0');
|
||||
fmt++;
|
||||
}
|
||||
if(*fmt == 'l') { islong = 1; fmt++; }
|
||||
if(*fmt == 'u') { unsig = 1; }
|
||||
|
||||
switch(*fmt) {
|
||||
case 'd':
|
||||
case 'u':
|
||||
{
|
||||
long v;
|
||||
ap -= sizeof(long);
|
||||
v = *(long *)ap;
|
||||
(void)islong;
|
||||
out = fmt_num(out, v, width, zero, left, unsig);
|
||||
fmt++;
|
||||
}
|
||||
break;
|
||||
|
||||
case 's':
|
||||
{
|
||||
char *s;
|
||||
int len, i, pad;
|
||||
ap -= sizeof(char *);
|
||||
s = *(char **)ap;
|
||||
if(!s) s = "(null)";
|
||||
len = strlen(s);
|
||||
pad = width - len;
|
||||
if(pad < 0) pad = 0;
|
||||
if(!left) for(i = 0; i < pad; i++) *out++ = ' ';
|
||||
for(i = 0; i < len; i++) *out++ = s[i];
|
||||
if(left) for(i = 0; i < pad; i++) *out++ = ' ';
|
||||
fmt++;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
{
|
||||
int c;
|
||||
ap -= sizeof(int);
|
||||
c = *(int *)ap;
|
||||
*out++ = (char)c;
|
||||
fmt++;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
*out++ = '%';
|
||||
if(*fmt) *out++ = *fmt++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
*out = 0;
|
||||
return (int)(out - buf);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* inttypes.h -- the fixed-width types game.c uses, for the 1990 TI compiler.
|
||||
*/
|
||||
#ifndef VT_INTTYPES_H_
|
||||
#define VT_INTTYPES_H_
|
||||
|
||||
typedef unsigned char uint8_t;
|
||||
typedef signed short int16_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef signed long int32_t;
|
||||
typedef unsigned long uint32_t;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* stdio.h -- the tiny subset termtris needs on the VT1200.
|
||||
* There is no stdio in the TI r5.01 runtime; compat.c provides sprintf.
|
||||
*/
|
||||
#ifndef VT_STDIO_H_
|
||||
#define VT_STDIO_H_
|
||||
|
||||
typedef int FILE;
|
||||
|
||||
extern FILE *stdout;
|
||||
|
||||
int sprintf(char *buf, char *fmt, ...);
|
||||
int fflush(FILE *fp);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* fmt.c -- Task 2 acceptance: the exact sprintf formats game.c uses.
|
||||
* Results are NUL-terminated in out[6][16]; tests/fmt.lua prints them.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
|
||||
char out[6][16];
|
||||
|
||||
int main(void)
|
||||
{
|
||||
sprintf(out[0], "%10ld", 12345L);
|
||||
sprintf(out[1], "%2ld", 7L);
|
||||
sprintf(out[2], "%8ld", 42L);
|
||||
sprintf(out[3], "%2d. %s %ld", 1, "ab", 99L);
|
||||
sprintf(out[4], "%-3s", "x");
|
||||
sprintf(out[5], "%%");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
-- loader.lua -- debugger-assisted COFF loader acceptance harness.
|
||||
--
|
||||
-- Boots the firmware with VT1200_SKIP_LEVEL1=1 (the documented divergence) to
|
||||
-- the OS, then at frame LOADER_AT pokes:
|
||||
-- * the loader payload at bit 0x01880000 (raw dwt/loader/build/loader.bin)
|
||||
-- * a GSPLNK COFF image at LOADER_BUF (default bit 0x01900000)
|
||||
-- * the loader descriptor at 0x01882C00
|
||||
-- and sets PC = 0x01880000. It does not use the network, the rdebug service
|
||||
-- or any firmware transport: this is the debugger-assisted milestone.
|
||||
--
|
||||
-- The loader itself copies the COFF sections to their linked bit addresses and
|
||||
-- calls create_process(entry, "LOADER", 0x6000, 1, 0). This script only pokes
|
||||
-- the input and reads the result; it never writes the app's code or markers.
|
||||
--
|
||||
-- Env:
|
||||
-- LOADER_BIN loader payload (default dwt/loader/build/loader.bin)
|
||||
-- LOADER_COFF app COFF (default dwt/loader/build/marker.coff)
|
||||
-- LOADER_BUF COFF buffer bit address (default 0x01900000)
|
||||
-- LOADER_AT injection frame (default 2500)
|
||||
-- LOADER_END inspection/exit frame (default 2700)
|
||||
-- LOADER_OUT evidence directory (default dwt/loader/evidence)
|
||||
-- LOADER_SNAP 1 = write a PNG at LOADER_END (default 0)
|
||||
-- LOADER_VERBOSE 1 = print the result block every 25 frames
|
||||
|
||||
local function hex(v, w) return string.format("%0" .. (w or 8) .. "X", v) end
|
||||
|
||||
local machine = manager.machine
|
||||
local FMT_MARK = tonumber(os.getenv("FMT_MARK") or "0x01A00000")
|
||||
local cpu = machine.devices[':maincpu']
|
||||
local dbg = machine.debugger
|
||||
local prog = cpu.spaces['program']
|
||||
|
||||
if not dbg then
|
||||
print("LOADER: debugger not enabled; pass -debug")
|
||||
machine:exit()
|
||||
return
|
||||
end
|
||||
|
||||
local AT = tonumber(os.getenv("LOADER_AT") or "2500")
|
||||
local ENDF = tonumber(os.getenv("LOADER_END") or "2700")
|
||||
local BIN = os.getenv("LOADER_BIN") or "dwt/loader/build/loader.bin"
|
||||
local COFF = os.getenv("LOADER_COFF") or "dwt/loader/build/marker.coff"
|
||||
local BUF = tonumber(os.getenv("LOADER_BUF") or "0x01900000")
|
||||
local OUT = os.getenv("LOADER_OUT") or "dwt/loader/evidence"
|
||||
local SNAP = os.getenv("LOADER_SNAP") == "1"
|
||||
local MODE = tonumber(os.getenv("LOADER_MODE") or "0")
|
||||
local A8DESC = os.getenv("LOADER_A8_DESC") == "1"
|
||||
local VERBOSE = os.getenv("LOADER_VERBOSE") == "1"
|
||||
|
||||
local LOADER_BASE = 0x01880000
|
||||
local DESC = 0x01882C00
|
||||
local RESULT = 0x01882D00
|
||||
local RETURN_STUB = 0x01883100
|
||||
local RETURN_SP = 0x01883110
|
||||
|
||||
local function rd(p) return io.open(p, "rb") end
|
||||
local function slurp(p)
|
||||
local f, err = rd(p)
|
||||
if not f then error("cannot open " .. p .. ": " .. tostring(err)) end
|
||||
local s = f:read("*a")
|
||||
f:close()
|
||||
return s
|
||||
end
|
||||
|
||||
local function u16(s, o)
|
||||
return s:byte(o + 1) + s:byte(o + 2) * 256
|
||||
end
|
||||
|
||||
local function u32(s, o)
|
||||
return s:byte(o + 1) + s:byte(o + 2) * 256
|
||||
+ s:byte(o + 3) * 65536 + s:byte(o + 4) * 16777216
|
||||
end
|
||||
|
||||
local function parse_coff(d)
|
||||
local c = { data = d }
|
||||
c.magic = u16(d, 0)
|
||||
c.nscns = u16(d, 2)
|
||||
c.opthdr = u16(d, 16)
|
||||
c.sections = {}
|
||||
if c.opthdr > 0 then
|
||||
c.optmagic = u16(d, 20)
|
||||
c.entry = u32(d, 20 + 16)
|
||||
c.textstart = u32(d, 20 + 20)
|
||||
c.datastart = u32(d, 20 + 24)
|
||||
end
|
||||
local base = 20 + c.opthdr
|
||||
for i = 0, c.nscns - 1 do
|
||||
local o = base + i * 40
|
||||
local name = d:sub(o + 1, o + 8):gsub("%z", "")
|
||||
c.sections[#c.sections + 1] = {
|
||||
name = name,
|
||||
paddr = u32(d, o + 8),
|
||||
size = u32(d, o + 16), -- bits
|
||||
scnptr = u32(d, o + 20),
|
||||
flags = u32(d, o + 36),
|
||||
}
|
||||
end
|
||||
return c
|
||||
end
|
||||
|
||||
local function poke_bytes(bitaddr, data)
|
||||
local n = math.floor((#data + 1) / 2)
|
||||
for i = 0, n - 1 do
|
||||
local lo = data:byte(i * 2 + 1) or 0
|
||||
local hi = data:byte(i * 2 + 2) or 0
|
||||
prog:write_u16(bitaddr + i * 16, lo + hi * 256)
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
local function verify_bytes(bitaddr, data)
|
||||
local n = math.floor((#data + 1) / 2)
|
||||
local bad = 0
|
||||
for i = 0, n - 1 do
|
||||
local lo = data:byte(i * 2 + 1) or 0
|
||||
local hi = data:byte(i * 2 + 2) or 0
|
||||
if prog:read_u16(bitaddr + i * 16) ~= lo + hi * 256 then
|
||||
bad = bad + 1
|
||||
end
|
||||
end
|
||||
return n, bad
|
||||
end
|
||||
|
||||
local function w32(a, v) prog:write_u32(a, v) end
|
||||
local function r32(a)
|
||||
local ok, v = pcall(function() return prog:read_u32(a) end)
|
||||
if ok then return v end
|
||||
return 0xDEADBEEF
|
||||
end
|
||||
|
||||
local function rstr(a, n)
|
||||
local t = {}
|
||||
for i = 0, n - 1 do
|
||||
local c = prog:read_u8(a + i * 8)
|
||||
if c == 0 then break end
|
||||
t[#t + 1] = string.char(c)
|
||||
end
|
||||
return table.concat(t)
|
||||
end
|
||||
|
||||
local loader = slurp(BIN)
|
||||
local coff = slurp(COFF)
|
||||
local app = parse_coff(coff)
|
||||
|
||||
print(string.format("LOADER: payload %s (%d B) -> bit %s", BIN, #loader, hex(LOADER_BASE)))
|
||||
print(string.format("LOADER: COFF %s (%d B) -> bit %s", COFF, #coff, hex(BUF)))
|
||||
print(string.format("LOADER: file magic=%04X nscns=%d opthdr=%d optmagic=%s entry=%s textstart=%s",
|
||||
app.magic, app.nscns, app.opthdr,
|
||||
app.optmagic and string.format("%04X", app.optmagic) or "none",
|
||||
hex(app.entry or 0), hex(app.textstart or 0)))
|
||||
for _, s in ipairs(app.sections) do
|
||||
print(string.format("LOADER: sec %-8s paddr=%s size=%d bits (%d B) scnptr=%#x flags=%#x",
|
||||
s.name, hex(s.paddr), s.size, math.floor(s.size / 8), s.scnptr, s.flags))
|
||||
end
|
||||
|
||||
local TRACE = os.getenv("LOADER_TRACE") == "1"
|
||||
|
||||
local frames = 0
|
||||
local injected = false
|
||||
local done = false
|
||||
local last_count = -1
|
||||
|
||||
local function status_name(v)
|
||||
local t = {
|
||||
[0] = "OK",
|
||||
[1] = "bad descriptor",
|
||||
[2] = "truncated COFF / section headers",
|
||||
[3] = "bad COFF magic",
|
||||
[4] = "section size not whole words",
|
||||
[5] = "target outside RAM",
|
||||
[6] = "target overlaps loader page or COFF buffer",
|
||||
[7] = "relocation records present (unsupported)",
|
||||
[8] = "target not in a free-list block",
|
||||
[9] = "no entry point",
|
||||
}
|
||||
return t[v] or ("error " .. tostring(v))
|
||||
end
|
||||
|
||||
local function result_dump()
|
||||
local st = r32(RESULT + 0x000)
|
||||
print(string.format(
|
||||
"LOADER: result status=%d (%s) entry=%s text=%s/%d bits data=%s/%d bits bss=%s/%d bits relocs=%d pcb=%s nscns=%d len=%d",
|
||||
st, status_name(st), hex(r32(RESULT + 0x020)),
|
||||
hex(r32(RESULT + 0x040)), r32(RESULT + 0x060),
|
||||
hex(r32(RESULT + 0x080)), r32(RESULT + 0x0A0),
|
||||
hex(r32(RESULT + 0x0C0)), r32(RESULT + 0x0E0),
|
||||
r32(RESULT + 0x100), hex(r32(RESULT + 0x120)),
|
||||
r32(RESULT + 0x140), r32(RESULT + 0x160)))
|
||||
end
|
||||
|
||||
local function inject()
|
||||
-- 1. loader itself
|
||||
local n, bad = 0, 0
|
||||
poke_bytes(LOADER_BASE, loader)
|
||||
n, bad = verify_bytes(LOADER_BASE, loader)
|
||||
print(string.format("LOADER: loader poked %d words, readback %d bad at %s",
|
||||
n, bad, hex(LOADER_BASE)))
|
||||
|
||||
-- 2. COFF image
|
||||
poke_bytes(BUF, coff)
|
||||
n, bad = verify_bytes(BUF, coff)
|
||||
print(string.format("LOADER: COFF poked %d words, readback %d bad at %s",
|
||||
n, bad, hex(BUF)))
|
||||
|
||||
-- 3. pre-fill every .bss section with A5A5, so a loaded zero is meaningful
|
||||
for _, s in ipairs(app.sections) do
|
||||
if s.flags & 0x80 ~= 0 then
|
||||
for i = 0, math.floor(s.size / 16) - 1 do
|
||||
prog:write_u16(s.paddr + i * 16, 0xA5A5)
|
||||
end
|
||||
print(string.format("LOADER: pre-filled .bss %s..%s with A5A5",
|
||||
hex(s.paddr), hex(s.paddr + s.size)))
|
||||
end
|
||||
end
|
||||
|
||||
-- 4. descriptor: magic "LD" "R1", buf, len, mode=0 (spin)
|
||||
w32(DESC + 0x00, 0)
|
||||
prog:write_u16(DESC + 0x00, 0x4C44)
|
||||
prog:write_u16(DESC + 0x10, 0x5231)
|
||||
w32(DESC + 0x20, BUF)
|
||||
w32(DESC + 0x40, #coff)
|
||||
w32(DESC + 0x60, MODE)
|
||||
print(string.format("LOADER: descriptor @%s magic=%04X/%04X buf=%s len=%d mode=%d",
|
||||
hex(DESC), prog:read_u16(DESC + 0x00), prog:read_u16(DESC + 0x10),
|
||||
hex(r32(DESC + 0x20)), r32(DESC + 0x40), r32(DESC + 0x60)))
|
||||
|
||||
-- 5. return stub. mode 0 ignores it (the loader jumps to ORIG_PC
|
||||
-- itself); mode 1 returns here, and the stub resumes the interrupted
|
||||
-- PC so the caller yields and the scheduler can run the child.
|
||||
local orig_pc = cpu.state['PC'].value
|
||||
if MODE == 1 then
|
||||
prog:write_u16(RETURN_STUB, 0xC080) -- jauc <orig_pc>
|
||||
prog:write_u16(RETURN_STUB + 0x10, orig_pc & 0xFFFF)
|
||||
prog:write_u16(RETURN_STUB + 0x20, math.floor(orig_pc / 0x10000) & 0xFFFF)
|
||||
else
|
||||
prog:write_u16(RETURN_STUB, 0xC0FF) -- jruc self
|
||||
end
|
||||
|
||||
-- 6. enter the loader. A8=0 selects the fixed descriptor; SP points at
|
||||
-- the return slot so mode=1 would return to the spin stub.
|
||||
-- Leave SP alone for mode 0: the loader pushes onto the current
|
||||
-- process stack, then restores it and resumes at ORIG_PC. (mode 1
|
||||
-- would use the return stub.)
|
||||
w32(0x01882A20, orig_pc) -- LD_ORIG_PC
|
||||
print(string.format("LOADER: will resume at %s after create_process", hex(orig_pc)))
|
||||
cpu.state['A8'].value = A8DESC and DESC or 0
|
||||
if MODE == 1 then
|
||||
-- Simulate a CALLA from the current process: push the return
|
||||
-- address on the *firmware* stack (a real transport would do this).
|
||||
local sp = cpu.state['SP'].value - 0x20
|
||||
w32(sp, RETURN_STUB)
|
||||
cpu.state['SP'].value = sp
|
||||
end
|
||||
cpu.state['PC'].value = LOADER_BASE
|
||||
injected = true
|
||||
print(string.format("LOADER: frame %d: entered loader at %s (caller SP=%s)",
|
||||
frames, hex(LOADER_BASE), hex(cpu.state['SP'].value)))
|
||||
end
|
||||
|
||||
local function inspect()
|
||||
result_dump()
|
||||
|
||||
-- Verify the placement from RAM: .text/.data must equal the file bytes.
|
||||
for _, s in ipairs(app.sections) do
|
||||
if s.scnptr ~= 0 and s.size > 0 then
|
||||
local nb = math.floor(s.size / 8)
|
||||
local bytes = coff:sub(s.scnptr + 1, s.scnptr + nb)
|
||||
local _, bad = verify_bytes(s.paddr, bytes)
|
||||
print(string.format("LOADER: placed %s at %s: %d bytes, %d differing",
|
||||
s.name, hex(s.paddr), nb, bad))
|
||||
end
|
||||
end
|
||||
|
||||
local base = COFF:match("([^/]+)$") or COFF
|
||||
if base:find("marker") then
|
||||
print(string.format("LOADER: marker app: start=%s data=%s data2=%s bss=%s count=%d",
|
||||
hex(r32(0x018E0000)), hex(r32(0x018E0020)), hex(r32(0x018E0040)),
|
||||
hex(r32(0x018E0060)), r32(0x018E0080)))
|
||||
elseif base:find("xhello") then
|
||||
print(string.format("LOADER: xhello: stage=%d strlen=%d display=%s root=%s window=%s sync=%s",
|
||||
r32(0x01890140), r32(0x01890160), hex(r32(0x01890100)),
|
||||
hex(r32(0x01890110)), hex(r32(0x01890120)), hex(r32(0x01890130))))
|
||||
end
|
||||
if base:find("fmt") then
|
||||
for i = 0, 5 do
|
||||
print(string.format("FMT:%d [%s]", i, rstr(FMT_MARK + i * 0x80, 16)))
|
||||
end
|
||||
end
|
||||
print(string.format("LOADER: PC at inspection = %s", hex(cpu.state['PC'].value)))
|
||||
end
|
||||
|
||||
emu.register_frame_done(function()
|
||||
frames = frames + 1
|
||||
if frames == AT and not injected then
|
||||
inject()
|
||||
if TRACE then dbg:command("trace /tmp/opencode/ld1.tr") end
|
||||
end
|
||||
if injected and TRACE and frames == AT + 1 then
|
||||
dbg:command("trace off")
|
||||
print("LOADER: trace written to /tmp/opencode/ld.tr")
|
||||
end
|
||||
if injected and not done then
|
||||
if TRACE and frames <= AT + 40 then
|
||||
print(string.format("LOADER: trace f=%d PC=%s SP=%s A14=%s status=%s entry=%s",
|
||||
frames, hex(cpu.state['PC'].value), hex(cpu.state['SP'].value),
|
||||
hex(cpu.state['A14'].value), hex(r32(RESULT + 0x000)),
|
||||
hex(r32(RESULT + 0x020))))
|
||||
end
|
||||
local st = r32(RESULT + 0x000)
|
||||
-- status 0 means OK; nonzero is final too once parsing has begun
|
||||
if st ~= 0xFFFFFFFF and not done then
|
||||
-- The loader writes 0 on success. A nonzero status is final
|
||||
-- only after it has been written once (it is 0 from power-on,
|
||||
-- so require the loader to have run: PC no longer at entry or
|
||||
-- the app counter moved).
|
||||
end
|
||||
if VERBOSE and frames % 25 == 0 then
|
||||
result_dump()
|
||||
end
|
||||
if base_contains_marker then
|
||||
local c = r32(0x018E0080)
|
||||
if c ~= last_count then
|
||||
if last_count ~= -1 then
|
||||
print(string.format("LOADER: marker counter %d -> %d (child running)",
|
||||
last_count, c))
|
||||
end
|
||||
last_count = c
|
||||
end
|
||||
end
|
||||
end
|
||||
if frames >= ENDF then
|
||||
if injected then
|
||||
done = true
|
||||
inspect()
|
||||
if SNAP then
|
||||
local screen = nil
|
||||
for _, s in pairs(machine.screens) do screen = s break end
|
||||
if screen then
|
||||
local out = OUT .. "/loader_frame" .. frames .. ".png"
|
||||
local ok, err = pcall(function() return screen:snapshot(out) end)
|
||||
print(string.format("LOADER: snapshot %s ok=%s %s",
|
||||
out, tostring(ok), tostring(err or "")))
|
||||
end
|
||||
end
|
||||
end
|
||||
machine:exit()
|
||||
end
|
||||
end, "vt1200-loader")
|
||||
|
||||
-- (Lua has no forward use of a name defined later; set a small flag here.)
|
||||
base_contains_marker = (COFF:match("([^/]+)$") or ""):find("marker") ~= nil
|
||||
|
||||
dbg.visible_cpu = cpu
|
||||
dbg.execution_state = 'run'
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/bin/sh
|
||||
# fmt.sh -- Task 2 acceptance. Builds tests/fmt.c with compat.c, loads it, and
|
||||
# checks the six formatted strings byte-for-byte.
|
||||
set -eu
|
||||
|
||||
DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
APP=$(cd "$DIR/.." && pwd)
|
||||
ROOT=$(cd "$APP/../.." && pwd)
|
||||
M=${MAME:-/home/arti/code/k-space/mame0289/vt1200}
|
||||
LOG="$APP/build/fmt.log"
|
||||
|
||||
mkdir -p "$APP/build"
|
||||
"$APP/build.sh" "$DIR/fmt.c" "$APP/src/vt1200/compat.c" >"$APP/build/fmt-build.log" 2>&1
|
||||
|
||||
FMT=$(awk '/_out/ {print $1; exit}' "$APP/build/fmt.map")
|
||||
VT1200_SKIP_LEVEL1=1 FMT_MARK="0x$FMT" \
|
||||
LOADER_AT=2500 LOADER_END=2700 \
|
||||
LOADER_BIN="$ROOT/dwt/loader/build/loader.bin" \
|
||||
LOADER_COFF="$APP/build/fmt.coff" \
|
||||
LOADER_OUT="$APP/build" \
|
||||
"$M" vt1200 -rompath "$ROOT/harness/mame/roms" \
|
||||
-video none -sound none -nothrottle -skip_gameinfo \
|
||||
-debug -debugger none \
|
||||
-autoboot_script "$DIR/fmt.lua" -autoboot_delay 0 \
|
||||
>"$LOG" 2>&1 || true
|
||||
|
||||
grep 'FMT:' "$LOG" | tail -8
|
||||
for want in 'FMT:0 [ 12345]' 'FMT:1 [ 7]' 'FMT:2 [ 42]' \
|
||||
'FMT:3 [ 1. ab 99]' 'FMT:4 [x ]' 'FMT:5 [%]'; do
|
||||
grep -Fq "$want" "$LOG" || {
|
||||
echo "fmt.sh: missing: $want (see $LOG)" >&2
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
echo "fmt: PASS"
|
||||
Reference in new issue
Block a user