Files
arti 41b0979aef c-hello: first C program on the VT1200, running on real hardware
The 1987 TI K&R toolchain already compiled and linked a fib test, but nothing
had ever been executed on a VT1200. This is that program: hello.c draws HELLO
into video RAM (1 bpp, bit 0x04000000, no X11) and is loaded through the COFF
loader with hostload.py, on the real unit and in MAME.

Two runtime traps shaped the design, both recorded in the README:

  * boot.obj's exit() is `for(;;)` and _c_int00 ends in `reti`.  That is a
    standalone-ROM runtime, not a process: the child spun forever, and because
    the VT1200 OS is cooperative it took the whole terminal down (illegal
    opcode on hardware).  entry.asm replaces boot.obj -- set STK/FP, call
    _main, `rets` back to the create_process trampoline, the same return
    xhello uses -- and the image carries no .cinit (tables are built in code).
  * Pointers are bit addresses and pointer arithmetic scales by object size,
    so p + n on a char * is n bytes.  The pre-fix frame in evidence/ is that
    bug: 32 one-byte bars spread across a row.

Framebuffer geometry is not portable: the real unit is 1280 px wide at a
256-byte row pitch, MAME's canvas is 1024 px at 128.  build.sh builds both
(ROW_BYTES), and the README records how the real pitch was measured from a
real-unit frame rather than taken from MAME.

Evidence: real-unit frames in harness/c-hello/evidence/ (the pre-fix stripes
and the working HELLO), and dwt/loader/evidence/real-unit-xhello/ for the X
window that ran on the unit earlier.
2026-10-29 18:25:38 +02:00

116 lines
4.0 KiB
Bash
Executable File

#!/bin/sh
# Build the C hello world with the 1987 TI TMS34010 SDK (K&R C compiler).
#
# harness/c-hello/build.sh
#
# Pipeline: gspcpp -> gspcc -> gspcg -> gspa -> gsplnk, all under DOSBox.
# entry.asm is assembled separately: it is the process entry (sets STK/FP,
# calls _main, `rets`) and replaces boot.obj, whose exit path is a `for(;;)`
# spin followed by an `reti` -- both wrong for a create_process child.
#
# .text/.data are linked at bit 0x018A0000, .bss at 0x01A00000 (both inside the
# firmware's large free block).
#
# Outputs: build/hello.coff (the linker output the COFF loader eats),
# hello.map, hello.lst, entry.lst, hello.obj
set -eu
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
DIR=$(cd "$(dirname "$0")" && pwd)
WORK="$DIR/work"
OUT="$DIR/build"
IMG="$ROOT/tools/tms34010-sdk/1987-05 TMS34010 SDK/1987-11-06 TMS34010 Simulator-C Tools-ASM Tools.img"
DOSBOX_CONF="$ROOT/harness/sdk-scratch/dosbox-bench.conf"
TEXT=018A0000
BSS=01A00000
# `build.sh emu` builds a 128-byte-stride variant for MAME's 1024x1024 canvas.
MODE=${1:-real}
NAME=hello
if [ "$MODE" = "emu" ]; then NAME=hello-emu; fi
mkdir -p "$WORK" "$OUT"
if [ ! -x "$WORK/GSPCG.EXE" ]; then
mcopy -s -n -i "$IMG" "::/A2/*" "$WORK/"
mcopy -s -n -i "$IMG" "::/C2/*" "$WORK/"
fi
if [ "$MODE" = "emu" ]; then
sed 's/#define ROW_BYTES 256/#define ROW_BYTES 128/' "$DIR/hello.c" > "$WORK/HELLO.C"
else
cp "$DIR/hello.c" "$WORK/HELLO.C"
fi
cp "$DIR/entry.asm" "$WORK/ENTRY.ASM"
{
printf '%s\n' '-a'
printf '%s\n' '-o HELLO.out'
printf '%s\n' '-m HELLO.map'
printf '%s\n' '-e _start'
printf '%s\n' 'HELLO.obj'
printf '%s\n' 'ENTRY.obj'
printf '%s\n' '-l RTS.LIB'
printf '%s\n' 'MEMORY'
printf '%s\n' '{'
printf '%s\n' " prog: origin = ${TEXT}h, length = 20000h"
printf '%s\n' " ram: origin = ${BSS}h, length = 40000h"
printf '%s\n' '}'
printf '%s\n' 'SECTIONS'
printf '%s\n' '{'
printf '%s\n' ' .text: {} > prog'
printf '%s\n' ' .data: {} > prog'
printf '%s\n' ' .cinit: {} > prog'
printf '%s\n' ' .bss: {} > ram'
printf '%s\n' '}'
} > "$WORK/HELLO.CMD"
sed -i 's/$/\r/' "$WORK/HELLO.CMD"
rm -f "$WORK"/HELLO.OBJ "$WORK"/HELLO.OUT "$WORK"/HELLO.LST \
"$WORK"/HELLO.MAP "$WORK"/HELLO.HI "$WORK"/HELLO.LO \
"$WORK"/HELLO.ASM "$WORK"/HELLO.CPP "$WORK"/HELLO.IF \
"$WORK"/ENTRY.OBJ "$WORK"/ENTRY.LST
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
timeout 240 dosbox -conf "$DOSBOX_CONF" -noprimaryconf -nolocalconf \
-c "mount c $WORK" -c "c:" \
-c "gspcpp HELLO" \
-c "gspcc HELLO" \
-c "gspcg HELLO" \
-c "gspa HELLO HELLO.obj HELLO.lst -l" \
-c "gspa ENTRY ENTRY.obj ENTRY.lst -l" \
-c "gsplnk HELLO.cmd" \
-c "exit" -exit > "$WORK/dosbox.log" 2>&1
if [ ! -f "$WORK/HELLO.OUT" ]; then
echo "build.sh: GSPLNK produced no HELLO.OUT" >&2
sed 's/\r$//' "$WORK/HELLO.MAP" 2>/dev/null | tail -20 >&2 || true
exit 1
fi
if [ ! -f "$WORK/HELLO.OBJ" ] || [ ! -f "$WORK/ENTRY.OBJ" ]; then
echo "build.sh: missing object file (compile/assemble failed)" >&2
for f in HELLO.LST ENTRY.LST; do
sed 's/\r$//' "$WORK/$f" 2>/dev/null | grep -i 'error' | grep -vi 'no errors' >&2 || true
done
exit 1
fi
# NB: the C code generator writes "***" banner comments into the .s file, so the
# assembly listing is full of asterisks; only real error lines are matched here.
for f in HELLO.LST ENTRY.LST; do
if sed 's/\r$//' "$WORK/$f" | grep -i 'error' | grep -qvi 'no errors'; then
echo "build.sh: $f diagnostics:" >&2
sed 's/\r$//' "$WORK/$f" | grep -in 'error' | grep -vi 'no errors' >&2
exit 1
fi
done
cp "$WORK/HELLO.OUT" "$OUT/$NAME.coff"
cp "$WORK/HELLO.OBJ" "$OUT/hello.obj"
cp "$WORK/ENTRY.OBJ" "$OUT/entry.obj"
sed 's/\r$//' "$WORK/HELLO.MAP" > "$OUT/hello.map"
sed 's/\r$//' "$WORK/HELLO.LST" > "$OUT/hello.lst"
sed 's/\r$//' "$WORK/ENTRY.LST" > "$OUT/entry.lst"
[ -f "$WORK/HELLO.ASM" ] && sed 's/\r$//' "$WORK/HELLO.ASM" > "$OUT/hello.s"
echo "build.sh: $OUT/$NAME.coff ($(wc -c < "$OUT/hello.coff") bytes)"
sed -n '1,40p' "$OUT/hello.map"