Adds the dumped U3 EPROM image (tms27pc512.BIN) and the analysis it enabled. Findings (FIRMWARE.md): - Memory map: code+data at CPU $8000-$FFFF (EPROM upper 32KB, 1:1); lower half unused. Valid HC11 vectors; SCI rx ISR at $B85C fills a ring buffer, main-loop parser dispatches. - Full command set recovered from the two jump tables: 58 ESC commands ($98AC) + control chars ($994C). Decoded semantics for @/G/H/K/L (confirm earlier guesses) plus NEW commands: Q=set-row, R=set-col, I=draw-page, i=display-page (10-page double-buffering), A-F=vector graphics, X/Y=pen. - 8 fonts at $BC03 (6x8, 6x10, 7x10, 12x16, 12x20); glyph bitmaps in ROM (~$C000-$DFFF, exact base TBD). - Built-in demo enabled by jumper 6 (confirms the JP1 map). Tooling: hc11dis.py, a purpose-built 68HC11 disassembler (the toolchain has no m68hc11 target), with the analysis reproducible from the addresses noted. The firmware is now the authoritative command spec, superseding the need for the Babcock programming manual. Feeds both vPLASMA and the replica firmware. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
149 lines
8.5 KiB
Python
149 lines
8.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Minimal-but-practical Motorola 68HC11 disassembler for the PD01D221 ROM.
|
|
CPU address == ROM offset for $8000-$FFFF (EPROM upper 32KB maps 1:1)."""
|
|
import sys
|
|
|
|
# addressing modes and their extra operand length (beyond opcode)
|
|
INH='inh'; IMM8='imm8'; IMM16='imm16'; DIR='dir'; EXT='ext'; IDX='idx'; IDY='idy'; REL='rel'
|
|
BITDIR='bitdir'; BITIDX='bitidx'; BITIDY='bitidy' # BSET/BCLR (mask)
|
|
BRDIR='brdir'; BRIDX='bridx'; BRIDY='bridy' # BRSET/BRCLR (mask+rel)
|
|
|
|
# page 0
|
|
P0 = {
|
|
0x00:('TEST',INH),0x01:('NOP',INH),0x02:('IDIV',INH),0x03:('FDIV',INH),
|
|
0x04:('LSRD',INH),0x05:('ASLD',INH),0x06:('TAP',INH),0x07:('TPA',INH),
|
|
0x08:('INX',INH),0x09:('DEX',INH),0x0A:('CLV',INH),0x0B:('SEV',INH),
|
|
0x0C:('CLC',INH),0x0D:('SEC',INH),0x0E:('CLI',INH),0x0F:('SEI',INH),
|
|
0x10:('SBA',INH),0x11:('CBA',INH),0x12:('BRSET',BRDIR),0x13:('BRCLR',BRDIR),
|
|
0x14:('BSET',BITDIR),0x15:('BCLR',BITDIR),0x16:('TAB',INH),0x17:('TBA',INH),
|
|
0x19:('DAA',INH),0x1B:('ABA',INH),
|
|
0x1C:('BSET',BITIDX),0x1D:('BCLR',BITIDX),0x1E:('BRSET',BRIDX),0x1F:('BRCLR',BRIDX),
|
|
0x20:('BRA',REL),0x21:('BRN',REL),0x22:('BHI',REL),0x23:('BLS',REL),
|
|
0x24:('BCC',REL),0x25:('BCS',REL),0x26:('BNE',REL),0x27:('BEQ',REL),
|
|
0x28:('BVC',REL),0x29:('BVS',REL),0x2A:('BPL',REL),0x2B:('BMI',REL),
|
|
0x2C:('BGE',REL),0x2D:('BLT',REL),0x2E:('BGT',REL),0x2F:('BLE',REL),
|
|
0x30:('TSX',INH),0x31:('INS',INH),0x32:('PULA',INH),0x33:('PULB',INH),
|
|
0x34:('DES',INH),0x35:('TXS',INH),0x36:('PSHA',INH),0x37:('PSHB',INH),
|
|
0x38:('PULX',INH),0x39:('RTS',INH),0x3A:('ABX',INH),0x3B:('RTI',INH),
|
|
0x3C:('PSHX',INH),0x3D:('MUL',INH),0x3E:('WAI',INH),0x3F:('SWI',INH),
|
|
0x40:('NEGA',INH),0x43:('COMA',INH),0x44:('LSRA',INH),0x46:('RORA',INH),
|
|
0x47:('ASRA',INH),0x48:('ASLA',INH),0x49:('ROLA',INH),0x4A:('DECA',INH),
|
|
0x4C:('INCA',INH),0x4D:('TSTA',INH),0x4F:('CLRA',INH),
|
|
0x50:('NEGB',INH),0x53:('COMB',INH),0x54:('LSRB',INH),0x56:('RORB',INH),
|
|
0x57:('ASRB',INH),0x58:('ASLB',INH),0x59:('ROLB',INH),0x5A:('DECB',INH),
|
|
0x5C:('INCB',INH),0x5D:('TSTB',INH),0x5F:('CLRB',INH),
|
|
0x60:('NEG',IDX),0x63:('COM',IDX),0x64:('LSR',IDX),0x66:('ROR',IDX),
|
|
0x67:('ASR',IDX),0x68:('ASL',IDX),0x69:('ROL',IDX),0x6A:('DEC',IDX),
|
|
0x6C:('INC',IDX),0x6D:('TST',IDX),0x6E:('JMP',IDX),0x6F:('CLR',IDX),
|
|
0x70:('NEG',EXT),0x73:('COM',EXT),0x74:('LSR',EXT),0x76:('ROR',EXT),
|
|
0x77:('ASR',EXT),0x78:('ASL',EXT),0x79:('ROL',EXT),0x7A:('DEC',EXT),
|
|
0x7C:('INC',EXT),0x7D:('TST',EXT),0x7E:('JMP',EXT),0x7F:('CLR',EXT),
|
|
0x80:('SUBA',IMM8),0x81:('CMPA',IMM8),0x82:('SBCA',IMM8),0x83:('SUBD',IMM16),
|
|
0x84:('ANDA',IMM8),0x85:('BITA',IMM8),0x86:('LDAA',IMM8),0x88:('EORA',IMM8),
|
|
0x89:('ADCA',IMM8),0x8A:('ORAA',IMM8),0x8B:('ADDA',IMM8),0x8C:('CPX',IMM16),
|
|
0x8D:('BSR',REL),0x8E:('LDS',IMM16),0x8F:('XGDX',INH),
|
|
0x90:('SUBA',DIR),0x91:('CMPA',DIR),0x92:('SBCA',DIR),0x93:('SUBD',DIR),
|
|
0x94:('ANDA',DIR),0x95:('BITA',DIR),0x96:('LDAA',DIR),0x97:('STAA',DIR),
|
|
0x98:('EORA',DIR),0x99:('ADCA',DIR),0x9A:('ORAA',DIR),0x9B:('ADDA',DIR),
|
|
0x9C:('CPX',DIR),0x9D:('JSR',DIR),0x9E:('LDS',DIR),0x9F:('STS',DIR),
|
|
0xA0:('SUBA',IDX),0xA1:('CMPA',IDX),0xA2:('SBCA',IDX),0xA3:('SUBD',IDX),
|
|
0xA4:('ANDA',IDX),0xA5:('BITA',IDX),0xA6:('LDAA',IDX),0xA7:('STAA',IDX),
|
|
0xA8:('EORA',IDX),0xA9:('ADCA',IDX),0xAA:('ORAA',IDX),0xAB:('ADDA',IDX),
|
|
0xAC:('CPX',IDX),0xAD:('JSR',IDX),0xAE:('LDS',IDX),0xAF:('STS',IDX),
|
|
0xB0:('SUBA',EXT),0xB1:('CMPA',EXT),0xB2:('SBCA',EXT),0xB3:('SUBD',EXT),
|
|
0xB4:('ANDA',EXT),0xB5:('BITA',EXT),0xB6:('LDAA',EXT),0xB7:('STAA',EXT),
|
|
0xB8:('EORA',EXT),0xB9:('ADCA',EXT),0xBA:('ORAA',EXT),0xBB:('ADDA',EXT),
|
|
0xBC:('CPX',EXT),0xBD:('JSR',EXT),0xBE:('LDS',EXT),0xBF:('STS',EXT),
|
|
0xC0:('SUBB',IMM8),0xC1:('CMPB',IMM8),0xC2:('SBCB',IMM8),0xC3:('ADDD',IMM16),
|
|
0xC4:('ANDB',IMM8),0xC5:('BITB',IMM8),0xC6:('LDAB',IMM8),0xC8:('EORB',IMM8),
|
|
0xC9:('ADCB',IMM8),0xCA:('ORAB',IMM8),0xCB:('ADDB',IMM8),0xCC:('LDD',IMM16),
|
|
0xCE:('LDX',IMM16),0xCF:('STOP',INH),
|
|
0xD0:('SUBB',DIR),0xD1:('CMPB',DIR),0xD2:('SBCB',DIR),0xD3:('ADDD',DIR),
|
|
0xD4:('ANDB',DIR),0xD5:('BITB',DIR),0xD6:('LDAB',DIR),0xD7:('STAB',DIR),
|
|
0xD8:('EORB',DIR),0xD9:('ADCB',DIR),0xDA:('ORAB',DIR),0xDB:('ADDB',DIR),
|
|
0xDC:('LDD',DIR),0xDD:('STD',DIR),0xDE:('LDX',DIR),0xDF:('STX',DIR),
|
|
0xE0:('SUBB',IDX),0xE1:('CMPB',IDX),0xE2:('SBCB',IDX),0xE3:('ADDD',IDX),
|
|
0xE4:('ANDB',IDX),0xE5:('BITB',IDX),0xE6:('LDAB',IDX),0xE7:('STAB',IDX),
|
|
0xE8:('EORB',IDX),0xE9:('ADCB',IDX),0xEA:('ORAB',IDX),0xEB:('ADDB',IDX),
|
|
0xEC:('LDD',IDX),0xED:('STD',IDX),0xEE:('LDX',IDX),0xEF:('STX',IDX),
|
|
0xF0:('SUBB',EXT),0xF1:('CMPB',EXT),0xF2:('SBCB',EXT),0xF3:('ADDD',EXT),
|
|
0xF4:('ANDB',EXT),0xF5:('BITB',EXT),0xF6:('LDAB',EXT),0xF7:('STAB',EXT),
|
|
0xF8:('EORB',EXT),0xF9:('ADCB',EXT),0xFA:('ORAB',EXT),0xFB:('ADDB',EXT),
|
|
0xFC:('LDD',EXT),0xFD:('STD',EXT),0xFE:('LDX',EXT),0xFF:('STX',EXT),
|
|
}
|
|
# page 1 ($18): Y versions
|
|
P1 = {
|
|
0x08:('INY',INH),0x09:('DEY',INH),0x1C:('BSET',BITIDY),0x1D:('BCLR',BITIDY),
|
|
0x1E:('BRSET',BRIDY),0x1F:('BRCLR',BRIDY),0x30:('TSY',INH),0x35:('TYS',INH),
|
|
0x38:('PULY',INH),0x3A:('ABY',INH),0x3C:('PSHY',INH),
|
|
0x60:('NEG',IDY),0x63:('COM',IDY),0x64:('LSR',IDY),0x66:('ROR',IDY),
|
|
0x67:('ASR',IDY),0x68:('ASL',IDY),0x69:('ROL',IDY),0x6A:('DEC',IDY),
|
|
0x6C:('INC',IDY),0x6D:('TST',IDY),0x6E:('JMP',IDY),0x6F:('CLR',IDY),
|
|
0x8C:('CPY',IMM16),0x8F:('XGDY',INH),0x9C:('CPY',DIR),
|
|
0xA0:('SUBA',IDY),0xA1:('CMPA',IDY),0xA2:('SBCA',IDY),0xA3:('SUBD',IDY),
|
|
0xA4:('ANDA',IDY),0xA5:('BITA',IDY),0xA6:('LDAA',IDY),0xA7:('STAA',IDY),
|
|
0xA8:('EORA',IDY),0xA9:('ADCA',IDY),0xAA:('ORAA',IDY),0xAB:('ADDA',IDY),
|
|
0xAC:('CPY',IDY),0xAD:('JSR',IDY),0xAE:('LDS',IDY),0xAF:('STS',IDY),
|
|
0xBC:('CPY',EXT),0xCE:('LDY',IMM16),0xDE:('LDY',DIR),0xDF:('STY',DIR),
|
|
0xE0:('SUBB',IDY),0xE1:('CMPB',IDY),0xE2:('SBCB',IDY),0xE3:('ADDD',IDY),
|
|
0xE4:('ANDB',IDY),0xE5:('BITB',IDY),0xE6:('LDAB',IDY),0xE7:('STAB',IDY),
|
|
0xE8:('EORB',IDY),0xE9:('ADCB',IDY),0xEA:('ORAB',IDY),0xEB:('ADDB',IDY),
|
|
0xEC:('LDD',IDY),0xED:('STD',IDY),0xEE:('LDY',IDY),0xEF:('STY',IDY),
|
|
0xFE:('LDY',EXT),0xFF:('STY',EXT),
|
|
}
|
|
# page 2 ($1A): CPD, and X/Y cross indexed
|
|
P2 = {0x83:('CPD',IMM16),0x93:('CPD',DIR),0xA3:('CPD',IDX),0xB3:('CPD',EXT),
|
|
0xAC:('CPY',IDX),0xEE:('LDY',IDX),0xEF:('STY',IDX)}
|
|
# page 4 ($CD): X/Y cross indexed
|
|
P4 = {0xA3:('CPD',IDY),0xAC:('CPX',IDY),0xEE:('LDX',IDY),0xEF:('STX',IDY)}
|
|
|
|
def disasm(rom, addr, count):
|
|
out=[]
|
|
for _ in range(count):
|
|
start=addr
|
|
op=rom[addr]; addr+=1
|
|
tab=P0; pfx=''
|
|
if op==0x18: pfx='18 '; op=rom[addr]; addr+=1; tab=P1
|
|
elif op==0x1A: pfx='1A '; op=rom[addr]; addr+=1; tab=P2
|
|
elif op==0xCD: pfx='CD '; op=rom[addr]; addr+=1; tab=P4
|
|
ent=tab.get(op)
|
|
if ent is None:
|
|
out.append((start,f".byte ${op:02X}",'?')); continue
|
|
mn,mode=ent; operand=''; tgt=None
|
|
if mode==INH: pass
|
|
elif mode==IMM8: operand=f"#${rom[addr]:02X}"; addr+=1
|
|
elif mode==IMM16: operand=f"#${(rom[addr]<<8)|rom[addr+1]:04X}"; addr+=2
|
|
elif mode==DIR: operand=f"${rom[addr]:02X}"; addr+=1
|
|
elif mode==EXT:
|
|
v=(rom[addr]<<8)|rom[addr+1]; addr+=2; operand=f"${v:04X}"; tgt=v
|
|
elif mode==IDX: operand=f"${rom[addr]:02X},X"; addr+=1
|
|
elif mode==IDY: operand=f"${rom[addr]:02X},Y"; addr+=1
|
|
elif mode==REL:
|
|
rel=rom[addr]; addr+=1; d=rel-256 if rel>127 else rel
|
|
tgt=(addr+d)&0xFFFF; operand=f"${tgt:04X}"
|
|
elif mode==BITDIR: operand=f"${rom[addr]:02X} #${rom[addr+1]:02X}"; addr+=2
|
|
elif mode==BITIDX: operand=f"${rom[addr]:02X},X #${rom[addr+1]:02X}"; addr+=2
|
|
elif mode==BITIDY: operand=f"${rom[addr]:02X},Y #${rom[addr+1]:02X}"; addr+=2
|
|
elif mode==BRDIR:
|
|
dd=rom[addr]; mk=rom[addr+1]; rel=rom[addr+2]; addr+=3
|
|
s=rel-256 if rel>127 else rel; tgt=(addr+s)&0xFFFF
|
|
operand=f"${dd:02X} #${mk:02X} ${tgt:04X}"
|
|
elif mode==BRIDX:
|
|
dd=rom[addr]; mk=rom[addr+1]; rel=rom[addr+2]; addr+=3
|
|
s=rel-256 if rel>127 else rel; tgt=(addr+s)&0xFFFF
|
|
operand=f"${dd:02X},X #${mk:02X} ${tgt:04X}"
|
|
elif mode==BRIDY:
|
|
dd=rom[addr]; mk=rom[addr+1]; rel=rom[addr+2]; addr+=3
|
|
s=rel-256 if rel>127 else rel; tgt=(addr+s)&0xFFFF
|
|
operand=f"${dd:02X},Y #${mk:02X} ${tgt:04X}"
|
|
raw=' '.join(f'{rom[b]:02X}' for b in range(start,addr))
|
|
out.append((start,f"{pfx}{mn} {operand}".strip(), raw, mn, tgt))
|
|
return out
|
|
|
|
if __name__=='__main__':
|
|
rom=open(__import__('os').path.join(__import__('os').path.dirname(__file__),'tms27pc512.BIN'),'rb').read()
|
|
start=int(sys.argv[1],16); count=int(sys.argv[2]) if len(sys.argv)>2 else 40
|
|
for row in disasm(rom,start,count):
|
|
a=row[0]; txt=row[1]; raw=row[2]
|
|
print(f"{a:04X}: {raw:<12} {txt}")
|