mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-30 12:03:54 +02:00
reference: TMS34010 SDK investigation; prove GSPA round trip
The 1987 DOS toolchain (GSPA/GSPLNK/GSPROM) runs under DOSBox and reassembles 32 KB of the v2.2 ROM byte-for-byte after five documented fixups to smart34010 output. The 1990 r5.01 tools are OS/2 NE and do not run in DOS. Adds the converter, round-trip script, and the DOSBox/dosemu benchmark.
This commit is contained in:
1 parent
960b256b00
commit
7d299451da
8 files changed
+923
No files matched your search
@@ -0,0 +1,28 @@
|
||||
# extracted SDK material and run products — never staged
|
||||
work/
|
||||
cgtools/
|
||||
sdb/
|
||||
disk1987/
|
||||
disk1985asm/
|
||||
disk1985demo/
|
||||
os2test/
|
||||
bench/
|
||||
dosemu-home/
|
||||
|
||||
*.bin
|
||||
*.obj
|
||||
*.out
|
||||
*.map
|
||||
*.lst
|
||||
*.hex
|
||||
*.tag
|
||||
*.HI
|
||||
*.LO
|
||||
*.$$$
|
||||
|
||||
# local intermediate text/disassembly
|
||||
*.txt
|
||||
*.asm
|
||||
bench.py
|
||||
__pycache__/
|
||||
*.pyc
|
||||
@@ -0,0 +1,47 @@
|
||||
# SDK scratch: the 1987 TMS34010 DOS toolchain
|
||||
|
||||
This directory is a scratch area for the investigation in
|
||||
`reference/tms34010-sdk.md`. The SDK under `tools/tms34010-sdk/` is only read;
|
||||
everything here is extracted work product.
|
||||
|
||||
## Round trip
|
||||
|
||||
```sh
|
||||
./roundtrip.sh 0x1000 32768 3F208000
|
||||
```
|
||||
|
||||
`OFFSET SIZE BASE_BIT`: disassemble that ROM region with
|
||||
`tools/smart34010/dis34 -o`, convert it to GSPA syntax with `dis2as34.py`,
|
||||
assemble/link/convert with the 1987 DOS tools under DOSBox, interleave the
|
||||
high/low Intel-hex files, and diff against the ROM. It prints `BYTE-EXACT`
|
||||
and the md5 of the reassembled region on success.
|
||||
|
||||
The tools are extracted on first use from:
|
||||
|
||||
```
|
||||
tools/tms34010-sdk/1987-05 TMS34010 SDK/
|
||||
1987-11-06 TMS34010 Simulator-C Tools-ASM Tools.img
|
||||
```
|
||||
|
||||
into `work/` (A2 = assembler/linker, C2 = C compiler/runtime). The ROM is
|
||||
never written.
|
||||
|
||||
## Benchmark
|
||||
|
||||
```sh
|
||||
python3 -u bench2.py # DOSBox vs dosemu, 3 runs, two workloads
|
||||
```
|
||||
|
||||
`bench2.py` uses `dosbox-bench.conf` (dynamic core, `cycles=max`) and a private
|
||||
dosemu `HOME` in `dosemu-home/`. The dosemu batch must end in `exitemu`;
|
||||
`-E` alone leaves DOS running. Results are in the report.
|
||||
|
||||
## Notes
|
||||
|
||||
* `dis2as34.py` needs the `-o` opcode comments from `dis34`; they are what
|
||||
disambiguate `MOVI` short/long, long relative `JR` vs absolute `JA`, and the
|
||||
B-file `SP`.
|
||||
* `gsprom -i` writes two files (high and low byte); interleave low then high.
|
||||
Its record addresses are 16-bit word indices, not bit addresses.
|
||||
* DOSBox 8.3 names are uppercased; check for `RT.OBJ`, not `rt.obj`. dosemu's
|
||||
`lredir` keeps lowercase names.
|
||||
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
"""DOSBox vs dosemu on the real workload: GSPA assembling TMS34010 source.
|
||||
|
||||
Workload A: gspa t1 t1.obj t1.lst -l (4 KB region, listing)
|
||||
Workload B: gspa -q rt16 rt16.obj (16 KB region, no listing)
|
||||
|
||||
Both headless: DOSBox with SDL dummy drivers and core=dynamic/cycles=max;
|
||||
dosemu in -t (terminal/S-Lang) mode. Three measured runs each, plus one
|
||||
warm-up. Raw COFF md5 differs only in the 4-byte time stamp, so a
|
||||
timestamp-normalised md5 is also reported.
|
||||
"""
|
||||
import hashlib
|
||||
import os
|
||||
import statistics
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
BENCH = ROOT / "bench"
|
||||
DHOME = ROOT / "dosemu-home"
|
||||
DBENCH = DHOME / ".dosemu" / "drive_c" / "bench"
|
||||
RUNS = 3
|
||||
|
||||
|
||||
def norm_md5(p):
|
||||
if not p.exists():
|
||||
return "-"
|
||||
b = bytearray(p.read_bytes())
|
||||
if len(b) >= 8:
|
||||
b[4:8] = b"\0\0\0\0"
|
||||
return hashlib.md5(bytes(b)).hexdigest()
|
||||
|
||||
|
||||
def raw_md5(p):
|
||||
return hashlib.md5(p.read_bytes()).hexdigest() if p.exists() else "-"
|
||||
|
||||
|
||||
def clean(d, base):
|
||||
for name in (base + ".OBJ", base + ".LST", "MACLIB.$$$",
|
||||
base.lower() + ".obj", base.lower() + ".lst", "maclib.$$$"):
|
||||
f = d / name
|
||||
if f.exists():
|
||||
f.unlink()
|
||||
|
||||
|
||||
def run_dosbox(base, args):
|
||||
clean(BENCH, base)
|
||||
cmds = []
|
||||
for _ in range(1):
|
||||
cmds += ["-c", "gspa " + args]
|
||||
env = dict(os.environ, SDL_VIDEODRIVER="dummy", SDL_AUDIODRIVER="dummy")
|
||||
t0 = time.perf_counter()
|
||||
rc = subprocess.run(
|
||||
["timeout", "120", "dosbox", "-conf", str(ROOT / "dosbox-bench.conf"),
|
||||
"-noprimaryconf", "-nolocalconf",
|
||||
"-c", "mount c " + str(BENCH), "-c", "c:"] + cmds +
|
||||
["-c", "exit", "-exit"],
|
||||
env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode
|
||||
dt = time.perf_counter() - t0
|
||||
# DOSBox uppercases 8.3 names
|
||||
obj = BENCH / (base.upper() + ".OBJ")
|
||||
return dt, rc, raw_md5(obj), norm_md5(obj), obj.stat().st_size if obj.exists() else 0
|
||||
|
||||
|
||||
def run_dosemu(base, args, bat):
|
||||
clean(DBENCH, base)
|
||||
script = "@echo off\r\nc:\r\ncd \\bench\r\ngspa %s\r\nexitemu\r\n" % args
|
||||
(DBENCH / bat).write_bytes(script.encode())
|
||||
env = dict(os.environ, HOME=str(DHOME))
|
||||
t0 = time.perf_counter()
|
||||
rc = subprocess.run(
|
||||
["timeout", "120", "dosemu", "-t", "-quiet", "-E", r"c:\bench" + "\\" + bat],
|
||||
env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode
|
||||
dt = time.perf_counter() - t0
|
||||
# dosemu/lredir keeps the lowercase names written by FreeCom
|
||||
obj = DBENCH / (base.lower() + ".obj")
|
||||
if not obj.exists():
|
||||
obj = DBENCH / (base.upper() + ".OBJ")
|
||||
return dt, rc, raw_md5(obj), norm_md5(obj), obj.stat().st_size if obj.exists() else 0
|
||||
|
||||
|
||||
def bench(name, base, args, bat):
|
||||
print("\n== %s == gspa %s" % (name, args))
|
||||
run_dosbox(base, args) # warm-up
|
||||
run_dosemu(base, args, bat)
|
||||
results = {}
|
||||
for label, fn in (("dosbox", lambda: run_dosbox(base, args)),
|
||||
("dosemu", lambda: run_dosemu(base, args, bat))):
|
||||
rows = [fn() for _ in range(RUNS)]
|
||||
results[label] = rows
|
||||
for dt, rc, raw, norm, size in rows:
|
||||
print(" %-7s %6.2fs rc=%d size=%d raw=%s norm=%s" %
|
||||
(label, dt, rc, size, raw[:8], norm[:8]))
|
||||
print(" %-7s median %.2fs" % (label, statistics.median(r[0] for r in rows)))
|
||||
n0 = {r[3] for r in results["dosbox"]}
|
||||
n1 = {r[3] for r in results["dosemu"]}
|
||||
print(" normalised object identical: %s (%s)" % (n0 == n1 and len(n0) == 1, n0))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
bench("A: 4 KB with listing", "t1", "t1 t1.obj t1.lst -l", "bench.bat")
|
||||
bench("B: 16 KB, no listing", "rt16", "-q rt16 rt16.obj", "bench16.bat")
|
||||
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Convert smart34010 (dis34 -o) output into TI gspa/as34 2.00 syntax.
|
||||
|
||||
Usage: dis2as34.py BASE_HEX SIZE_BYTES INPUT.ASM > OUT.ASM
|
||||
|
||||
The input must be produced with ``dis34 -o`` (opcode words in a trailing
|
||||
comment) because the 1987 assembler is *not* byte-faithful on its own:
|
||||
|
||||
* ``MOVI`` is one mnemonic with a short (16-bit, sign-extended) and a long
|
||||
(32-bit) form. gspa auto-selects by "does the value fit in 16 bits", so a
|
||||
long MOVI of 0x53E0 (which the ROM encodes as a 3-word instruction) is
|
||||
silently shortened -- and sign extension then changes its meaning. The
|
||||
explicit ``W`` / ``L`` operand forces the form.
|
||||
* the same applies to ADDI/CMPI/SUBI.
|
||||
* dis34 prints a *long relative* JR (16-bit displacement) as ``ja<cc>``, the
|
||||
same mnemonic it uses for the 3-word absolute JA. The opcode word count
|
||||
disambiguates: 2 words = long JR, 3 words = absolute JA.
|
||||
* dis34 prints B-file register 15 as ``sp`` too; gspa's bare ``sp`` is the
|
||||
A-file register. B-file SP has to be written ``b15`` (the assembler rejects
|
||||
``a15``).
|
||||
* register-indirect displacements are signed 16-bit fields. dis34 prints the
|
||||
raw two's-complement bits (0xFFE0); gspa rejects those as out of bounds, so
|
||||
they are rendered as signed decimal (-32).
|
||||
* constants: dis34 emits ``0xHHHH``; gspa wants ``0HHHHh`` (must start with a
|
||||
decimal digit, so 0xC00000B0 becomes 0C00000B0h).
|
||||
* a PC-relative branch whose target is not an instruction boundary in the
|
||||
region (outside it, or -- as happens in data tables -- inside a multi-word
|
||||
instruction) cannot be expressed as a label. gspa also mishandles a
|
||||
negative absolute ``.set`` target in a JR. The exact opcode words are
|
||||
therefore emitted as ``.word`` for those branches.
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
|
||||
DISP = re.compile(r"(\*\s*[ab]\d+|\*\s*sp)\(\s*0x([0-9a-fA-F]+)\s*\)")
|
||||
HEX = re.compile(r"0x([0-9a-fA-F]+)")
|
||||
FORM_OPS = {"movi", "addi", "cmpi", "subi"}
|
||||
RELATIVE = re.compile(r"^(jreq|jrne|jrlt|jrle|jrgt|jrge|jrc|jrnc|jrlo|jrls|jrhi|jrhs|jrnz|jrz|"
|
||||
r"jruc|jrxn|jrxnn|jrxz|jrxeq|jrxgt|jrxle|jryn|jrynn|jryz|jryeq|jrygt|jryle|"
|
||||
r"call|callr)$")
|
||||
DSJ_OPS = {"dsj", "dsjs", "dsjeq", "dsjne"}
|
||||
|
||||
|
||||
def ti_hex(digits: str) -> str:
|
||||
return "0" + digits.upper() + "h"
|
||||
|
||||
|
||||
def fix_disp(m):
|
||||
base, digits = m.group(1), m.group(2)
|
||||
v = int(digits, 16)
|
||||
if v >= 0x8000:
|
||||
return f"{base}({v - 0x10000})"
|
||||
return f"{base}(0{digits.upper()}h)"
|
||||
|
||||
|
||||
def raw_words(words):
|
||||
return " .word " + ", ".join(ti_hex(w) for w in words)
|
||||
|
||||
|
||||
def convert(src: str, base: int, size: int) -> str:
|
||||
end = base + size * 8
|
||||
lines = src.splitlines()
|
||||
defined = set()
|
||||
for line in lines:
|
||||
code = line.partition(";")[0]
|
||||
dm = re.match(r"^(L[0-9A-Fa-f]{8}):", code)
|
||||
if dm:
|
||||
defined.add(dm.group(1).upper())
|
||||
|
||||
out = []
|
||||
referenced = set()
|
||||
for line in lines:
|
||||
code, sep, comment = line.partition(";")
|
||||
for lm in re.findall(r"\b(L[0-9A-Fa-f]{8})\b", code):
|
||||
referenced.add(lm.upper())
|
||||
words = re.findall(r"\b[0-9a-fA-F]{4}\b", comment)
|
||||
stripped = code.rstrip()
|
||||
# long relative JR printed as JA (see module docstring)
|
||||
mm = re.match(r"^(\s*)ja([a-z]+)(\s+)(\S.*)$", stripped)
|
||||
if mm and len(words) == 2:
|
||||
stripped = f"{mm.group(1)}jr{mm.group(2)}{mm.group(3)}{mm.group(4)}"
|
||||
parts = stripped.split()
|
||||
op = parts[0].lower() if parts else ""
|
||||
# B-file SP (see docstring)
|
||||
if op == "rl" and words and stripped.endswith(", sp"):
|
||||
if int(words[0], 16) & 0x1f == 0x1f:
|
||||
stripped = stripped[:-3] + "b15"
|
||||
# short/long immediate forms (see docstring)
|
||||
if op in FORM_OPS and words:
|
||||
if len(words) == 2:
|
||||
stripped += ", W"
|
||||
mm = re.match(r"^(\s*\w+\s+)0x([0-9a-fA-F]+)(,.*)$", stripped)
|
||||
if mm and int(mm.group(2), 16) >= 0x8000:
|
||||
stripped = (mm.group(1) + str(int(mm.group(2), 16) - 0x10000)
|
||||
+ mm.group(3))
|
||||
elif len(words) == 3:
|
||||
stripped += ", L"
|
||||
# relative branch to a label that is not an instruction boundary
|
||||
mm = re.match(r"^(\s*)(\w+)(\s+.*?)(L[0-9A-Fa-f]{8})\s*$", stripped)
|
||||
if mm and mm.group(4).upper() not in defined:
|
||||
op2 = mm.group(2).lower()
|
||||
if op2 in DSJ_OPS or RELATIVE.match(op2):
|
||||
stripped = raw_words(words)
|
||||
# relative branch to an absolute address outside the region
|
||||
mm = re.match(r"^(\s*)([a-z]+)(\s+)0x([0-9a-fA-F]+)\s*$", stripped)
|
||||
if mm and RELATIVE.match(mm.group(2).lower()):
|
||||
target = int(mm.group(4), 16)
|
||||
if not (base <= target < end):
|
||||
stripped = raw_words(words)
|
||||
stripped = DISP.sub(fix_disp, stripped)
|
||||
stripped = HEX.sub(lambda mm: ti_hex(mm.group(1)), stripped)
|
||||
out.append(stripped)
|
||||
missing = sorted(referenced - defined)
|
||||
if missing:
|
||||
out.append("")
|
||||
for name in missing:
|
||||
off = int(name[1:], 16) - base
|
||||
out.append(f"{name} .set " + ("0%Xh" % off if off >= 0 else str(off)))
|
||||
return "\n".join(out) + "\n"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
base = int(sys.argv[1], 16)
|
||||
size = int(sys.argv[2])
|
||||
sys.stdout.write(convert(open(sys.argv[3]).read(), base, size))
|
||||
@@ -0,0 +1,17 @@
|
||||
[sdl]
|
||||
fullscreen=false
|
||||
autolock=false
|
||||
output=surface
|
||||
|
||||
[dosbox]
|
||||
machine=svga_s3
|
||||
memsize=16
|
||||
|
||||
[cpu]
|
||||
core=dynamic
|
||||
cycles=max
|
||||
|
||||
[mixer]
|
||||
nosound=true
|
||||
|
||||
[autoexec]
|
||||
@@ -0,0 +1,17 @@
|
||||
[sdl]
|
||||
fullscreen=false
|
||||
autolock=false
|
||||
output=surface
|
||||
|
||||
[dosbox]
|
||||
machine=svga_s3
|
||||
memsize=16
|
||||
|
||||
[cpu]
|
||||
core=normal
|
||||
cycles=3000
|
||||
|
||||
[mixer]
|
||||
nosound=true
|
||||
|
||||
[autoexec]
|
||||
Executable
+104
@@ -0,0 +1,104 @@
|
||||
#!/bin/sh
|
||||
# Assemble a region of the VT1200 ROM with the 1987 TI TMS34010 toolchain
|
||||
# (GSPA/GSPLNK/GSPROM, run under DOSBox) and compare the result byte-for-byte
|
||||
# with the original.
|
||||
#
|
||||
# Usage: roundtrip.sh OFFSET SIZE BASE_BIT
|
||||
# OFFSET byte offset into vt1200_v22.bin
|
||||
# SIZE region size in bytes
|
||||
# BASE_BIT bit address the region is linked at (e.g. 3F208000)
|
||||
#
|
||||
# Example: roundtrip.sh 0x1000 16384 3F208000
|
||||
#
|
||||
# The ROM is only read. All work happens in this directory.
|
||||
set -eu
|
||||
|
||||
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
|
||||
DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
WORK="$DIR/work"
|
||||
ROM="$ROOT/vt1200_v22.bin"
|
||||
DIS="$ROOT/tools/smart34010/dis34"
|
||||
|
||||
OFF=${1:?offset}
|
||||
SIZE=${2:?size}
|
||||
BASE=${3:?base bit address}
|
||||
OFF=$((OFF))
|
||||
SIZE=$((SIZE))
|
||||
|
||||
mkdir -p "$WORK"
|
||||
# tools: extract once from the 1987-11-06 floppy image
|
||||
if [ ! -x "$WORK/GSPA.EXE" ]; then
|
||||
IMG="$ROOT/tools/tms34010-sdk/1987-05 TMS34010 SDK/1987-11-06 TMS34010 Simulator-C Tools-ASM Tools.img"
|
||||
mcopy -s -n -i "$IMG" "::/A2/*" "$WORK/"
|
||||
mcopy -s -n -i "$IMG" "::/C2/*" "$WORK/"
|
||||
fi
|
||||
|
||||
# TI hex constants must begin with a decimal digit
|
||||
TBASE=$BASE
|
||||
case "$TBASE" in
|
||||
[0-9]*) ;;
|
||||
*) TBASE="0$TBASE" ;;
|
||||
esac
|
||||
|
||||
dd if="$ROM" of="$WORK/rt.bin" bs=1 skip="$OFF" count="$SIZE" 2>/dev/null
|
||||
"$DIS" -b "$BASE" -o "$WORK/rt.bin" > "$WORK/rt.dis"
|
||||
python3 "$DIR/dis2as34.py" "$BASE" "$SIZE" "$WORK/rt.dis" > "$WORK/RT.ASM"
|
||||
sed -i '1i .text' "$WORK/RT.ASM"
|
||||
echo '.end' >> "$WORK/RT.ASM"
|
||||
|
||||
cat > "$WORK/RT.CMD" <<EOF
|
||||
-a
|
||||
-o rt.out
|
||||
-m rt.map
|
||||
rt.obj
|
||||
SECTIONS
|
||||
{
|
||||
.text ${TBASE}h: {}
|
||||
}
|
||||
EOF
|
||||
sed -i 's/$/\r/' "$WORK/RT.CMD"
|
||||
|
||||
rm -f "$WORK"/RT.OBJ "$WORK"/RT.OUT "$WORK"/RT.LST "$WORK"/RTHI.HI "$WORK"/RTLO.LO
|
||||
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
|
||||
timeout 180 dosbox -conf "$DIR/dosbox.conf" -noprimaryconf -nolocalconf \
|
||||
-c "mount c $WORK" -c "c:" \
|
||||
-c "gspa rt rt.obj rt.lst -l" \
|
||||
-c "gsplnk rt.cmd" \
|
||||
-c "gsprom -i rt.out rthi rtlo" \
|
||||
-c "exit" -exit >/dev/null 2>&1
|
||||
|
||||
python3 - "$WORK" "$SIZE" <<'EOF'
|
||||
import sys
|
||||
work, size = sys.argv[1], int(sys.argv[2])
|
||||
|
||||
def readhex(p):
|
||||
d = {}
|
||||
for line in open(p):
|
||||
line = line.strip()
|
||||
if not line.startswith(':'):
|
||||
continue
|
||||
b = bytes.fromhex(line[1:])
|
||||
if b[3] != 0:
|
||||
continue
|
||||
a = (b[1] << 8) | b[2]
|
||||
for i, x in enumerate(b[4:4 + b[0]]):
|
||||
d[a + i] = x
|
||||
return d
|
||||
|
||||
hi = readhex(work + "/RTHI.HI")
|
||||
lo = readhex(work + "/RTLO.LO")
|
||||
out = bytearray()
|
||||
for a in sorted(hi):
|
||||
out.append(lo[a])
|
||||
out.append(hi[a])
|
||||
orig = open(work + "/rt.bin", "rb").read()
|
||||
n = min(len(orig), len(out))
|
||||
bad = [i for i in range(n) if orig[i] != out[i]]
|
||||
print("assembled %d bytes, original %d bytes, %d differing" % (len(out), len(orig), len(bad)))
|
||||
if len(out) != len(orig) or bad:
|
||||
print("FAIL")
|
||||
for i in bad[:5]:
|
||||
print(" %#x: orig %s out %s" % (i, orig[i:i+8].hex(), out[i:i+8].hex()))
|
||||
sys.exit(1)
|
||||
print("md5 " + __import__('hashlib').md5(out).hexdigest() + " BYTE-EXACT")
|
||||
EOF
|
||||
@@ -0,0 +1,482 @@
|
||||
# The TMS34010 SDK in `tools/tms34010-sdk`: what runs, and what it buys us
|
||||
|
||||
Investigated 2026-10-18. Short version:
|
||||
|
||||
* The **1987 DOS toolchain runs** under DOSBox (and dosemu) on Linux: GSPA
|
||||
(assembler), GSPLNK (linker), GSPROM (hex converter), GSPAR (archiver), and
|
||||
the K&R C compiler (GSPCPP/GSPCC/GSPCG).
|
||||
* `smart34010` output **does** assemble with it, but not literally: five small
|
||||
fixups are needed. With them, a **32 KB region of the v2.2 ROM reassembles
|
||||
byte-for-byte**, md5-identical to the original. See the results table.
|
||||
* The **1990 r5.01 tools are OS/2 1.x NE binaries**, not DOS. They do not run in
|
||||
DOSBox; using them would need an OS/2 environment. The 1987 DOS tools are the
|
||||
usable set.
|
||||
* There is **no `as34` / `lnk34`** in this SDK. The assembler is `GSPA.EXE` and
|
||||
the linker `GSPLNK.EXE` (the names `smart34010`'s README mentions are from a
|
||||
later TI package). GSPA 2.00 is enough for the round trip.
|
||||
* The C compiler works and links with `rts.lib` into an absolute COFF image.
|
||||
It is **K&R C, not ANSI**: `int f(int a)` is a syntax error, `int f(a) int a;`
|
||||
works.
|
||||
* For a VT1200 payload the useful part is the **address model**: the linker's
|
||||
`SECTIONS` origin is a **bit address**, the COFF carries the full 32-bit bit
|
||||
address, and the bytes are extracted from GSPROM's high/low EPROM files. The
|
||||
old converter's own hex addresses wrap at 64 KB and must not be used for
|
||||
placement.
|
||||
|
||||
Everything below was run against the ROM with md5
|
||||
`b46a3e442ccb86134d2356bd70d22fd2`; the ROM was only read.
|
||||
|
||||
---
|
||||
|
||||
## 1. What is in the SDK, and what actually executes
|
||||
|
||||
| package | files | host | runs here? |
|
||||
|---|---|---|---|
|
||||
| `1985-05-20 … Assembly Language Package` | GSPA/GSPLNK/GSPAR **v1.04** | DOS MZ | yes, but older |
|
||||
| `1987-11-06 … Simulator-C Tools-ASM Tools.img` | GSPA **2.00**, GSPLNK **2.00**, GSPAR 2.00, GSPROM **2.01**, GSPCV 2.00 (A2); GSPCPP/GSPCC/GSPCG **2.01**, RTS.LIB, FLIB.LIB (C2) | DOS MZ | **yes — the set used here** |
|
||||
| `1990-11-19 … Code Generation Tools r5.01` | gspa, gspac, gspar, gspcg, gspcl, gsplnk, gspopt, gsprom, clist | **OS/2 1.x NE** | **no** (DOSBox cannot run NE/OS2) |
|
||||
| `1990-11-19 … C Source Debugger r5.00` | db340.exe (DOS MZ), gspmon | DOS + target monitor | debugger runs, but needs a monitor/target link |
|
||||
| `1991-07 TIGA Promo Kit` | DOS art/demo disks, TIGA docs | DOS | not relevant to VT1200 |
|
||||
| `Docs/` | Users Guide, Assembly Tools Guide, C Compiler Guide, reference cards, TIGA, production data | PDF | reference only |
|
||||
| `Patents/` | five graphics-architecture patents | PDF | not relevant |
|
||||
|
||||
The 1990 NE files were checked both with `file` and by parsing the NE header:
|
||||
signature `NE`, version 5.1, **target OS byte `0x01` = OS/2**. The 1985/1987
|
||||
files are plain `MZ for MS-DOS`, 16-bit real mode, and need at most 640 KB.
|
||||
|
||||
The **1987 disk is the sweet spot**: v2.00 is the version whose tools guide
|
||||
documents the `W`/`L` immediate-form operands and the `.usect`/`.if` features
|
||||
used below. The 1985 v1.04 tools are older and were not needed.
|
||||
|
||||
### Running them headless
|
||||
|
||||
DOSBox 0.74-3 works with the SDL dummy drivers; a bare `-conf` file with
|
||||
`core=dynamic` / `cycles=max` makes it ~5× faster than the `cycles=3000` config
|
||||
used earlier. The commands in `roundtrip.sh` show the exact form.
|
||||
|
||||
dosemu 1.4.0.8 also works: `dosemu -t -quiet -E 'c:\bench\bench.bat'`, where the
|
||||
batch ends in `exitemu`. `-E` alone does not exit DOS. See the benchmark
|
||||
section.
|
||||
|
||||
---
|
||||
|
||||
## 2. The round-trip experiment (the proof)
|
||||
|
||||
### Method
|
||||
|
||||
For a region at file offset `OFF`, size `SIZE`, linked at bit address `BASE`:
|
||||
|
||||
1. `dis34 -b BASE -o region.bin > region.dis` — disassemble with the opcode
|
||||
words in a trailing comment (`-o` is required; the fixups use them).
|
||||
2. `dis2as34.py BASE SIZE region.dis > RT.ASM` — apply the five fixups
|
||||
(section 3), prepend `.text`, append `.end`.
|
||||
3. Assemble and link:
|
||||
|
||||
```
|
||||
gspa rt rt.obj rt.lst -l
|
||||
gsplnk rt.cmd
|
||||
gsprom -i rt.out rthi rtlo
|
||||
```
|
||||
|
||||
with `rt.cmd`:
|
||||
|
||||
```
|
||||
-a
|
||||
-o rt.out
|
||||
-m rt.map
|
||||
rt.obj
|
||||
SECTIONS
|
||||
{
|
||||
.text 3F208000h: {}
|
||||
}
|
||||
```
|
||||
|
||||
The origin is a **bit address**. `-a` makes an absolute module; `-m` writes a
|
||||
map; `-e` would set an entry point.
|
||||
4. `gsprom -i` writes **two** Intel-hex files (high byte and low byte for a
|
||||
16-bit EPROM pair). Interleave **low byte then high byte** per word.
|
||||
5. Compare with `dd`-extracted original bytes.
|
||||
|
||||
`roundtrip.sh` does exactly this and prints the md5.
|
||||
|
||||
### Results
|
||||
|
||||
| region | file offset | base (bit) | size | result |
|
||||
|---|---|---|---|---|
|
||||
| boot/POST code | `0x1000` | `3F208000` | 4 KB | **0 differing**, md5 `a4018fed12e062658bc81957f149265b` |
|
||||
| same, larger | `0x1000` | `3F208000` | 16 KB | **0 differing**, md5 `0a2e679bc864d52d32c59740142c5b96` |
|
||||
| same, larger | `0x1000` | `3F208000` | **32 KB** | **0 differing**, md5 `43adb6669571041985f3e26fd9ff7cb9` |
|
||||
| another code region | `0x8000` | `3F240000` | 4 KB | **0 differing**, md5 `438e743431d56e0c12ededce1c36952a` |
|
||||
|
||||
The ROM md5 after all runs is unchanged: `b46a3e442ccb86134d2356bd70d22fd2`.
|
||||
|
||||
A 64 KB region trips `dis34` itself (`unexpected end of input at byte 65536`);
|
||||
split larger spans into ≤32 KB pieces. This is a disassembler limitation, not an
|
||||
assembler one.
|
||||
|
||||
### What this proves, and what it does not
|
||||
|
||||
It proves the assembler accepts the disassembler's instruction syntax and
|
||||
reproduces the **exact instruction encodings** for 32 KB of real firmware,
|
||||
including PC-relative branches, absolute calls, data words, and immediate
|
||||
forms. It does **not** prove the payload runs on a VT1200; nothing here was
|
||||
executed on the target.
|
||||
|
||||
---
|
||||
|
||||
## 3. The five fixups (and why they are needed)
|
||||
|
||||
`dis34` output is *close* to GSPA syntax but not directly assemblable. The
|
||||
converter `harness/sdk-scratch/dis2as34.py` is the executable form of this list.
|
||||
|
||||
1. **Hex constants.** GSPA has no `0x`: a hex constant must start with a decimal
|
||||
digit and end in `h`. `0x220` → `0220h`; `0xC00000B0` → `0C00000B0h` (nine
|
||||
digits are accepted and truncated to 32 bits). GSPCV does the same conversion
|
||||
for the old `>xxxx` syntax.
|
||||
|
||||
2. **`MOVI` short vs long.** GSPA has one `MOVI` mnemonic with a 16-bit
|
||||
sign-extended form and a 32-bit form, and auto-selects by "does the value fit
|
||||
in 16 bits (unsigned)". That is **not** encoding-preserving: a long
|
||||
`MOVI 0x53E0` (three words) becomes a short `MOVIW` (two words), and the
|
||||
short form sign-extends to `0xFFFF53E0`. Force the form with the third
|
||||
operand: `movi 053E0h, sp, L` or `..., W`. The opcode word count in the `-o`
|
||||
comment tells which form the ROM used. The same applies to `ADDI`, `CMPI`,
|
||||
and `SUBI`.
|
||||
|
||||
3. **B-file `SP`.** `dis34` prints register 15 as `sp` regardless of file.
|
||||
Bit 4 of the encoded register field says which file: `0x0F` = A-file SP,
|
||||
`0x1F` = B-file SP. GSPA's bare `sp` matched the ROM for most forms (memory
|
||||
operands such as `*sp(offset)` assembled as B15; `MOVI`/`MMFM` register
|
||||
operands as A15), but an `RL` destination chose A15 where the ROM has B15
|
||||
(0x312F vs 0x313F). `b15` forces the B-file register; `a15` is rejected.
|
||||
Check the register field, not the printed name.
|
||||
|
||||
4. **Long relative `JR` printed as `JA`.** In `dis34`, the 2-word relative jump
|
||||
(opcode low byte `0x00`, 16-bit displacement) is printed with the `ja<cc>`
|
||||
mnemonic, the same one used for the 3-word absolute `JA`. The word count
|
||||
distinguishes them: 2 words → rewrite to `jr<cc>`, 3 words → leave `ja<cc>`.
|
||||
Without this, every long relative branch grows by a word and the image
|
||||
diverges.
|
||||
|
||||
5. **Branches whose target is not an instruction boundary.** This happens for
|
||||
targets outside the disassembled region (common) and, in data-table areas,
|
||||
for targets inside a multi-word instruction. GSPA also mishandles a negative
|
||||
absolute `.set` value in a `JR` (it emits the absolute `JA` form and errors).
|
||||
Emit the exact opcode words from the `-o` comment as `.word` for those
|
||||
branches. For `DSJS`, whose short range is only ±32 words, this is the only
|
||||
reliable way.
|
||||
|
||||
One practical note: with the slow `cycles=3000` DOSBox config, a 16 KB source
|
||||
with `-l` takes about 35 s wall time; a 32 KB source takes about 1.5–2 minutes.
|
||||
That is DOSBox being cycle-limited, not the assembler hanging. The benchmark
|
||||
section uses `core=dynamic`/`cycles=max`, which removes most of it.
|
||||
|
||||
---
|
||||
|
||||
## 4. Addresses, sections, and output formats
|
||||
|
||||
### The unit is bits
|
||||
|
||||
The TMS34010 is bit-addressed. In this toolchain:
|
||||
|
||||
* The assembler's location counter and labels are in **bits**; each instruction
|
||||
word advances it by 16. A listing address of `00000030` is 3 words in.
|
||||
* `SECTIONS` binding is a **bit address**: `.text 3F208000h: {}` produced
|
||||
`.text 0 3F208000 00020000` in the map (origin bit, length bits).
|
||||
* The linker's `MEMORY` origins/lengths are bits too.
|
||||
* The COFF output carries the true 32-bit bit address. In `RT16.OUT` the
|
||||
optional header says `text = 0x3F208000`, `code size = 0x20000` bits, and the
|
||||
`.text` section header says `paddr = vaddr = 0x3F208000`, `size = 0x20000`
|
||||
bits. The file header magic is `0x0090`, flags `0x103` (including `0x40`,
|
||||
the TMS34010 little-endian "16 bits per word, LSB first" flag).
|
||||
* To place a payload at bit address `A`, link `.text A: {}` and put file byte
|
||||
`i` at physical byte address `A>>3 + i`. Do not re-link at a different address
|
||||
than the load address: the C compiler emits `CALLA` and absolute pointers.
|
||||
|
||||
### GSPROM's hex addresses are not the load address
|
||||
|
||||
`gsprom -i in out_hi out_lo` produces two 8-bit Intel-hex files, one high byte
|
||||
and one low byte per 16-bit word. The address in those records is a **16-bit
|
||||
word index inside a 64 KB byte window**, not the bit address and not the full
|
||||
byte address. Measured:
|
||||
|
||||
| section bit origin | first record address |
|
||||
|---|---|
|
||||
| `0x00020000` | `0x2000` |
|
||||
| `0x3F208000` | `0x0800` |
|
||||
| `0x3F240000` | `0x4000` |
|
||||
|
||||
That matches `((bit >> 3) & 0xFFFF) >> 1`. The data order is still correct, so
|
||||
take the byte stream in order and place it at the address from the map/COFF.
|
||||
`gsprom -t` (TI-tagged) has the same 16-bit address limitation.
|
||||
|
||||
For a loader, the clean source of truth is the **COFF** (`-a` output): section
|
||||
`paddr`/`vaddr` and the optional header's entry point are full 32-bit bit
|
||||
addresses. A small COFF reader can emit a raw image with its true load address;
|
||||
GSPROM is only a convenience for an EPROM programmer.
|
||||
|
||||
### Linker command syntax gotchas
|
||||
|
||||
* Comments are `/* … */`; `;` is a syntax error in a command file.
|
||||
* `.text > prog` is a syntax error. Use either
|
||||
`.text 3F208000h: {}` (bind directly) or `.text: {} > prog` (named memory).
|
||||
* `-a` = absolute executable, `-m` = map, `-o` = output, `-e` = entry symbol,
|
||||
`-l lib` = library. `-c` / `-cr` select the C ROM/RAM autoinit models.
|
||||
|
||||
---
|
||||
|
||||
## 5. The C compiler
|
||||
|
||||
It runs, and the whole pipeline works:
|
||||
|
||||
```
|
||||
gspcpp cmain # .c -> .cpp
|
||||
gspcc cmain # .cpp -> .if
|
||||
gspcg cmain # .if -> .asm
|
||||
gspa cmain # .asm -> .obj
|
||||
gsplnk cmain.cmd # .obj + rts.lib -> .out, map
|
||||
gsprom -i cmain.out …
|
||||
```
|
||||
|
||||
A test with `fib` and `main` linked cleanly with:
|
||||
|
||||
```
|
||||
-c
|
||||
-o cmain.out
|
||||
-m cmain.map
|
||||
cmain.obj
|
||||
-l rts.lib
|
||||
MEMORY
|
||||
{
|
||||
prog: origin = 3F208000h, length = 10000h
|
||||
ram: origin = 0h, length = 100000h
|
||||
}
|
||||
SECTIONS
|
||||
{
|
||||
.text: {} > prog
|
||||
.cinit: {} > prog
|
||||
.data: {} > prog
|
||||
.bss: {} > ram
|
||||
}
|
||||
```
|
||||
|
||||
The map confirms:
|
||||
|
||||
```
|
||||
ENTRY POINT SYMBOL: "_c_int00" address: 3F2082F0
|
||||
.text 3F208000 00000A90
|
||||
.cinit 3F208A90 00000060
|
||||
.bss 00000000 0000FE40 (uninitialized)
|
||||
```
|
||||
|
||||
So the compiler, assembler, runtime library, linker, and converter form a
|
||||
complete C path. **It was not executed on a VT1200.**
|
||||
|
||||
### Language and ABI facts worth knowing
|
||||
|
||||
From `SPVU005A` and the generated code:
|
||||
|
||||
* **K&R C only.** ANSI prototypes (`int f(int a)`) are a syntax error;
|
||||
`int f(a) int a; { … }` works. Plan for old-style declarations.
|
||||
* `char` 8 bits, `short` 16, **`int` and `long` 32**, pointers **32-bit bit
|
||||
addresses**, `float` 32, `double` 64.
|
||||
* Alignment: objects are on 16-bit boundaries (bit addresses with four LSBs
|
||||
zero); structure/array members are packed.
|
||||
* Dedicated registers: `SP` = system stack, `A14` (`STK`) = program stack,
|
||||
`A13` (`FP`) = frame pointer; `A8` = integer/pointer return value. `B0–B14`
|
||||
are unused by the compiler and available to assembly. Callee saves the A
|
||||
registers it uses except `A8`; `FS1` must be 32 inside C code.
|
||||
* Arguments are pushed on the **program stack** in reverse order, widened to
|
||||
32 bits; calls are `CALLA` (absolute), so **code is position-dependent**.
|
||||
* Runtime: link `rts.lib` with `-c` (ROM autoinit; `boot.obj` is pulled in and
|
||||
`_c_int00` becomes the entry) or `-cr` (loader does the `.cinit` copy). The
|
||||
boot code allocates `SYS_STACK` in `.bss`, sets `FP`/`STK`/`SP`, calls
|
||||
`_main`, then `_exit`.
|
||||
* C sections: `.text` (code + string literals + float constants), `.cinit`
|
||||
(initialized-global table), `.bss` (globals + both stacks + heap). `.data` is
|
||||
the assembler's, not the C compiler's. `.bss` must be RAM.
|
||||
|
||||
For a payload, the simplest C model is: link `.text`/`.cinit` at the load
|
||||
address, put `.bss` in a free RAM range, and enter at `_c_int00`. A pure-assembly
|
||||
payload avoids the runtime entirely.
|
||||
|
||||
---
|
||||
|
||||
## 6. Building a VT1200 payload: practical recipe
|
||||
|
||||
Assembly-only payload (smallest, no runtime):
|
||||
|
||||
```
|
||||
.text
|
||||
start:
|
||||
movi 01234h, a0
|
||||
move a0, @0C0000010h, 0 ; example on-chip register
|
||||
rets
|
||||
.end
|
||||
```
|
||||
|
||||
Link command:
|
||||
|
||||
```
|
||||
-a
|
||||
-e start
|
||||
-o payload.out
|
||||
-m payload.map
|
||||
payload.obj
|
||||
SECTIONS
|
||||
{
|
||||
.text 02000000h: {} /* example: bit 0x02000000 = byte 0x400000, in RAM */
|
||||
}
|
||||
```
|
||||
|
||||
Then either read `payload.out` as COFF (true bit address in the section header)
|
||||
or `gsprom -i payload.out hi lo`, interleave low/high, and place the bytes at
|
||||
`0x02000000 >> 3 = 0x400000`.
|
||||
|
||||
The RAM region is `0x0000000`–`0x1FFFFFF` (4 MiB) and the ROM is
|
||||
`0x3F200000`–`0x3FFFFFFF` in the MAME model; choose a free RAM range for a
|
||||
payload, or load into RAM and jump. The SDK supplies the image and its entry
|
||||
point, **not** the network delivery mechanism. Delivery is a separate problem:
|
||||
the COFF gives a loader the exact bit address and entry point; GSPROM's hex is
|
||||
only for burning EPROMs.
|
||||
|
||||
For C, use the same idea with `.text`/`.cinit` at the payload address and
|
||||
`.bss` in RAM; `-c` will include `boot.obj` and make `_c_int00` the entry.
|
||||
|
||||
---
|
||||
|
||||
## 7. Documentation: what the SDK adds over the repo
|
||||
|
||||
The repo already has `docs/TMS34010-users-guide.pdf`/`.txt` (the 1988 hardware
|
||||
user's guide). The SDK's unique, useful additions are:
|
||||
|
||||
* **`SPVU004A TMS34010 Assembly Language Tools Users Guide (1987).pdf`** — the
|
||||
key document: assembler directives and expression rules, the COFF appendix
|
||||
(file/section/relocation/symbol formats), linker `MEMORY`/`SECTIONS`,
|
||||
`gsprom`, and the simulator. This is where the bit-address and `W`/`L`
|
||||
behaviour is specified.
|
||||
* **`SPVU004A … Assembly Language Tools Reference Card (1987).pdf`** — compact
|
||||
invocation/option/directive summary; the fastest lookup.
|
||||
* **`SPVU005A TMS34010 C Compiler Reference Guide (1988-08).pdf`** — data
|
||||
types, ABI, runtime environment, library reference. The ABI section is what
|
||||
makes C/assembly interworking possible.
|
||||
* **`SPVU005A … C Compiler Reference Card (1988-08).pdf`** — quick ABI/options.
|
||||
* `SPVU005 TMS34010 C Compiler Users Guide (1986-12).pdf` — older; the 1988
|
||||
guide supersedes it.
|
||||
* `SPVU002A Software Development Board Users Guide` + pocket reference — the
|
||||
SDB and its monitor; useful only if you have that board.
|
||||
* `SPVU021A C Source Debugger Users Guide (1991-09)` — the `db340`/`gspmon`
|
||||
monitor workflow; not directly applicable to the VT1200 unless a compatible
|
||||
monitor can be loaded.
|
||||
* `SPVU006 Math Graphics Function Library` and the 1987 `Graphics Math Function
|
||||
Library` disk (`GRAFIX.LIB`, `TEXT.LIB`, `MATH.LIB` + sources) — optional
|
||||
drawing primitives; not evaluated against the VT1200's mono framebuffer.
|
||||
* `SPVS002A/C` production data — hardware electrical/timing detail; the repo's
|
||||
user's guide covers the programming model.
|
||||
* `SPVU015A/C` TIGA, `SPVU018A` TIGA art, `SPVU027` graphics library — TIGA is
|
||||
a PC graphics-interface standard, not the VT1200 terminal; not relevant.
|
||||
* `Docs/Video Interfaces/*` — TLC3407x RAMDAC/palette datasheets; the VT1200 is
|
||||
monochrome, so not relevant.
|
||||
* `Patents/` — graphics-architecture patents; no code or format value.
|
||||
|
||||
---
|
||||
|
||||
## 8. Findings about `smart34010` itself
|
||||
|
||||
These are worth fixing upstream (they cost real time here):
|
||||
|
||||
1. **Long relative `JR` is printed as `JA`.** In `dis34.c`, the `0x00` low-byte
|
||||
case (16-bit displacement, 2-word instruction) calls `printf("ja%s …")`;
|
||||
only the 8-bit case prints `jr%s`. This makes the output non-assemblable
|
||||
without the word-count fix. Example: `jagt L3F211EE0 ; c700 0167` is a
|
||||
relative jump, but `jagt` is the 3-word absolute instruction.
|
||||
2. **B-file `SP` is printed as `sp`.** The register name hides bit 4; `sp`
|
||||
(A-file) and `b15` (B-file) are different encodings.
|
||||
3. **`movi` hides the short/long choice.** The disassembler prints one mnemonic
|
||||
for both encodings, while the assembler auto-selects (and may change the
|
||||
meaning via sign extension). The `-o` word count is the only clue.
|
||||
4. **64 KB input fails** with `unexpected end of input at byte 65536`; use
|
||||
≤32 KB regions or fix the offset arithmetic.
|
||||
5. Data tables decoded linearly can produce branches into the middle of
|
||||
instructions; `-a` (analyze) may help, but for byte-exact reassembly the
|
||||
`.word` fallback in the converter is what matters.
|
||||
|
||||
The good news: the instruction syntax itself (mnemonics, operands, `@`/`*`
|
||||
addressing, labels, `.word` data) matches GSPA 2.00 closely; the problems are
|
||||
these specific ambiguities, not a different dialect.
|
||||
|
||||
---
|
||||
|
||||
## 9. DOSBox vs dosemu: benchmark
|
||||
|
||||
Separate from the toolchain result. The owner asked whether dosemu is actually
|
||||
faster. Workload is the real assembler, **GSPA 2.00** (there is no `as34` in
|
||||
this SDK), on the round-trip sources.
|
||||
|
||||
* **A:** `gspa t1 t1.obj t1.lst -l` — 4 KB region, 1,478 lines, with listing.
|
||||
* **B:** `gspa -q rt16 rt16.obj` — 16 KB region, 6,124 lines, no listing.
|
||||
* DOSBox 0.74-3, `core=dynamic`, `cycles=max`, `SDL_VIDEODRIVER=dummy`,
|
||||
`SDL_AUDIODRIVER=dummy`.
|
||||
* dosemu 1.4.0.8, `-t -quiet`, private `HOME`, batch ending `exitemu`.
|
||||
* 1 warm-up + 3 measured runs; median; all exit status 0.
|
||||
* COFF output compared with the 4-byte time stamp zeroed (raw COFF differs only
|
||||
in that stamp).
|
||||
|
||||
| workload | DOSBox median | dosemu median | ratio |
|
||||
|---|---:|---:|---:|
|
||||
| A: 4 KB with listing | **1.99 s** | **0.35 s** | 5.7× |
|
||||
| B: 16 KB no listing | **2.23 s** | **0.43 s** | 5.2× |
|
||||
|
||||
The timestamp-normalised object md5 is identical for every run of each
|
||||
workload and across the two emulators:
|
||||
|
||||
* A: `e9c59beb4a5e29e391495ba2b312fc79`
|
||||
* B: `69a525b4689f60f51afc3059b66b66a8`
|
||||
|
||||
Raw md5s differ because bytes 4–7 of the COFF header are the creation time; the
|
||||
assembled bytes are the same.
|
||||
|
||||
Setup cost: DOSBox needed only an existing config; dosemu auto-created
|
||||
`~/.dosemu`, required a drive C mapping and a batch (because `-E` does not
|
||||
exit without `exitemu`), and prints harmless CD-ROM/`unknown window sizes`
|
||||
warnings. That is a one-off few-minute setup, not per-run.
|
||||
|
||||
**Recommendation:** dosemu is materially faster (≈5×) under these settings and
|
||||
is worth using for batch/iterative assembly. DOSBox remains the known-good
|
||||
fallback and is what `roundtrip.sh` uses. Note that with DOSBox's original
|
||||
`cycles=3000` config the gap is much larger (the 16 KB listing workload took
|
||||
~35 s), because DOSBox was being cycle-limited, not because dosemu is 70×
|
||||
faster.
|
||||
|
||||
---
|
||||
|
||||
## 10. Reproduction
|
||||
|
||||
Scripts live in `harness/sdk-scratch/` (scratch; not part of the SDK):
|
||||
|
||||
* `roundtrip.sh OFF SIZE BASE` — extract, disassemble, convert, assemble, link,
|
||||
convert, interleave, diff. Example:
|
||||
`./roundtrip.sh 0x1000 32768 3F208000`
|
||||
* `dis2as34.py BASE SIZE IN.ASM` — the five fixups.
|
||||
* `bench2.py` — the DOSBox/dosemu benchmark (needs the private dosemu `HOME`
|
||||
under `harness/sdk-scratch/dosemu-home`).
|
||||
* `dosbox.conf` / `dosbox-bench.conf` — headless DOSBox configs.
|
||||
|
||||
The SDK itself is untouched; all extraction goes to `harness/sdk-scratch/`
|
||||
(`work/`, `cgtools/`, `sdb/`, `disk1987/`), which is not staged.
|
||||
|
||||
---
|
||||
|
||||
## 11. Open questions / not tested
|
||||
|
||||
* No payload was executed on a VT1200 or in MAME. The round trip proves the
|
||||
encoder, not the runtime or the network delivery.
|
||||
* No OS/2 environment was set up, so the r5.01 tools (and their `gspopt`
|
||||
optimizer) were not exercised. If the newer code generator matters, that is a
|
||||
separate OS/2-in-QEMU project.
|
||||
* The 1987 `GSPSIM` simulator was not run; MAME is the better execution model
|
||||
for this project.
|
||||
* The Math Graphics Function Library and TIGA libraries were not evaluated
|
||||
against the VT1200's monochrome display.
|
||||
* The optimal free RAM range for a payload depends on what the OS has allocated;
|
||||
the toolchain can link anywhere, but the loader must choose a safe range.
|
||||
Reference in new issue
Block a user