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 R2.0 / EM

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 (0x0400) into RAM at 0x7900 for execution. It uses a 16-bit evaluation stack (pointed to by RD, located at 0x797F) and variables stored in RAM at 0x7800+.

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–0x77FF
RAM 8K at 0x6000–0x7FFF
Interpreter loop RAM at 0x7900 (copied from ROM 0x0400)
Dispatch table ROM at 0x0100–0x01FF (128 entries × 2 bytes)
Data stack RAM 0x797F (RD pointer, grows downward, 16-bit values)
Variables RAM 0x7800+ (indexed by bytecode operand)
Return stack RAM 0x79FF (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 0x083A/0x077D
R2 Return stack (0x79FF, 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 (0x797F, grows downward)
RE Dispatch table index (RE.1 = 0x01)
RF Interpreter PC

Definitions

kk 8-bit constant / variable index / branch offset
bb 8-bit signed branch offset
aaaa 16-bit absolute RAM address
[aaaa] Value at RAM address aaaa (16-bit)
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)
VE Fixed 16-bit variable at M(785F/7860) (R2.0 system variable page 0x78)
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 (0x29) and STBNK (0x51) 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(784F) 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(784E)   = raw − 1
bank_reg[n] = M(784E) × 4 + n    (n = 0..3, written to 0x3FC0–0x3FC3)
read_addr = ((byte1 | 0x80) << 8) | byte2

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

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

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 0x7900 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

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 = 0x04/0xB3 → handler at 0x04B3. First byte there is 0x06 (entry-point selector), so handler body starts at 0x04B4.

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.

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
06 0206 1st Pop TOS into R9, set X=RD, return to handler body
15 0215 R0 Procedure CALL: save RC to return stack, set RC=RB
20 0220 1st, R0 Push constant from handler body (2 bytes: high, low)
28 0228 1st No operation: SEP RF, immediate return
29 0229 1st, R0 Push zero (0x0000) to data stack
2B 022B 1st, R0 Push one (0x0001) to data stack
32 0232 1st, R0 Indirect CALL: read address from stack, validate tag, chain to 0x15
39 0239 R0 Pop TOS, OR high+low to test zero, SEP RB
82 0282 1st, R0 Pop TOS into R9 and NOS into RA (two 16-bit values from data stack)
88 0288 1st, R0 Pop TOS into RA (SEX RD; LDXA→PHI RA; LDXA→PLO RA; NBR; SEP RB)
9D 029D 1st, R0 Copy R2→RA (return stack pointer), SEP RB
AD 02AD 1st SEX RC; multi-level dispatch via page 0x03 (R2.0-specific)

Syntax

Instructions
Opcode Mnemonic ParameterForth Handler Dispatch Description
00 ADD TOS, NOS+ 04B3 06→04B4 TOS = NOS + TOS (16-bit add)
01 SHL TOS2* 04BA 06→04BB TOS = TOS × 2 (16-bit shift left)
02 SUB TOS, NOS- 04BF 06→04C0 TOS = NOS − TOS (16-bit subtract)
03 ADD TOS, 11+ 04C6 06→04C7 TOS = TOS + 1
04 ADD TOS, 22+ 04CA 06→04CB TOS = TOS + 2
05 ISLT < 0644 82→0645 TOS = (TOS < NOS) ? 1 : 0 (unsigned). Swaps operands then uses ISGT logic (SD/SDB). R1.7 equiv = opcode 0x4D
06 ISGT > 064C 06→064D TOS = (TOS > NOS) ? 1 : 0 (16-bit unsigned)
07 ISEQ = 065C 06→065D TOS = (TOS == NOS) ? 1 : 0 (16-bit)
08bb JZ aaaa?BRANCH 04E5 39→04E6 Pop TOS; if TOS == 0, jump to aaaa (forward, aaaa = PC + 1 + bb)
09bb JZ aaaa?BRANCH 0516 39→0517 Pop TOS; if TOS == 0, jump to aaaa (backward, aaaa = PC + 1 + bb - 256)
0Abb JP aaaaBRANCH 04EA 8F→04EB Unconditional jump to aaaa (forward, aaaa = PC + 1 + bb)
0Bbb JP aaaaBRANCH 051B 8F→051C Unconditional jump backward (aaaa = PC + 1 + bb − 256)
0Cbb LOOP aaaa(LOOP) 04F4 2B→04F5 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) 04F6 9D→04F7 Same as LOOP but increment by TOS (variable step) instead of 1. Branch backward to aaaa.
0E LOOP FRAME(DO) 0525 82→0526 Push loop frame to return stack: TOS→SP+0/1 (counter), NOS→SP+2/3 (limit). Pops both from data stack.
0F POPR R> 0635 8F→0636 Pop 16-bit value from return stack (R2), push to data stack
10 PUSHR >R 063C 88→063D Pop TOS from data stack, push to return stack (R2)
11 RET EXIT 054A 8F→054B Return from subroutine: pop RC from return stack
12 AND TOS, NOSAND 0550 06→0551 TOS = NOS AND TOS (16-bit bitwise AND)
13 ISZ TOS0= 0562 8E→0563 TOS = (TOS == 0) ? 1 : 0.
14 ISNEG TOS0< 0570 8F→0571 TOS = (TOS < 0) ? 1 : 0. Tests bit 7 of TOS high byte.
15 LD TOS, [TOS]@ 059D 88→059E TOS = M(TOS):M(TOS+1). Load 16-bit value from M(TOS).
16 LD [TOS], NOS! 05B2 88→05B3 M(TOS) = NOS (store 16-bit NOS to M(TOS), pops both)
17 LD TOS.0, [TOS]C@ 05C6 88→05C7 TOS.0 = M(TOS) (load single byte from M(TOS))
18 XOR TOS, NOSXOR 057C 06→057D TOS = TOS XOR NOS (16-bit bitwise XOR)
19 LD [TOS], NOS.0C! 05D9 88→05DA M(TOS) = NOS.low. Store low byte of NOS to M(TOS), pops both.
1A MCPY [NOS], [3OS]CMOVE 05DE 7C→05DF Memory copy: pops 3 values (TOS=count, NOS=dest, 3OS=source); copies full 16-bit count bytes from source to dest
1B MCPY3 [TOS], [NOS]CMOVE (3) 0692 82→0693 Copy 3 bytes from NOS (source) to TOS (dest). Pops both.
1C DROP DROP 05F5 8F→05F6 Drop TOS (discard top of stack)
1D OVER OVER 05F9 90→05FA Copy NOS to TOS (push copy of second stack value on top)
1E DUP DUP 05FD 92→05FE Duplicate TOS (push copy of top value)
1F DUPNZ ?DUP 0603 8E→0604 Duplicate TOS if nonzero (push copy); if TOS == 0, no change
20 SWAP SWAP 0610 82→0611 Exchange TOS and NOS on data stack (swap top two 16-bit values)
21 SUB TOS, 11- 04CE 0C→04CF TOS = TOS − 1
22aaaa PUSH aaaaLIT 061F 8F→0620 Push 16-bit immediate value aaaa onto data stack
23kk PUSH kkLIT 0624 8F→0625 Push 8-bit constant kk onto data stack
24 PUSH RR@ 062E 9D→062F Read 16-bit value from top of return stack (R2), push to data stack (return stack unchanged)
25 LD [TOS], 00 C! 067D 88→067E M(TOS) = 0x00. Pops TOS.
26 ADD24 [NOS], [TOS]D+ (24-bit) 06A5 82→06A6 24-bit (3-byte) add: M(NOS) = M(NOS) + M(TOS), big-endian. Pops both addresses.
27 OR TOS, NOSOR 069A 06→069B TOS = NOS OR TOS (16-bit bitwise OR)
28 LD24 [TOS], NOS! (24-bit) 06B7 88→06B8 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.
29 LDBNK [TOS]C@ (banked) 08DC B3→08DF Read 1 byte from banked ROM at 3-byte encoded address on TOS (byte0=bank code, byte1:byte2=16-bit offset). Entry 0xB3 sets R6=0x7848 (bank shadow var), R7=0x3FC0 (bank reg base). Temporarily switches bank registers 0x3FC0–0x3FC3 to the target bank, reads the byte, pushes it zero-extended to TOS, then restores home bank (shadow=0, regs=00/01/02/03). R1.7 equivalent = opcode 0x39.
2Aaaaa LD TOS, [aaaa]@ 0599 AD·89→059B TOS = [aaaa] (load 16-bit, push to stack)
2B LD [TOS], 11 C! 0682 88→0683 M(TOS) = 0x01. Pops TOS.
2Baaaa LD [aaaa], TOS! 05AF AD·89→05B1 [aaaa] = TOS (pop 16-bit, store to address)
2Caaaa LD TOS.0, [aaaa]C@ 05C2 AD·89→05C4 TOS = [aaaa] (load byte, push)
2Daaaa LD [aaaa], TOS.0C! 05D6 AD·89→05D8 [aaaa] = TOS (pop, store low byte only)
2Ebb LD TOS.0, [78bb]C@ 075F AD·38→0760 Load 1 byte from variable at 0x78bb, zero-extend, push to stack
2Fbb LD [78bb], TOS.0C! 076D AD·00→0771 Pop TOS, store low byte to variable at 0x78bb
30 FILL FILL 068E 7E→068F Fill memory: pops 3 values (TOS=count, NOS=dest addr, 3OS=fill byte); fills count.low bytes at dest with fill.low
31

PUSH

VE, kk

VE @ kk + 0583 98→0585

TOS = VE + kk. VE unchanged.

32

PUSH

[VE].0, kk

VE @ kk + C@ 05B9 98→05BB

TOS = M(VE + kk) (load byte). VE unchanged.

33 POP

[VE].0, kk

VE @ kk + C! 05CD 98→05CF

M(VE + kk) = TOS.0 (store low byte). VE unchanged.

34aaaakk LDAND [aaaa], kkC@ kk AND 0557 AD·89→055A TOS = [aaaa] & kk (load byte, AND with mask, push)
35 ADD24 [NOS], TOSD+ (24-bit) 0740 82→0741 M(NOS) = M(NOS) + TOS - Add 16-bit TOS to 24-bit (3-byte) value at M(NOS), big-endian. Pops both.
36 SUB24 [NOS], TOSD- (24-bit) 0750 82→0751 M(NOS) = M(NOS) − TOS - Subtract 16-bit TOS from 24-bit (3-byte) value at M(NOS), big-endian. Pops both.
37 NOOP NOOP 077C 28→SEP RF No operation
38 ISZ [TOS]C@ 0= 1046 88→1047 TOS = (M(TOS) == 0) ? 1 : 0.
39 EXEC TOSEXECUTE 18AE 88→18AF Call handler at M(TOS). Pops TOS, dispatches to target.
3A PUSH 78B7LIT 19F2
3B PUSH 7C06LIT 19F9
3C PUSH 7C09LIT 19FC
3D PUSH 7C0CLIT 19FF
3E PUSH 7C0ELIT 1A02
3F PUSH 7C11LIT 1A05
40 PUSH 7C2CLIT 1A0B
41 PUSH 7C4ELIT 1A0E
42 PUSH 7C70LIT 1A11
43 PUSH 7C92LIT 1A14
44 PUSH 7CB4LIT 1A17
45 PUSH 7CD6LIT 1A1A
46 PUSH 7D08LIT 1A21
47 PUSH FFFFLIT 1BDB 20 Push 0xFFFF (constant in handler body)
48 PUSH 20LIT 1BDE 20 Push 0x0020 / space (constant in handler body)
49 PUSH 0LIT 1BE1 20 Push 0x0000 (constant in handler body)
4A PUSH 1LIT 1BE4 20 Push 0x0001 (constant in handler body)
4B PUSH 2LIT 1BE7 20 Push 0x0002 (constant in handler body)
4C PUSH 4LIT 1BEA 20 Push 0x0004 (constant in handler body)
4D PUSH 7LIT 1BED 20 Push 0x0007 (constant in handler body)
4E PUSH DLIT 1BF0 20 Push 0x000D / CR (constant in handler body)
4F PUSH 2BLIT 1BF3 20 Push 0x002B / + (constant in handler body)
50 PUSH 2DLIT 1BF6 20 Push 0x002D / - (constant in handler body)
51 STBNK [TOS], 0! (banked) 1C01 15→1C02 Store zero to banked memory at 24-bit address pointed to by [TOS]. Converts 24-bit linear addr to bank+offset, switches bank regs (0x3FC0-3FC3), writes 0x00, restores home bank.
52 MCPY [TOS], [NOS]CMOVE 1C1E 15→1C1F Conditional copy: reads length byte M(NOS). If zero, drops both addresses (no copy). If nonzero, copies M(NOS)+2 bytes from NOS to TOS (includes 2-byte header). 
53 MCPY6 [TOS], [NOS]CMOVE (6) 1C4F 15→1C50 Copy 6 bytes from NOS (source) to TOS (dest). Pops both.
54 SUB24 [TOS], 1DEC (24-bit) 1CB3 15→1CB4 Decrement 24-bit (3-byte) value at address TOS by 1. Pops TOS.
55 CMP24 [TOS], [NOS]COMPARE (24-bit) 1CBC 15→1CBD Compare 3 bytes (24-bit) at [TOS] vs [NOS]. Returns 0 if equal, nonzero if different. Pops both.
56 LD TOS, [7C16]@ 1E0D 15→1E0E Load 16-bit value from fixed address 0x7C16, push to stack
57 IDX24 7AC8index (custom) 1E1D 15→1E1E Index into 24-bit array: TOS = 7AC8 + (TOS - 1) * 3. Converts 1-based index to address of 3-byte entry in table at 0x7AC8.
58 MCPY6 78B7, [TOS]CMOVE (6) 1E66 15→1E67 Copy 6 bytes from TOS address to fixed destination 0x78B7. Pops TOS.
59 MCPY6 [TOS], 78B7CMOVE (6) 1E6A 15→1E6B Copy 6 bytes from fixed source 0x78B7 to [TOS]. Pops TOS. 
5B PUSH 3CDELIT 1BD8 20 Push constant 0x3CDE
5C PUSH FFFFLIT 1BDB 20 Push 0xFFFF - DUPLICATE of opcode 0x47
5D PUSH 7LIT 1BED 20 Push 0x0007 - DUPLICATE of opcode 0x4D
5E PUSH 8000LIT 1BFE 20 Push constant 0x8000
60 SUB24 [TOS], 1DEC (24-bit) 1CB3 15→1CB4 DUPLICATE of opcode 0x54 (same handler address)
61 CMP24 [TOS], [NOS]COMPARE (24-bit) 1CBC 15→1CBD DUPLICATE of opcode 0x55 (same handler address)
63 PUSH 1A11LIT 1DAC 20 Push constant 0x1A11 (possibly a procedure address)
80kk–FFkk SYS aaaaCODE (native) - - 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 Description
E9 04 SYS 04D2 04D2 82→04D3 ADD [TOS], NOS
M(TOS) = M(TOS) + NOS (16-bit add in-place). Pops both.
9D 05 SYS 053A 053A AD·89→053C CALLI [aaaa]
Indirect call: reads 16-bit target from table at aaaa + TOS×2, calls handler at that address. Pops TOS (index).
C8 05
SYS 0590 0590 98→0594 PUSH [VE], kk
TOS = M(VE + kk) : M(VE + kk + 1) (load 16-bit word, big-endian). VE unchanged.
8C 06 SYS 0618 0618 7C→0619 ROT
Rotate 3OS item to TOS (Forth ROT). Before: TOS=A, NOS=B, 3OS=C → After: TOS=C, NOS=A, 3OS=B
C3 06 SYS 0686 0686 88→0687 ADD [TOS], NOS.0
M(TOS) = M(TOS) + NOS.low (byte add). Pops both.
E1 06
SYS 06C2 06C2 7C→06C3 COMP
Multi-byte compare: compares TOS.0 bytes at NOS against 3OS. Returns 0 (equal), 1 (NOS > 3OS), or FFFF (NOS < 3OS)
E7 0B SYS 0BCE 0BCE B3→0BD2 DELAY
Software delay. Duration controlled by M(6009): if 0, exits immediately; if nonzero, delays proportional to value. No I/O.