Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Bus timing and wait states

The TI-84 Plus ASIC inserts programmable T-states into Flash, RAM, and LCD transactions. CPU-speed mode selects one of four LCD-delay registers, which also gates the memory waits in port 0x2E; port 0x2F separately controls the high-speed LCD-ready interval and a documented programmable-timer prescaler.

OS 2.55MP programs this block once during retail boot. The ROM bytes and trace pin the register values. Public measurements, TilEm, and Wabbitemu supply the detailed timing contract; MAME provides an explicit omission comparison.

Evidence and limits

EvidenceWhat it establishesConfidence
boot_bus_timing_init at 3F:41BD41D3exact OS values for ports 0x290x2C, 0x2E, and 0x2F[confirmed]
Resolved cold-boot traceall six writes execute in order before normal CPU speed is selected[confirmed]
Whole-ROM immediate-port scanno second control-flow-verified write to these registers in the analyzed ROM[confirmed]
TilEm and Wabbitemu sourceindependent decode of speed selection, LCD instruction delays, and memory wait bits[standard]
Native Wabbitemu executionreset state, speed masks and frequencies, all seven delay latches, wait-gate selection, and port-0x2D side effects[standard]
MAME 0.287 sourcebinary CPU-speed selection and absence of the delay-register block[standard]
Guarded MAME ASIC-control runraw speed readback, measured 6:15 instruction throughput, absent delay ports, and soft-reset retention[standard]
Public hardware testsintended bit meanings, LCD failure thresholds, and mode-3 timer divisor[standard]

The trace proves what the ROM writes and which CPU-speed values execute. It does not measure the ASIC bus electrically. Emulator-added clock counts are reported as emulator behavior, not physical measurements.

Register block

PortRoleSelected by
0x20CPU-speed modesoftware; low two bits form mode 0–3
0x29LCD instruction delay and memory-wait gates for speed mode 0port 0x20 & 3 = 0
0x2Asame controls for speed mode 1port 0x20 & 3 = 1
0x2Bsame controls for speed mode 2port 0x20 & 3 = 2
0x2Csame controls for speed mode 3port 0x20 & 3 = 3
0x2Dquartz and low-power controlindependent block; see Clock, timers, and power
0x2Eone-T-state Flash and RAM access selectorsgated by bits 0–1 of the active 0x290x2C register
0x2Fhigh-speed LCD-ready interval and documented mode-3 timer prescalerfield selected by port 0x20

TilEm reads back the last byte written to ports 0x290x2C, 0x2E, and 0x2F. Wabbitemu registers one generic latch handler across the complete 0x290x2F range. Its port 0x2D therefore stores a raw byte instead of implementing the separate low-power contract. MAME maps only port 0x20 from this block; its I/O map has no handler for the six delay-register writes. [standard]

Selection pipeline

Let $s = p_{20} \bmod 4$. The active speed-dependent register is [standard]

$$ D_s = p_{29+s} $$

That one byte controls two independent effects:

  • bits 2–7 select the T-states added to each LCD-port instruction;
  • bit 0 enables the Flash bits in port 0x2E, and bit 1 enables the RAM bits.

Timing model. The register behavior is [standard], and the diagram’s widths are conceptual. OS 2.55MP’s register bytes and executed speed values are [confirmed].

Changing port 0x20 changes the active byte immediately in TilEm and Wabbitemu. The OS does not need to rewrite ports 0x290x2C when it moves between 6 and 15 MHz. Both emulators recompute memory delays on an accepted speed write. [standard]

Under the public TI-84 Plus contract and TilEm, speed value 0 selects nominal 6 MHz and values 1–3 select nominal 15 MHz. Wabbitemu’s default TI-84 Plus context clamps writes 2–3 to mode 1. Its external extraSpeed option instead assigns 20 and 25 MHz to modes 2 and 3. MAME stores the raw byte and selects 15 MHz for every nonzero value. These are software policies, not evidence for extra physical TI-84 Plus clocks. OS 2.55MP uses only values 0 and 1 in the captured workflows. See Clock, timers, and power for frequency and ASIC-revision caveats. [confirmed] for executed values; [standard] for public and emulator behavior.

A guarded initialized-core run writes 0xFC0xFF. The reset timer_version = 0 context reads back modes 0/1/1/1 at 6, 15, 15, and 15 MHz. Directly setting timer_version = 1 produces modes 0/1/2/3 at 6, 15, 20, and 25 MHz. The direct setting represents Wabbitemu front-end state; it is not a calculator port transition. [standard]

A guarded MAME run writes 00, 01, 02, 03, and FF to port 0x20 and reads the same five raw bytes back. A 50-T-state RAM counter advances 12,000 times during five 20 ms frames after write zero and 30,000 times after write one. The exact 2.5 ratio dynamically distinguishes 6 MHz from 15 MHz. A MAME soft reset retains raw speed 0x03; this is driver behavior, not a calculator reset claim. [standard]

Boot configuration

boot_bus_timing_init at 3F:41BD writes the complete block after the retail boot RAM probes and link-assist initialization: [confirmed]

3F:41BD  LD A,0x17
3F:41BF  OUT (0x29),A
3F:41C1  LD A,0x27
3F:41C3  OUT (0x2A),A
3F:41C5  LD A,0x2F
3F:41C7  OUT (0x2B),A
3F:41C9  LD A,0x3B
3F:41CB  OUT (0x2C),A
3F:41CD  LD A,0x45
3F:41CF  OUT (0x2E),A
3F:41D1  LD A,0x4B
3F:41D3  OUT (0x2F),A

The resolved trace executes the writes at clocks 1,747,536 through 1,747,628. Port 0x20 still reads zero at 3F:653E. The OS later writes zero at ram:0DD5 and selects speed mode 1 at ram:0C72. [confirmed]

TilEm resets its internal registers to the older values 0x14, 0x27, 0x2F, 0x3B, 0x44, and 0x4A. The retail boot writes above replace every differing value before normal OS operation. Emulator reset defaults therefore must not be mistaken for TI-84 Plus OS policy. [standard]

MAME accepts the later port-0x20 speed write but drops all six boot writes to ports 0x290x2C, 0x2E, and 0x2F. It therefore runs the ROM at the selected base clock without the programmable LCD or memory additions described below. A native patterned-write run reads zero from every port 0x290x2F both before and after the writes. [standard]

LCD instruction delay — ports 0x290x2C

TilEm and Wabbitemu add [standard]

$$ T_{\mathrm{LCD}} = D_s >> 2 $$

T-states to each Z80 IN or OUT instruction targeting LCD ports 0x100x13. The two low bits do not contribute to this count; they gate memory waits.

The OS bytes decode as follows:

Speed modeActive portOS byteAdded LCD T-statesLow-bit gates
00x290x175Flash and RAM
10x2A0x279Flash and RAM
20x2B0x2F11Flash and RAM
30x2C0x3B14Flash and RAM

At nominal 6 MHz, five T-states are about 0.833 µs. At nominal 15 MHz, nine T-states are 0.6 µs. These are additions to the Z80 I/O instruction, not the complete interval between two LCD transfers. [standard]

Published hardware tests report that values below 0x0C can make LCD writes stop responding, while read behavior has a different lower boundary. The exact threshold and failure mode should be remeasured by controller and ASIC revision. [standard] for the published observation; [hypothesis] for cross-revision behavior.

Memory waits — port 0x2E

Each selected bit adds one T-state to one memory-access class. Bits 0–2 apply only when active register $D_s$ has bit 0 set. Bits 4–6 apply only when $D_s$ has bit 1 set. [standard]

Port-0x2E bitMemoryAccess classEmulator placement
0Flashopcode/M1 fetcheach fetched opcode or prefix byte
1Flashnon-opcode readoperands, data, and stack reads
2Flashattempted writeevery CPU write routed to Flash
3unused by the documented delay blockstored on readback
4RAMopcode/M1 fetcheach fetched opcode or prefix byte
5RAMnon-opcode readoperands, data, and stack reads
6RAMwriteevery CPU write routed to RAM
7unused by the documented delay blockstored on readback

The boot value 0x45 sets bits 6, 2, and 0. Because all four active-register bytes have gate bits 0 and 1 set, the OS policy in every speed mode is: [confirmed] for register values; [standard] for the access decode.

AccessFlash additionRAM addition
opcode/M1 fetch1 T-state0
non-opcode read00
write1 T-state1 T-state

A CB-, ED-, DD-, or FD-prefixed Z80 instruction performs two M1 fetches. Both TilEm and Wabbitemu apply the opcode wait to the prefix and following opcode. A repeated DD or FD prefix adds another M1 fetch in both cores. [standard] for the Z80 bus cycle and pinned emulator source paths.

Indexed CB instructions expose a model difference. The Z80 fetches DD or FD and CB with M1 signaling, then reads the displacement and final opcode without M1. TilEm follows this split in z80main.h:674 and z80ddfd.h:301. Wabbitemu routes the final opcode through CPU_opcode_fetch at core/core.c:832, then decrements R at line 836. Its wait model therefore counts three opcode waits while its visible refresh count remains two. The hash-guarded describe_prefix_fetch_models.py CLI reproduces this result from the pinned source trees. The exact assembled HWPFX program also reproduces the split in the pinned Wabbitemu runtime: its indexed-CB row adds 30 timer ticks, compared with 25 for the three ordinary one-prefix rows and 29 for repeated DD. [confirmed] for the emulator run; physical ASIC placement remains open.

The delay follows the physical page selected by the mapper in TilEm. An opcode executed from banked RAM uses the RAM M1 bit; the same logical address backed by Flash uses the Flash bit. See Paging for physical-page resolution. [standard]

One T-state is about 0.167 µs at nominal 6 MHz and 0.067 µs at nominal 15 MHz. The register therefore preserves a cycle margin, not a fixed wall-time margin, when CPU speed changes. [standard]

LCD-ready interval — port 0x2F

The LCD instruction delay above slows the I/O instruction itself. Port 0x2F controls a second mechanism: at high speed, the ASIC deasserts port-0x02 bit 1 for a longer programmable interval after an LCD transaction. The OS lcd_wait helper polls that bit before accessing the controller. See LCD controller and display bus. [standard]

Speed mode selects one field: [standard]

Speed modeFieldWidth
0no high-speed ready hold
1bits 0–12 bits
2bits 2–43 bits
3bits 5–73 bits

For a selected field $f$, TilEm and Wabbitemu use [standard]

$$ T_{ready} = 48 + 64f $$

The boot value 0x4B produces:

Speed modeField valueReady holdNominal interpretation
0noneCPU and per-access delay provide the low-speed spacing
13240 T-states16 µs at 15 MHz
22176 T-statesmode not used by the traced OS path
32176 T-statesmode not used by the traced OS path

TilEm restarts this ready timer on every modeled access to ports 0x100x13, including reads. Wabbitemu derives readiness from the last successful LCD write. This disagreement matters for read-heavy code and requires a physical test. [standard] for emulator behavior; [hypothesis] for ASIC read behavior.

The guarded Wabbitemu initialized-core run sets speed mode 1 and field 3, then writes the LCD at T-state 2,000. Port 0x02 reads 0xE1 at T-state 2,240 and 0xE3 at 2,241. An accepted status read at 2,241 leaves the write timestamp at 2,000 and leaves port 0x02 at 0xE3. The comparison is strict rather than inclusive: readiness requires elapsed time greater than 240 T-states in this implementation. [standard]

Programmable-timer mode-3 divisor

Public documentation assigns a second role to the selected port-0x2F field. For programmable-timer sources in the 0xC0 family, the field selects divisor $f+1$; speed mode 0 applies no divisor. [standard]

With the OS byte 0x4B, the documented divisors are 1, 4, 3, and 3 for speed modes 0–3. TilEm treats the 0xC0 family like its ordinary CPU-clock modes, and Wabbitemu’s timer-source update does not use port 0x2F. The prescaler is therefore absent from both compared emulator paths. [standard]

OS 2.55MP’s timer API can select 0xC0-family sources. The prepared HWTMR probe counts source-0xE0 expiries against a crystal reference in CPU-speed modes 0–3. Its exact assembled image completes in pinned Wabbitemu and measures a prescaler near one, matching that emulator’s source implementation. No result from a physical calculator has been recorded. [confirmed] for the probe and emulator run; [hypothesis] for the physical divisor.

Emulator comparison

BehaviorTilEmWabbitemuMAME 0.287jsTIfied 20170706a
Port-0x20 writelow two bits select modes 0–3; nonzero runs at 15 MHzdefault TI-84 Plus state clamps modes 2–3 to mode 1; external extraSpeed enables 20/25 MHzstores the raw byte; zero selects 6 MHz and any nonzero value selects 15 MHzselects the browser emulator’s CPU-speed state
Active 0x290x2C registerindexed by port 0x20 & 3indexed by the accepted CPU-speed moderegisters absentdelay values are stored
LCD instruction additionactive byte shifted right by twosameabsentLCD uses its own busy interval
Memory gates and 0x2E bitsall six access classesall six access classesabsentno source-equivalent per-access wait insertion identified
Port 0x2Dlow-power control outside this blockraw fifth delay latch; no timer or low-power transitionabsentstored control state
High-speed ready startevery LCD-port read or writelast successful LCD writeprogrammable interval absentLCD readiness uses randomized controller timing
LCD controller rejectionready bit and controller modelalso has a separate fixed 60-T-state controller-access guardT6A04 device behavior without the ASIC delay blockcontroller transfers use the jsTIfied LCD timer
Mode-3 timer prescalernot modelednot modeled in the compared timer pathnot modelednot modeled in the timer-source decoder

The matching memory and LCD-instruction decode corroborates the public bit layout. MAME cannot corroborate that decode because it omits the block. The readiness, speed, and timer differences remain emulator policy, not proof of physical timing.

The same guarded Wabbitemu run dynamically checks three linked cases. Port 0x2A = 0x27 adds nine T-states to a status read. With port 0x2E = 0x45, the enabled additions are Flash opcode fetch, Flash write, and RAM write. A write of speed value 3 reads back mode 1 in the reset TI-84 Plus context because timer_version = 0 disables the external extra-speed modes. [standard]

A dedicated speed run reads all seven reset latches as zero and verifies raw byte readback across ports 0x290x2F. With active-register values 0x00/0x01/0x02/0x03 and port 0x2E = 0x77, modes 0–3 produce wait masks 0x00/0x07/0x38/0x3F: none, all Flash classes, all RAM classes, and all six classes. Writing 0x5A to port 0x2D changes only that latch. The active wait mask, CPU frequency, timer version, programmable-timer state, LCD-active state, HALT, interrupt line, and T-state count remain unchanged. These observations describe Wabbitemu’s registered handler, not physical low-power behavior. [standard]

Reproducing the decode

tools/bus_timing.py is a pure register decoder. tools/describe_bus_timing.py prints all four speed modes from the boot values:

nix develop -c python tools/describe_bus_timing.py

The default documented profile reports:

documented (WikiTI pages retrieved 2026-08-09): speed-mode=1 clock=15MHz port20=01/01
  port2e=0x45 port2f=0x4B
  mode  port value  MHz  LCD-I/O  Flash +1T      RAM +1T        LCD-ready  doc-div
   0    0x29  0x17    6      5T     M1,write       write            0T       /1
   1    0x2A  0x27   15      9T     M1,write       write          240T       /4
   2    0x2B  0x2F   15     11T     M1,write       write          176T       /3
   3    0x2C  0x3B   15     14T     M1,write       write          176T       /3

doc-div is the public port-0x2F divisor decode. It is not an emulator claim. Compare the four pinned implementations, including ignored MAME writes and jsTIfied’s stored-delay model, with:

nix develop -c python tools/describe_bus_timing.py --compare
nix develop -c python tools/describe_bus_timing.py --compare --json

Pass --extra-speeds to model Wabbitemu’s external 20/25 MHz option. The flag does not describe the default TI-84 Plus configuration.

Repeated --write PORT=VALUE options make altered settings explicit. JSON is available for scripts:

nix develop -c python tools/describe_bus_timing.py \
  --write 0x20=0 --write 0x29=0x17 --write 0x2e=0x45 --json

Run the native boundary checks with the pinned Wabbitemu adapter:

wabbit_lcd_parent=$(mktemp -d /tmp/ti84-wabbit-lcd.XXXXXX)
python tools/run_wabbitemu_lcd_edge_probe.py \
  --rom tools/rom.bin \
  --binary "$wabbit_tmp/wabbitemu-headless" \
  --output-dir "$wabbit_lcd_parent/run" --json

wabbit_speed_parent=$(mktemp -d /tmp/ti84-wabbit-speed.XXXXXX)
python tools/run_wabbitemu_speed_edge_probe.py \
  --rom tools/rom.bin \
  --binary "$wabbit_tmp/wabbitemu-headless" \
  --output-dir "$wabbit_speed_parent/run" --json

mame_asic_parent=$(mktemp -d /tmp/ti84-mame-asic.XXXXXX)
nix shell nixpkgs#mame --command python tools/run_mame_asic_probe.py \
  --expected-mame-sha256 \
    fc5f4aba1aa6eb115d66decad13bb3f5313b9f3be9cff7c785d8d88e3fca0b91 \
  --output-dir "$mame_asic_parent/run" --json

The manifest labels the run as initialized-core emulator evidence and records both input hashes. tools/wabbitemu_lcd_probe.py derives its pointer, latch, ready-hold, delay, wait-bit, and speed-clamp expectations from the reusable LCD and bus-timing libraries.

tools/wabbitemu_speed_probe.py derives speed, latch, and wait-mask expectations from tools/bus_timing.py. Its guarded CLI records both input hashes and labels the direct timer_version = 1 configuration explicitly. tools/mame_asic.py reuses the MAME timing profile for raw readback, binary clock selection, and the absent delay block.

The executed initialization can be recovered from a full boot trace:

nix develop -c python tools/tilem_trace_resolve.py /tmp/boot.trace \
  --initial-mapping ti84p-reset --io-ports 20,29-2f

Open physical tests

The read-only ASIC register snapshot captures ports 0x20, 0x290x2C, 0x2E, and 0x2F before a mutating timing test. It does not measure any delay. No physical snapshot is recorded. [confirmed] for the probe bytes; [hypothesis] for pending readback values.

The guarded memory-bus timing probe prepares paired timer-2 measurements for all six access classes. Its counted loops separate fixed-Flash opcode fetches, Flash data reads, safe Flash reset writes, RAM opcode fetches, RAM non-opcode reads, and idempotent RAM writes. It restores the entry timing byte and an initially idle timer 2. No exported physical result has been recorded. [confirmed] for the probe bytes and decoder; [hypothesis] for pending measurements.

The guarded prefix-M1 timing probe prepares paired RAM-M1 measurements for unprefixed, CB, ED, DD, repeated-DD, and indexed-CB shapes. The indexed-CB row distinguishes TilEm and documented Z80 M1 placement from Wabbitemu’s extra wait. The exact image completes through the cleanup boundary in pinned Wabbitemu and selects its three-wait model. No physical result has been recorded. [confirmed] for the probe bytes, decoder, and emulator run; [hypothesis] for pending measurements.

The guarded programmable-timer probe prepares the port-0x2F mode-3 measurement. It also distinguishes crystal divisor, counter-zero, and expiry-status models while timers 1 and 2 are idle. The exact image completes through cleanup in pinned Wabbitemu. No exported physical result has been recorded. [confirmed] for the probe bytes, decoder, and emulator run; [hypothesis] for pending measurements.

  • Run all three prepared timing matrices on TA2 and TA3 units.
  • Repeat the loop test with active-register gate bits 0 and 1 cleared to verify whether they disable all corresponding 0x2E effects.
  • Measure the interval from LCD writes and reads to port-0x02 bit 1 becoming ready. This distinguishes TilEm’s every-access restart from Wabbitemu’s write-based model.
  • Find the lowest reliable ports-0x290x2C values for each LCD controller revision without assuming the published 0x0C threshold is universal.
  • Compare the HWTMR port-0x2F results across CPU-speed modes and ASIC revisions.
  • Compare nominal and measured T-state wall times on TA2 and TA3 ASICs.

Sources

SourceUse
OS 2.55MP boot_bus_timing_init and resolved boot traceboot register values, write order, and later CPU-speed transitions
WikiTI ports 0x29, 0x2A, 0x2B, and 0x2Cspeed selection, gate bits, LCD instruction delay, and published failure thresholds
WikiTI port 0x2Esix memory-access classes and prefix observation
WikiTI port 0x2FLCD-ready intervals and mode-3 timer prescaler
TilEm x4_io.c, x4_memory.c, and x4_init.cdelay decode, cycle placement, ready timer, and reset defaults
Wabbitemu 83psehw.c and core.cindependent delay decode, cycle placement, and readiness comparison
MAME 0.287 ti85.cpp and ti85_m.cppmapped I/O ports, raw speed readback, binary clock selection, and absent delay block
jsTIfied deployed 20170706a artifact and readable mirrorfourth CPU-speed, stored-delay, LCD-ready, and timer-source comparison