tests: M1 GSPA/MAME smoke test
This commit is contained in:
1 parent
e081921b72
commit
e0a721e3e1
9 files changed
+262
No files matched your search
@@ -0,0 +1 @@
|
||||
int add(int a, int b) { return a + b; }
|
||||
@@ -0,0 +1,3 @@
|
||||
static int arr[4];
|
||||
|
||||
int *second(void) { return &arr[1]; }
|
||||
@@ -0,0 +1,36 @@
|
||||
; entry.asm -- M1 smoke-test entry, linked at bit 018A0000.
|
||||
;
|
||||
; M1 has no stack frame, no calls and no TI ABI. The generated functions are
|
||||
; leaf functions: i32/pointer arguments in A0-A3, result in A0.
|
||||
;
|
||||
; Marker words (bit addresses, 0x20 bits apart = 4 bytes):
|
||||
; 018A8000 add(20, 22)
|
||||
; 018A8020 load(018A8100)
|
||||
; 018A8040 second() == &arr[1]
|
||||
;
|
||||
; _late is linked but not called: it is a compile-time check that a surviving
|
||||
; GEP offset is 24 bits, not 192.
|
||||
.text
|
||||
.globl _start
|
||||
_start:
|
||||
setf 32, 0, 1 ; FS1 = 32 for 32-bit MOVE
|
||||
setf 16, 0, 0 ; FS0 = 16, for any i16 code
|
||||
movi 20, a0
|
||||
movi 22, a1
|
||||
calla _add
|
||||
movi 018A8000h, a2
|
||||
move a0, *a2, 1
|
||||
|
||||
movi 018A8100h, a2
|
||||
movi 12345678h, a3
|
||||
move a3, *a2, 1
|
||||
move a2, a0
|
||||
calla _load
|
||||
movi 018A8020h, a2
|
||||
move a0, *a2, 1
|
||||
|
||||
calla _second
|
||||
movi 018A8040h, a2
|
||||
move a0, *a2, 1
|
||||
|
||||
rets
|
||||
@@ -0,0 +1,9 @@
|
||||
/* Late-GEP check: p + 3 bytes must reach llc as ADDI 24 -- the address unit is
|
||||
* a bit and tms-bitaddr has already scaled the offset -- not ADDI 192.
|
||||
*
|
||||
* The plan's source also compared p + 3 with another pointer. That form
|
||||
* lowers to setcc/branches, which the M1 backend deliberately does not have;
|
||||
* this form exercises the same late-GEP contract without it. See the M1
|
||||
* implementation design, sections 7.3 and 7.5.
|
||||
*/
|
||||
int late(char *p) { return (int)(p + 3); }
|
||||
@@ -0,0 +1,16 @@
|
||||
-a
|
||||
-o M1.OUT
|
||||
-m M1.MAP
|
||||
-e _start
|
||||
M1.obj
|
||||
MEMORY
|
||||
{
|
||||
prog: origin = 018A0000h, length = 8000h
|
||||
ram: origin = 018A9000h, length = 1000h
|
||||
}
|
||||
SECTIONS
|
||||
{
|
||||
.text: {} > prog
|
||||
.data: {} > prog
|
||||
.bss: {} > ram
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
int load(int *p) { return *p; }
|
||||
@@ -0,0 +1,36 @@
|
||||
-- m1-check.lua -- load the M1 COFF with dwt/loader/loader.lua and read the
|
||||
-- marker words the generated code wrote.
|
||||
--
|
||||
-- M1_LOADER_LUA path to vendor/vt1200/dwt/loader/loader.lua
|
||||
-- M1_EXPECT_LOAD expected load() result (default 0x12345678)
|
||||
-- M1_EXPECT_SECOND expected &arr[1] in bits
|
||||
--
|
||||
-- The marker read runs one frame before loader.lua's inspection/exit frame.
|
||||
|
||||
dofile(os.getenv("M1_LOADER_LUA"))
|
||||
|
||||
local ENDF = tonumber(os.getenv("LOADER_END") or "2700")
|
||||
local EXPECT_LOAD = tonumber(os.getenv("M1_EXPECT_LOAD") or "0x12345678")
|
||||
local EXPECT_SECOND = tonumber(os.getenv("M1_EXPECT_SECOND") or "0x018A9020")
|
||||
local frames = 0
|
||||
local checked = false
|
||||
|
||||
emu.register_frame_done(function()
|
||||
frames = frames + 1
|
||||
if frames == ENDF - 1 and not checked then
|
||||
checked = true
|
||||
local prog = manager.machine.devices[':maincpu'].spaces['program']
|
||||
local add = prog:read_u32(0x018A8000)
|
||||
local load = prog:read_u32(0x018A8020)
|
||||
local second = prog:read_u32(0x018A8040)
|
||||
print(string.format("M1: add=%d load=0x%08X second=0x%08X",
|
||||
add, load, second))
|
||||
if add == 42 and load == EXPECT_LOAD and second == EXPECT_SECOND then
|
||||
print("M1 MAME: PASS")
|
||||
else
|
||||
print(string.format(
|
||||
"M1 MAME: FAIL (want add=42 load=0x%08X second=0x%08X)",
|
||||
EXPECT_LOAD, EXPECT_SECOND))
|
||||
end
|
||||
end
|
||||
end, "m1-check")
|
||||
Executable
+154
@@ -0,0 +1,154 @@
|
||||
#!/bin/sh
|
||||
# M1 end-to-end smoke test.
|
||||
#
|
||||
# scripts/m1/run.sh
|
||||
#
|
||||
# Compiles four C functions with scripts/tms-clang + llc -O0, assembles and
|
||||
# links them with the 1987 GSPA/GSPLNK 2.00 under DOSBox, then loads the COFF
|
||||
# into MAME through dwt/loader/loader.lua and checks the marker words:
|
||||
#
|
||||
# add(20, 22) == 42
|
||||
# load(&word) == 0x12345678
|
||||
# second() == &arr[1] (arr + 32 bits)
|
||||
#
|
||||
# Overridable: TMS_CLANG TMS_OPT TMS_BITADDR TMS_LLC MAME DOSBOX DOSBOX_CONF
|
||||
# SDK_IMG M1_WORK M1_SDK LOADER_AT LOADER_END
|
||||
set -eu
|
||||
|
||||
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
|
||||
DIR=$ROOT/scripts/m1
|
||||
|
||||
LLVM_BIN=${LLVM_BIN:-$ROOT/build/llvm-clean-build/bin}
|
||||
TMS_CLANG=${TMS_CLANG:-$LLVM_BIN/clang}
|
||||
TMS_OPT=${TMS_OPT:-$LLVM_BIN/opt}
|
||||
TMS_BITADDR=${TMS_BITADDR:-$ROOT/tools/tms-bitaddr/tms-bitaddr}
|
||||
TMS_LLC=${TMS_LLC:-$LLVM_BIN/llc}
|
||||
|
||||
MAME=${MAME:-$ROOT/build/mame-src/vt1200}
|
||||
DOSBOX=${DOSBOX:-dosbox}
|
||||
DOSBOX_CONF=${DOSBOX_CONF:-$ROOT/vendor/vt1200/harness/sdk-scratch/dosbox-bench.conf}
|
||||
SDK_IMG=${SDK_IMG:-$ROOT/vendor/vt1200/tools/tms34010-sdk/1987-05 TMS34010 SDK/1987-11-06 TMS34010 Simulator-C Tools-ASM Tools.img}
|
||||
|
||||
WORK=${M1_WORK:-$ROOT/build/m1}
|
||||
SDK=${M1_SDK:-$ROOT/build/m1-sdk}
|
||||
ASM=$WORK/asm
|
||||
LOADER_LUA=$ROOT/vendor/vt1200/dwt/loader/loader.lua
|
||||
LOADER_BIN=$ROOT/vendor/vt1200/dwt/loader/build/loader.bin
|
||||
LOADER_AT=${LOADER_AT:-2500}
|
||||
LOADER_END=${LOADER_END:-2700}
|
||||
|
||||
fail() {
|
||||
echo "m1: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
for tool in "$TMS_CLANG" "$TMS_OPT" "$TMS_BITADDR" "$TMS_LLC"; do
|
||||
[ -x "$tool" ] || fail "missing tool: $tool"
|
||||
done
|
||||
|
||||
mkdir -p "$ASM" "$WORK/evidence" "$WORK/run/nvram" "$WORK/run/cfg"
|
||||
|
||||
echo "== compile and select"
|
||||
export TMS_CLANG TMS_OPT TMS_BITADDR
|
||||
for name in add load late const_gep; do
|
||||
"$ROOT/scripts/tms-clang" -O2 -o "$ASM/$name.ll" "$DIR/$name.c"
|
||||
"$TMS_LLC" -march=tms34010 -O0 -filetype=asm \
|
||||
"$ASM/$name.ll" -o "$ASM/$name.s" \
|
||||
|| fail "llc failed for $name.c"
|
||||
echo " $name.s: $(grep -c . "$ASM/$name.s") lines"
|
||||
done
|
||||
|
||||
echo "== check the bit-address constants"
|
||||
grep -Eq '^[[:space:]]*ADDI[[:space:]]+24,[[:space:]]*A0' "$ASM/late.s" \
|
||||
|| fail "late.s has no ADDI 24 (late GEP got scaled again?)"
|
||||
grep -Eq 'ADDI[[:space:]]+192' "$ASM/late.s" \
|
||||
&& fail "late.s contains ADDI 192: late GEP was scaled twice"
|
||||
grep -Eq '^[[:space:]]*MOVI[[:space:]]+_arr\+32,[[:space:]]*A0' "$ASM/const_gep.s" \
|
||||
|| fail "const_gep.s has no 'MOVI _arr+32': constant GEP addend is not in bits"
|
||||
grep -Eq '_arr\+4[^0-9]' "$ASM/const_gep.s" \
|
||||
&& fail "const_gep.s references _arr+4 (bytes) instead of _arr+32 (bits)"
|
||||
echo " late.s: $(grep -E 'ADDI' "$ASM/late.s" | head -1 | sed 's/^[[:space:]]*//')"
|
||||
echo " const_gep.s: $(grep -E 'MOVI' "$ASM/const_gep.s" | head -1 | sed 's/^[[:space:]]*//')"
|
||||
|
||||
echo "== extract the 1987 SDK"
|
||||
if [ ! -f "$SDK/GSPA.EXE" ]; then
|
||||
command -v mcopy >/dev/null 2>&1 || fail "mtools (mcopy) is required"
|
||||
[ -f "$SDK_IMG" ] || fail "SDK image not found: $SDK_IMG"
|
||||
mkdir -p "$SDK"
|
||||
mcopy -s -n -i "$SDK_IMG" "::/A2/*" "$SDK/"
|
||||
mcopy -s -n -i "$SDK_IMG" "::/C2/*" "$SDK/"
|
||||
fi
|
||||
[ -f "$SDK/GSPA.EXE" ] || fail "GSPA.EXE not extracted into $SDK"
|
||||
|
||||
echo "== assemble and link with GSPA/GSPLNK under DOSBox"
|
||||
cat "$DIR/entry.asm" "$ASM/add.s" "$ASM/load.s" "$ASM/late.s" \
|
||||
"$ASM/const_gep.s" > "$SDK/M1.ASM"
|
||||
# The linker command file must keep CRLF line endings.
|
||||
sed 's/\r$//' "$DIR/link.cmd" | sed 's/$/\r/' > "$SDK/M1.CMD"
|
||||
rm -f "$SDK/M1.OBJ" "$SDK/M1.OUT" "$SDK/M1.MAP" "$SDK/M1.LST"
|
||||
|
||||
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
|
||||
timeout "${DOSBOX_TIMEOUT:-300}" "$DOSBOX" -conf "$DOSBOX_CONF" \
|
||||
-noprimaryconf -nolocalconf \
|
||||
-c "mount c $SDK" -c "c:" \
|
||||
-c "gspa M1 M1.obj M1.lst -l" \
|
||||
-c "gsplnk M1.cmd" \
|
||||
-c "exit" -exit > "$WORK/dosbox.log" 2>&1 \
|
||||
|| { tail -20 "$WORK/dosbox.log" >&2; fail "DOSBox failed"; }
|
||||
|
||||
[ -f "$SDK/M1.LST" ] || fail "GSPA produced no listing (see $WORK/dosbox.log)"
|
||||
sed 's/\r$//' "$SDK/M1.LST" > "$WORK/m1.lst"
|
||||
grep -q 'No Errors' "$WORK/m1.lst" \
|
||||
|| { grep -i -B2 -A2 'error' "$WORK/m1.lst" >&2 || true; fail "GSPA reported errors"; }
|
||||
[ -f "$SDK/M1.OUT" ] || fail "GSPLNK produced no M1.OUT"
|
||||
sed 's/\r$//' "$SDK/M1.MAP" > "$WORK/m1.map"
|
||||
cp "$SDK/M1.OUT" "$WORK/m1.coff"
|
||||
cp "$SDK/M1.ASM" "$WORK/m1.asm"
|
||||
|
||||
ENTRY=$(awk '$2 == "_start" { print $1; exit }' "$WORK/m1.map")
|
||||
[ "$ENTRY" = "018A0000" ] || fail "linker entry _start is '$ENTRY', want 018A0000"
|
||||
grep -q 'ENTRY POINT SYMBOL: "_start"' "$WORK/m1.map" \
|
||||
|| fail "linker map has no _start entry point"
|
||||
|
||||
BSS_ORIGIN=$(awk '$1 == ".bss" && $2 == "0" { print $3; exit }' "$WORK/m1.map")
|
||||
BSS_LENGTH=$(awk '$1 == ".bss" && $2 == "0" { print $4; exit }' "$WORK/m1.map")
|
||||
[ -n "$BSS_ORIGIN" ] || fail "linker map has no .bss section"
|
||||
[ "$BSS_LENGTH" = "00000080" ] \
|
||||
|| fail ".bss is $BSS_LENGTH bits, expected 00000080 (only arr[4])"
|
||||
EXPECT_SECOND=$(printf '0x%X' "$((0x$BSS_ORIGIN + 32))") # &arr[1] = arr + 32 bits
|
||||
echo " linked .text at 018A0000, .bss at $BSS_ORIGIN (128 bits)"
|
||||
echo " expected &arr[1] = $EXPECT_SECOND"
|
||||
|
||||
echo "== run the COFF in MAME"
|
||||
[ -x "$MAME" ] || fail "MAME binary not built: $MAME"
|
||||
[ -f "$LOADER_LUA" ] || fail "COFF loader missing: $LOADER_LUA"
|
||||
[ -f "$LOADER_BIN" ] || fail "COFF loader payload missing: $LOADER_BIN"
|
||||
|
||||
VT1200_SKIP_LEVEL1=1 \
|
||||
LOADER_AT="$LOADER_AT" LOADER_END="$LOADER_END" \
|
||||
LOADER_BIN="$LOADER_BIN" LOADER_COFF="$WORK/m1.coff" \
|
||||
LOADER_OUT="$WORK/evidence" \
|
||||
M1_LOADER_LUA="$LOADER_LUA" \
|
||||
M1_EXPECT_LOAD=0x12345678 M1_EXPECT_SECOND="$EXPECT_SECOND" \
|
||||
timeout "${MAME_TIMEOUT:-600}" "$MAME" vt1200 \
|
||||
-rompath "$ROOT/vendor/vt1200/harness/mame/roms" \
|
||||
-video none -sound none -nothrottle -skip_gameinfo \
|
||||
-nvram_directory "$WORK/run/nvram" \
|
||||
-cfg_directory "$WORK/run/cfg" \
|
||||
-debug -debugger none \
|
||||
-autoboot_script "$DIR/m1-check.lua" -autoboot_delay 0 \
|
||||
> "$WORK/mame.log" 2>&1 \
|
||||
|| { tail -30 "$WORK/mame.log" >&2; fail "MAME run failed"; }
|
||||
|
||||
MARKERS=$(grep -E '^M1: add=' "$WORK/mame.log" | tail -1 || true)
|
||||
[ -n "$MARKERS" ] || { tail -30 "$WORK/mame.log" >&2; fail "MAME produced no M1 markers"; }
|
||||
echo " $MARKERS"
|
||||
|
||||
grep -q 'LOADER: result status=0 (OK)' "$WORK/mame.log" \
|
||||
|| { grep 'LOADER: result' "$WORK/mame.log" >&2 || true; fail "COFF loader did not report status 0"; }
|
||||
grep -q 'placed .text .* 0 differing' "$WORK/mame.log" \
|
||||
|| fail "COFF loader could not verify the placed .text bytes"
|
||||
grep -q '^M1 MAME: PASS' "$WORK/mame.log" \
|
||||
|| { tail -30 "$WORK/mame.log" >&2; fail "marker check failed"; }
|
||||
|
||||
echo "M1 MAME: PASS"
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
# M1 test gate: compile four C functions, assemble/link with GSPA/GSPLNK,
|
||||
# load the COFF in MAME and check the marker words.
|
||||
set -eu
|
||||
ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
|
||||
exec "$ROOT/scripts/m1/run.sh" "$@"
|
||||
Reference in new issue
Block a user