EMMA 02

Pseudo Code

  • Home
  • Download
  • Change Log
  • Help
  • Compilation
    • Windows
    • macOS
    • Ubuntu
    • Fedora
    • openSUSE
  • Forums
  • Tape Conversion
  • Machine Code
    • SYSTEM00
    • CDP1801
    • CDP1802
    • CDP1804
    • CDP1805
    • Differences
  • Pseudo Code
    • AMVBAS
    • AM4KBAS
    • CARDTRAN
    • Chip-8, 8X, ETI-660 & Elf
    • FEL-1
    • FPA-1
    • FPL-2
    • FPL-4
    • GPL-2
    • GPL-3
    • GPL-4
    • GPL-A (2K RAM)
    • GPL-A (2K ROM)
    • PTC R1.7
    • PTC R2.0
    • ST2
    • ST4
    • STK
    • Super-chip
    • Test-Word
  • XML Code
    • Main Elements
    • I/O
    • System
    • A/D Convertor
    • BASIC
    • Batch wav
    • Bootstrap
    • Cassette
    • CD4536B
    • CDP1851
    • CDP1852
    • CDP1854
    • CDP1855
    • CDP1877
    • CDP1878
    • CDP1879
    • Debugger
    • COMX Diagnostic
    • Dip switch
    • Disk
    • EF Buttons
    • Flip Flop
    • Front Panel
    • GUI
    • HEX Modem
    • I/O Group
    • Keyboard
    • Keyfile
    • Keypad
    • Locations
    • Memory
    • MM57109
    • Payphone
    • Printer
    • RTC
    • SCN2671
    • Sound
    • Splash
    • USB
    • Video
    • Videodump
    • Vt
    • X Modem
  • BASIC
    • General Information
    • COMX BASIC V1.00
    • Floating Point BASIC 2.2
    • PECOM 32 BASIC
    • PECOM 64 BASIC 1.0 & 4.0
    • Quest Super BASIC 1.4
    • Quest Super BASIC 3.0
    • Quest Super BASIC 5.0
    • Quest Super BASIC 6.0
    • RCA BASIC3 V1.1
    • Telmac SBASIC v24.3
    • Error Messages
  • Computer List

 

 

 

 

 

 

 

 

 

 

 

 

PTC-701 R1.7 / McDonalds

Information

The PTC-701 system ROM contains a stack-based bytecode interpreter that executes application programs stored in the banked app ROM. For general hardware information, see the General Information section.

The interpreter dispatch loop is copied from ROM (0x0500) into RAM at 0x6100 for execution. It uses a 16-bit evaluation stack (pointed to by RD, located at 0x61FF) and variables stored in RAM at 0x6000+.

Opcodes are single bytes. Bit 7 selects between two dispatch methods:

  • Low opcodes (0x00–0x7F): Handler address looked up from a 128-entry table at ROM 0x0100–0x01FF
  • High opcodes (0x80–0xFF): Handler address partially encoded inline in the bytecode stream

Each handler's first byte is an entry-point selector loaded into R3, allowing multiple opcodes to share handler code with different setup paths. This compact design fits a complete virtual machine (VM) in ~1.5K of native code.

Architecture

CPU CDP1802 @ 2.4576 MHz
System ROM 16K at 0x0000–0x3FFF
App ROM 32K, bank-switched at 0x4000–0x5FFF
RAM 8K at 0x6000–0x7FFF
Interpreter loop RAM at 0x6100 (copied from ROM 0x0500)
Dispatch table ROM at 0x0100–0x01FF (128 entries × 2 bytes)
Data stack RAM 0x61FF (RD pointer, grows downward, 16-bit values)
Variables RAM 0x6000+ (indexed by bytecode operand)
Return stack RAM 0x625F (R2 pointer, grows downward, saves/restores RC)
Bytecode PC Register RC (walks through banked app ROM)

Register Usage

R0 Inline dispatch via address 0x00FE
R1 Interrupt register 0x0D00
R2 Return stack (0x625F, saves/restores RC for CALL/RETURN)
R3 Handler entry point selector
R9 Data pointer (X register target in some handlers)
RB Handler address / temporary pointer
RC Bytecode program counter
RD Data stack pointer (RD.1 = 0x61, grows downward)
RE Dispatch table index (RE.1 = 0x01)
RF Interpreter PC

Definitions

kk 8-bit constant / variable index / mask value
bb 8-bit signed branch offset
aaaa 16-bit absolute RAM address
[aaaa] Value at RAM address aaaa (16-bit)
mmmm Second 16-bit RAM address (used when an instruction takes two inline addresses)
TOS Top of data stack (16-bit value at M(RD)/M(RD+1))
NOS Next on stack (16-bit value below TOS)
3OS Third on stack (16-bit value below NOS)
[RP] ROM Pointer — indirect read from app ROM via banked pointer at M(60CA/60CB). Auto-advances after read.
VA Fixed 16-bit variable at M(6056/6057)
VB Fixed 16-bit variable at M(6058/6059)
VC Fixed 16-bit variable at M(605A/605B)
VD Fixed 16-bit variable at M(605C/605D)
VE Fixed 16-bit variable at M(605E/605F)
0..9, A..F Hexadecimal digits

Note: All binary operations (TOS = NOS op TOS) pop both operands and push the result unless otherwise noted.

Note: All byte loads are zero-extended to 16-bit before pushing to the data stack.

Bank Address Encoding

The PTC-701 uses a 3-byte encoded address format for all banked ROM/RAM access. Opcodes LDBNK (0x39), STBNK (0x5A), LDBNK (0x5B), and SETBK (0x6B) all decode addresses using this scheme. Byte 0 bit 7 selects between two memory windows:

Window 1 (byte 0 bit 7 = 1) - bank register 0x3FA2, address range 0x4000–0x5FFF:

bank      = ((byte0 << 3) | 0x80) + (byte1 >> 5)
addr_high = (byte1 & 0x1F) + 0x40
addr_low  = byte2

Writes bank value to M(604F) and hardware register M(3FA2).

Window 2 (byte 0 bit 7 = 0) - bank registers 0x3FC0–0x3FC3, address range 0x8000–0xFFFF:

raw       = (byte0 << 1) | (byte1 >> 7)
M(604E)   = raw − 1
bank_reg[n] = M(604E) × 4 + n    (n = 0..3, written to 0x3FC0–0x3FC3)
read_addr = ((byte1 | 0x80) << 8) | byte2

Only writes bank registers when M(604E) changes from its previous value.

On exit, all bank operations restore the home bank from M(60C9).

Handler Dispatch

Dispatch Table at ROM 0x0100

The handler address table occupies ROM 0x0100–0x01FF and contains 128 entries (one per low opcode 0x00–0x7F). Each entry is 2 bytes stored as high byte first, low byte second at address 0x0100 + (opcode × 2).

The interpreter dispatch loop uses register RE (with RE.1 pre-set to 0x01) as a pointer into this table. After fetching an opcode and shifting left (SHL), the result is placed in RE.0, giving the table offset directly. Two reads via RE retrieve the 16-bit handler address into RB:

Both low opcodes (0x00–0x7F) and high opcodes (0x80–0xFF / SYS) use the same dispatch chain. The SYS path computes the handler address differently (from the opcode itself and the following byte) but then follows the identical pattern: LDA RB reads the first byte at the handler as an entry-point selector, loads it into R3.0, and executes via SEP R3. SYS handlers therefore use the same entry-point mechanism as low opcodes.

; From the dispatch table lookup at ROM 0x0504:
; (this routine is copied to RAM 0x6100 during startup)
PLO  RE        ; RE.0 = (opcode << 1) - table offset
LDA  RE        ; D = M(0x01xx) = handler address high byte, RE++
PHI  RB        ; RB.1 = handler high
LDN  RE        ; D = M(0x01xx+1) = handler address low byte
PLO  RB        ; RB.0 = handler low -> RB = full handler address

Unused opcode slots contain 0x0000 or 0xFFFF. Valid entries point to handler code in the system ROM (typically pages 0x05–0x21). The first byte at each handler address is not executed as an instruction - it is consumed by the dispatch loop as the entry-point selector for R3.

Example: opcode 0x00 (ADD) → table offset = 0x00 × 2 = 0x00 → reads ROM at 0x0100/0x0101 = 0x05/0x6B → handler at 0x056B. First byte there is 0x06 (entry-point selector), so handler body starts at 0x056C.

Dispatch Chain Notation

The Dispatch column shows the execution chain from the handler address to the actual code. The first byte at each handler address is an entry-point selector - it is loaded into R3.0 and causes a jump to address 0x02xx. Some entry points perform a second dispatch by reading the next handler byte, creating a two-level chain shown as XX·YY→ZZZZ.

black – traced   orange – partly traced   goldenrod – disassembled   red – guess

Entry-Point Routines (page 0x02)

Reached via three mechanisms: 1st = first-byte dispatch (entry selector at handler address), 2nd = second-stage via entry A4 or 03 (LDA RB→PLO R3), R0 = SEP R0 trampoline (0x00FE→LDA RB→PLO R3→SEP R3).

Byte Address Via Function
00 0200 R0 SEX R6 trampoline: sets X=R6, reads next handler byte as second dispatch (LDA RB→PLO R3). Used by entry 0x94 to make 0x0285 read from M(R6) instead of M(RC).
03 0203 1st MUL setup: SEX RB, then two-level dispatch via LDA RB→PLO R3 to second-stage routine 0xB7 to pop operands. Used by MUL (0x06).
06 0206 1st Pop TOS into R9, set X=RD, return to handler body
0B 020B 1st Direct execute: immediately returns to handler body (SEP RB). No parameter or stack setup.
0C 020C 1st Add inline constant to TOS: reads 2-byte constant (low, high) from handler body via LDA RB, adds to TOS via ADD/ADC. Used by opcodes 0x03 (+1), 0x04 (+2), 0x27 (-1).
15 0215 R0 CALL via computed address: push RC to return stack (R2), set RC = RB (handler address)
20 0220 1st, R0 Push constant from handler body: reads 2 bytes via LDA RB (high, low), pushes as 16-bit value onto data stack. Used by PUSH 0000–FFFF (opcodes 0x72–0x76).
28 0228 1st Immediate return: SEP RF. Returns to interpreter loop without any action. Used by NOOP (0x7F).
29 0229 1st, R0 Push zero: pushes 16-bit 0x0000 onto data stack (uses GHI R0 where R0.1=0x00)
2B 022B 1st, R0 Push one (0x0001): GHI RE (=0x01); DEC RD; STR RD; GHI R0 (=0x00); DEC RD; STR RD; SEP RB. Used by LOOP (0x0C) as increment=1, and by ISLT (0x4D) as true result.
32 0232 1st, R0 Indirect call via data stack: pop address from TOS, DEC to get entry byte, read entry selector, dispatch to handler at that address
39 0239 R0 Byte copy loop: DEC R8; LDA RA; STR R9; INC R9; GLO R8; BNZ (loop). Copies R8.0 bytes from M(RA) to M(R9). Used by MCPY (0x1A) and MCPY3 (0x38).
3D 023D R0 Byte copy loop (mid-entry): GLO R8; BNZ → LDA RA; STR R9; INC R9; DEC R8; GLO R8; BNZ (loop). Enters the 0x39 copy loop at the count-check, skipping the initial DEC R8. Used by MCOPY (0x1A) for count.high passes, STBNK (0x5A), and LDBNK (0x5B).
50 0250 R0 Fill loop: GLO R8; BNZ; DEC R8; GLO RA; STR R9; INC R9; repeat until R8.0==0. Fills R8.0 bytes at M(R9) with RA.0. Used by FILL (0x46) via secondary dispatch from SEP R0→0x00FE→LDA RB(=0x50)→PLO R3→SEP R3.
54 0254 R0 Store RA to var A: LDI 56; PLO R6 (R6=0x6056); SEX R6; GLO RA; STXD; GHI RA; STXD; SEP RB. Stores 16-bit RA to var A (0x6056/57). Variant of 0x59 but uses X=R6 instead of X=RD.
59 0259 R0 Push RA to data stack: SEX RD; GLO RA; STXD; GHI RA; STXD; SEP RB. Used by ISLT (0x4D) to re-push NOS, and by LDVCD (0x37, when X=R6 stores to VC).
5A 025A R0 Store RA via STXD×2 (no SEX): GLO RA; STXD; GHI RA; STXD; SEP RB. Entry within 0x59 body, skips SEX RD. Target depends on current X register. Used by LDVCD (0x37, X=R6→stores to VC).
5F 025F R0 Store R9 via STXD×2 (no SEX): GLO R9; STXD; GHI R9; STXD; SEP RB. Entry within 0x60 body, skips SEX RD. Target depends on current X register.
60 0260 R0 Push R9 to data stack: SEX RD; GLO R9; STXD; GHI R9; STXD; SEP RB. Used by ISLT (0x4D) to re-push TOS.
66 0266 1st, R0 Pop TOS→R9: SEX RD, pops 16-bit TOS into R9 (high then low via LDXA×2), returns to handler body. Used by ROMCPY, DISP.
67 0267 R0 Pop TOS→R9 (no SEX): LDXA→PHI R9; LDXA→PLO R9; SEP RB. Entry within 0x66 body, skips SEX RD. Target depends on current X register.
6C 026C 1st, R0 Pop TOS into R8: SEX RD; LDXA→PHI R8; LDXA→PLO R8; SEP RB. Used by TOBCD (0x48), FMBCD (0x49), STBNK (0x5A), LDBNK (0x5B).
6D 026D R0 Pop TOS into R8 (no SEX): LDXA→PHI R8; LDXA→PLO R8; SEP RB. Entry within 0x6C body, skips SEX RD. Target depends on current X register.
72 0272 1st Load R9 from M(6051/52) and RA from M(6053/54) (ROMCPY post-state: dest ptr, src ptr), return to handler body
78 0278 1st Pop 3 values: TOS→R8, NOS→R9, 3OS→RA (with NBR padding between pairs); sets X=RD; returns to handler body (FILL, NEG, LOAD, COMP)
7E 027E 1st, R0 Pop TOS→R9, NOS→RA: SEX RD; then falls into 0x7F body (LDXA→PHI R9; LDXA→PLO R9; NBR; LDXA→PHI RA; LDXA→PLO RA; NBR; SEP RB). Reads from data stack (X=RD). Used by ADDM (0x05), SWAP (0x20), ISLT (0x4D), SUB24 (0x6E).
7F 027F 2nd Read two 16-bit values into R9 and RA (no SEX): LDXA→PHI R9; LDXA→PLO R9; NBR; LDXA→PHI RA; LDXA→PLO RA; NBR; SEP RB. Entry within 0x7E body, skips SEX RD. Source depends on current X register. Used by MCPY3 (0x58) and ADD24 (0x59) inline-address variants (X=RC via entry 0xA4).
84 0284 1st, R0 Pop TOS into RA: SEX RD; then falls into 0x85 body (LDXA→PHI RA; LDXA→PLO RA; NBR; SEP RB). Reads from data stack (X=RD). Used by LD TOS,[TOS], LD [TOS],NOS, CLRB, SETB, etc.
85 0285 2nd, R0 Read 16-bit into RA (no SEX): LDXA→PHI RA; LDXA→PLO RA; NBR; SEP RB. Entry within 0x84 body, skips SEX RD. Source depends on current X register. As second-stage routine (via A4·85, X=RC): reads 2 bytes from bytecode stream into RA.
8A 028A 1st, R0 SEX RD: set X=RD (data stack), return to handler body. TOS at M(RD).
8B 028B 1st Direct execute: immediately returns to handler body (SEP RB). No parameter or stack setup.
8C 028C 1st OVER setup: INC RD×2 (skip TOS), reads NOS into R9.1 (high) and D (low), DEC RD×2 (back to original), SEP RB to handler body which pushes copy as new TOS.
8E 028E 1st Read TOS into R9, move RD down by 2, return to handler body. Used by DUP (0x1E).
94 0294 1st Variable indirect: SEX RB, reads R6.0 from handler body, second dispatch via 0x00 then third via next byte. Used by STRCP, NUMCP, NUMCZ, PUSH VE, PUSH [VE].0, PUSH [VE], POP [VE].
99 0299 R0 Add M(RA) to RA (16-bit with carry): SEX RA; GLO RA; ADD; PLO RA; GHI RA; ADCI 00; PHI RA; SEP RB. Used by ADD TOS,[TOS] (0x35).
9A 029A R0 Add M(X) to RA (16-bit): GLO RA; ADD; PLO RA; GHI RA; ADCI 00; PHI RA; SEP RB. Entry within 0x99 body (skips SEX RA). Adds value at current X to RA with carry. Used by FMHEX (0x4B), TOHEX (0x4C).
A1 02A1 R0 Dual-purpose entry. First call (via SEP R0→PLO R3→SEP R3): lands at 0x02A1 = SEP RB, returns immediately to handler body (sets R3.0=A1 for later). Subsequent calls (via SEP R3 from handler loop): R3 resumes at 0x02A2 = BR 9A → jumps to 0x029A (GLO RA; ADD; PLO RA; GHI RA; ADCI 00; PHI RA; SEP RB). Adds M(X) to RA with carry, returns. Used by CKSUM (0x47): first call sets up R3, then loop calls SEP R3 repeatedly for the add step (SEX RA set once before loop).
A4 02A4 1st SEX RC trampoline: sets X=RC (bytecode stream), reads next handler byte as second dispatch
A7 02A7 2nd, R0 Read 16-bit from M(60xx) and push to data stack: LDXA→PLO R6 (R6=60xx); LDA R6→PLO RE (high byte); DEC RD; LDN R6→STR RD (push low); DEC RD; GLO RE→STR RD (push high); SEP RB. Used by LD TOS,[60bb] and MUL result retrieval.
B2 02B2 2nd Read single byte from M(60xx), zero-extend to 16-bit, push to data stack: LDXA→PLO R6 (R6=60xx); LDN R6→D (byte value); GHI R0→D=0x00; BR 02AA; PLO RE (RE.0=0x00); DEC RD; LDN R6→STR RD (push low byte); DEC RD; GLO RE→STR RD (push high=0x00); SEP RB. Used by LD TOS.0,[60bb] (opcode 0x40).
B7 02B7 2nd, R0 Pop 16-bit from data stack and store to M(60xx): LDXA→PLO R6 (R6=60xx); LDA RD→STR R6 (high byte to M(60xx)); INC R6; LDA RD→STR R6 (low byte to M(60xx+1)); SEP RB. Used by LD [60bb],TOS and MUL operand setup.
BF 02BF 2nd Pop TOS and store low byte only to M(60xx): LDXA→PLO R6 (R6=60xx); INC RD (skip TOS high); BR 02BC; LDA RD→D (read TOS low, RD advances past); STR R6 (store to M(60xx)); SEP RB. Used by LD [60bb],TOS.0 (opcode 0x41).
CA 02CA 1st, R0 Handler body setup: reads 2 bytes from handler body (LDA RB) into R6.0 and R7.0, then SEP RB. Used by LCD, DELAY.
CF 02CF R0 Set bits in HW register: SEX R6; LDA RB (mask); OR; STR R6; STR R7; SEP RB. ORs mask byte from handler body with M(R6) (RAM shadow), stores result to both M(R6) and M(R7) (HW register).
D5 02D5 R0 Clear bits in HW register: SEX R6; LDA RB (mask); XRI FF; AND; STR R6; STR R7; SEP RB. Inverts mask, ANDs with M(R6) (RAM shadow), stores result to both M(R6) and M(R7) (HW register).
E8 02E8 R0 Jump to 0x03A7: ROM data field decoder, processes bytes at M(RA), extracts bit fields, returns via SEP RB
EB 02EB R0 Jump to 0x03DA: banked address decoder (see Bank Address Encoding). Decodes 3-byte address at M(RA) into physical address and bank select value. Returns via SEP RB.
EE 02EE R0? Jump to 0x046B: serial handshake routine (TBC). Tests EF1/EF3/Q, manages serial state. Returns via SEP RB.
F1 02F1 R0? Jump to 0x04AE: serial handshake routine (TBC). Tests EF1/EF2/Q, manages serial state. Returns via SEP RB.
F4 02F4 R0 Jump to 0x0451: bank register update. Computes bank values from M(604E) and writes to 0x3FC0–0x3FC3 (window 2 bank select registers). Returns via SEP RB.
F7 02F7 R0 Jump to 0x042C: page advance. If RA.1 < 0x60 (ROM window 1): increment bank at M(604F), write to M(3FA2), reset RA.1 = 0x40. If RA.1 == 0x00 (ROM window 2): increment M(604E), update bank regs 0x3FC0–0x3FC3, reset RA.1 = 0x80. Returns via SEP RB.
FA 02FA R0 Jump to 0x0300: MUL loop (shift-and-add multiply). Shifts product left, shifts multiplier left (MSB→DF), adds multiplicand when DF=1. Loops R8.0 iterations. Returns via LBR 020B (SEP RB). Used by MUL (0x06).
FD 02FD R0 Jump to 0x0347: DIV loop (restoring division). Shifts quotient/dividend left, trial-subtracts divisor, restores on borrow. Loops R8.0 iterations. Returns via LBR 020B (SEP RB). Used by DIV (0x07).

Syntax

Instructions
Opcode Mnemonic ParameterForth Handler Dispatch Definition
00 ADD TOS, NOS+ 056B 06→056C TOS = NOS + TOS (16-bit add)
01 SHL TOS2* 0572 06→0573 TOS = TOS × 2 (16-bit shift left)
02 SUB TOS, NOS- 0577 06→0578 TOS = NOS − TOS (16-bit subtract)
03 ADD TOS, 11+ 057E 0C→0581 TOS = TOS + 1
04 ADD TOS, 22+ 0582 0C→0585 TOS = TOS + 2
05 ADD [TOS], NOS+! 058A 7E→058B M(TOS) = M(TOS) + NOS (16-bit add in-place). Pops both.
06 MUL TOS, NOS* 09F8 03·B7→09FB TOS = NOS × TOS (16-bit result, overflow discarded)
07 DIV TOS, NOS/MOD 0A23 29→0A24 TOS = NOS / TOS (quotient), NOS = NOS mod TOS (remainder). 16-bit unsigned division. Pushes two results.
08bb JZ aaaa?BRANCH 059D 8A→059E Pop TOS; if TOS == 0, jump to aaaa (forward, aaaa = PC + 1 + bb)
09bb JZ aaaa?BRANCH 05D5 8A→05D6 Pop TOS; if TOS == 0, jump to aaaa (backward, aaaa = PC + 1 + bb - 256)
0Abb JP aaaaBRANCH 05A5 8B→05A6 Unconditional jump to aaaa (forward, aaaa = PC + 1 + bb)
0Bbb JP aaaaBRANCH 05DD 8B→05DE Unconditional jump backward (aaaa = PC + 1 + bb − 256)
0Cbb LOOP aaaa(LOOP) 05AF 2B→05B0 Increment 16-bit counter at top of return stack (SP+0/1) by 1. Compare with limit at SP+2/3. If counter > limit (signed): branch backward to aaaa. Otherwise: exit loop.
0Dbb LOOP TOS, aaaa(+LOOP) 05B1 0B→05B2 Same as LOOP but increment by TOS (variable step) instead of 1. Branch backward to aaaa.
0E LOOP FRAME(DO) 05E7 7E→05E8 Push loop frame to return stack: TOS→SP+0/1 (counter), NOS→SP+2/3 (limit). Pops both from data stack.
0F LOOP CLRLEAVE 05F2 0B→05F3 Clear loop frame limit: zeroes SP+2/3 (limit). Does not modify counter or pop the frame.
10 EXEC TOSEXECUTE 05FE 32→05FF Call handler at M(TOS). Pops TOS, dispatches to target.
11 RET EXIT 060F 8B→0610 Return from subroutine: pop RC from return stack
12 AND TOS, NOSAND 0615 06→0616 TOS = NOS AND TOS (16-bit bitwise AND)
13 ISZ TOS0= 0627 8A→0628 TOS = (TOS == 0) ? 1 : 0.
14 ISNEG TOS0< 0635 8B→0636 TOS = (TOS < 0) ? 1 : 0. Tests bit 7 of TOS high byte.
15 LD TOS, [TOS]@ 0661 84→0662 TOS = M(TOS):M(TOS+1). Load 16-bit value from M(TOS).
16 LD [TOS], NOS! 0676 84→0677 M(TOS) = NOS (store 16-bit NOS to M(TOS), pops both)
17 LD TOS.0, [TOS]C@ 068A 84→068B TOS.0 = M(TOS) (load single byte from M(TOS))
19 LD [TOS], NOS.0C! 0694 84→0695 M(TOS) = NOS.low. Store low byte of NOS to M(TOS), pops both.
1A MCPY [NOS], [3OS]CMOVE 0699 78→069A Memory copy: pops 3 values (TOS=count, NOS=dest, 3OS=source); copies full 16-bit count bytes from source to dest
1C DROP DROP 06A5 8B→06A6 Drop TOS (discard top of stack)
1D OVER OVER 06A9 8C→06AA Copy NOS to TOS (push copy of second stack value on top)
1E DUP DUP 06AD 8E→06AE Duplicate TOS (push copy of top value)
1F DUPNZ ?DUP 06B3 8A→06B4 Duplicate TOS if nonzero (push copy); if TOS == 0, no change
20 SWAP SWAP 06BE 7E→06BF Exchange TOS and NOS on data stack (swap top two 16-bit values)
21 ROT ROT 06C6 78→06C7 Rotate 3OS item to TOS (Forth ROT). Before: TOS=A, NOS=B, 3OS=C → After: TOS=C, NOS=A, 3OS=B
22aaaa PUSH aaaaLIT aaaa 06D0 8B→06D1 Push 16-bit immediate value aaaa onto data stack
23kk PUSH kkLIT kk 06D5 8B→06D6 Push 8-bit constant kk onto data stack
24 PUSH RR@ 06DF 0B→06E0 Read 16-bit value from top of return stack (R2), push to data stack (return stack unchanged)
25 POPR R> 06EA 8B→06EB Pop 16-bit value from return stack (R2), push to data stack
26 PUSHR >R 06F1 84→06F2 Pop TOS from data stack, push to return stack (R2)
27 SUB TOS, 11- 0586 0C→0589 TOS = TOS − 1
29 NAVBK NAVBK 11A0 2B→11A1 Conditional bank navigation. Reads byte at M(60CA/CB): if bit 7 = 1, returns immediately (already at target). If bit 7 = 0, follows linked-list of forward offsets through app ROM (lower 7 bits = offset to next record) until finding a record with bit 7 = 1, then updates M(60CA/CB). Same as SETBK phase 1 only; no phase 2 traversal, no bank select write.
2A LD TOS.0, [RP]C@+ 1280 8A→1281 Load byte from app ROM via [RP], push to data stack. Advance pointer by 1.
2B STRCP STRCP 07F5 94→07F9 String copy with space padding: copies VD bytes from M(VC) to M(VA), pads (VB-VD) spaces to fill field width VB
2C NUMCP NUMCP 081D 94→0821 Numeric copy with zero padding: pads (VB-VD) zero bytes at M(VA) first, then copies VD bytes from M(VC). Right-aligns in field width VB.
2D NUMCZ NUMCZ 0815 94→0819 Numeric copy with '0' padding: pads (VB-VD) ASCII '0' bytes at M(VA), then copies VD bytes from M(VC). Right-aligns in field width VB.
2E LD [TOS], 00 C! 0875 84→0876 M(TOS) = 0x00. Pops TOS.
2F LD [TOS], 11 C! 0879 84→087A M(TOS) = 0x01. Pops TOS.
30 ADD [TOS], NOS.0C+! 087D 84→087E M(TOS) = M(TOS) + NOS.low (byte add). Pops both.
31 MCPY3 [TOS], [NOS]CMOVE3 088B 7E→088C Copy 3 bytes from NOS (source) to TOS (dest). Pops both.
32 ADD24 [NOS], [TOS]D+24 089C 7E→089D 24-bit (3-byte) add: M(NOS) = M(NOS) + M(TOS), big-endian. Pops both addresses.
33 OR TOS, NOSOR 0892 06→0893 TOS = NOS OR TOS (16-bit bitwise OR)
34 COMP COMPARE 08C8 78→08C9 Multi-byte compare: compares TOS.0 bytes at NOS against 3OS. Returns 0 (equal), 1 (NOS > 3OS), or FFFF (NOS < 3OS)
35 ADD TOS, [TOS]+@ 08E2 84→08E3 TOS = TOS + M(TOS) (16-bit add with carry)
36 LD24 [TOS], NOS!24 08AE 84→08AF Store NOS as 24-bit (zero-extended) at M(TOS): M(TOS)=0x00, M(TOS+1)=NOS.1, M(TOS+2)=NOS.0. Pops both.
37 LDVCD [TOS]LDVCD 07E3 84→07E4 VD = M(TOS), VC = TOS+1 (address); pops TOS
38 MCPY [TOS], [NOS]CMOVE 08E8 7E→08E9 Copy M(NOS) bytes from NOS+1 to TOS; count is first byte at source address. Pops both.
39 LDBNK [TOS]C@BK 10BA 84→10BB Read 1 byte from banked ROM at 3-byte encoded address (see Bank Address Encoding). Pushes byte to TOS. Restores home bank on exit.
3Aaaaa LD TOS, [aaaa]@ aaaa 065D A4·85→065F TOS = [aaaa] (load 16-bit, push to stack)
3Baaaa LD [aaaa], TOS! aaaa 0673 A4·85→0675 [aaaa] = TOS (pop 16-bit, store to address)
3Caaaa LD TOS.0, [aaaa]C@ aaaa 0686 A4·85→0688 TOS = [aaaa] (load byte, push)
3Daaaa LD [aaaa], TOS.0C! aaaa 0691 A4·85→0693 [aaaa] = TOS (pop, store low byte only)
3Ebb LD TOS, [60bb]@ 60bb 0B2D A4·A7→0B2F Load 16-bit from variable at 0x60bb, push to stack
3Fbb LD [60bb], TOS! 60bb 0B33 A4·B7→0B35 Pop TOS (16-bit), store to variable at 0x60bb
40bb LD TOS.0, [60bb]C@ 60bb 0B2A A4·B2→0B2C Load 1 byte from variable at 0x60bb, push to stack
41bb LD [60bb], TOS.0C! 60bb 0B30 A4·BF→0B32 Pop TOS, store low byte to variable at 0x60bb
42kk LD [60BD], kkC! kk 0B36 94→0B39 Store immediate byte kk to fixed address 0x60BD
43 DISP DISPLAY 072F 66→0730 Character insertion into display buffer. Pops TOS as dest position. Inserts formatted chars from var A into cursor positions (0x5F markers) in buffer at dest.
44 LCD LCD 1848 CA→184B Full HD44780 display refresh. Writes 2×32 chars from RAM buffer 0x6080–0x60BF to LCD. Each byte translated via table at 0x68xx.
45 DELAY DELAY 1A9C CA→1A9F Software delay. Duration controlled by M(6009): if 0, exits immediately; if nonzero, delays proportional to value. No I/O.
46 FILL FILL 0884 78→0885 Fill memory: pops 3 values (TOS=count, NOS=dest addr, 3OS=fill byte); fills count.low bytes at dest with fill.low
47 CKSUM CHECKSUM 08F1 84→08F2 Checksum: pops TOS (start address) and NOS (count). Loops count times: adds byte at current address to the address itself, then advances. Pushes final address. If count == 0, pushes TOS unchanged.
48 TOBCD TOBCD 0907 6C→0908 Binary to packed BCD (double-dabble algorithm). Pops TOS as byte count N. Converts N binary bytes at M(6038..6037+N) to packed BCD at M(60(37−N)..6037). No stack result.
49 FMBCD FMBCD 0959 6C→095A Packed BCD to binary (reverse double-dabble algorithm). Pops TOS as byte count N. Converts BCD at M(60(38−N)..6037) to binary at M(6038..6037+N). Inverse of TOBCD. No stack result.
4B FMHEX FMHEX 0A72 94→0A76 ASCII hex to nibble. Converts VD hex chars from M(VC) (right-to-left) into VB nibble bytes at M(VA). Zero-pads remaining output. Uses vars VA–VD.
4C TOHEX TOHEX 0AC1 94→0AC5 Binary to ASCII hex. Converts VD bytes from M(VC) to VB hex chars at M(VA) (right-to-left, 2 chars per byte). Inverse of FMHEX. Uses vars VA–VD.
4D ISLT < 06F8 7E→06F9 TOS = (TOS < NOS) ? 1 : 0 (16-bit unsigned)
4E ISGT > 0700 06→0701 TOS = (TOS > NOS) ? 1 : 0 (16-bit unsigned)
4F ISEQ = 070F 06→0710 TOS = (TOS == NOS) ? 1 : 0 (16-bit)
52kk PUSH VE, kkVE+ kk 0647 94→064B TOS = VE + kk. VE unchanged.
53kk PUSH [VE].0, kkVE+C@ kk 067D 94→0681 TOS = M(VE + kk) (load byte). VE unchanged.
55kk PUSH [VE], kkVE+@ kk 0654 94→0658 TOS = M(VE + kk) : M(VE + kk + 1) (load 16-bit word, big-endian). VE unchanged.
56kk POP [VE], kkVE+! kk 066A 94→066E M(VE + kk) = TOS.0 (store low byte). VE unchanged.
57aaaakk LDAND [aaaa], kkLDAND aaaa kk 061C A4·85→061E TOS = [aaaa] & kk (load byte, AND with mask, push)
58aaaammmm MCPY3 [aaaa], [mmmm]CMOVE3 aaaa mmmm 0888 A4·7F→088A Copy 3 bytes from [mmmm] to [aaaa]. Fixed count of 3.
59aaaammmm ADD24 [mmmm], [aaaa]D+24 mmmm aaaa 0899 A4·7F→089B [mmmm] = [mmmm] + [aaaa] — 24-bit (3-byte) big-endian add.
5Aaaaammmm STBNK [aaaa], mmmm!BK aaaa mmmm 156E 6C→156F Banked write: copy TOS bytes from RAM at mmmm to banked address at [aaaa] (see Bank Address Encoding). Pops TOS (count).
5Baaaammmm LDBNK aaaa, [mmmm]C@BK aaaa mmmm 15CC 6C→15CD Banked read: copy TOS bytes from banked address at [mmmm] to RAM at aaaa (see Bank Address Encoding). Pops TOS (count).
5D LD VC, TOSVC ! 0B3C 8B→0B3D Pop TOS (16-bit), store to VC
5E LD TOS, VCVC @ 0B10 8A→0B11 Load 16-bit from VC, push to stack
5F LD VD, TOSVD ! 0B41 8B→0B42 Pop TOS (16-bit), store to VD
60 LD TOS, VDVD @ 0B15 8A→0B16 Load 16-bit from VD, push to stack
61 LD VA, TOSVA ! 0B46 8B→0B47 Pop TOS (16-bit), store to VA
62 LD TOS, VAVA @ 0B1A 8A→0B1B Load 16-bit from VA, push to stack
63 LD VB, TOSVB ! 0B4B 8B→0B4C Pop TOS (16-bit), store to VB
64 LD TOS, VBVB @ 0B1F 8A→0B20 Load 16-bit from VB, push to stack
66aaaa CALLI [aaaa]EXECUTE 05FF A4·85→0601 Indirect call: reads 16-bit target from table at aaaa + TOS×2, calls handler at that address. Pops TOS (index).
67 LD VE, [RP]@+VE! 12A8 8A→12A9 VE = M(RP) : M(RP+1). Load 16-bit word from app ROM, store to VE, advance pointer by 2.
6A LD TOS, [RP]@+ 12A0 8A→12A1 Load 16-bit word from app ROM via [RP], push to stack, advance pointer by 2
6B SETBK SETBK 11A2 29→11A3 Full bank navigation. Phase 1: reads byte at M(60CA/CB): if bit 7 = 0, follows linked-list of forward offsets through app ROM (lower 7 bits = offset to next record) until finding a record with bit 7 = 1; updates M(60CA/CB). If bit 7 is already set, the pointer is unchanged. Phase 2: reads pointer from M(60C0/C1/C2), traverses a second linked list in banked ROM from that address (same offset mechanism), until a record with bit 7 = 1 is found. Restores home bank on exit. No data transfer.
6E SUB24 [NOS], [TOS]D-24 09B6 7E→09B7 M(NOS) = M(NOS) − M(TOS) — 24-bit (3-byte) subtract, big-endian. Pops both addresses.
6F ADD24 [NOS], TOSD+24 0A4D 7E→0A4E M(NOS) = M(NOS) + TOS — Add 16-bit TOS to 24-bit (3-byte) value at M(NOS), big-endian. Pops both.
70 SUB24 [NOS], TOSD-24 0A63 7E→0A64 M(NOS) = M(NOS) − TOS — Subtract 16-bit TOS from 24-bit (3-byte) value at M(NOS), big-endian. Pops both.
72 PUSH 0000LIT 0000 212E 20 Push 0x0000 (constant in handler body)
73 PUSH 0001LIT 0001 2132 20 Push 0x0001 (constant in handler body)
74 PUSH 0002LIT 0002 2136 20 Push 0x0002 (constant in handler body)
75 PUSH 0003LIT 0003 213A 20 Push 0x0003 (constant in handler body)
76 PUSH FFFFLIT FFFF 213E 20 Push 0xFFFF (constant in handler body)
7F NOOP NOOP 0BE3 28→0BE4 No operation
80kk–FFkk SYS aaaaSYS aaaa - - System dispatch to target address aaaa. Address calculation: aaaa = (kk << 8) | ((opcode & 0x7F) << 1). Only even target addresses are reachable. Note that kk is a program memory page value; multiple opcodes can target the same page of code.

Example: opcode DD 07 → kk = 07, kk << 8 = 0x0700, (opcode & 0x7F) = 0x5D, 0x5D << 1 = 0xBA, address = 0x0700 | 0xBA = 0x07BA
SYS Targets
Opcode Mnemonic Handler Dispatch Function
CA 05 SYS 0594 0594 8A→0595 SHR TOS — 16-bit logical shift right
A0 06 SYS 0640 0640 06→0641 XOR: TOS = NOS XOR TOS (16-bit bitwise XOR)
91 07 SYS 0722 0722 84→0723 Average: TOS = (TOS + NOS) / 2 (16-bit add then shift right)
CC 07 SYS 0798 0798 8B→0799 String index: compute address VC + VD, load byte, push to stack
DD 07 SYS 07BA 07BA 72→07BB String strip: strip bit 7 from each char in display buffer using ROMCPY state pointers and var D count
A9 08 SYS 0852 0852 94→0853 Variable-indirect compare setup: load params from vars, fall through to COMP
DC 08 SYS 08B8 08B8 7E→08B9 COMP3: 3-byte compare of M(TOS) vs M(NOS). Returns 0/1/FFFF.
DF 08 SYS 08BE 08BE 94→08BF Variable-indirect COMP: load count from var, addresses from stack, compare bytes
D3 09 SYS 09A6 09A6 78→09A7 Multi-byte add: M(NOS) += M(TOS), N bytes (big-endian, N in R8.0)
E0 09 SYS 09C0 09C0 78→09C1 Multi-byte subtract: M(NOS) -= M(TOS), N bytes (big-endian, N in R8.0)
E8 09 SYS 09D0 09D0 6C→09D1 Multi-byte negate: zero-subtract N bytes at address on stack
F1 09 SYS 09E2 09E2 6C→09E3 Clear and prepare BCD workspace (fill computed area with zeros)
89 0A SYS 0A12 0A12 6C→0A13 BCD shift/multiply operation (shift left with BCD correction)
A3 0A SYS 0A46 0A46 84→0A47 ADD24 variant: add 16-bit TOS to 24-bit value at computed address
AE 0A SYS 0A5C 0A5C 84→0A5D SUB24 variant: subtract 16-bit TOS from 24-bit value at computed address
AB 0B SYS 0B56 0B56 66→0B57 Indexed address load: compute address from TOS + M(TOS), store to var A
F2 0B SYS 0BE4 0BE4 8B→0BE5 VE block setup: load VE, set R9=VE, RA=VE+6, R8=VE+3 for block operation
96 0C SYS 0C2C 0C2C 84→0C2D Display buffer operation: manipulate buffer at 0x62xx using TOS as parameter
C2 0C SYS 0C84 0C84 7E→0C85 Indexed byte store: store NOS.0 at address (TOS + M(TOS))
B7 0E SYS 0E6E 0E6E 8B→0E6F CALL native: save RC to return stack, set up native 1802 execution context
CD 0E SYS 0E9A 0E9A 8A→0E9B Context switch: save interpreter state, transfer to native execution at computed address
8D 0F SYS 0F1A 0F1A 84→0F1B Bank state save: store bank/address to M(6050-54) from TOS
9C 0F SYS 0F38 0F38 84→0F39 Bank state restore: load bank/address from M(6050-54), store to TOS address
C9 0F SYS 0F92 0F92 8B→0F93 ROM pointer advance: read TOS as offset, advance ROM pointer M(60CA/CB)
DE 0F SYS 0FBC 0FBC 8B→0FBD Store TOS to M(60C4/C5), copy M(60B7-B9) to M(60BA-BC) working area
85 10 SYS 100A 100A 8B→100B Copy M(60B7-B9) base to M(60BA-BC) working area, decode bank address
B0 10 SYS 1060 1060 8B→1061 Bank navigation: traverse linked list in banked ROM via M(60CB/BC)
C3 10 SYS 1086 1086 8B→1087 Bank base setup: copy M(60B7-B9) to working area, traverse linked list
DB 10 SYS 10B6 10B6 84→10B7 LDBNK variant: read 1 byte from banked ROM at 3-byte address, push to stack
E9 10 SYS 10D2 10D2 8B→10D3 Banked read via M(60CA/CB): decode bank, read sequential bytes from ROM
A7 12 SYS 124E 124E 8B→124F Record interpreter: reads bytes sequentially from app ROM via [RP]. Each byte (bit 7 set) is decoded via dispatch table at ROM page 0x20, selecting sub-handlers. Self-repeating (backs up RC by 2).
EB 12 SYS 12D6 12D6 8B→12D7 Bank base copy: copy M(60B7-B9) to M(60BA-BC) and decode bank for subsequent access
8E 13 SYS 131C 131C 66→131D ROMCPY: block copy from banked ROM to RAM. Pops TOS (dest address), NOS (offset). Adds offset to 24-bit base at M(60B7/B8/B9), decodes bank (see Bank Address Encoding), copies data to dest.
90 13 SYS 1320 1320 8B→1321 ROMCPY to fixed dest: block copy from banked ROM to RAM at 0x6260 via M(60CA/CB) pointer
C1 13 SYS 1382 1382 8B→1383 BANKPTR: compute banked ROM address from base + TOS offset (see Bank Address Encoding), store pointer to M(60C0/C1/C2). No data transfer.
D8 13 SYS 13B0 13B0 8B→13B1 Banked block read (SEQ): set Q, read block from banked ROM via M(60CA/CB) to RAM
DA 13 SYS 13B4 13B4 8B→13B5 Banked block read (REQ): reset Q, read block from banked ROM via M(60CA/CB) to RAM
DF 13 SYS 13BE 13BE 8B→13BF Banked block read (REQ variant): reset Q, read block with alternate pointer setup
97 15 SYS 152E 152E 84→152F Banked decode + copy: decode bank from TOS, copy source address to var A
A2 15 SYS 1544 1544 72→1545 Banked block write (variant 1): pop count and address, write to banked ROM/RAM
BF 15 SYS 157E 157E 6C→157F Banked decode + read: pop count, decode bank address, copy bytes to RAM
C8 15 SYS 1590 1590 84→1591 Banked store: decode bank from TOS, write data to banked address
D0 15 SYS 15A0 15A0 72→15A1 Banked block write (variant 2): pop count and address, write to banked ROM/RAM with state save
E9 15 SYS 15D2 15D2 6C→15D3 Banked block read with SEQ: pop count, decode bank, copy bytes with Q set
FF 15 SYS 15FE 15FE - Bank deselect and state restore (shared exit routine)
9D 16 SYS 163A 163A 94→163B Banked byte read via var: decode bank from variable, read byte from banked address
B4 16 SYS 1668 1668 6C→1669 Banked multi-byte read: pop count, decode bank, read block to RAM via LCD char table
E9 16 SYS 16D2 16D2 84→16D3 Banked sequential read: decode bank from TOS, read bytes with page-crossing detection
A2 17 SYS 1744 1744 CA→1747 Set bit 7 of HW register M(3FA1) (RAM shadow M(6001))
A6 17 SYS 174C 174C CA→174F Clear bit 7 of HW register M(3FA1) (RAM shadow M(6001))
AA 17 SYS 1754 1754 CA→1757 Set bit 6 of HW register M(3FA1) (RAM shadow M(6001))
AE 17 SYS 175C 175C CA→175F Clear bit 6 of HW register M(3FA1) (RAM shadow M(6001))
B7 17 SYS 176E 176E 8A→176F Test bits 0-1 of M(6060): push 1 if both set, else push 0
CA 17 SYS 1794 1794 8A→1795 Test EF1: push 1 if EF1 active, else push 0
DB 17 SYS 17B6 17B6 CA→17B9 Write M(6022) to HW register M(3F60)
DE 17 SYS 17BC 17BC CA→17BF Write M(6021) to HW register M(3F62)
E1 17 SYS 17C2 17C2 CA→17C5 Write M(601F):M(6020) to HW registers M(3F82):M(3F83)
E9 17 SYS 17D2 17D2 CA→17D5 Clear bit 3 of HW register M(3FA1) (RAM shadow M(6001))
EE 17 SYS 17DC 17DC CA→17DF Clear bit 3 of HW register M(3FA1) with alternate setup
FC 17 SYS 17F8 17F8 CA→17FB Conditional clear bit 3 of HW register M(3FA1): test M(6001), clear if condition met
86 18 SYS 180C 180C CA→180F Shift TOS.0 left 5 bits, write to HW register via M(3FA0)
90 18 SYS 1820 1820 CA→1823 Set bit 7 of HW register M(3FF0) (RAM shadow M(6064))
94 18 SYS 1828 1828 CA→182B Clear bit 7 of HW register M(3FF0) (RAM shadow M(6064))
A4 18 SYS 1848 1848 CA→184B LCD refresh: write 2×32 chars from display buffer 0x6080–0x60BF to HD44780
CC 18 SYS 1898 1898 CA→189B Keyboard scan: scans 6 ports 0x3F20–0x3F25. Returns key code, or 0x0000 if no key.
A6 19 SYS 194C 194C 29→194D Push 0x0000 (no-op return: entry 0x29 pushes zero, returns immediately)
A7 19 SYS 194E 194E 8A→194F Set bank window: store TOS.0 to M(604E), select bank page
AC 19 SYS 1958 1958 CA→195B Set bank register: write TOS.0 with bit 7 set to shadow M(604F), write masked value to M(3FA2)
B2 19 SYS 1964 1964 8B→1965 Reset status: write 0x10 to M(3FA0), clear R0.1
BC 19 SYS 1978 1978 CA→197B Clear bit 2 of HW register M(3FA0), clear R5.0, reset interrupt state
C4 19 SYS 1988 1988 CA→198B Write 0x02 to HW register M(3F63) (RAM shadow M(6067)), configure serial/timer
D7 19 SYS 19AE 19AE 8A→19AF Context restore: reload R2, RD, RC from saved state at M(601B-1F)
E2 19 SYS 19C4 19C4 8A→19C5 Serial RX poll: test EF4; if active → receive byte and push result; if inactive → push 0x0000 (no data)
F0 19 SYS 19E0 19E0 CA→19E3 Clear HW register M(3F0E): write 0 via entry 0xCA with R6=A7, R7=0E
DB 1A SYS 1AB6 1AB6 6C→1AB7 Serial TX (SEQ): set Q, pop TOS as byte count, transmit bytes from data stack
DE 1A SYS 1ABC 1ABC 6C→1ABD Serial TX (REQ): reset Q, pop TOS as byte count, transmit bytes from data stack
86 1B SYS 1B0C 1B0C 6C→1B0D Serial block TX (SEQ): set Q, pop count, transmit block from banked address
88 1B SYS 1B10 1B10 6C→1B11 Serial block TX (REQ): reset Q, pop count, transmit block from banked address
BA 1B SYS 1B74 1B74 8A→1B75 Re-init interpreter: set R6.1=0x80, R2.1=0x81, RD.1=0x82, RF.1=0x05
C1 1B SYS 1B82 1B82 CA→1B85 Set bits in HW register M(3FF0): OR TOS.0 (masked to bits 0-1) with M(6064), write to M(3FF0)
D0 1B SYS 1BA0 1BA0 CA→1BA3 Clear bit 0 of HW register 0x3FF1 (RAM shadow at 0x6065)
E0 1B SYS 1BC0 1BC0 6C→1BC1 LCD character table update: pop count, write character mappings to RAM 0x68xx lookup table
B8 1C SYS 1C70 1C70 CA→1C73 Clear bit 4 of HW register M(3F80) (RAM shadow M(6066))
BD 1C SYS 1C7A 1C7A 6C→1C7B Display buffer init: pop count, initialize display character buffer at 0x60xx
E4 1D SYS 1DC8 1DC8 6C→1DC9 INP 4 read: input from port 4, process result into display/state buffer
C3 1E SYS 1E86 1E86 8A→1E87 Multi-dispatch: set RE.0=2, branch to sub-handler based on TOS value
8A 1F SYS 1F14 1F14 8A→1F15 Set R5.0 = 0x04 (mode/state configuration)

CALLI Targets (Traced)

CALLI (opcode 0x66) performs indirect calls through function pointer tables. Each table entry is a 2-byte handler address. The handler at addr−1 contains an entry byte (same dispatch mechanism as the record interpreter).

Table at 0x2812
Index Table addr Target Entry Definition
10 2832 0AC2 94 Packed hex-to-ASCII: converts byte from source to 2 ASCII hex chars at dest buffer

Record Interpreter (SYS 124E)

SYS 124E implements a third level of interpretation. It reads structured data bytes from the app ROM and dispatches sub-handlers via a lookup table at ROM page 0x20. The record interpreter is self-repeating (backs up RC by 2 each iteration) and processes bytes until a CALL entry transfers control elsewhere.

Entry Byte Convention

The record interpreter uses a different address convention than the main bytecode interpreter:

Interpreter Table stores Entry byte at Mechanism
Main bytecode (0x6100) Handler address (= entry byte location) stored address LDA RB reads entry, RB advances to body
Record interpreter (entry 0x32) Body address (= code start) stored address − 1 DEC RB; LDA RB reads entry at addr−1, RB advances to body

Both dispatch through the same entry-point mechanism. The entry byte at target−1 determines whether the sub-handler is native 1802 code (entry 0x8B) or a bytecode CALL (entry 0x15).

Record Opcodes (ROM page 0x20 dispatch table)
Byte SHL Table Target Entry Definition
82 04 2004 1039 8B Field decode + arithmetic: copy pointer to working area, decode fields, subtract var 0x60B8/B9, store result to 0x60C4/C5
90 20 2020 237F 15 CALL to bytecode: push RC to return stack, set RC = 0x237F. RET returns to continue record processing.
94 28 2028 145D 8B REQ + multi-byte ROM→RAM copy: reset Q, read bytes from app ROM, store to computed RAM address
9E 3C 203C 1453 8B SEQ + multi-byte ROM→RAM copy: set Q, read bytes from app ROM, store to computed RAM address

Interpreter Dispatch Loop (RAM at 0x6100)

The following native CDP1802 code is copied from ROM 0x0500 into RAM 0x6100 at startup. It forms the fetch-decode-execute cycle of the VM:

6100: LDA  RC        ; fetch next bytecode, RC++
6101: SHL            ; bit 7 -> DF, remaining bits x 2 = table index
6102: BDF  610E      ; if bit 7 was set -> high opcode path
6104: PLO  RE        ; RE.0 = table index (low opcodes)
6105: LDA  RE        ; read handler addr high from table at 0x01xx
6106: PHI  RB        ; RB.1 = handler high
6107: LDN  RE        ; read handler addr low from table
6108: PLO  RB        ; RB.0 = handler low
6109: LDA  RB        ; first byte at handler = entry-point selector
610A: PLO  R3        ; R3.0 = entry point
610B: SEP  R3        ; transfer control to handler
610C: BR   6100      ; <- handler returns here via SEP RF, loop back

610E: PLO  RB        ; high opcode: RB.0 = SHL result (partial addr)
610F: LDA  RC        ; read next byte = handler addr high page
6110: PHI  RB        ; RB.1 = inline high byte
6111: LDA  RB        ; first byte at handler = entry-point selector
6112: PLO  R3        ; R3.0 = entry point
6113: SEP  R3        ; transfer control to handler
6114: BR   6100      ; loop back

6116: LBR  051A      ; escape to ROM (interpreter exit/reinit)

Memory Map (Bytecode Variables)

For the complete memory map and RAM organisation, see the Memory section.

Hardware I/O Registers

For the complete I/O register map, see the I/O Map section.

Startup Trace

For the complete annotated cold boot trace, see PTC-701 Startup Boot Trace.

Notes

  • This is a stack-based bytecode VM, unlike the register-based GPL/Chip-8 family. Operations pop operands from the data stack and push results back.
  • Opcode encoding: low opcodes (0x00–0x7F) are 1–4 bytes (opcode + optional parameters). High opcodes (0x80–0xFF) are always 2 bytes: a SYS call to a computed even address.
  • Fixed constant pushes (opcodes 0x03, 0x04, 0x27) read their values from the handler body in ROM, not from the bytecode stream. They are single-byte opcodes despite pushing 16-bit values.
  • The interpreter has no V registers (unlike GPL/Chip-8). Variables are stored as 16-bit words in RAM and accessed by index.
  • Bank switching is used to read application data from the 32K ROM through the 8K window at 0x4000–0x5FFF. The bytecode PC (RC) walks through this banked region.
  • SYS can target system ROM, banked app ROM (bank-dependent!), or RAM - making the bytecode capable of dispatching to any handler address.
  • Many opcodes remain unverified or only partially understood. The mnemonics above are best-effort deductions from handler code patterns and are subject to revision.
  • The PTC bytecode language is significantly more complex than the GPL family - it has ~100 low opcodes plus 128 possible SYS targets vs ~16 opcodes for GPL variants.
  • The system implements three levels of interpretation: (1) native CDP1802 code in system ROM, (2) the PTC bytecode interpreter (opcodes 0x00–0xFF), and (3) a “record interpreter” (SYS 124E) that processes structured data bytes from the app ROM. Each record byte dispatches to sub-handlers (at 0x1039, 0x1453, 0x145D, etc.) via a lookup table at ROM page 0x20. The record interpreter is self-repeating and exits via a CALL entry that transfers to new bytecode.