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,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