mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-30 12:03:54 +02:00
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.
483 lines
21 KiB
Markdown
483 lines
21 KiB
Markdown
# 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.
|