65816-llvm-mos/tests/coremark/README.md
2026-09-29 17:22:52 -05:00

4.8 KiB
Raw Blame History

CoreMark — EEMBC's standard embedded benchmark

CoreMark 1.0 ported to the W65816 / Apple IIgs target. Source is vendored under coremark-src/ from github.com/eembc/coremark.

CoreMark exercises three distinct algorithm families:

  1. Linked list traversal + insert/sort (core_list_join.c)
  2. Matrix init + multiply (core_matrix.c)
  3. State machine processing a string (core_state.c)

…plus utility code (CRC, RNG) in core_util.c. Total ~2000 LOC.

This is the embedded benchmark vendors publish CoreMark/MHz scores against (Cortex-M0, AVR, RISC-V, ...). It's a useful cross-platform sanity check on our backend's code-quality.

Files

  • coremark-src/ — vendored EEMBC source (read-only)
  • core_portme.h / .c — W65816 porting layer (timing, malloc, printf bridge)
  • build.sh — compile the 5 core .c files + portme
  • runCoreMark.sh — build + link + run under MAME

Building

bash tests/coremark/build.sh --layer2

--layer2 enables -mllvm -w65816-dbr-safe-ptrs (smaller, faster pointer code; correct here because CoreMark only touches malloc/static- array memory). It is optional: CoreMark plus the runtime it pulls in (printf, soft-float) no longer fits the ~44 KB below the IO window at 0xC000 either way, so runCoreMark.sh links it multi-segment like the Lua test (--segment-cap, extra segments at bank 4+) and runs it through scripts/runMultiSeg.sh.

Default iteration count is 3. CoreMark only validates a run of at least 10 seconds, and one iteration takes about 10.8 s of emulated time (2026-09), so 3 leaves room for the compiler to get faster. Override for longer runs:

ITERATIONS=10 bash tests/coremark/runCoreMark.sh

Running

bash tests/coremark/runCoreMark.sh --layer2

This is a correctness gate, not just a benchmark. core_portme.c sends CoreMark's console output to $02:6000 and takes its clock from a millisecond counter at $02:5010 that the MAME harness (scripts/runMultiSeg.sh) updates every frame; bare metal runs with IRQs off, so libc's VBL clock() never moves. The script fails unless CoreMark reports "Correct operation validated" (its own CRC checks plus the 10-second rule) and the seed, list, matrix, state and final CRCs match the values a host build of the same sources prints. CoreMark's own "Total time" and "Iterations/Sec" are the score.

The run ends with 0xC0DE written to $025000 (0xBEEF there means it started but never finished); elapsed ticks are at $025002.

Note: running CoreMark under MAME inside this project's restricted shell crashes MAME (same SIGSEGV as Lua's full interpreter run — see feedback_lua_compile_test.md). The build produces a valid binary; the run only works in an unrestricted shell. Workaround: copy coreMark.bin out of the sandbox and run with the same runInMame.sh invocation directly.

Size vs Calypsi (5 core files, ITERATIONS=1, PERFORMANCE_RUN)

File Ours (L2+threshold=50) Calypsi 5.16 Ratio
core_list_join.o 10,008 9,073 1.10×
core_main.o 11,588 19,772 0.59×
core_matrix.o 10,660 11,078 0.96×
core_state.o 7,256 9,944 0.73×
core_util.o 3,156 4,631 0.68×
TOTAL 42,668 54,498 0.78×

We beat Calypsi by 22% on CoreMark overall. (Since the inline- threshold dropped from 75 to 50 target-wide, core_matrix.o improved from 1.37× → 0.96× by no longer inlining 5 nested-loop helpers.)

Notes on the porting layer

  • ee_u32 is unsigned long (not unsigned int — on W65816 int is 16-bit; long is 32-bit). CoreMark depends on 32-bit ee_u32 for CRC and timing math.
  • MEM_METHOD = MEM_STATIC — a single 2 KB static array in BSS. Avoids dynamic alloc and the resulting heap-management overhead.
  • start_time / stop_time use clock() which returns the 60 Hz VBL counter. EE_TICKS_PER_SEC = 60.
  • HAS_FLOAT = 1 — CoreMark uses double precision for the score calculation; our soft-double handles it.
  • MULTITHREAD = 1 — single-context. The IIgs doesn't have threads.

Comparing builds

Lua and CoreMark together cover roughly disjoint code patterns:

Pattern Lua CoreMark
VM dispatch yes (luaV_execute 30+ case switch) no
Recursive descent parsing yes (lparser.c) no
String + hash table yes no
Linked-list traversal + sort (small) yes
Matrix init + multiply no yes
State machine (JSON tokenizer in smoke) yes (formal CoreMark state)
CRC yes (in smoke) yes
Recursion-heavy yes no

So they complement each other for backend coverage. Both now compile to under-or-near Calypsi size with the standard Layer 2 + threshold=75 config.