Skip to content

Headless runner: inspection commands, debug console output, flash cache profiling - #568

Open
tommyb3ll wants to merge 4 commits into
CE-Programming:masterfrom
tommyb3ll:headless-tools
Open

tommyb3ll wants to merge 4 commits into
CE-Programming:masterfrom
tommyb3ll:headless-tools

Conversation

@tommyb3ll

@tommyb3ll tommyb3ll commented Oct 8, 2026 •

Copy link
Copy Markdown

These are the emulator tools I used while speeding up FlowCE (a KhiCAS fork), rewritten for upstream. Three commits, each usable on its own:

  1. headless: peek, poke, keydown, keyup, regs, stats and lcd-dma. Read and write memory, hold a key while inspecting the calculator, see where a program is stuck (registers and IX frames), and read the cycle counters, flash cache misses included.
  2. core: a new opt-in define, DEBUG_CONSOLE_SUPPORT, prints the debug console without DEBUG_SUPPORT, so dbg_printf output can reach cemu-headless and the autotester (on stderr). It is off by default (a commented-out line in core/Makefile, like DEBUG_SUPPORT), so other consumers of the core are unaffected, as calc84maniac suggested.
  3. Flash cache profiling: per-line entry and miss counters (line-profile), an ordered trace of the lines entered for cache simulations (line-trace), and flash-cycles <n> to approximate pre-revision-M flash timing with a revision M ROM.

Everything is documented in headless_protocol.md. Tested on Linux with a TI-84 Plus CE ROM: each command on a running program; the line profile and the trace agree (same number of line entries); flash-cycles 10 makes a KhiCAS computation about 2.4 times slower; dbg_printf output appears on stderr when the core is built with -DDEBUG_CONSOLE_SUPPORT, and not without it.

For what it's worth, on KhiCAS cache misses were about 30% of the cycles, but a cache simulation of its trace showed that even a perfect code layout would remove only about a quarter of them.

Assisted by Claude code (plz don't discriminate just trying to help ;)

Comment thread core/flash.c Outdated
Comment thread tests/autotester/headless_cli.cpp Outdated
Comment thread tests/autotester/headless_cli.cpp Outdated
Comment thread core/mem.c Outdated
tommyb3ll and others added 3 commits October 12, 2026 16:13
For scripted tests and profiling of programs: read and write memory, hold a key
while inspecting the calculator, see where a program is stuck (registers, IX
frames), and read the cycle counters, flash cache misses included, which show
how much time a program loses to the revision M serial flash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Builds without the debugger (cemu-headless, the autotester) drop what programs write to the
debug console ports. With the new opt-in define DEBUG_CONSOLE_SUPPORT (see core/Makefile), they
print it through gui_console_printf / gui_console_err_printf, as the debugger build does when
soft commands are on, so test scripts can read a program's dbg_printf output. It is off by
default, so other consumers of the core are not affected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… timing

Since revision M, code runs from serial flash through an 8 KB cache, and a miss
costs about 197 cycles: a large program can spend a third of its time there.
The core can now count, per 32-byte flash line, the entries and the misses
(flash_line_profile), or report every line entered (flash_line_hook), and
cemu-headless exposes both (line-profile, line-trace). flash-cycles gives every
flash read a fixed cost instead, close to a pre-revision-M calculator with n=10.
Measured with them on KhiCAS: misses were ~30% of its cycles, and a cache
simulation of its trace showed that even a perfect code layout would remove
only about a quarter of them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
As suggested in review: #if defined(DEBUG_CONSOLE_SUPPORT) || defined(DEBUG_SUPPORT) instead of
&& !defined(DEBUG_SUPPORT). A build with the debugger now prints what programs write to the debug
console ports through this code too, before the debugger's soft commands are looked at, so
whatever the soft commands setting; opening the debugger and clearing the console are still soft
commands.
@adriweb

adriweb commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

Meat proxying Codex's tests with this PR checked out:

Preserve the software-command setting when handling debug-console writes

The new console handler executes whenever either DEBUG_CONSOLE_SUPPORT or DEBUG_SUPPORT is defined. It intercepts writes to the debug-output addresses, prints the buffered message, and exits the switch with break before reaching the existing debug_get_flags() & DBG_SOFT_COMMANDS check.
So I guess we want this, to handle everything:

#if (defined(DEBUG_SUPPORT) && defined(DBG_SOFT_COMMANDS)) || defined(DEBUG_CONSOLE_SUPPORT))

Other findings:

  • Restarting a trace to the same path can corrupt it. It opens and truncates the new file before flushing the old stream. Reproduced: buffered entries [1,2,3], restart, then [4] produces [4,2,3].

  • Trace write failures silently report success. Both trace-writing paths ignore fwrite and fclose failures. A failed output stream still returns OK line-trace off, undermining the resulting cache analysis.

  • Fixed flash timing breaks the statistics. flash-cycles bypasses read accounting too. During a 100 ms run, total cycles increased by about 4.8 million while flash-reads and flash-delay stayed frozen. Reads and timing should remain accounted for independently of cache simulation.

@adriweb adriweb left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(See PR comment)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants