Architecture
The TI-84 Plus CE emulator is designed as a cross-platform system with a clean separation between the emulation core and platform-specific UI code. This architecture enables the same emulator to run on web, Android, and iOS with minimal platform-specific code.System Overview
Dual Backend Design
The mobile apps (Android & iOS) and web app are designed to work with two interchangeable emulator backends:Rust Core (Default)
Our from-scratch implementation written in Rust:- Fully custom - No external emulator dependencies
- Small binary - WASM is ~96KB gzipped for web
- Fast - Optimized Rust compiled to native code or WASM
- Platform-agnostic - Zero OS dependencies, works everywhere
CEmu Backend (Reference)
CEmu is the established open-source TI-84 Plus CE emulator:- Battle-tested - Years of development and bug fixes
- Reference implementation - Used for parity testing
- Known-correct behavior - When our Rust core differs, CEmu is usually right
Both backends implement the same C API defined in
core/include/emu.h, allowing apps to switch between them at build time without any code changes.Why Two Backends?
The dual backend design serves several purposes:Parity Testing
Compare our Rust core behavior against CEmu to verify correctness. Any differences reveal bugs in our implementation.
Bug Investigation
When something breaks, switch to CEmu to determine if it’s our bug or a ROM/hardware quirk that CEmu also exhibits.
Feature Development
Test new UI features (touch input, menus, file loading) before Rust implementation catches up with all peripherals.
Performance Baseline
Compare frame rates and responsiveness between backends to identify optimization opportunities.
Rust Core Structure
The Rust core (core/) is organized into several modules that mirror the TI-84 Plus CE hardware architecture.
Module Organization
Fromcore/src/lib.rs:6-32:
Key Components
CPU (cpu/)
CPU (cpu/)
The eZ80 CPU implementation with:
- Full instruction set - All Z80 and eZ80 opcodes
- ADL mode - 24-bit addressing extension
- Cycle-accurate timing - Matches hardware cycle counts
- Flag computation - Accurate S, Z, H, P/V, N, C flags
MLT, LEA, and special indexed load/store operations.Memory (memory/)
Memory (memory/)
Memory subsystem implementations:
- Flash - 4MB non-volatile storage (sectors 0-255)
- RAM - 256KB system RAM
- VRAM - Video RAM for LCD (320x240x16-bit)
- Port Memory - Memory-mapped I/O registers
Bus (bus/)
Bus (bus/)
Address decoding and memory routing:
- 24-bit address space - Full eZ80 addressing
- Region detection - Routes reads/writes to correct subsystem
- Wait state handling - Adds extra cycles for flash access
- I/O event tracking - Records peripheral operations for debugging
Peripherals (peripherals/)
Peripherals (peripherals/)
Hardware peripheral implementations:
- LCD Controller - 320x240 16-bit color display at 0xE30000
- Keypad - 8x7 key matrix at 0xF50000
- Timers - 3 general-purpose timers at 0xF20000
- RTC - Real-time clock for date/time
- Interrupts - Interrupt controller at 0xF00000
- Backlight - LCD backlight control
Scheduler (scheduler/)
Scheduler (scheduler/)
Cycle-accurate event scheduling:
- Event queue - Priority queue of pending events
- Cycle counting - Tracks total emulated cycles
- Timer callbacks - Fires events at specific cycle counts
- Peripheral coordination - Schedules LCD refreshes, timer ticks, etc.
Emulator (emu/)
Emulator (emu/)
Main orchestrator that ties everything together:
- Initialization - Sets up CPU, memory, peripherals
- Execution loop - Runs CPU for requested cycles
- Frame rendering - Converts VRAM to ARGB8888 framebuffer
- State management - Save/load emulator state
- File injection - Loads .8xp/.8xv programs into flash
Memory Map
The TI-84 Plus CE uses a 24-bit address space (16MB addressable). Here’s the complete memory layout:| Address Range | Size | Description | Wait States |
|---|---|---|---|
| 0x000000-0x3FFFFF | 4MB | Flash ROM | 2-3 cycles |
| 0x400000-0xCFFFFF | 9MB | Unmapped (returns 0xFF) | 0 |
| 0xD00000-0xD3FFFF | 256KB | System RAM | 0 |
| 0xD40000-0xD657FF | ~150KB | Video RAM (VRAM) | 0 |
| 0xD65800-0xDFFFFF | ~2.4MB | Unmapped | 0 |
| 0xE00000-0xE0FFFF | 64KB | Control Ports | 0 |
| 0xE10000-0xE1FFFF | 64KB | Flash Controller | 0 |
| 0xE30000-0xE300FF | 256B | LCD Controller | 0 |
| 0xF00000-0xF0001F | 32B | Interrupt Controller | 0 |
| 0xF20000-0xF2003F | 64B | Timers (3x GPT) | 0 |
| 0xF50000-0xF5003F | 64B | Keypad Controller | 0 |
| 0xFF0000-0xFF00FF | 256B | Control Ports (OUT0 access) | 0 |
Flash Organization
The 4MB flash is organized into 256 sectors of 16KB each:- Sectors 0-31 (0x000000-0x07FFFF) - Operating system (TI-OS)
- Sectors 32-255 (0x080000-0x3FFFFF) - Archive (user programs, AppVars)
RAM Layout
The 256KB system RAM is divided into regions used by TI-OS:- Low RAM - Stack, system variables, interrupt vectors
- User Memory - Program variables, lists, matrices
- VAT (Variable Allocation Table) - Tracks variable locations
- Command Shadow - Screen buffer for text mode
VRAM Format
VRAM stores the 320x240 display as 16-bit color values:- Format: 565 RGB (5 bits red, 6 bits green, 5 bits blue)
- Size: 320 × 240 × 2 bytes = 150KB
- Layout: Row-major, left-to-right, top-to-bottom
C API Interface
The emulator core exposes a simple C API defined incore/include/emu.h. This API is used by all platform apps (Android JNI, iOS Swift, web WASM).
Core Functions
Fromcore/include/emu.h:12-48:
Thread Safety
Fromcore/src/lib.rs:54-68:
The framebuffer pointer returned by
emu_framebuffer() is only valid while the mutex is held. Apps should copy the framebuffer data immediately after calling this function.Integration with Apps
Each platform integrates the emulator core differently:Android (JNI)
The Android app uses JNI to call Rust functions:- Rust core compiled to
libemu_core.sofor each ABI (arm64-v8a, armeabi-v7a, x86_64, x86) - Kotlin code loads the library via
System.loadLibrary("emu_core") - JNI wrapper declares
externalfunctions matching the C API - Native thread runs emulation loop, calling
emu_run_cycles()at ~60 FPS - Framebuffer copied to Android
Bitmapfor display
iOS (Swift)
The iOS app links against a static library:- Rust core compiled to
libemu_core.afor target architecture - Bridging header exposes C API to Swift
- Swift code calls C functions directly (e.g.,
emu_create(),emu_run_cycles()) - Dispatch queue runs emulation on background thread
- Framebuffer converted to
CGImagefor SwiftUI display
Web (WASM)
The web app uses WebAssembly:- Rust core compiled to
emu_core.wasmwith wasm-bindgen - JavaScript glue generated by wasm-pack
- React components call WASM functions via imported module
- Web Worker runs emulation loop to avoid blocking UI
- Framebuffer copied to
ImageDatafor canvas rendering
Emulation Loop
All apps follow a similar emulation loop pattern:Build System
The project uses a unified build system with platform-specific scripts.Build Script
FromREADME.md:90-122:
android, ios
Options:
--release- Release build (default)--debug- Debug build--cemu- Use CEmu backend instead of Rust--install- Android: Install APK after build--sim- iOS: Build for Simulator--all-abis- Android: Build all ABIs (default: arm64 only)
Makefile Targets
FromMakefile:7-149, common targets:
What the Emulator Does
FromREADME.md:67-77, the emulator recreates TI-84 Plus CE hardware:
- Loads ROM - The calculator’s operating system (you provide this)
- Executes CPU - Runs eZ80 instructions at ~48MHz emulated speed
- Renders Display - 320x240 16-bit color LCD at 60 FPS
- Handles Input - 8x7 key matrix matching physical calculator
- Emulates Peripherals - Timers, real-time clock, interrupts, etc.
Next Steps
API Reference
Detailed documentation of the C API and Rust internals
Development
Development workflows, debugging tools, and contributing guidelines
Peripherals
Deep dive into LCD, keypad, timers, and other hardware
Testing
Testing strategies, parity testing with CEmu, and trace comparison