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

Interrupts (IM1)

TI-84 Plus OS 2.55MP — Interrupt masks, status, acknowledgement, dispatch, and low-power wake.

The TI-84 Plus OS runs the Z80 in interrupt mode 1 (IM1) and polls the ASIC’s interrupt status. This page separates the USB event gate from the legacy controller at ports 0x03 and 0x04, then follows acknowledgement, priority, and HALT wake behavior.

Evidence layers

EvidenceScopeConfidence
Page-0 bytes from im1_vector at ram:0038 through ram:0244IM1 entry, USB and legacy gates, source-test order, handlers, acknowledgement, and exit[confirmed]
Power-cycle trace from tools/macros/power-cycle.macroOS mask writes, low-power HALT, ON wake, status read, debounce, and restoration[confirmed]
WikiTI ports 0x03 and 0x04Bit-level enable, status, clear-on-zero, timer-rate, mapping, battery-selector, and low-power contract[standard]
TilEm commit f56ad63 and Wabbitemu commit 48c2dc0Two executable interpretations of the registers and their fidelity gaps[standard]
MAME 0.287 ti84pv3 driver and Lua I/O traceThird implementation, headless ON-wake execution, and explicit MACHINE_NOT_WORKING gaps[standard]
Guarded TilEm direct-core interrupt probeStored-mask readback, internal policy, acknowledgement, ON/link edges, timer callbacks, and reset ordering[standard]
Guarded TilEm direct-core link probeRaw link-activity and assist idle, receive, and error interrupt transitions[standard]
Guarded Wabbitemu interrupt edge probeInitialized-core mask, timer, acknowledgement, completion, and low-power transitions[standard]
Guarded MAME legacy-interrupt probeCPU-I/O-space status, mask, ON-edge, fixed-timer, and soft-reset observations[standard]

The ROM proves how OS 2.55MP uses the registers. Public notes and emulators describe behavior inside the ASIC that the ROM cannot prove by itself. Emulator agreement is supporting evidence, not physical confirmation.

IM1 entry and context

IM1 accepts a maskable interrupt at im1_vector, the fixed address ram:0038. The vector jumps to int_entry_save_alt_regs at ram:006D, which swaps AF, BC, DE, and HL with the alternate register set. The normal exit swaps them back, executes EI, and returns with RETI. [confirmed]

ram:0038  jr ram:006D
ram:006D  ex af,af'
ram:006E  exx
ram:006F  in a,(0x55)
ram:0071  xor 0xFF
ram:0073  and 0x1F
ram:0075  jr z,ram:003A

The handler uses the alternate general registers as its working context. It assumes IY = flags at 0x89F0 and uses the interrupted stack. It does not push IY, IX, or a complete register frame at entry. [confirmed]

The normal exit restores the standard OS mask after source-specific work: [confirmed]

ram:00E4  ld a,0x0B
ram:00E6  bit 0,(iy+0x16)
ram:00EA  jr z,ram:00EE
ram:00EC  add a,0x04          ; select 0x0F when timer 2 is wanted
ram:00EE  out (0x03),a
ram:00F0  ex af,af'
ram:00F1  exx
ram:00F2  ei
ram:00F3  reti

USB gate and legacy controller

Port 0x55 is the active-low USB interrupt summary. The three instructions at int_entry_save_alt_regs + 0x02 invert and mask its low five bits. A result of zero jumps directly to interrupt_legacy_status at ram:003A. A nonzero result enters the USB activity-hook and port-0x56 event paths before the handler considers the legacy controller. [confirmed]

This ordering does not make port 0x55 a summary of ON, standard-timer, or legacy link requests. Those sources appear at port 0x04. Port 0x56 is a USB line-event bitmap, not the mask for port 0x04. [confirmed] for the separate ROM paths; [standard] for the register roles.

The disconnected TilEm x4 model returns 0x1F from port 0x55 and zero from port 0x56. Its ordinary trace therefore takes int_entry_save_alt_regs + 0x02interrupt_legacy_status without USB event work. [standard]

See USB ASIC and link assist for the port-0x56 event-bit branches and page-35 handlers.

Port 0x03: mask, acknowledgement, and power mode

Port 0x03 controls the four legacy interrupt sources and the ASIC’s behavior when the Z80 executes HALT. Public notes document readback for enable bits 0, 1, 2, and 4. TilEm and Wabbitemu return the complete stored byte, but physical readback of bit 3 is not documented. [standard] for the public fields and emulator behavior; [hypothesis] for physical bit-3 readback.

BitMeaningEffect of writing zeroEvidence
0ON interrupt enableddisable and acknowledge the ON requestPublic register contract; OS writes and both emulators [standard]
1standard timer 1 enableddisable and acknowledge timer 1Public register contract; OS writes and TilEm [standard]
2standard timer 2 enableddisable and acknowledge timer 2Public register contract; OS writes and TilEm [standard]
3write control: one keeps hardware powered during HALT; zero selects low power on HALTselect low power for the next HALTPublic register contract; OS shutdown sequence and both emulators [standard]
4legacy link-activity interrupt enableddisable and acknowledge link activityPublic register contract; OS shutdown mask and TilEm [standard]
5–7no documented functionPublic register contract [standard]

Bit 3 changes what HALT does. A write with bit 3 clear does not enter low power by itself. The CPU must execute HALT, and an enabled wake source must later request an interrupt. [standard]

The OS uses these values: [confirmed] for each ROM write and branch; [standard] for the hardware effect.

ValueEnabled legacy sourcesHALT behaviorOS use
0x08nonepoweredcommon clear-on-zero acknowledgement and shutdown cleanup
0x09ONpoweredtransient standard-timer-1 acknowledgement path
0x0Astandard timer 1poweredtransient ON acknowledgement path
0x0BON and standard timer 1powerednormal mask
0x0FON and both standard timerspowerednormal exit when (IY+0x16) bit 0 requests timer 2
0x11ON and link activitylow powershutdown and wake loop

Port 0x04 read: source and ON status

Reading port 0x04 returns status. Bit 3 is the live active-low ON level; it is not an interrupt request. Bits 5–7 report programmable-timer completion even when the corresponding timer mode did not request a maskable interrupt. [standard]

BitRead meaningOS useEvidence
0ON request pendingbranch to on_irq at ram:015BROM test at ram:00D2ram:00D5 [confirmed]; latch role [standard]
1standard timer 1 pendingbranch to standard_timer1_irq at ram:0167ROM test at ram:00D6ram:00D9 [confirmed]; pending role [standard]
2standard timer 2 pendingbranch to ram:01F1ROM test at ram:00C8ram:00CB [confirmed]; pending role [standard]
3one when ON is released, zero while presseddebounce reads at ram:0975ROM interpretation [confirmed]; electrical level [standard]
4legacy link activity pendingbranch to legacy_link_irq at ram:01E0ROM test at ram:00CDram:00D0 [confirmed]; pending role [standard]
5programmable timer 1 finishedtest timer-1 mode at port 0x31ROM tests at ram:0041 and ram:013A [confirmed]; completion role [standard]
6programmable timer 2 finishedpage-35 handler with A = 0x0BROM tests at ram:0046 and ram:0154 [confirmed]; completion role [standard]
7programmable timer 3 finishedtest timer-3 mode at port 0x37ROM tests at ram:003C and ram:012C [confirmed]; completion role [standard]

The OS reads one status byte and retains it in A while testing the source bits. Programmable timers 1 and 3 receive an extra check of bit 1 in their own mode/status ports before the OS calls their banked handlers. A finished bit can therefore be visible without being eligible for interrupt service. [confirmed]

Port 0x04 write: three unrelated controls

Writing port 0x04 does not acknowledge the status returned by a read. A write selects the memory-map mode, standard-timer rate, and battery-comparator input. [standard]

BitsWrite meaningEvidence
0zero selects independent mapping; one selects paired mappingOS writes and mapper behavior [confirmed] for use; public contract and emulators [standard] for hardware
2–1standard-timer rate index 03, fastest to slowestOS writes 0x06; public formula and emulators [standard]
5–3unused in the public contract[standard]
7–6raw battery-comparator selectorOS battery-test writes; public contract [standard]

For TI-84 Plus standard timer 1, the published quartz-domain period is

$$ T_1 = \frac{64 + 80i}{32768}\text{ seconds}, $$

where $i$ is bits 2–1. Timer 2 runs at twice that frequency. OS value 0x06 selects independent mapping, rate index 3, and battery selector zero. [confirmed] for the OS value; [standard] for the field meanings and formula.

The battery-selector bits identify comparator configurations. The ROM’s write order does not prove that the raw two-bit number is a monotonic voltage level. Physical threshold and bit-order measurements remain open. [hypothesis]

See Paging for paired mapping and Clock, timers, and power for exact timer rates.

Dispatch order and simultaneous sources

The legacy-status path tests port-0x04 bits in this order: [confirmed]

PriorityStatus bitCandidateAdditional gate
17programmable timer 3port 0x37 bit 1
25programmable timer 1port 0x31 bit 1
36programmable timer 2handler selected through ram:0154
42standard timer 2none in the dispatcher
54legacy link activitynone in the dispatcher
60ON requestnone in the dispatcher
71standard timer 1none in the dispatcher
ram:003A  in a,(0x04)
ram:003C  bit 7,a
ram:0041  bit 5,a
ram:0046  bit 6,a
ram:00C8  bit 2,a
ram:00CD  bit 4,a
ram:00D2  rra                 ; original bit 0 enters carry
ram:00D6  rra                 ; original bit 1 enters carry

An eligible handler exits through acknowledgement instead of resuming at the next lower-priority bit. Simultaneous eligible sources are therefore not all dispatched from one port-0x04 read. A timer-1 or timer-3 completion bit whose mode gate is clear is skipped, allowing the next candidate to be tested. [confirmed]

The common port-0x03 write of 0x08 clears all four legacy source bits at once under the public clear-on-zero contract and TilEm’s model. Simultaneous lower-priority legacy requests can therefore be coalesced by the higher-priority service. A programmable-timer completion is acknowledged separately through its mode/status port. [standard] for latch behavior; [confirmed] for the OS write sequence.

Clear-on-zero acknowledgement

The common acknowledgement helper preserves the handler-supplied byte in A, writes 0x08, then writes the saved byte: [confirmed]

ram:00DC  push af
ram:00DD  ld a,0x08
ram:00DF  out (0x03),a
ram:00E1  pop af
ram:00E2  out (0x03),a

The first write clears the ON, standard-timer, and link source latches because their enable bits are all zero. Bit 3 remains one, so this acknowledgement does not request low power. The second write exposes the handler-supplied value only until the normal exit writes 0x0B or 0x0F. [confirmed] for values and ordering; [standard] for clear-on-zero semantics.

Leaving a legacy pending bit uncleared causes another maskable request after EI. Rewriting the same all-enabled mask is not an acknowledgement because each set bit leaves its source enabled and pending. [standard]

Programmable timers use a separate contract. Writing their mode/status ports 0x31, 0x34, or 0x37 clears finished/overflow state and removes that timer’s request in TilEm and the public hardware description. Port 0x03 does not clear bits 5–7. [standard]

Standard and programmable timer distinction

The standard timers belong to the legacy mask block. Port-0x03 bits 1 and 2 enable their interrupt requests, and clearing those bits acknowledges them. Their rate comes from port-0x04 bits 2–1. [standard]

The three programmable timers have source, mode/status, and counter triplets at ports 0x300x38. Their port-0x04 bits are completion observations, not enable bits. Timer mode bit 1 selects whether completion requests a maskable interrupt. [standard]

OS 2.55MP uses programmable timer 1 for its timer bcall state machine and programmable timer 3 for a USB timeout path. standard_timer1_irq drives keypad scanning, cursor blink, the run indicator, and Auto Power Down (APD). [confirmed]

See Clock, timers, and power for programmable-timer modes, the bcall ABI, and kernel-tick consumers.

ON request versus ON level

Port-0x04 bit 0 is the ON interrupt latch. Bit 3 is the button’s current active-low level. The dispatcher selects the ON handler from bit 0, while on_key_debounce_power at ram:0964 repeatedly samples bit 3 to classify a stable press or release. [confirmed]

The power-cycle trace reads 0x01 after the injected ON press. Bit 0 reports the request and bit 3 clear reports the held key. The two meanings coincide in this event but remain independent fields. [confirmed]

TilEm requests an ON interrupt on both press and release transitions when enabled. Wabbitemu latches only a transition into its pressed state. The ROM handles either stable level, but it cannot determine the physical ASIC’s edge policy. [standard] for emulator behavior; [hypothesis] for the unmeasured physical policy.

See Keypad and ON-key hardware for debounce timing and the OS ON state machine.

Port-0x03 bit 4 controls the legacy link-activity interrupt. The shutdown mask 0x11 uses it as a wake source. The port-0x04 bit-4 dispatcher branch enters legacy_link_irq, the power-restoration path. [confirmed] for OS use; [standard] for the interrupt source.

Normal operation uses mask 0x0B, so legacy link interrupts are disabled. Standard timer 1 still performs a periodic silent-link check at ram:01B1: the raw path reads port 0x00, and the assist path reads port 0x09. This polling is separate from a direct port-0x04 bit-4 request. [confirmed]

See Two-wire link port hardware for both detection paths.

Low-power entry and wake

poweroff_shared_tail at ram:0A24 first acknowledges legacy sources with 0x08. It then writes 0x06 to port 0x04, writes 0x11 to port 0x03, enables interrupts, and reaches poweroff_halt_loop at ram:0A5C. [confirmed]

ram:0A4B  out (0x04),a        ; A = 0x06
ram:0A4F  out (0x03),a        ; A = 0x11
ram:0A5B  ei
ram:0A5C  halt
ram:0A5D  jr ram:0A5C

Mask 0x11 disables both standard timers, enables ON and link activity, and clears the powered-HALT bit. The next HALT enters the ASIC low-power mode under the public contract. [confirmed] for the sequence; [standard] for the physical effect.

A resolved TilEm trace records the transition and ON wake: [confirmed]

clk=98010423   ram:0A29 OUT (0x03) <- 0x08
clk=99871166   ram:0A4B OUT (0x04) <- 0x06
clk=99871186   ram:0A4F OUT (0x03) <- 0x11
clk=99871258   ram:0A5C HALT
clk=99915117   ON pressed
clk=99915172   ram:006F IN  (0x55) -> 0x1F
clk=99915213   ram:003A IN  (0x04) -> 0x01
clk=99915377   ram:0964 ON wake/debounce path
clk=100195536  ram:09B5 power-on restoration

TilEm prevents programmable timers from waking HALT when both standard-timer bits in port 0x03 are clear. A programmable timer can still interrupt a running CPU in that state. This is an emulator policy approximating public reports that programmable timers do not reliably wake a halted CPU; it does not identify the physical ASIC mechanism. [standard]

MAME ON-wake trace

MAME 0.287 includes the ti84pv3 machine and accepts a 1 MiB OS 2.55MP image. The repository ROM has SHA-1 ffddb460d7d4e79cc8fbd288d6895fd113d7f3bf, while MAME’s reference image has SHA-1 d500540feca974f6e8fa269981cfb25dc951c338. MAME warns about this difference because the repository image contains locally assembled boot pages. [confirmed]

The Lua tap records the program counter after each I/O instruction. MAME reaches the shutdown mask, accepts an injected ON press, and enters the ROM debounce path: [confirmed]

MAME_IO frame=20 pc_after=0DF3 OUT (0x03) <- 0x08
MAME_IO frame=20 pc_after=0DF6 OUT (0x03) <- 0x00
MAME_IO frame=20 pc_after=0C97 OUT (0x03) <- 0x11
MAME_KEY frame=30 ON press
MAME_IO frame=31 pc_after=0071 IN (0x55) -> 0x1F
MAME_IO frame=31 pc_after=003C IN (0x04) -> 0x01
MAME_IO frame=31 pc_after=0977 IN (0x04) -> 0x01

The trace confirms ROM control flow under MAME. It does not confirm MAME’s register semantics. The driver itself marks every monochrome TI-84 Plus configuration MACHINE_NOT_WORKING. [standard]

Custom handler rules

A custom IM2 handler, or code that replaces OS interrupt service, must account for each controller independently: [standard]

  • Preserve every register and mapping state that interrupted code can observe. The OS shadow-register convention is safe only while the interrupted program leaves those alternate registers to the OS.
  • Read port 0x55 as an active-low USB gate and port 0x04 as legacy/completion status. Do not interpret port-0x04 bit 3 as a pending source.
  • Acknowledge legacy sources by clearing their port-0x03 bits, then restore the intended mask. Acknowledge programmable timers through their own mode/status ports.
  • Keep handler code and any data it requires in mapped memory. Banked calls must preserve the interrupted mapping or restore it before returning.
  • Service or deliberately disable USB events. A port-0x03 acknowledgement does not clear port-0x55/0x56 state.
  • Enable a source capable of waking the chosen power mode before executing HALT.

Chaining to the OS handler also inherits its assumptions: IY points to flags, normal RAM and page mappings are active, and the alternate general registers are available. [confirmed]

Emulator comparison

BehaviorTilEm x4Wabbitemu 83+SE/84+jsTIfied 20170706aConsequence
Port 0x03 readreturns stored maskreturns stored maskstores the interrupt maskmask reads agree [standard]
Legacy clear-on-zeroclears ON, timer 1, timer 2, and link pending state on port-0x03 writesclears ON directly; disabling an overdue standard timer catches its phase up in the same port-0x03 handler; port 0x02 can also catch it uptracks standard-timer and ON latches in emulator stateOS-style acknowledgement is modeled with different internal policies [standard]
Link statusimplements port-0x04 bit 4omits bit 4 from port-0x04 readslink state participates in the interrupt modelsoftware agreement does not establish electrical wake behavior [standard]
Standard timersexplicit pending interrupt bits when enabledderives status from elapsed phase while enabledschedules timer state in emulator cycle counterssimultaneous-source and latch tests can differ [standard]
Programmable completionexposes finished bits 5–7 independently of interrupt modeexposes timer-underflow bits 5–7retains per-timer completion and loop stateall separate completion from mode, with different timer cores [standard]
HALT behaviorport-0x03 bit 3 selects powered/low-power behavior; standard-timer mask controls programmable wake suppressionapproximates low power by changing LCD activity and suppresses programmable-timer requests while haltedhalted CPU state is part of the browser schedulerno model proves physical ASIC power domains [standard]
USB gatedisconnected fixed values 0x55 = 0x1F, 0x56 = 0partial Fake USB event modelfixed disconnected valuesconnected USB interrupt service needs another test target [standard]

Wabbitemu’s source comments state uncertainty about its standard-interrupt write behavior. Its model is useful as an independent implementation comparison, but disagreement must remain explicit. [standard]

Native TilEm interrupt edges

A guarded direct-core run compiles the complete TilEm tree at commit f56ad637d0524ee841dd381be6ecbaf5b8975600. It calls the registered x4 port handlers and timer callbacks, plus the public keypad, link, timer, and reset functions. It does not execute the ROM. [standard]

Port 0x03 stores all eight written bits. Writes 00, 01, 02, 04, 08, 10, and FF produce identical readback. The same writes select internal ON, power-on-HALT, and link enables from bits 0, 3, and 4. Either standard-timer bit clears TilEm’s NO_HALT_INT flag on all three programmable timers; both bits clear set that flag. All three timer flags agree in every case. [standard]

The probe seeds all four legacy latches, all three programmable completion flags, and all three programmable CPU requests. Applying the seven values above through port 0x03 produces status E8 E9 EA EC E8 F8 FF. Applying them through port 0x02 produces the same sequence. Both paths leave the three programmable requests at internal mask 0x38, and port 0x03 leaves completion bits 5–7 visible. This directly checks TilEm’s two clear-on-zero implementations. [standard]

The ON sequence produces 00 00 09 08 01 00 09 08 00: masked press, enable while held, release, acknowledge released, press, acknowledge held, release, disable, and press while disabled. TilEm therefore latches both enabled transitions. Timer callbacks with both masks clear remain at 08. With both enabled, timer 1, either timer-2 callback, and both sources produce 0A, 0C, 0C, and 0E. [standard]

Reset schedules timer 1, timer 2A, and timer 2B at initial delays 1,600, 1,300, and 1,000 µs with 9,277 µs periods. Port-0x04 writes select periods 1953, 4395, 6836, and 9277 µs for all three without changing the current interval. An enabled external link-line transition sets status 18; either acknowledgement path clears it to 08, and a disabled transition does not latch. [standard]

With the CPU halted and both standard timers disabled, programmable-timer-1 expiry leaves completion status 0x28 but no CPU request. Enabling either standard timer clears the gate and produces internal request 0x08; a running CPU also receives the request when both standard timers are disabled. This is TilEm’s policy, not a physical wake measurement. [standard]

TilEm reset writes stored port 0x03 = 0x0B directly after the generic keypad reset clears the internal ON enable. A fresh core therefore reads 0x0B while the internal ON enable is zero. Reset also retains poweronhalt; after the probe first clears it, reset again reads 0x0B while the retained power field is zero. Writing 0x0B through the port handler synchronizes both fields to one. This inconsistency affects experiments that begin at initialized-core reset without executing the ROM’s first mask write. [standard]

A separate guarded link matrix checks the interrupt-facing parts of the same implementation while exercising complete raw and assist transactions. An enabled external peer-line transition asserts the raw link-activity request. Assist idle-ready reports 0x22, receive-ready reports 0x31, and both assert the CPU interrupt. Reading receive data changes status to 0x20. Illegal both-low input reports 0x64; its first status read clears the CPU request while retaining error status 0x60. These are direct TilEm handler results, not physical interrupt-edge or acknowledgement measurements. [standard]

Two isolated executions produce identical canonical native JSON with SHA-256 1c1209e9c3f625b07c42288c21e9a5dbadddb38f12aee995c1fbc8daf1f8e8ad. The binary SHA-256 is 23037df0fee48b3ec15656aae80b6181d97211e8eec325c2be81eef02b1ff840. [standard]

Native Wabbitemu interrupt edges

A guarded initialized-core run checks the source model through the registered port handlers. Port 0x03 resets to 0x00 and stores all eight bits of 0xFF. Writing 0xFE clears a seeded ON latch while preserving 0xFE as the mask readback. [standard]

Wabbitemu selects standard-timer-1 rates of 512, 227, 158, and 108 Hz. The native periods round to 1,953,125, 4,405,286, 6,329,114, and 9,259,259 ns. Timer 2 at index 3 has a 4,629,630 ns period and starts 2,314,815 ns after timer 1. These are emulator intervals, not host-clock measurements or the documented physical formula. [standard]

The expiry comparison is strict. At exactly one 108 Hz timer-1 period, port 0x04 reads released-ON status 0x08 and the CPU interrupt line remains clear. At the next representable emulator time, status is 0x0A and the line asserts. The sequence 0x0A0x080x0A then reads 0x08: disabling the overdue timer runs the handler’s catch-up loop before re-enabling it. A port-0x02 zero write produces the same idle result through Wabbitemu’s second acknowledgement path. [standard]

With all three programmable underflow flags seeded, port 0x04 reads 0xE8. With the CPU halted, port-0x03 bit 3 clear changes Wabbitemu’s LCD activity field from on to off; setting bit 3 restores it. This is its visual low-power approximation, not evidence that a physical ASIC powers down the controller. [standard]

Native Wabbitemu USB interrupt edges

A separate initialized-core run checks Wabbitemu’s USB gate. With both USB request fields clear, port 0x55 reads 0x1F. Directly selecting line, protocol, or both requests produces 0x1B, 0x0F, and 0x0B. These values confirm active-low bits 2 and 4 in this emulator. [standard]

Writing zero to port 0x57 does not mask a line event. A subsequent write of 0x08 to port 0x4A raises the CPU interrupt, changes port 0x56 from 0x50 to 0x58, and changes port 0x55 from 0x1F to 0x1B. Repeating the same write after clearing only the CPU interrupt raises it again. The run invokes Wabbitemu’s handlers directly; it does not execute the ROM dispatcher or establish physical interrupt acknowledgement behavior. [standard]

MAME 0.287 provides a third comparison with larger known gaps: [standard]

AreaMAME ti84pv3 behaviorDifference from the public contract
Port 0x03 readcalls the same status reader used by port 0x04returns status and ON level instead of the stored mask
Port 0x03 writemasks ON and standard-timer pending fields with the written enable bitsmodels clear-on-zero for bits 0–2, but omits link bit 4 and low-power bit 3
Port 0x02 writewrites ON and standard-timer status through a handler whose comment says it is being ignoreddoes not match the documented port-0x03 acknowledgement ownership
Standard timersallocates fixed 256 Hz and 512 Hz callbacksport-0x04 rate writes do not select the published 107.79–512 Hz range
Programmable timersrequests an interrupt when mode bit 1 is clear and sets the port-0x04 completion field on that same branchreverses the documented interrupt-enable polarity and loses independent completion visibility
Link and low powerno legacy link-pending field or ASIC power-domain transitioncan execute the ROM wake path but cannot test physical link wake or low-power behavior
USBreturns fixed 0x1F and zero from ports 0x55 and 0x56disconnected path only

Native MAME interrupt edges

The guarded MAME run parks the Z80 in a DI loop on page-0 RAM and disables the programmable timers. At reset, ports 0x03 and 0x04 both read released ON status 0x08. Writes 00, 01, 02, 04, 08, 10, and FF to port 0x03 leave both reads at 0x08; the driver does not return the written mask. [standard]

Writing 0x07 to port 0x02 directly creates status 0x0F. Subsequent port-0x03 writes 0x01, 0x06, 0xFF, and 0x00 retain ON only, retain both standard timers, retain all three fields, and clear all three fields. Port 0x04 consequently reads 09, 0E, 0F, and 08. Port 0x02 still reads ASIC status 0xC3; its write and read handlers have unrelated meanings. [standard]

The live-input sequence begins with ON masked. A press, enabling ON while the button remains held, release, enabled press, enabled release, and bit-0-clear acknowledgement produce 00, 00, 08, 01, 09, and 08. The adapter waits through both the video input update and a 256 Hz timer-1 sample after each forced transition. This confirms MAME’s press-only latch and release rearming without executing the ROM handler. [standard]

One 20 ms frame with only timer 1, only timer 2, or both enabled produces status 0x0A, 0x0C, or 0x0E. Timer-1 status reaches 0x0A after both port-0x04 configuration writes 0x00 and 0x06. These frame-level results show that both callbacks run and that neither configuration suppresses timer 1; the pinned source, rather than this coarse interval, establishes the exact fixed 256 Hz and 512 Hz rates. [standard]

A scheduled soft reset retains seeded status 0x0F through the reset callback. After a port-0x02 zero write clears pending state, the retained standard-timer masks regenerate 0x0E; a new ON press changes the status to 0x07. MAME’s reset hook restores the mapper but does not restore these interrupt fields. This is emulator reset behavior, not physical warm-reset evidence. Two isolated runs produce identical canonical parsed native JSON with SHA-256 bb4b38d444692b5136d96264fa3acf9fe95ef2f6a1879ab72e9a2ad8077c1def. [standard]

Reusable debugging tools

tools/ti84re/hardware/interrupt_controller.py provides typed decoders, exact timer periods, clear-on-zero acknowledgement, ROM status-test order, USB active-low decoding, and immutable TilEm and MAME legacy-interrupt state models. tools/ti84re/hardware/describe_interrupt_controller.py exposes focused CLI commands: [confirmed] for the ROM/public decoders; [standard] for the emulator model.

nix develop -c python3 -m ti84re.hardware.describe_interrupt_controller mask 0x0B
nix develop -c python3 -m ti84re.hardware.describe_interrupt_controller status 0x8B
nix develop -c python3 -m ti84re.hardware.describe_interrupt_controller config 0x06
nix develop -c python3 -m ti84re.hardware.describe_interrupt_controller ack 0xF7 0x08

The trace command resolves mapper state, restricts output to interrupt ports, annotates each value, and collapses consecutive identical polls: [confirmed]

nix develop -c python3 -m ti84re.hardware.describe_interrupt_controller trace \
  /tmp/tilem-power-cycle.trace --clock 97000000-101000000

Use --all to retain repeated ON-level polling and --json for machine-readable output.

tools/ti84re/emulators/wabbitemu/interrupt_probe.py adds the pinned Wabbitemu timer-rate and edge oracle. Run its exact-ROM, hash-recording CLI with:

wabbit_interrupt_parent=$(mktemp -d /tmp/ti84-wabbit-interrupt.XXXXXX)
python3 -m ti84re.emulators.wabbitemu.run_interrupt_edge_probe \
  --rom tools/rom.bin \
  --binary "$wabbit_tmp/wabbitemu-headless" \
  --output-dir "$wabbit_interrupt_parent/run" --json

The ROM is only a core-initialization fixture in this mode. No TI-OS instruction executes.

tools/ti84re/emulators/tilem/interrupt.py supplies the typed direct-core report and checks it against the reusable TilEm state model. The guarded builder requires the exact clean source commit and tree. The runner requires the binary SHA-256 and refuses to reuse an output directory. The “Legacy interrupt matrix” section in tools/notes/emulator-probes.md contains the reproduction commands.

tools/ti84re/emulators/tilem/link.py checks the raw-activity and assist-interrupt transitions against the shared link model in tools/ti84re/link/port.py. Its guarded builder and runner use the same clean-source and binary-hash requirements. The “Raw link and assist matrix” section in tools/notes/emulator-probes.md contains the command.

tools/ti84re/emulators/mame/interrupt.py parses the complete MAME report and checks it against the reusable state model. Its guarded CLI records the exact MAME, ROM, Lua, logs, and evidence scope:

mame_interrupt_parent=$(mktemp -d /tmp/ti84-mame-interrupt.XXXXXX)
nix shell nixpkgs#mame --command python3 -m ti84re.emulators.mame.run_interrupt_probe \
  --expected-mame-sha256 \
    fc5f4aba1aa6eb115d66decad13bb3f5313b9f3be9cff7c785d8d88e3fca0b91 \
  --output-dir "$mame_interrupt_parent/run" --json

tools/ti84re/emulators/mame/trace.py, tools/ti84re/emulators/mame/run_io_trace.py, and tools/probes/mame/mame_io_trace.lua provide the equivalent headless MAME path. The Lua tap accepts comma-separated ports and ranges, collapses identical polls, records post-I/O PCs, and can inject ON at selected video frames: [confirmed]

nix shell nixpkgs#mame -c python3 -m ti84re.emulators.mame.run_io_trace \
  --seconds 2 --ports 03-04,55-56 \
  --on-press-frame 30 --on-release-frame 34

MAME prints a checksum warning for the locally assembled ROM and identifies the expected and actual hashes. Keep that warning with captured evidence.

Resolved findings and open hardware tests

  • [confirmed] OS 2.55MP tests USB activity before reading legacy status, but ports 0x55/0x56 remain separate from ports 0x03/0x04.
  • [confirmed] The port-0x04 test order is programmable timer 3, timer 1, timer 2, standard timer 2, link, ON, then standard timer 1.
  • [confirmed] The OS acknowledgement sequence is 0x08 → handler-supplied byte → 0x0B or 0x0F.
  • [confirmed] Shutdown writes 0x11 and executes HALT; the trace wakes through port-0x04 value 0x01 after an ON press.
  • [standard] Programmable completion bits remain observable independently of their interrupt-mode bits.
  • [standard] The guarded TilEm run verifies stored mask readback, both clear-on-zero paths, ON press and release latches, both timer-2 callbacks, link transitions, programmable-timer HALT gating, and the reset readback/internal-policy mismatch.
  • [standard] The guarded TilEm link run independently verifies raw activity, assist idle and receive requests, data acknowledgement, and interrupt-only acknowledgement of a retained error flag.
  • [standard] The guarded Wabbitemu run verifies complete mask readback, ON clearing, strict standard-timer expiry, both standard-timer acknowledgement paths, programmable completion readback, and its LCD-based low-power approximation.
  • [standard] The guarded Wabbitemu USB run verifies its active-low line/protocol summary, mask-independent line event, and repeat-event path. It does not verify the ROM dispatcher or physical USB interrupt behavior.
  • [standard] The guarded MAME run verifies shared status reads, three-bit mask behavior, direct port-0x02 status injection, press-only ON sampling, both standard-timer pending bits within scheduled frames, and interrupt-field retention across soft reset.
  • [hypothesis] Physical TA2 and TA3 tests should measure ON request edges, link wake transitions, simultaneous legacy-source coalescing, programmable-timer wake behavior, and battery-selector thresholds.

Sources

SourceUsed for
WikiTI port 0x03enable mask, clear-on-zero acknowledgement, normal value, and low-power-on-HALT contract
WikiTI port 0x04read-status fields, write controls, timer formula, and programmable completion distinction
TilEm x4_io.c, x4_init.c, keypad.c, link.c, and timers.clegacy latches, reset ordering, ON/link edges, timer completion, HALT policy, and disconnected USB values
Wabbitemu 83psehw.cindependent standard-interrupt, mapping, ON, timer, and low-power implementation
MAME 0.287 ti85.cpp and ti85_m.cppTI-84 Plus machine status, I/O map, interrupt masks, standard timers, programmable timers, and fixed USB reads
jsTIfied deployed 20170706a artifact and readable mirrorfourth interrupt-mask, timer, ON, link, halted-state, and fixed-USB implementation
Local OS 2.55MP page-0 bytesentry, gates, test order, handlers, acknowledgement, and exit
/tmp/tilem-power-cycle.traceshutdown and ON-wake execution sequence