Advanced Features and Debugging in Virtual TI (VTI Emulator)Virtual TI (VTI) is a powerful, long-standing emulator for Texas Instruments graphing calculators (primarily the TI-⁄84 family, TI-89/Titanium, and compatible models). While many users know VTI for basic emulation and running programs, it also offers a rich set of advanced features and debugging tools that can greatly accelerate development, reverse engineering, testing, and learning. This article explores those advanced capabilities, practical workflows for debugging calculator programs and OS-level code, tips for maximizing productivity, and common pitfalls to avoid.
Overview of advanced capabilities
Virtual TI’s advanced features fall into several categories:
- CPU and peripheral emulation fidelity — precise modeling of the Z80 and 68k CPU families and their peripherals.
- Breakpoints and single-step execution — fine-grained control over program execution for debugging.
- Memory inspection and modification — view and change RAM/ROM/flash and bank-switching states.
- I/O and port tracing — monitor hardware registers, key matrix, link port, LCD, and interrupts.
- Save states and snapshots — capture and restore emulator state for repeatable tests.
- Scripted automation (where supported) — automate tasks like input sequences or regression tests.
- File transfer and linking — send programs, tokens, or binaries between host and emulated calculator.
Below we examine each of these areas in detail and show how to use them in typical development and debugging scenarios.
Getting started: configuration and builds
Before diving into debugging, make sure you have a VTI build suitable for your target model and development needs.
- Use a version of Virtual TI that matches the target calculator family (Z80 vs 68k). Some community forks provide improved debugging features and better OS compatibility — prefer those for low-level work.
- Configure the emulator to match realistic hardware: set clock speed, LCD model, and link port behavior if options are available. Misconfigured timing or peripherals can make bugs appear or vanish.
- If you’re testing third-party or modified OS images, keep a clean stock ROM image available for comparison. Always keep backups of original ROMs and RAM snapshots.
Breakpoints, single-step, and run control
Breakpoints and single-step execution are the core of interactive debugging.
- Software breakpoints: set a breakpoint at a specific PC (program counter) value or function entry. When hit, the emulator halts and shows registers and memory.
- Memory access breakpoints: stop on read/write to an address or range — very useful for tracking where a variable or hardware register is modified.
- Conditional breakpoints: some builds allow breakpoints that trigger only when a register or memory matches a condition (e.g., when A == 0).
- Single-step modes: step into (step instruction), step over (skip subroutine calls), and run-to-return features speed navigation of code flow.
- Interrupt-aware stepping: ensure the emulator’s debugger can step across interrupt entries and returns, or else you may miss IRQ-driven behavior.
Practical tip: place a breakpoint at the beginning of your program’s entry point to ensure you catch initialization issues before the OS or runtime changes state.
Registers, flags, and CPU state inspection
When halted, inspect the full CPU state:
- General-purpose registers (e.g., Z80’s AF, BC, DE, HL pairs; 68k’s D and A registers).
- Program counter (PC) and stack pointer (SP).
- CPU flags (zero, carry, sign, overflow) and mode bits.
- Bank registers and memory mapping information for banked ROMs/flash.
Use the register view to verify calling conventions, parameter passing, and preserved registers across interrupts and subroutine calls. Compare expected values from your source (or assembly listings) with actual values shown by VTI.
Memory viewers and editing
VTI provides memory viewers that let you inspect RAM, ROM, and mapped device regions in hex and ASCII.
- Navigate logical address spaces and physical banks. For bank-switched calculators, identify which bank is mapped to which address range at runtime.
- Watch specific addresses (watchpoints) to pause execution when values change.
- Edit memory directly to patch values in-place — useful for testing fixes without rebuilding. Be cautious: changing ROM images or checksums can cause unexpected behavior.
Example uses:
- Patch a variable to a large value to force an error path and test handling.
- Replace a problematic routine with a no-op to isolate failure causes.
I/O, peripherals, and device tracing
Calculators interact with peripherals (LCD controller, key matrix, timers, link port). VTI can help trace those interactions.
- Port tracing: log reads/writes to I/O ports and memory-mapped registers. Use this to find which code toggles the LCD contrast, scan keys, or enables interrupts.
- Link port emulation and tracing: monitor link traffic (send/receive) when testing connectivity between calculators or host tools. Some VTI builds support emulation of PC-side link behavior for program transfers.
- LCD and display tracing: view raw framebuffer writes and rendering timing to debug graphical glitches, sprite placement, or DMA issues.
- Timer and interrupt tracing: identify sources of periodic interrupts and confirm interrupt vector addresses.
Practical debugging scenario: if your program sometimes freezes while drawing, enable port and LCD tracing to see if a long-running busy-wait or incorrect status bit polling is responsible.
Logging and trace windows
Turn on instruction or event logging to produce execution traces that you can analyze.
- Instruction trace: record every CPU instruction executed (can be large) for deterministic replay and postmortem analysis. Use filters (e.g., trace only a subroutine) to keep logs manageable.
- Event logs: record hardware events, breakpoint hits, and link transfers.
- Exportable traces: save logs to files for offline analysis or to share with collaborators.
Tip: combine trace logs with source-level listings (with addresses) so you can map trace lines to source code or assembly.
Save states, snapshots, and replayability
Save the full emulator state (CPU, memory, peripherals) to a snapshot file.
- Use snapshots to reproduce bugs reliably: capture the moment just before a fault and reload to step forward repeatedly.
- Keep snapshot sequences for regression testing across emulator versions or code changes.
- If your emulator supports deterministic replay, use it to catch Heisenbugs caused by timing or input race conditions.
File transfer, tokenization, and token-view
For higher-level debugging, transfer programs and data between your host machine and the emulated calculator.
- Send TI-BASIC programs (tokenized or plain) and assembly binaries. Confirm tokenization is correct by comparing with a token viewer.
- Use file utilities to inspect archive contents (APPVAR, ASM archives) from within the emulator or externally.
- When debugging BASIC, stepping through tokenized tokens and inspecting the program counter inside the BASIC interpreter helps find runtime logic errors.
Scripted automation and reproducible tests
Some VTI builds or companion tools allow scripted input sequences or automation:
- Automate keypresses to reach specific UI states, run tests, or reproduce user sequences.
- Combine automation with snapshots for batch regression testing.
- For complex tests, script input and compare framebuffer outputs against expected images.
If your VTI build lacks scripting, consider external tools that simulate link-port transfers or GUI automation to drive the emulator.
Debugging OS routines and ROM code
Advanced reverse-engineering or OS-level debugging requires special care.
- Work with a clean ROM dump and symbol/label maps if available. Maps help you correlate addresses to OS function names. Community projects often publish symbol maps for TI ROMs.
- Set breakpoints inside OS routines, but be aware that halting OS code may interfere with timing-sensitive hardware. Use snapshots to isolate critical moments.
- When modifying OS code (patching ROM/flash), test under many scenarios (cold boot, warm reset) to ensure your changes don’t impair startup sequences or restore mechanisms.
Common pitfalls and how to avoid them
- Timing differences: emulators are rarely cycle-perfect. Bugs that depend on precise timing (race conditions, tight loops) may not reproduce. Use an emulator with configurable clocking or test on real hardware when timing-sensitive.
- Bank mapping confusion: forgetting which ROM bank is mapped leads to inspecting the wrong memory region. Always verify bank registers before changing memory.
- Side effects of editing state: editing registers or memory to “fix” a bug can mask root causes. Use edits to test hypotheses, but revert and reproduce to confirm.
- Log volume: full instruction traces grow quickly and can become unwieldy. Use filters and targeted breakpoints to reduce noise.
Example debugging workflow (Z80 program)
- Load the program into VTI and ensure it appears in the program list.
- Set a breakpoint at the program’s entry (e.g., the program’s token or ASM start).
- Run the program until the breakpoint hits. Inspect registers and stack to confirm parameters.
- Step through initialization code; set watchpoints on key variables or hardware registers.
- If a crash occurs, save a snapshot immediately before reproducing, then replay and single-step into the crash. Record an instruction trace around the fault.
- Use memory editing to try hypothesized fixes quickly; once validated, apply changes to source and rebuild.
Tools and complementary utilities
While VTI is powerful, pairing it with other tools improves productivity:
- Disassemblers and cross-assemblers (e.g., Brass, SPASM, TIASM) to build and inspect code.
- Symbol maps and source-level debugging helpers for identifying OS functions.
- Serial/Link utilities that emulate or monitor TI link communications.
- ROM-diff tools to compare patched images and track changes.
Consider maintaining a small test harness on calc that exercises routines automatically, making regression runs faster.
Final notes and best practices
- Reproducibility is king: use snapshots, scripts, and saved logs to make bugs repeatable.
- Start broad, then narrow: use coarse breakpoints and logging, then add targeted watchpoints and single-step when you have a likely suspect.
- Validate on hardware for timing-dependent bugs. Emulators are excellent for logic and functional debugging but may not catch all hardware quirks.
- Share concise reproduction steps and saved snapshots with collaborators — it greatly speeds diagnosis.
If you want, I can:
- Show a concrete step-by-step example debugging a simple Z80 assembly routine (with screenshots-style step descriptions), or
- Provide sample scripts/commands for automating tests in your VTI build.
Leave a Reply