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

Execution protection

TI-84 Plus OS 2.55MP — Flash-page and RAM-chunk fetch controls.

Ports 0x210x26 define where the ASIC permits instruction fetches. The retail boot writes five protection registers before it changes the normal memory map. Emulator source supplies reproducible models for the resulting checks, but several boundary details still require physical measurement.

Evidence boundaries

EvidenceWhat it establishesConfidence
Retail boot page 3Fprotected writes, register values, and _SetFlashLowerBound behavior[confirmed]
Complete-ROM static I/O scanone write each to ports 0x21, 0x22, 0x25, and 0x26; two writes to port 0x23; no resolved port-0x24 access[confirmed]
TilEm x4, Wabbitemu, and jsTIfied sourcethree executable software models, including their disagreements[standard]
Guarded TilEm boundary tracesfetch, return, warning, and reset sequences at pages 07, 08, 29, and 2A[confirmed] for the pinned emulator run
Guarded Wabbitemu boundary runsfetch, return, marker, and instrumented reset sequences at pages 07, 08, 09, 29, and 2A[confirmed] for the pinned emulator run
Guarded RAM execution runschunk-edge and mode disagreements under pinned TilEm and Wabbitemu[confirmed] for the pinned emulator runs
Guarded Wabbitemu protected-port runregistered-port gate, readback, high-field handling, and 16-bit RAM-bound storage[standard]
WikiTI port pagespublic inclusive-bound descriptions and the larger-device port-0x24 extension[standard]
Physical TA2/TA3 behaviorlower-edge, reset, violation, and overlay details[hypothesis] until measured

The emulator equations below are source-verified descriptions of those programs. Agreement between an emulator and a public table does not promote an unmeasured ASIC behavior to [confirmed].

Registers and boot values

The boot sequence establishes these values. [confirmed]

PortBoot valueModeled role
0x210x00bits 4–5 select the repeating RAM address mask; bits 0–1 also select the Flash protection group
0x220x08lower Flash no-execute page
0x230x29upper Flash no-execute page
0x250x10lower executable RAM chunk in 1 KiB units
0x260x20upper executable RAM chunk in 1 KiB units

boot_execution_protection_init at 3F:41D5 performs the five writes through 3F:4206: [confirmed]

3F:41D5  ld a,0x00
3F:41D7  nop
3F:41D8  nop
3F:41D9  im 1
3F:41DB  di
3F:41DC  out (0x21),a
3F:41DE  di

3F:41DF  ld a,0x08
3F:41E1  nop
3F:41E2  nop
3F:41E3  im 1
3F:41E5  di
3F:41E6  out (0x22),a
3F:41E8  di

3F:41E9  ld a,0x29
3F:41EB  nop
3F:41EC  nop
3F:41ED  im 1
3F:41EF  di
3F:41F0  out (0x23),a
3F:41F2  di

3F:41F3  ld a,0x10
3F:41F5  nop
3F:41F6  nop
3F:41F7  im 1
3F:41F9  di
3F:41FA  out (0x25),a
3F:41FC  di

3F:41FD  ld a,0x20
3F:41FF  nop
3F:4200  nop
3F:4201  im 1
3F:4203  di
3F:4204  out (0x26),a
3F:4206  di

Each output is preceded by fetched bytes 00 00 ED 56 F3 D3. TilEm advances its protected-write recognizer only while these bytes come from physical Flash 0xB00000xBFFFF or 0xF00000xFFFFF. Wabbitemu does not recognize the six bytes. It accepts a port-0x14 lock change whenever the output instruction executes on one of its privileged pages. Both implementations then accept writes to ports 0x210x26 only while Flash is unlocked. [standard]

The ROM proves that it emits the required byte sequence from page 3F. It does not by itself prove the complete privileged-page set or the response to an invalid sequence. [confirmed] for the bytes; [standard] for the differing emulator gates.

Flash instruction fetches

TilEm x4 applies the following test after it resolves a logical address to a physical Flash page $p$: [standard]

$$ \mathit{denied}_{\mathrm{TilEm}} = (\mathtt{port22} \le p \le \mathtt{port23}) $$

The boot values therefore deny pages 0x080x29, inclusive. Pages 0x000x07 and 0x2A0x3F remain executable. Reversing the bounds makes the interval empty in this model. [standard]

Wabbitemu implements a different lower edge: [standard]

return bank->page <= flash_lower || bank->page > flash_upper;

Its forbidden interval is (port22, port23]. With the boot values, Wabbitemu allows page 0x08, while TilEm denies it. Both deny page 0x09 and page 0x29. WikiTI describes the lower bound as inclusive, which agrees with TilEm, but the physical page-0x08 result remains unmeasured. [standard] for the published contract and source comparison; [hypothesis] for hardware.

Boot execution ranges. The written bounds are [confirmed], and the emulator predicates are [standard]. The physical page-08 boundary remains [hypothesis]. The RAM panel introduces the mode-dependent chunk model detailed below.

Both emulator paths apply this rule to opcode fetches. Ordinary Flash data reads use a separate path. The locked certificate-page read censor is also a separate mechanism. [standard]

jsTIfied stores ports 0x22 and 0x23 only while its Flash gate is open and builds page-level run_lock entries. A denied fetch sets its halted/reset state to 2. Ports 0x25 and 0x26 are stored but do not participate in the fetch predicate, so jsTIfied cannot test the documented 1 KiB RAM-bound behavior. Its port-0x21 handler instead rebuilds coarser RAM-page execution groups. These are properties of the pinned JavaScript source, not ASIC evidence. [standard]

WikiTI also says page 0x00 always remains executable and that a forbidden fetch resets the calculator. Wabbitemu always permits page 0 and resets the CPU on a violation when no debugger callback is installed. TilEm’s interval test can deny page 0 when port 0x22 = 0. Its opcode-read handler raises an execution exception, and its Z80 loop performs a full calculator reset after the fetched opcode completes. These custom-bound and post-violation behaviors remain physical test cases. [standard]

Guarded TilEm boundary trace

A controlled fixture tests both sides of the boot interval rather than inferring runtime behavior from source alone. Each derived ROM changes only six erased bytes at target pp:7FF0 to this marker routine:

ld a,pp
ld (0x8478),a
ret

The 75-byte assembly program at ram:9D95 first reads those six bytes as data and compares them with its embedded signature. It then seeds 0x8478, maps the target page, and executes CALL 0x7FF0 at ram:9DBD. A mismatch returns without attempting the fetch. The fixture builder requires the exact complete ROM hash, verifies that the patched span was FF FF FF FF FF FF, and writes a new ROM copy rather than modifying tools/rom.bin.

The pinned headless TilEm run produced these control-flow sequences. Clock deltas are relative to the CALL; absolute clocks include UI launch timing. [confirmed]

PageRecorded sequence after ram:9DBDTilEm warning countOutcome
0707:7FF0 at +8, 07:7FF2 at +23, return ram:9DC0 at +470returned
08attempted 08:7FF0 at +8, reset entry at +15; no 08:7FF2 or return1violation reset
29attempted 29:7FF0 at +8, reset entry at +15; no 29:7FF2 or return1violation reset
2A2A:7FF0 at +8, 2A:7FF2 at +23, return ram:9DC0 at +470returned

TilEm records the denied target’s first opcode-fetch address before its main loop services the pending execution exception. It does not advance to the marker store at pp:7FF2; the next record is logical 0x8000, followed by the retail reset stub’s mapping writes and boot continuation. The first post-reset record is resolved with stale pre-reset mapper state because TLMT has no explicit internal-reset event, so the classifier uses the logical 0x8000 transition and the subsequent reset-stub sequence.

The machine-code and trace identities were: [confirmed]

PageProbe SHA-256Trace SHA-256
0787c11964b6cf67624b2eff46e1a962c56f1684dd48db931a5cb68e08c1b84b4e250cc9d2b8b3c85f5edb6391e847993e27e6c308c4a70d62dd5cfc8168af8e68
08ddd023d522d301315c0f4929f348499faca08c708e96c1333bf85e32505f9534f9c1f142430aafc47b514ef220a707be01de02678e6cd22fcb1f6e5fb024eeac
29f671bdb62e6bad19f33402eb919e70631cf7cc8f00b9f7f52114d052f86cea78ee3dac7ec1843c2a82ee321c0a3a16c95bc5898d3c70fb97296127dbf2020007
2Ad5f72f96562ef5e96f4ddaa12954548d210650d9ca6bec365f75f1bb6f3bad1bb9db26bc7ef69d97907118d0124213603632d9e2f3d9ebb56680b87d8644636d

This dynamically confirms the inclusive 0829 interval and reset policy implemented by this TilEm build. It does not decide the physical page-08 boundary or validate Wabbitemu’s lower-exclusive model. [confirmed] for TilEm; [hypothesis] for the physical lower edge and violation response.

TilEm reset and exception scope

TilEm’s tilem_calc_reset resets the Z80, LCD controller, link port, keypad, Flash command state, MD5 accelerator, programmable timers, and model-specific hardware state in sequence. The TI-84 Plus callback then selects this starting state: [standard]

GroupReset value
Z80 register pairs except PC0xFFFF
PC, R bit 70x8000, 0x80
IFF1, IFF2, IM, interrupt requests, HALTzero
Mapper windowspage 00, certificate page 3E, boot page 3F, boot page 3F
CPU speed6 MHz
Protection ports 0x210x23, 0x25, 0x2600, 08, 29, 10, 20
Flash command gate, state, and busy flaglocked, array-read mode, idle
LCD controllerinactive, contrast 32, 8-bit mode, increment 7, row stride 16
Link output and assist, keypad, MD5, programmable timerscleared or reset defaults

The reset retains all memory arrays, including LCD backing memory. It also retains Flash program-address, data, toggle, override-group, and emulation fields. External link-emulator state remains. The TI-84 Plus callback leaves port 0x05, ports 0x090x0F, the three RTC fields, and LCD_WAIT unchanged. Scheduler and debugger state also survives: the Z80 clock, access timestamps, emulation flags, dynamic timers, and breakpoints. [standard]

A guarded direct-core run seeds eight reset groups and nine retained groups. All 17 match the source model. It records PC=0x8000, SP=0xFFFF, mapper windows 00/3E/3F/3F, and a retained dynamic timer with 4,321 clocks remaining. Direct internal seeding tests TilEm implementation state, not a physical reset. [confirmed]

TilEm’s forbidden-fetch path does not suppress the opcode. The M1 read raises TILEM_EXC_FLASH_EXEC, returns the byte, and the Z80 executes the complete instruction. The main loop checks the exception afterward and calls tilem_calc_reset. The direct-core fixture maps Flash page 08 at 0x4000 and RAM page 0x40 at 0x8000. Its forbidden instruction is LD (0x8000),A with A=0x5A. The run stops on the exception after reset with PC=0x8000 and AF=SP=0xFFFF, while physical RAM byte 0x100000 contains 0x5A. [confirmed]

The boundary fixture above begins with LD A,pp; reset follows that first instruction, before its marker-store instruction. Its unchanged marker proves only that the second opcode did not execute. It does not prove that TilEm suppresses the forbidden opcode itself.

The direct-core binary SHA-256 is ab0a862b1fbb7f8a09a075fbd0ec61ebb0bab84d12d2a9c2a650813476cc7e5a. The builder requires clean TilEm commit f56ad637d0524ee841dd381be6ecbaf5b8975600 and Git tree 58316afe35d69e69353f0f743698144153051d4a.

Guarded Wabbitemu boundary run

The pinned Wabbitemu core executed the same six-byte ROM markers and 75-byte RAM probes. The native adapter first cold-boots the fixture ROM. It waits until the retail boot establishes 0x08, 0x29, 0x4000, and 0x83FF as the Flash and RAM bounds. It also requires mode 0 and relocked Flash. Every run reached this state after 134,845 instructions and 1,746,999 T-states at 3F:4223. [confirmed] for the pinned emulator run.

The adapter then maps physical RAM page 1 at 0x8000, copies the probe to ram:9D95, verifies the complete copy through the logical mapping, and sets PC=0x9D95. This is a direct emulator-core injection, not an OS variable or UI launch. An execution-violation callback counts the event and calls the same CPU_reset function used by Wabbitemu’s callback-free path. [confirmed] for the harness behavior.

The native core produced these sequences: [confirmed]

PageSequence from ram:9DBDProbe instructionsMarkerOutcome
0707:7FF0, 07:7FF2, return ram:9DC05407returned
0808:7FF0, 08:7FF2, return ram:9DC05408returned
09attempted 09:7FF0; no 09:7FF2 or return; one reset52A0violation reset
29attempted 29:7FF0; no 29:7FF2 or return; one reset52A0violation reset
2A2A:7FF0, 2A:7FF2, return ram:9DC0542Areturned

The probe seeds the marker with A0 immediately before the call. The denied pages therefore show that the marker store at pp:7FF2 did not execute. Page 08 returns while page 09 resets, dynamically distinguishing Wabbitemu’s lower-exclusive interval from TilEm’s inclusive interval. This establishes the pinned emulator’s behavior only. The physical lower edge and violation state remain unmeasured. [confirmed] for Wabbitemu; [hypothesis] for hardware.

The native binary SHA-256 is 07d56ac311cc6726d95f0e76987ce34af8814d07bcf1528f6b25375c083489f2. It was built from pinned Wabbitemu commit 48c2dc0e6d1d87bb5cf9611efbeb0d048b19c422 and source-tree SHA-256 a8a4f97fc7952770bed317b4a477f80345894da38d14fad8f0bf0ee60aae71ba. The derived fixture identities were: [confirmed]

PageFixture ROM SHA-256Probe SHA-256
07ed2372b459cddd89deea6a27d00cd6f757d612c4f63db4feaf134665ad2e78cf87c11964b6cf67624b2eff46e1a962c56f1684dd48db931a5cb68e08c1b84b4e
08b0d32c8f3af1f87c8fce8f7966ab45d588a8ed42ed9ce7708de38b4d7dc57934ddd023d522d301315c0f4929f348499faca08c708e96c1333bf85e32505f9534
097f2443e3aecceaa8c1ad60e0de4e2316caad3d17802ec3a719567a05e25a244cf121bae475d56947bec80090bb3047fab478cc86db4dec897e4161f78df14584
291590ddf2681c3636e119df3759909c43b62a49a9dbc74f5a4f00d6500ae9017df671bdb62e6bad19f33402eb919e70631cf7cc8f00b9f7f52114d052f86cea78
2A1ee90aef8e9795ef56b668ae36560ad6a4c99938055cd0e5763b930b0f585d2ad5f72f96562ef5e96f4ddaa12954548d210650d9ca6bec365f75f1bb6f3bad1b

Wabbitemu reset scope

Wabbitemu’s CPU_reset is a CPU and mapper reset, not a complete hardware reinitialization. The function writes only these groups for the TI-84 Plus model: [standard]

GroupReset value
PC, SP0x0000
Interrupt mode1
Interrupt, EI block, IFF1, IFF2, HALT, and I/O flagscleared
Prefix statezero
Ports 0x27 and 0x28 remap countszero
RAM execution bounds0x00000x03FF
Mapper windowsboot page 3F, Flash page 00, Flash page 00, RAM page 00
Boot-map and page-0-change flagscleared
Legacy protected_page[4] array and selected groupzero

The function retains the other directly seeded state. This includes general and alternate CPU registers, I, R, bus state, RAM contents, the Flash command state, Flash lock and bounds, protection mode and selectors, timer frequency and T-states, delay registers, MD5 state, standard and programmable timers, RTC, keypad and ON state, raw link, link assist, USB, GPIO, and the LCD object. [standard]

The frontend calc_reset calls CPU_reset and then the LCD reset callback. The LCD callback clears display memory and its queue, disables output, zeros the coordinates and last-read latch, selects 8-bit words, and sets contrast 32. It retains the LCD access timestamp and port-0x2F delay field. No other peripheral reset follows. [standard]

A guarded initialized-core run seeds 14 component groups before calling CPU_reset. All 14 retain their seeded values. The reset mapping is 3F/00/00/00, the RAM bounds are 0x00000x03FF, and the frontend-equivalent call produces the LCD state above while retaining last_tstate = 654321 and lcd_delay = 61. Direct state seeding isolates field retention; it does not reproduce a physical warm or cold reset. [confirmed] for the pinned Wabbitemu run.

An execution violation calls CPU_reset inside CPU_opcode_fetch, then continues the same CPU_step. A seeded FLASH_PROGRAM violation ends the command state, fetches boot bytes 3E 07, executes LD A,0x07, and finishes at PC=0x0002 after seven T-states. A seeded FLASH_ERROR violation fetches opcode 0x3E, returns status byte 0xE0 for the immediate read, clears the error flag, executes LD A,0xE0, and also finishes at PC=0x0002. The command step remains FLASH_ERROR in the second case. This is an emulator control-flow quirk, not a physical reset model. [confirmed]

The reset-retention manifest guards OS 2.55MP ROM SHA-256 7d9a7d96d89fc552ebee6afdbdd011fdc6047be9c16d308245dff07eb1f7bd6d and native-binary SHA-256 386be74e738f2a0f9ad17f12bae4cd44994b5a73835ab10d488c7b8232afd87e.

RAM instruction fetches

TilEm x4 reduces a physical RAM byte offset $a$ to a 1 KiB chunk address. For port-0x21 mode $t = 0,1,2,3$, it computes: [standard]

$$ \begin{aligned} M_t &= (\mathtt{0x8000} \ll t) - \mathtt{0x400} \\ m &= a \mathbin{\&} M_t \\ \mathit{allowed} &= \mathtt{port25}\cdot\mathtt{0x400} \le m \le \mathtt{port26}\cdot\mathtt{0x400} \end{aligned} $$

The mask clears the low ten address bits, so the comparisons operate at 1 KiB granularity. The upper comparison is inclusive. A port-0x26 value of 0x20 therefore includes the complete 0x80000x83FF chunk whenever $M_t$ can produce 0x8000. [standard]

Coverage with the boot bounds

The table covers the TI-84 Plus’s eight physical RAM pages. Page notation uses the ordinary selector spelling 0x800x87. “Chunk 0” means the first 1 KiB at page offset 0x00000x03FF. [standard]

ModeTilEm maskRepetitionFully executable pagesPartly executable pages
00x7C0032 KiB0x81, 0x83, 0x85, 0x87none
10xFC0064 KiB0x81, 0x85chunk 0 of 0x82 and 0x86
20x1FC00128 KiB0x81chunk 0 of 0x82
30x3FC00256 KiB0x81chunk 0 of 0x82

Modes 2 and 3 have the same coverage within the first 128 KiB. Their masks diverge only when an address reaches the next 128 KiB. Reversing ports 0x25 and 0x26 denies every RAM chunk in TilEm because the inclusive interval is empty. [standard]

Wabbitemu comparison

Wabbitemu’s source comments list the fully executable page pattern implied by the four modes. Its executable predicate contains this expression: [standard]

if (bank->page & (2 >> (prot_mode + 1)))
    return TRUE;

For mode 0, the shifted value is 1, so every odd RAM page returns early. For modes 1–3, the shifted value is zero. Those modes fall through to one global address comparison instead of applying a repeating mask. The default bounds produce this actual coverage: [standard]

ModeWabbitemu fully executable pagesPartly executable pages
00x81, 0x83, 0x85, 0x87chunk 0 of 0x82
10x81chunk 0 of 0x82
20x81chunk 0 of 0x82
30x81chunk 0 of 0x82

The extra mode-0 chunk comes from Wabbitemu’s inclusive global range check, not its page shortcut. Its mode-2 and mode-3 coverage happens to match TilEm within 128 KiB under these bounds. The source arithmetic still differs for other RAM sizes and bound values. These implementation results are not ASIC evidence.

Wabbitemu stores ram_lower and ram_upper as 16-bit unsigned fields. Its port handlers multiply the 8-bit port value by 0x400 before assigning those fields. Values 0x400xFF therefore wrap modulo 0x10000. For example, writing 0x40 to both ports produces the implemented interval 0x00000x03FF, not 0x100000x103FF. TilEm retains the wider products. [standard] for the emulator sources; [hypothesis] for physical high-value behavior.

Guarded RAM execution runs

Two guarded runners exercise the predicates through opcode fetches. Both use a six-byte target routine that stores a case marker at 0x8478 and returns. The source program reads back all six target bytes before it seeds 0x8478 with 0xA0 and calls the target. A returned case records its case marker. A denied case retains 0xA0, omits the target store at logical target +2, and records one reset. [confirmed] for the pinned emulator runs.

The Wabbitemu adapter cold-boots the exact ROM through the retail protection sequence. It then configures the requested RAM fields and injects the guarded source at physical RAM page 1, ram:9D95, plus the target routine. This is a direct emulator-core injection. Every default-bound case reached the baseline at 3F:4223 after 134,845 instructions and 1,746,999 T-states. Returned cases executed 47 injected instructions; denied cases reset on instruction 45. [confirmed]

The TilEm runner changes only the immediate byte at 3F:41D6 when it selects a nonzero mode. Mode 1 changes that byte from 0x00 to 0x10; the derived ROM SHA-256 is 47b38fa0fd747529dea85d4fe54d24bafdadeee29c8ade82014f4452ef52699f. The OS launches a self-installing assembly program through the normal variable and UI path. The program writes the marker through data accesses before the guarded call. [confirmed]

The runtime comparison produced these boundary results: [confirmed]

ModePhysical targetTilEmWabbitemuPredicate detail
0page 0x82, offset 0x03F0violation resetreturnedWabbitemu includes page-2 chunk 0 through its global range; TilEm’s mode-0 mask maps it below the lower bound
0page 0x82, offset 0x0400violation resetviolation resetfirst target in chunk 1
1page 0x82, offset 0x03F0returnedreturnedtarget lies wholly inside chunk 0
1page 0x82, offset 0x0400violation resetviolation resetfirst target in chunk 1
1page 0x85, offset 0x3FF0returnedviolation resetTilEm repeats the full-page window after 64 KiB; Wabbitemu uses one global range
1page 0x86, offset 0x03F0returnedviolation resetTilEm repeats the page-2 upper chunk after 64 KiB; Wabbitemu uses one global range

The TilEm target fetch occurs seven clocks after each CALL. Allowed marker routines return 44 clocks after the call. Denied targets reset seven clocks after the attempted fetch. TilEm completes the first target opcode during that interval. Wabbitemu’s callback records the violation and invokes the same CPU_reset function as its callback-free path. Timing between the two runners is not compared because the Wabbitemu harness reports instruction counts, not a TLMT clock trace.

The other Wabbitemu cases cover all four modes under the boot bounds: [confirmed]

ModeReturned targetsViolation-reset targets
0page 0x81 offset 0x3FF0; page 0x82 offset 0x03F0; page 0x83 offset 0x3FF0page 0x82 offset 0x0400; page 0x84 offset 0x0000
1page 0x81 offset 0x3FF0; page 0x82 offset 0x03F0page 0x82 offset 0x0400; page 0x85 offset 0x3FF0; page 0x86 offset 0x03F0
2page 0x81 offset 0x3FF0; page 0x82 offset 0x03F0page 0x82 offset 0x0400; page 0x83 offset 0x0000
3page 0x81 offset 0x3FF0; page 0x82 offset 0x03F0page 0x82 offset 0x0400; page 0x83 offset 0x0000

A separate Wabbitemu run configured both chunk ports to 0x40. The native report recorded the wrapped bounds 0x00000x03FF. Mode-0 page 0x80, offset 0x0000, returned; page 0x82, offset 0x0000, reset. The first result comes from the wrapped global interval. The second lies outside it and has no odd-page shortcut. [confirmed] for Wabbitemu; [hypothesis] for hardware.

The pinned identities for the cross-emulator cases were:

Mode and targetTilEm probe SHA-256TilEm trace SHA-256Wabbitemu probe SHA-256
mode 0, 0x82+0x03F0a0853c1ea1f900a7b8b4c26d1091e5696265b993a214d20836c182743ae330c30bde946b277f0c3fe7c6040931ea1df6c265aa11d4fad6394cfeea5955dfe18b783d757f767b0d89df7c68881413e0cb47a6652da2c94829f90972e3eb2a64cb
mode 0, 0x82+0x04009f82e2df6960cc6e0658c1db4b19e755bd544105be73fa476f8b17e34527a11666853fc88e6a934577f1e70916df012f77e02ea7ab7e77b2caa4a9cda0a5e602d068ef192978d9fbddada76d6f55320263315ad2b3344e64751cc33f9aa58d5f
mode 1, 0x82+0x03F07ef4086cf9fe4e938215cf3592435d13fcd0874a239e58fbdc50c78719531ff231601907572c2060adaa76d2031a138e225a5e73bdc8f84c50505178e1e871eef0843119d9a19ab5f5578f61160a8cb5ce723d12ed2b3ea13a5d9cdfc8857ce7
mode 1, 0x82+0x04001531839a1d11895ad14ddada9974da4c307eb1ba09b5b660b3f1858bf2659a7f8a9ded0bb3479a86587579ae647a4c2604689f3f0dea9beba96b7fb1117415a9d2ba9523c63f7645f61cfad45677b9afc3bc47a64b251f2d8f3daafadc8525b0
mode 1, 0x85+0x3FF04996653aca01db9c7ce67d7a367810cf4a07ee42fa65991920232a81d6b3074ca76f11d993e1b5cf6e19feca1af4321637670a9f1c7f5232159096ea2ba0839f857a38aa6ccf163ebac779c775d812e9ad9df844c870a9bbc42fdad7932da959
mode 1, 0x86+0x03F08851278b8f3b54b7a7e7a0ff206b03f98bbec6528a3d705598a054dcf5f501a0e976f06992278db20f1b00c6faf2caf4140d7635de1af180f6491d103e0719ce3c989491f4031cfac972cd72af55824d6a1ca8f384315ac3fbbd5ed0ad15a3c0

The TilEm assembly source SHA-256 is eafb257ff0190bfa8417269c981ab0ff94508e92a31b716c01814b0afe4bb2ca. The Wabbitemu assembly source SHA-256 is e21fe4374eec887b4877ad27ccb77dd458dcb38c24e48cb167ebb1438fc8d43c. These runs establish emulator behavior only. Physical mode repetition, high-value wrapping, chunk endpoints, and reset state remain open measurements.

Port 0x24 larger-device extension

WikiTI assigns two high Flash-bound bits to port 0x24: bit 0 extends port 0x22, and bit 1 extends port 0x23. TilEm’s color xc model uses exactly those two bits when it compares pages. Its TI-84 Plus x4 model has no port-0x24 case. [standard]

The complete OS 2.55MP ROM scan finds no direct port-0x24 instruction and no access resolved from a nearby literal load into C or BC. Computed accesses across calls or control-flow joins remain outside this static proof. [confirmed]

Port 0x24 is therefore a family extension, not part of the confirmed retail TI-84 Plus initialization path. Wabbitemu registers it on the TI-84 Plus-family device, but its two high-bit assignment expressions lack parentheses around the masked bit before shifting. Both expressions evaluate to zero for an 8-bit bus value under C operator precedence. [standard]

Native protected-register confirmation

The guarded initialized-core probe finds active, protected handlers at every port from 0x22 through 0x26. The reset core reads 0x10/0x30/0x00/0x00/0x00. These are Wabbitemu initialization values, not the retail boot values in the table above. All five writes are rejected while the in-memory Flash lock remains closed. Reads remain active. [standard]

The probe then opens the emulator’s lock directly and seeds the internal Flash bounds as 0x01A5 and 0x02B6. Writes of 0xCC and 0xDD to ports 0x22 and 0x23 produce 0x01CC and 0x02DD, confirming that both low-byte handlers preserve their seeded high bytes. Writing 0xFF to port 0x24 reads back 0xFF but changes the internal bounds to 0x00CC and 0x00DD. The native result matches Wabbitemu’s precedence defect. It does not test a physical larger-device extension. [standard]

The same run writes four edge values to both RAM-bound ports: [standard]

Written bytePort-0x25 readInternal lower fieldPort-0x26 readInternal upper field
0x3F0x3F0xFC000x3F0xFFFF
0x400x000x00000x000x03FF
0x410x010x04000x010x07FF
0xFF0x3F0xFC000x3F0xFFFF

The manifest records the exact ROM and native-binary hashes. Opening the lock and seeding high fields are direct emulator-core operations. This mode does not execute the ROM’s protected-byte sequence or attempt an opcode fetch.

_SetFlashLowerBound

The official bcall name does not match the port written by its body. _SetFlashLowerBound = 80CF maps to 3F:4784, which writes A to port 0x23 — the upper end of the modeled forbidden interval: [confirmed]

3F:4784  nop
3F:4785  nop
3F:4786  im 1
3F:4788  di
3F:4789  out (0x23),a
3F:478B  di
3F:478C  ret

Flash must already be unlocked for either emulator to accept the write. The routine preserves A and leaves interrupts disabled. [confirmed] for the routine; [standard] for the modeled write gate.

Mapping and forced overlays

Execution protection runs after logical-to-physical page resolution. TilEm applies ports 0x27 and 0x28 first, then chooses its Flash or RAM predicate from the resulting physical page. [standard]

Wabbitemu chooses its Flash-versus-RAM branch from the underlying bank. Its RAM path also evaluates the page shortcut before replacing the global address for a forced overlay. The two emulators can therefore disagree when an overlay forces RAM over a Flash-backed window or substitutes RAM page 0 or 1. See Paging. [standard]

The guarded Wabbitemu mapper run places a NOP in forced RAM over an underlying Flash HALT. Independent mode executes the NOP; paired mode disables the overlay and executes the HALT. This confirms the fetched-byte routing through the initialized core. Both underlying Flash pages are permitted by the boot protection bounds, so the run does not distinguish which protection predicate Wabbitemu evaluated. That ordering remains established by pinned source. [standard]

The normal OS boot and homescreen traces leave both overlays disabled, so this difference does not affect those executed paths. [confirmed]

MAME 0.287 omission

MAME’s TI-84 Plus I/O map does not register ports 0x220x28. Its opcode fetch path reads the mapped Flash or RAM without an execution-protection predicate. Port 0x14 records an unlock value, but the paging and Flash-write paths do not consult it. [standard]

A guarded native run writes patterns to ports 0x220x28; all seven ports still read zero. With port 0x21 = 0x33, writes CC DD AA 10 20 to 0x220x26 also read back as five zero bytes while a 50-T-state loop continues executing from RAM page 0. This covers one allowed-by-absence fetch path. It does not emulate any boundary or violation response. [standard]

MAME therefore cannot test any boundary or violation described on this page. The driver is marked MACHINE_NOT_WORKING, so this omission is a driver limit, not evidence that the physical ASIC lacks execution protection. See Flash memory for the resulting Flash-command behavior. [standard]

Reproducing the models

tools/execution_protection.py contains side-effect-free predicates and RAM coverage enumeration. The focused CLI prints both emulator results:

$ python tools/describe_execution_protection.py flash
Flash bounds 0x08-0x29
page 0x07: TilEm=allow Wabbitemu=allow
page 0x08: TilEm=deny Wabbitemu=allow
$ python tools/describe_execution_protection.py ram --compare-wabbitemu
RAM chunks 0x10-0x20
mode 0 TilEm-mask=0x7C00
  page 0x80: TilEm=- Wabbitemu=-
  page 0x81: TilEm=all Wabbitemu=all

Use --json for machine-readable output. Custom --lower, --upper, --mode, and --ram-pages values expose boundary cases without modifying an emulator.

The guarded trace runner builds all four boundary fixtures in a fresh output directory, runs them, and rejects a classification that disagrees with the TilEm predicate:

probe_parent=$(mktemp -d)
nix develop -c python tools/run_execution_protection_probe.py \
  --tilem "$TILEM" --output-dir "$probe_parent/run" --json

tools/execution_protection_fixture.py holds the exact-ROM patching, machine-code validation, reusable assembler entry point, packaging, and trace classifier. The CLI retains each derived ROM, program pair, log, trace, and a hash-complete manifest.json in the requested directory.

The direct reset probe supplies the missing single-opcode control. Its build and run commands are under “Reset and execution exception” in the repository’s tools/dynamic-tracing.md. The shared tools/tilem_core.py library validates the source tree and runs the binary. tools/tilem_reset.py parses the native report and checks the reset and retention vectors against the source model.

The Wabbitemu CLI uses the same fixture library and adds page 09 to distinguish the two lower-edge predicates:

wabbit_probe_parent=$(mktemp -d)
nix develop -c python tools/run_wabbitemu_execution_probe.py \
  --binary "$wabbit_tmp/wabbitemu-headless" \
  --output-dir "$wabbit_probe_parent/run" --json

It refuses an existing output directory. Each native report must contain the boot-register snapshot, exact injection mapping, call and target visit counts, marker value, reset count, fixture hashes, and native-binary hash.

The RAM runners accept repeatable MODE:PHYSICAL_PAGE:PAGE_OFFSET targets. Their defaults cover the cross-emulator disagreements and all Wabbitemu modes:

tilem_ram_parent=$(mktemp -d)
nix develop -c python tools/run_tilem_ram_execution_probe.py \
  --tilem "$TILEM" --output-dir "$tilem_ram_parent/run" --json

wabbit_ram_parent=$(mktemp -d)
nix develop -c python tools/run_wabbitemu_ram_execution_probe.py \
  --binary "$wabbit_tmp/wabbitemu-headless" \
  --output-dir "$wabbit_ram_parent/run" --json

The Wabbitemu CLI also accepts custom --lower-chunk and --upper-chunk values. The 0x40 wrap case is reproducible with:

wrap_parent=$(mktemp -d)
nix develop -c python tools/run_wabbitemu_ram_execution_probe.py \
  --binary "$wabbit_tmp/wabbitemu-headless" \
  --output-dir "$wrap_parent/run" \
  --lower-chunk 0x40 --upper-chunk 0x40 \
  --target 0:0:0 --target 0:2:0 --json

protected_port_parent=$(mktemp -d /tmp/ti84-protected-port.XXXXXX)
nix develop -c python tools/run_wabbitemu_protection_port_probe.py \
  --rom tools/rom.bin \
  --binary "$wabbit_tmp/wabbitemu-headless" \
  --output-dir "$protected_port_parent/run" --json

The boot bytes can be recovered independently:

$ python tools/disassemble_rom.py 0x3f --start 0x41d5 --end 0x4206
$ python tools/analyze_rom_io.py 0x21 0x22 0x23 0x24 0x25 0x26 --summary

Flash write-disable bcall [confirmed]

_FlashWriteDisable = 0x4F3C, body 3C:66D5, controls Flash programming, not the execution-protection bounds described above. After four padding NOPs, it saves AF, clears A, executes DI, selects IM 1, writes 0x00 to port 0x14, executes another DI, restores AF, and returns. The intervening OR A makes its JP NZ,ram:0000 unreachable on this path.

The body restores the caller’s AF but deliberately leaves maskable interrupts disabled. Callers that require interrupts must re-enable them after the bcall; they must not infer interrupt restoration from the saved AF. A controlled trace reaches the body once, observes OUT (0x14),A with A = 0x00, returns to the fixture, and then executes an explicit EI. The reduced result is in tools/data/community-bcall-semantics.csv. [confirmed] under TilEm; the physical Flash gate was not written on hardware in this test.

Community execution techniques

Crabcake’s original source uses two model-specific methods. On a 6 MHz TI-83 Plus it uses a Flash-unlock exploit, locates a protected OUT (0x16),A routine on Flash page 0x1F, and calls it with 0x00 or 0x07. Separate exploit code changes the port-0x05 mapping. On the TI-83+SE/TI-84+/SE family it does not rewrite ports 0x25 or 0x26. With interrupts disabled, it swaps all 0x4000 bytes between physical RAM pages 0x80 and 0x83, then maps page 0x83 into bank C. Code above 0xC000 remains at the same CPU address while residing on an executable odd physical page. The Crabcake release source contains both paths. [confirmed]

The TI-84-family cleanup restores port 0x05 to an assumed normal value, unconditionally enables interrupts, and has no error handler around the swap. These operations are explicit in the release source. [confirmed] The release does not distinguish the later 48 KiB alias model. That hardware remains unsupported without a physical probe.

zStart 1.3.013 implements its Execute >C000 option as a persistent policy. Its menu toggle first writes the assumed retail values 0x10 and 0x20 to ports 0x25 and 0x26, regardless of the new option state. If the option is enabled, later configuration and ON-script paths call unlockC000, which writes 0x00 and 0xFF after unlocking Flash writes. This is not a per-launch save/restore wrapper. The zStart release source confirms this control flow. [confirmed]

The Swords 2 source release contains FULLRENE.8xv, a 229-byte AppVar payload with the Axiom DE C0 signature. Both embedded command bodies contain this sequence: [confirmed]

    LD A,0x10
    JR NC,+1
    XOR A
    OUT (0x25),A

The incoming carry therefore selects 0x10 or 0x00 for port 0x25, and neither command body writes port 0x26. One command immediately calls _FlashWriteDisable and returns. The other restores port 0x06 from the stack before the same bcall and return. [confirmed] The artifact does not establish a hardware model matrix or behavior after an OS error. Those details remain [hypothesis].

tools/data/execution-protection-observations.csv records the emulator and physical-probe classifications in the preceding sections. It does not classify Crabcake, zStart, or Fullrene.

Resolved findings and open hardware tests

  • The boot writes 00, 08, 29, 10, and 20 to ports 0x21, 0x22, 0x23, 0x25, and 0x26 through protected byte sequences. [confirmed]
  • _SetFlashLowerBound writes port 0x23, despite its official name. [confirmed]
  • TilEm denies the inclusive Flash interval. Four guarded TilEm traces execute pages 07 and 2A and reset on attempted fetches from pages 08 and 29. Wabbitemu allows its programmed lower page: guarded native runs execute pages 07, 08, and 2A and reset on attempted fetches from pages 09 and 29. [standard] for the source comparison; [confirmed] for the pinned emulator runs.
  • TilEm applies a repeating RAM mask and inclusive 1 KiB chunk bounds. Wabbitemu’s modes 1–3 omit the intended page shortcut and its 16-bit fields wrap high chunk values. Guarded runs exercise the mode-0 extra chunk, both mode-1 repetition disagreements, the common chunk edge, all four Wabbitemu modes, and the 0x40 wrap case. [standard] for source behavior; [confirmed] for the pinned emulator runs.
  • The retail ROM has no statically resolved port-0x24 access. [confirmed]
  • jsTIfied implements page-level Flash and RAM execution groups, but its stored ports 0x25 and 0x26 do not affect instruction fetches. [standard]
  • A guarded initialized-core Wabbitemu run verifies the common protected-write gate across ports 0x220x26, the port-0x24 high-field clearing defect, and 16-bit RAM-bound wrap at 0x40 and above. [standard]
  • A guarded initialized-core Wabbitemu run confirms forced-RAM fetch routing in independent mode and underlying-window fetch routing in paired mode. It does not dynamically distinguish the Flash-versus-RAM protection predicate. [standard]
  • Wabbitemu’s low-level reset retains most peripheral and Flash state. Its frontend adds only an LCD reset. A guarded initialized-core run verifies all 14 seeded retention groups and shows that an execution violation continues the interrupted CPU_step through one boot instruction. [standard] for the source model; [confirmed] for the pinned run.
  • TilEm’s full reset reinitializes eight CPU, peripheral, and ASIC groups while retaining memory plus selected Flash, link, RTC, scheduler, and debugger fields. A forbidden opcode completes before that reset. A guarded direct-core run verifies the reset inventory and a surviving RAM-store side effect. [standard] for the source model; [confirmed] for the pinned run.

Physical tests must determine whether page 0x08 executes, what exception or reset state follows a violation, and whether lower-greater-than-upper disables each protection range. The read-only physical fetch suite prepares the retail-mode Flash edge and RAM chunk tests. Tests should also sweep all 1 KiB RAM boundaries in all four modes, repeat them with ports 0x27 and 0x28 active, and record the register state after warm and cold resets. Until then, emulator agreement is only a test oracle for emulator behavior.

Sources

SourceUse
OS 2.55MP and boot 1.03 ROM, especially boot_execution_protection_init and _SetFlashLowerBoundprotected writes and bcall body
TilEm calcs.c, z80.c, x4_init.c, and x4_memory.cfull reset sequence, TI-84 Plus reset fields, Flash and RAM fetch predicates, and post-opcode exception handling
TilEm x4 I/O model at f56ad63protected register writes and mask updates
Headless TilEm fork at 8da54573ac49fe271fa22c60924b4c6a7cb9639fboundary execution traces; binary SHA-256 1c1f7dbe04fe074c2b9aca1657d0eb5ac5cfd1f7cbd480725eb7fb39b8126f33, x4_memory.c SHA-256 ddaa1e45330e3e4ad49486bd5c3675a0a0dff01bfda4d01817ba3387e309ac89
TilEm xc memory model at f56ad63port-0x24 high-bound bits
Wabbitemu core.c at 48c2dc0Flash and RAM fetch predicates, CPU_reset, and execution-violation control flow
Wabbitemu calc.c and lcd.cfrontend reset scope and LCD reset fields
Wabbitemu device.c at 48c2dc0global protected-port write gate
Wabbitemu 83psehw.c at 48c2dc0port handlers and port-0x24 implementation
MAME ti85.cpp and ti85_m.cpp at mame0287absent execution-protection ports and unused Flash-unlock state
jsTIfied deployed 20170706a artifact and readable mirrorprotected writes, page-level run_lock, violation reset, and unused stored RAM-bound ports
WikiTI port 0x22, 0x23, 0x24, 0x25, and 0x26public register descriptions, treated as secondary evidence
Crabcake release archive, SHA-256 84f6660c86f715e09e03637b19df47abe46b86906ed34791bc4281959186f71e6 MHz protected-port path and TI-84-family page-swap path
zStart 1.3.013 release archive, SHA-256 7a1b7c69c85030b412bb6ea11ae71ac608b9882a9de3ab7dbef1faf69519c5e9persistent Execute >C000 configuration and ON-script restoration
Swords 2 source release, archive SHA-256 830878e3449221664b85eb3996992ad0f8b46b7e57183c337930b7e78e5a3397; FULLRENE.8xv SHA-256 327ea2ce2a603febc46490d9758cffc12c9fc926fc1d773a0d53a5ccdf5d4ec3original Fullrene Axiom command bodies