Each .bin file in v1/ contains one legal TLCS900H instruction form. For example, NOP.bin contains NOP tests, while SRC_B_DIRECT_R_ADC_M_IMM.bin contains the form that begins with source byte-memory direct-register addressing and then executes ADC (mem),#.
For each form, the generator chooses the fixed/static encoding bits and fuzzes every remaining variable field: register fields, sub-opcodes, condition fields, immediates, displacements, direct addresses, control-register ids, and initial CPU state.
These tests are intentionally per-instruction only:
- no external interrupt delivery
- no DMA stepping
- no MMIO or system bus devices
- flat 24-bit memory space
- non-opcode reads from uninitialized memory return deterministic random bytes
Legal synchronous instruction forms such as SWI and RETI are still included. They are tested as ordinary instruction behavior against the flat memory model.
Opcode bytes are installed at the randomized initial pc. If the CPU reads any other uninitialized address, the generator fills that byte from the test case PRNG and records it in the initial RAM dump so the JSON remains self-contained.
Every test case is seeded from its own name, for example:
LD_R8_IMM8 $03a
The generator also accepts a suite seed. The actual seed string is:
<suite seed>:<test name>
That means adding, removing, or reordering another instruction form does not change existing cases. To regenerate the exact same files, use the same generator, count, and suite seed.
--count is the number of tests per instruction form.
All integers are little-endian.
File header:
| Field | Type | Notes |
|---|---|---|
| magic | 8 bytes | T9SSTB1\0 |
| version | u32 | currently 1 |
| count | u32 | number of test records |
| family | string | instruction form name |
Each test record:
| Field | Type | Notes |
|---|---|---|
| name | string | test case name used as the PRNG seed boundary |
| opcode | bytes | u32 byte length followed by opcode bytes |
| initial | state | CPU state before executing one instruction |
| final | state | CPU state after executing one instruction |
| initial_ram | ram list | u32 count, then [address:u32, value:u32] byte entries |
| final_ram | ram list | same format, byte entries after execution |
| cycles | cycle list | u32 count, then cycle entries |
State fields are written as u32 values in this order:
xwa[4], xbc[4], xde[4], xhl[4],
xix, xiy, xiz, xsp, pc,
dmas[4], dmad[4], dmam[4], intnest,
cf, nf, vf, hf, zf, sf,
ca, na, va, ha, za, sa,
rfp, iff, halt,
op, pic, piq_size, piq[4], mar, mdr
Cycle entries are abstract TLCS900H bus transactions, not pin-level Z80 samples:
| Field | Type | Notes |
|---|---|---|
| clock | u64 | accumulated CPU clock when the transaction occurred |
| action | u32 | 1 read, 2 write |
| size | u32 | 1, 2, or 4 bytes |
| address | u32 | 24-bit address in low bits |
| data | u32 | little-endian transaction value |
| opcode | u32 | 1 if the read was entirely from installed opcode bytes |
Run:
python3 tlcs900h_bin_to_json.py v1/NOP.binThe JSON file is an array of test objects:
{
"name": "NOP $000",
"opcode": [0],
"initial": {
"pc": 123456,
"xwa": [0, 0, 0, 0],
"ram": [[123456, 0]]
},
"final": {
"pc": 123457,
"ram": [[123456, 0]]
},
"cycles": [
[0, 123456, 0, "r-m-", 1, "opcode"]
]
}cycles entries are:
[clock, address, data, flags, size, source]
flags uses the same broad spelling as the Z80 tests: r-m- for memory reads and -wm- for memory writes. source is opcode when the read came from installed instruction bytes, otherwise memory.
The generator lives in the Ares fork used for this project:
cmake --build <ares build dir> --target tlcs900h_sst
<ares build dir>/tlcs900h_sst --output=/Users/dave/dev/tlcs900h/v1 --count=1000 --seed=tlcs900h-v1Useful options:
--list list instruction forms
--only=FORM generate one instruction form
--count=N tests per instruction form
--seed=TEXT suite seed
--output=PATH output directory for .bin files
--group=byte optional comparison mode: group output by first opcode byte