Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/caljer1/9900dis/llms.txt

Use this file to discover all available pages before exploring further.

The TMS9995 is an enhanced version of the TMS9900. When you pass --cpu 9995, 9900dis enables decoding of four additional instructions not present in the TMS9900: MPYS and DIVS for signed multiplication and division, and LST and LWP for direct loading of the status and workspace pointer registers from a workspace register.

Enabling TMS9995 mode

Pass --cpu 9995 on the command line alongside your usual --rom and --listing arguments:
9900dis --cpu 9995 --rom cart.bin --listing cart.asm
This sets is9995=True in the ROM class constructor (rom.py:124). The two TMS9995-specific handlers, handle_9995_453() and handle_9995_4512(), check this flag at the top of their function bodies and immediately return False when it is not set — meaning no TMS9995 opcodes are ever recognised in the default --cpu 9900 mode.

Additional instructions

MPYS performs a signed 16×16-bit multiply. The source operand is multiplied by the signed value in r0, and the 32-bit signed result is stored in r0:r1.
FieldValue
Opcode bits 15–60b0000000111 (decimal 7)
Format handlerhandle_9995_453() in rom.py:344
OperandSingle source — same addressing modes as format 3.5.4
Example output:
MPYS    r3
The disassembler output follows the same "{:8}{}" template used by format 3.5.4 single-operand instructions.
DIVS performs a signed 32÷16-bit division. The 32-bit signed dividend is taken from r0:r1; the source operand is the 16-bit signed divisor. The quotient is placed in r0 and the remainder in r1.
FieldValue
Opcode bits 15–60b0000000110 (decimal 6)
Format handlerhandle_9995_453() in rom.py:344
OperandSingle source — same addressing modes as format 3.5.4
Example output:
DIVS    @>1234
Both MPYS and DIVS are decoded by the same handler (handle_9995_453) via the mne_9995_453 dictionary, which maps opcode 7 to "MPYS" and opcode 6 to "DIVS".
LST loads the status register directly from a workspace register. This is more efficient than the TMS9900 LIMI/STST pair for context-switching code.
FieldValue
Opcode bits 15–40b000000001000 (decimal 8)
Format handlerhandle_9995_4512() in rom.py:357
OperandWorkspace register rN (bits 3–0)
Example output:
LST     r0
The deconstruct9995_4512() method extracts a 12-bit opcode from bits 15–4 (word & 0xFFF0) >> 4) and a 4-bit register index from bits 3–0.
LWP loads the workspace pointer register directly from a workspace register, providing a fast register-to-WP transfer without needing LWPI followed by a memory indirection.
FieldValue
Opcode bits 15–40b000000001001 (decimal 9)
Format handlerhandle_9995_4512() in rom.py:357
OperandWorkspace register rN (bits 3–0)
Example output:
LWP     r1
LST and LWP are decoded by the same handler (handle_9995_4512) via the mne_9995_4512 dictionary, which maps opcode 8 to "LST" and opcode 9 to "LWP".

Instruction priority

The disassemble() method in rom.py:379 iterates a fixed handler list for every instruction word, stopping at the first handler that returns a non-False value:
handlers = [
    self.handleFormatHint,   # hints file overrides first
    self.handle351,          # format 3.5.1  (A, C, S, SOC, SZC, MOV + byte variants)
    self.handle352,          # format 3.5.2  (COC, CZC, XOR, MPY, DIV)
    self.handle353,          # format 3.5.3  (XOP)
    self.handle354,          # format 3.5.4  (B, BL, BLWP, CLR, SETO, INV, NEG, ...)
    self.handle355,          # format 3.5.5  (LDCR, STCR)
    self.handle356,          # format 3.5.6  (SBO, SBZ, TB)
    self.handle357,          # format 3.5.7  (all jumps)
    self.handle358,          # format 3.5.8  (SLA, SRA, SRC, SRL)
    self.handle359,          # format 3.5.9  (AI, ANDI, CI, LI, ORI)
    self.handle3510,         # format 3.5.10 (LWPI, LIMI)
    self.handle3511,         # format 3.5.11 (STST, STWP)
    self.handle3512,         # format 3.5.12 (RTWP)
    self.handle3513,         # format 3.5.13 (IDLE, RSET, CKOF, CKON, LREX)
    self.handle_9995_453,    # TMS9995 only  (MPYS, DIVS)
    self.handle_9995_4512,   # TMS9995 only  (LST, LWP)
    self.handleData          # fallback — always succeeds
]
The two TMS9995 handlers are positioned after all standard TMS9900 handlers and before the handleData fallback. Because handle_9995_453() and handle_9995_4512() return False immediately when is9995 is False, there is no performance penalty in default mode — the fallback handleData is reached in the same number of iterations as it would be without the TMS9995 handlers present.

Before and after: --cpu 9900 vs --cpu 9995

The following example shows how the same binary word >01C3 is decoded differently depending on the --cpu flag. The word >01C3 breaks down as:
  • Bits 15–6: 0b0000000111 = 7 → MPYS opcode in mne_9995_453
  • Bits 5–4 (ts): 0b00 → mode 0 (register direct)
  • Bits 3–0 (s): 0b0011 = 3 → r3
      DATA    >01C3            ; pc:>6010 w:>01C3
In TMS9900 mode every handler in the list returns False for this opcode (none of the standard format dictionaries contain key 7 with the right bit-field width), so handleData is reached and the word is emitted as a raw DATA pseudo-op. In TMS9995 mode, handle_9995_453() matches opcode 7 in mne_9995_453 and emits MPYS r3 instead.
The TMS9995 differs from the TMS9900 in several other respects that 9900dis does not model: it has 256 bytes of on-chip RAM mapped at >F000>F0FF and >FFFA>FFFF, an on-chip 16-bit timer, a different interrupt structure, and a modified memory map for I/O. 9900dis only models the additional instruction encodings introduced by the TMS9995. If you are disassembling TMS9995 ROMs you may need to add appropriate EQU definitions in your hints file to account for the differing memory-mapped I/O addresses.

Build docs developers (and LLMs) love