Run the test battery bare-metal under QEMU on Cortex-M4, M33 and M55 - #17
Conversation
Stage 1 (DspTap side) of docs/audit-fft-and-code-smells.md, Part 10: the embedded legs become real. Until now cmake/arm-cortex-m55-mps3.cmake linked platform files that only exist in MuTap, and CI built the static library with tests off. - platform/: cortexm_startup.c (MuTap's armv8m_startup.c, MSPLIM write guarded by __ARM_ARCH_8M_MAIN__ so it also serves the Armv7E-M M4), mps2_an505.ld and mps3_an547.ld copied, and a new mps2_an386.ld for QEMU's Cortex-M4 board (SSRAM1 4 MB @ 0x0 code, SSRAM2/3 4 MB @ 0x20000000 data, per hw/arm/mps2.c). - cmake/: arm-cortex-m4-mps2.cmake (TAP_DSP_M4_FPU OFF = soft-float, ON = fpv4-sp-d16 hard), arm-cortex-m33-mps2.cmake as MuTap's, the M55 file repointed at platform/. All set TAP_DSP_BARE_METAL (renamed from MUTAP_BARE_METAL); M4/M33 pin TAP_DSP_FFT_CMSIS OFF (no MVE). - tests/: one-shot bare-metal mode (gtest without pthreads/POSIX RE/stream redirection/filesystem, bare_metal_main.cpp with a positive filter of the emulation-sized selection, judged on "TAP_DSP_TESTS_COMPLETE rc=0"). - ci.yml: the compile-only M55 job becomes four QEMU legs, cortex-m4-softfp, cortex-m4f, cortex-m33, cortex-m55 (CMSIS Helium backend ON there, so the FFT backend parity suite runs on a Helium core for the first time), each ending with arm-none-eabi-size on the test binary. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZPTzNxo5Fe4EtpXXKf7Sy
Comment-only: the budget note in tests/bare_metal_main.cpp and the job comment in ci.yml carry the numbers from the first green run (cortex-m4-softfp 88 s, cortex-m4f 62 s, cortex-m33 97 s, cortex-m55 14 s; .text 912868 / 907468 / 906316 / 965636 bytes). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZPTzNxo5Fe4EtpXXKf7Sy
Embedded legs: first measured run (b18ad52, run 35257259009)All four legs and the three hosted legs passed at the first attempt; the Tap House Style workflow (drift + clang-tidy) passed too. Toolchain on the runners:
Notes from the logs:
The numbers are now also in the budget comment of 🤖 Generated with Claude Code https://claude.ai/code/session_019ZPTzNxo5Fe4EtpXXKf7Sy Generated by Claude Code |
tap
left a comment
There was a problem hiding this comment.
Hostile review B (process / downstream)
Scope: ownership, truthfulness of the PR body and the measurements comment against the CI logs, Part 10 compliance, cross-PR hazards against the other seven wave-1 branches, downstream (MuTap) effects, CI hygiene, commit hygiene. Reviewed at 1505e41 against origin/main 5ca3b1c. Hosted suite rebuilt in a private scratch dir with -DTAP_DSP_WERROR=ON: 160/160 pass, 0 warnings. The one-shot harness rebuilt on the host with -DTAP_DSP_BARE_METAL=ON: Running 114 tests from 25 test suites, TAP_DSP_TESTS_COMPLETE rc=0.
0. Ownership — clean
11 files touched: .github/workflows/ci.yml, cmake/arm-cortex-m33-mps2.cmake (new), cmake/arm-cortex-m4-mps2.cmake (new), cmake/arm-cortex-m55-mps3.cmake, platform/cortexm_startup.c, platform/mps2_an386.ld, platform/mps2_an505.ld, platform/mps3_an547.ld (all new), tests/CMakeLists.txt, tests/bare_metal_main.cpp (new), README.md (one Build paragraph + a fenced block replacing the old three-line paragraph). Every one is inside this agent's Part 12 allotment. No header, no root CMakeLists.txt, no docs. Nothing to report.
1. should-fix — the per-leg test count and the "parity suites passed" claims are not in any CI log
The comment says "114 tests from 25 suites per leg" and "all three CertifiedGeometries/* suites ... passed against Ooura on a Helium core". The CI step is ctest --test-dir build-${{ matrix.name }} --output-on-failure, which swallows the child's stdout on a pass. The entire test evidence in each embedded log is one line, e.g. cortex-m55:
2026-09-17T18:12:27.5438253Z 1/1 Test #1: tap_dsp_tests_emulated ........... Passed 14.15 sec
No [==========] Running N tests, no [ PASSED ], no suite names. 114/25 is the host number (I reproduced it); the target count is the same only by construction (the geometry lists in test_fft_backend.cpp:111/192/255 are unconditional Values(...), no #if TAP_DSP_FFT_CMSIS), and the ≥60 guard would not notice a suite compiled out. The comment presents host-derived facts as per-leg observations.
Fix: run the emulated test with ctest -V (one test, roughly 130 lines of gtest output) so the count, the suite list and the [ PASSED ] 114 tests. line are in the log that future ratchets and reviewers read; then the numbers in the bare_metal_main.cpp budget comment and the job comment are measured, not inferred. Until then, reword the comment.
What does check out against the logs, line for line: emulation 87.95 / 61.68 / 97.36 / 14.15 s; .text 912868 / 907468 / 906316 / 965636; .data 2072/2276; .bss 4126780/2094964; arm-none-eabi-gcc (15:13.2.rel1-2) 13.2.1 20231009; QEMU emulator version 8.2.2 (Debian 1:8.2.2+ds-0ubuntu1.18); libnewlib-arm-none-eabi 4.4.0.20231231-2; -- tap::dsp: float32 FFT backend = CMSIS-DSP Helium on the M55 with the nine CMSIS TUs compiled; install-step durations 45/59/35/33 s.
2. should-fix — "zero warnings" is true, but the legs do not build with TAP_DSP_WERROR, and the PR does not say so
Counted over the four downloaded job logs: warning: lines = 0/0/0/0, note: lines = 28/26/26/26 (all GCC -Wpsabi "parameter passing for argument of type 'std::span' changed in GCC 7.1"), Wconversion = 0. So the claim holds. But the hosted jobs configure with -DTAP_DSP_WERROR=ON (ci.yml:26) and the embedded configure line does not:
cmake -S . -B build-cortex-m4-softfp -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_TOOLCHAIN_FILE=cmake/arm-cortex-m4-mps2.cmake -DTAP_DSP_M4_FPU=OFF
Part 10 item 6 asks for -Wconversion on the int16x2_t casts to be checked "under the M4's GCC": today that check is a human reading a log. Fix: add -DTAP_DSP_WERROR=ON to the embedded configure (the logs say it passes today; notes are not affected by -Werror), or state in the PR body that the embedded legs are warning-tolerant by design and why.
3. should-fix — ubuntu-latest while the comments, the PR body and bench.yml all say 24.04
ci.yml:49 runs-on: ubuntu-latest; the job comment says "Ubuntu 24.04's qemu-system-arm (8.2) has mps2-an386, mps2-an505 and mps3-an547"; apt-get install -y -q gcc-arm-none-eabi qemu-system-arm is unpinned. The bench branch pins runs-on: ubuntu-24.04 with an explicit reason ("the plugin header below is pinned to the QEMU this image ships"). When ubuntu-latest moves to 26.04 the QEMU, GCC and newlib change under the recorded emulation times and .text figures with no diff in this repo. Fix: runs-on: ubuntu-24.04, matching bench.yml, and leave the "Toolchain versions" step as the drift detector it already is.
4. should-fix — "clang-format and clang-tidy clean on the new .cpp/.c" is a local claim CI cannot back
The Style workflow tidies the TUs in the hosted compile database (style.yml:26, -DTAP_DSP_BUILD_TESTS=ON, no toolchain). tests/bare_metal_main.cpp is compiled only under TAP_DSP_BARE_METAL and platform/cortexm_startup.c only appears on the cross link line; neither is in compile_commands.json (grep count 0 on a fresh hosted configure). So "the Tap House Style workflow passed too" is true and covers neither new file. clang-format --dry-run -Werror on both passes locally for me too. Fix: say in Verification that tidy on these two files was local, or give bare_metal_main.cpp a hosted OBJECT/tidy-only target so the house style job sees it.
5. cross-PR (wave-level blocker for whoever merges second; a note for this PR)
git merge-tree --write-tree of this branch against each sibling:
| branch | result | overlap |
|---|---|---|
wave1-stage0-counter |
clean | none |
wave1-stage1b-bench (#21) |
clean | README hunk lands correctly after the new paragraph (checked the merged text) |
wave1-stage2a-tests |
CONFLICT tests/CMakeLists.txt |
2a appends after line 40; this PR rewrites lines 40-42 into the if(TAP_DSP_BARE_METAL) block |
wave1-stage3a-traits |
clean | tests/CMakeLists.txt source list only |
wave1-stage5-spectrum |
clean | tests/CMakeLists.txt source list only |
wave1-docs-provenance |
clean | README at line 364, disjoint |
wave1-capi-fft |
clean | README at line 305, disjoint |
Two semantic hazards the textual merge will not flag:
- stage2a adds
tap_dsp_fft_parityandtap_dsp_fft_parity_default_flags, each linked toGTest::gtest_mainwithgtest_discover_tests(...). Under the cross toolchainsgtest_discover_testsruns the binary throughCMAKE_CROSSCOMPILING_EMULATORat build time with--gtest_list_tests; there is no argv on the target and semihosting exit codes are unreliable (this PR's own rationale). All four legs will break the day 2a lands on top of this. 2a needsif(NOT TAP_DSP_BARE_METAL)around both targets, or a one-shot main of its own. - Every new suite from 2a (
test_fft_oracle.cpp,test_fft_rt.cpp), 3a (test_fft_arith.cpp) and 5 (test_spectrum.cpp) compiles intotap_dsp_testsand is silently not run on target, because the filter is positive and the ≥60 guard cannot see an omission. That is the documented design, but 3a is the fixed-point substrate the legs exist for ("Fixed point's gates (Stage 3) run on those legs"). Suggest this PR add one sentence totests/CMakeLists.txtnext totarget_sources(... bare_metal_main.cpp): "adding a test file here does not put it on the target; name its suites inbare_metal_main.cpp", so the 2a/3a/5 owners see it at the merge conflict rather than months later.
Merge position: after stage0 (independent), before every other tests/CMakeLists.txt toucher, i.e. stage0 → #17 → #21 → 3a → 5 → 2a → docs → capi. #21 rebases cleanly (rc 0) and its bench.yml names match this PR exactly: cmake/arm-cortex-m4-mps2.cmake with -DTAP_DSP_M4_FPU=OFF|ON, cmake/arm-cortex-m33-mps2.cmake, cmake/arm-cortex-m55-mps3.cmake; it builds with -DTAP_DSP_BUILD_TESTS=OFF so TAP_DSP_BARE_METAL is inert there. Note that #21's own CI is red until this PR is on main (the M4 toolchain file does not exist on its branch), so it must merge after, not alongside.
6. should-fix (repo settings, not this diff) — nothing is a gate: branch protection on main is off
GET /repos/tap/DspTap/branches/main → "protected": false, required_status_checks.enforcement_level: "off", contexts: []. So: the disappearance of the build-embedded-m55 job name breaks nothing, and none of the seven CI jobs, drift or clang-tidy is required. The plan sentence "until they exist, no sentence in this plan calls an on-target property a DspTap gate" is now stale in both directions: the legs exist, and they still gate nothing in GitHub's sense. Recommend the repo owner require cortex-m4-softfp, cortex-m4f, cortex-m33, cortex-m55, linux-ooura, windows-ooura, macos-vdsp, drift / drift, clang-tidy before wave 2, and that the docs owner (not this PR) rewrite that Stage 1 bullet and Part 10 "What exists to copy" to record the measured numbers and the run id.
For the docs owner as well: CLAUDE.md:69 "The CMSIS-Helium backend is compile-verified under cmake/arm-cortex-m55-mps3.cmake; its runtime parity is the consuming library's job" is false once this merges (CLAUDE.md is outside this PR's ownership; flagging, not asking for an edit here). The README sentence this PR replaced was the same claim and is correctly gone.
7. should-fix — provenance and intent of the third copy are only in the PR body
Verified against /home/user/MuTap at 142361b: mps2_an505.ld and mps3_an547.ld are byte-identical to MuTap's; cortexm_startup.c differs from armv8m_startup.c only in the header, the copyright line, and the #if defined(__ARM_ARCH_8M_MAIN__) guard; the M33 toolchain differs in comments, the _tap_dsp_platform rename and TAP_DSP_BARE_METAL. In-tree provenance is uneven: cortexm_startup.c records the chain (MuTap ← SampleRateTap); mps2_an505.ld carries MuTap's own line "Ported from RatioTap's platform tree ... from SampleRateTap's" (true for MuTap's copy, misleading for one taken from MuTap); mps3_an547.ld has no provenance or license line at all. Nothing in-tree says which of the three family copies is canonical or that the duplication is deliberate pending the taphouse consolidation the plan names (Part 11 "Sharing"). Fix: a four-line platform/README.md (or header lines in the two .lds): copied from MuTap at <sha> on 2026-09-17, MIT, generalized to Armv7E-M here, intended canonical copy for the family / consumers retire theirs when they pin a tree containing it.
Downstream check: MuTap builds DspTap via add_subdirectory(submodules/dsptap) and DspTap's TAP_DSP_BUILD_TESTS defaults OFF when not top-level (CMakeLists.txt:115-117), so the MUTAP_BARE_METAL → TAP_DSP_BARE_METAL rename does not reach MuTap's M33/M55 legs. MuTap's own toolchains still say MUTAP_BARE_METAL; nothing to do here, noted for its Stage 1 PR.
8. nit — actions/checkout@v4 Node 20 deprecation in every job
Every one of the seven jobs ends with ##[warning]Node.js 20 is deprecated. The following actions target Node.js 20 but are being forced to run on Node.js 24: actions/checkout@v4. Pre-existing and also in style.yml (canonical, do not hand-edit). ci.yml is this PR's file: bump to actions/checkout@v5 here.
9. nit — PR body and footer
- Verification still says "I am watching the runs and will fix what they turn up ... Per-leg emulation time and
.textsize will be posted here from the logs once the legs are green." They were; point the body at the comment and the run id. - The body ends with both the "🤖 Generated with Claude Code + session URL" footer and a second
_Generated by ..._line. One is enough. - Comment attributions presented as findings: "Armv8-M security-state overhead in emulation" and "the larger
.textthere is the CMSIS twiddle/bit-reversal tables" are plausible and unmeasured; label them as attributions or drop them (house rule: measured, not remembered).
10. Part 10 items 1-7, for the record
1 platform files: done, with the AN386 map sourced from hw/arm/mps2.c as the plan required. 2 toolchains: done, TAP_DSP_M4_FPU forwarded through CMAKE_TRY_COMPILE_PLATFORM_VARIABLES. 3 harness: done; selection matches the plan's list, budget measured and written into the job comment. 4 matrix: done in MinSizeRel with the size step; the .text ceilings and the size-probe object are deferred and the PR says so (consistent with Stage 1b's "the .text ceilings from Part 10 item 4 live in the same job"). 5 icount: Stage 1b, stated. 6 side effects: SMLALD path ran via fir_kernels_test/1, zero -Wconversion output (see 2 for the caveat). 7 cost: ~2.5 min wall per leg against the plan's 10-15; runs on push and pull_request as the rest of ci.yml.
11. Commit hygiene — clean
Two commits: b18ad52 (the change) and 1505e41 (comment-only, the measured numbers). Both carry Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> and Claude-Session: trailers; author Timothy Place. Messages describe the change, not the diff. CI green on both SHAs (runs 35257259009/35257320034 and 35257639886/35257647954), Style green.
Verdict: no blocker inside this diff; fix 1-4 (all in owned files, small) before merge, and merge it first among the tests/CMakeLists.txt touchers so 2a/3a/5 rebase onto the one-shot block rather than the other way round.
Generated by Claude Code
tap
left a comment
There was a problem hiding this comment.
Hostile review A (correctness)
Read against QEMU hw/arm/mps2.c / mps2-tz.c (master, fetched today), GCC 13 config/arm/arm-cpus.in + arm.cc + arm-c.cc, CMake 3.28's GoogleTestAddTests.cmake, both CI runs' logs (35257639886 and 35257647954), and a scratch merge of this branch with claude/wave1-stage2a-tests (#24). Hosted -DTAP_DSP_WERROR=ON: 160/160. Host -DTAP_DSP_BARE_METAL=ON: 114 tests / 25 suites, 347 ms, marker printed — reproduces the PR's claim. No Arm compiler or QEMU here, so everything on-target is by reading.
Blocker
1. Merged with #24, the four legs do not configure; fixed naively, they do not build. git merge of the two branches conflicts textually in tests/CMakeLists.txt (#24 appends after the three hosted lines this PR moved into else()). Resolved the obvious way (#24's block after the if/else), cmake -DTAP_DSP_BARE_METAL=ON fails: tests/CMakeLists.txt:116 Unknown CMake command "gtest_discover_tests" because include(GoogleTest) now lives inside the hosted branch. Hoisting the include does not help: gtest_discover_tests (POST_BUILD) runs ${CROSSCOMPILING_EMULATOR} <exe> --gtest_list_tests (GoogleTestAddTests.cmake:99), i.e. qemu-system-arm -M mps2-an386 ... -kernel tap_dsp_fft_parity --gtest_list_tests, which QEMU rejects as an unknown option → "Error running test executable" → the build step fails. And even if discovery were skipped, #24's two parity executables link GTest::gtest_main (main(0, NULL) runs everything) with TAP_DSP_PARITY_MAX_N defaulting to 2^20: several 8 MB std::vector<double> on a 4 MB heap → bad_alloc → park → 30-minute timeout. Finally, #24's target_sources(tap_dsp_tests ... test_fft_oracle.cpp test_fft_rt.cpp) adds fft_oracle_test (14), fft_rt_test (8), fft_rt_guard, fft_oracle_self_check to this binary, none of which is in the positive filter, so on target they silently do not run; the < 60 guard cannot see an omission, only a typo.
Fix (this PR, since it owns the mode): hoist include(GoogleTest); add a helper, e.g. tap_dsp_add_gtest_executable(<target> FILTER <gtest filter>), that in hosted mode links GTest::gtest_main + gtest_discover_tests, and in bare-metal mode links GTest::gtest + bare_metal_main.cpp with the filter passed as a compile definition (TAP_DSP_BARE_METAL_FILTER="...") and one add_test with the PASS/FAIL regexes; set TAP_DSP_PARITY_MAX_N 4096 in all three toolchain files (the plan's "parity at N ≤ 4096", and #24 already documents "4096 on emulated targets" without anyone setting it). Then #24 calls the helper. Whichever PR merges second rebases; say so in both.
Should-fix
2. Nothing in the CI logs supports the counts or the times this PR writes down. ctest --output-on-failure suppresses a passing test's output, so none of the four logs contains a gtest summary, the TAP_DSP_TESTS_COMPLETE marker, or any SKIPPED line (grepped all four). The only measurable is ctest wall: run 1 = 51.3 / 82.5 / 97.8 / 14.3 s, run 2 = 95.2 / 69.2 / 89.2 / 14.2 s (m4-softfp / m4f / m33 / m55). The "88 s / 62 s / 97 s / 14 s" in ci.yml and bare_metal_main.cpp match neither run. Also note m4-softfp went 51 → 95 s on an identical binary: TCG wall on shared runners is ±50 % noise, which is the argument for icount (Stage 1b), not for wall budgets in comments.
Fix: run ctest -V (or --verbose) on the embedded legs so the gtest summary is in the log; make the marker line carry selected=%d skipped=%d (UnitTest::skipped_test_count()) and fail on skipped > 0 (gtest 1.14 has no fail-on-skip flag, and #24 introduces GTEST_SKIP); replace the hard-coded times with "see ctest wall in the log".
3. A fault costs 30 minutes and leaves no diagnostic. HardFault_Handler executes bkpt #0: under QEMU with no gdb attached, BKPT on M-profile pends DebugMonitor, which is disabled, so it escalates and the core locks up; Default_Handler spins. Either way the leg sits until TIMEOUT 1800 (30 of the job's 45 minutes) and the log shows nothing. MuTap has the same rig, but its selection is 3 minutes; here the slowest leg is 98 s.
Fix: in both handlers write a marker over semihosting (TAP_DSP_TESTS_FAULT <vector>) and _exit(2) (SYS_EXIT), add the marker to FAIL_REGULAR_EXPRESSION, and set TIMEOUT 600 (6× the slowest measured leg). Keep the bkpt under an #ifdef for debugger use.
4. 26–28 -Wpsabi notes per leg. note: parameter passing for argument of type 'std::span<const double>' changed in GCC 7.1 from kaiser.h:111/202, quantize.h:44, multitone_analysis.h:74/96/118 and libstdc++. They are notes, so neither -Werror nor tap_dsp_warnings touches them, and they bury a real warning. The ABI note is irrelevant to a single-toolchain static image.
Fix: -Wno-psabi in the four CMAKE_CXX_FLAGS_INIT.
5. runs-on: ubuntu-latest with comments and a sibling workflow that assume Ubuntu 24.04. The ci.yml comment says "Ubuntu 24.04's qemu-system-arm (8.2)"; bench.yml (Stage 1b) pins ubuntu-24.04 and QEMU 8.2.2's plugin header by SHA. ubuntu-latest is rolling to 26.04 (QEMU 10.x, a newer gcc-arm-none-eabi/newlib); when it does, these four legs silently change toolchain under the icount baselines while bench stays put.
Fix: runs-on: ubuntu-24.04; optionally assert the two versions in the "Toolchain versions" step.
6. The apt line depends on Recommends. libnewlib-arm-none-eabi and libstdc++-arm-none-eabi-newlib are only Recommends of gcc-arm-none-eabi; the install works because the runner's apt default installs Recommends, and for the same reason pulls ~90 packages (gstreamer, spice, pipewire, ceph) behind qemu-system-arm.
Fix: apt-get install -y -q --no-install-recommends gcc-arm-none-eabi libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib qemu-system-arm.
Nits
7. arm-none-eabi-size (Berkeley format) is the wrong instrument for the coming ceilings. The bss column (4 126 780) is the 4 MB .heap NOLOAD region, and text includes .rodata (the M55's +53 kB is CMSIS tables, not code). Use arm-none-eabi-size -A and read .text / .rodata per section when Stage 1b turns this into a gate.
8. MSPLIM guard is correct for GCC, not for clang. GCC 13 arm-cpus.in: armv7e-m → base 7EM, armv8-m.main → base 8M_MAIN, armv8.1-m.main → base 8M_MAIN; arm.cc:3834 builds __ARM_ARCH_<base>__, so __ARM_ARCH_8M_MAIN__ is defined for both -mcpu=cortex-m33 and -mcpu=cortex-m55 and not for cortex-m4. Clang/armclang define __ARM_ARCH_8_1M_MAIN__ for the M55 and would silently skip the write; CMSIS-Core tests both. #if defined(__ARM_ARCH_8M_MAIN__) || defined(__ARM_ARCH_8_1M_MAIN__).
9. Style / banners. cortexm_startup.c: irqLock / irqRestore are camelCase (STYLE §1), and the file is never tidy-checked because the driver compiles it from the link line, so it is not in compile_commands.json (style.yml iterates that). bare_metal_main.cpp puts SPDX/Copyright after 35 lines of prose; every sibling tests/test_*.cpp puts them on lines 1–2. The three .ld files carry no SPDX at all. Rename, move the banners, add /* SPDX-License-Identifier: MIT */ to the scripts.
10. actions/checkout@v4 produces a Node 20 deprecation ##[warning] in every one of the four job logs (pre-existing on the hosted jobs too); the new job could use v5.
Checked and not at issue
- AN386 script vs
mps2.c(mps2_common_init, lines 165–190 / 206–210): SSRAM1 4 MB @ 0, mirror @ 0x400000; SSRAM2/3 4 MB @ 0x20000000; PSRAM 16 MB @ 0x21000000; block RAM 16 KB @ 0x01000000. VTOR resets to 0 on the non-TZ MPS2 boards,.vectorsis first in CODE, stack top 0x20400000 is 8-aligned,__stack_limitis defined, LMA == VMA everywhere (noAT>), so the ELF loader places.dataand the startup rightly copies nothing. - AN505:
DATA4 MB @ 0x38000000 is ssram-1 + ssram-2 (2 + 2 MB, contiguous at NS 0x28000000, secure alias 0x38000000);init_svtor = 0x10000000. AN547sram 2is 4 MB, the script's 2 MB is conservative. TAP_DSP_FFT_CMSIS OFF CACHE BOOLin the M4/M33 toolchains: the toolchain runs insideproject(),option()never overrides an existing cache entry, and CMP0077 concerns normal variables only, so the pin wins unconditionally; the M55 log alone printsfloat32 FFT backend = CMSIS-DSP Helium.CMAKE_TRY_COMPILE_PLATFORM_VARIABLES TAP_DSP_M4_FPU: the forwarded normal variable survives the toolchain'sset(... CACHE BOOL)under CMP0126 NEW (floor is 3.22), so both reads pick the same float ABI.- Every suite and test name in the positive filter exists (25 suites = the count the host run reports);
fir_kernels_test/*.*selects all three profiles; noGTEST_SKIPin the current tree. Excluded suites (double psola/pvoc/yin/log_mel/nn, the Kaiser prototype searches, MultitoneAnalysis) are the right cut: on the host the float psola/pvoc tests already top the selection at 86 / 40 / 34 / 32 ms. - Startup order (CPACR +
dsb; isb, bss clear,initialise_monitor_handles,__libc_init_array,exit(main(0, NULL))) is MuTap's file byte-for-byte apart from the guard and the banner; the CPACR write is RAZ/WI on an FPU-less M4 and harmless in the soft-float build. - Toolchain flags:
fpv4-sp-d16is the M4F FPU name;-mfloat-abi=softselects newlib'sthumb/v7e-m/nofpmultilib, andrdimon.specs+-nostartfilesis correct for both ABIs (the legs link and run, which is the proof).
Verdict: COMMENT — sound on its own, but do not merge until (1) is settled with #24 in a way both PRs' CI exercise, and (2)/(3) land so a red leg is visible and cheap.
Generated by Claude Code
Blocker (integration with the Stage 2a tests, PR #24): tests/CMakeLists.txt now hoists include(GoogleTest) and provides tap_dsp_add_gtest_executable(<target> SOURCES ... [MAIN_FILTER <f>] [LABELS ...]) which in hosted mode links gtest_main and discovers tests, and under TAP_DSP_BARE_METAL links gtest + bare_metal_main.cpp and registers one CTest test judged on the completion marker. tap_dsp_tests uses it; the four toolchains default TAP_DSP_PARITY_MAX_N to 4096 so #24's parity target fits the legs. - bare_metal_main.cpp: NEGATIVE filter (TAP_DSP_BARE_METAL_FILTER, default "*"), so every suite a later PR compiles in runs on target unless excluded by name; the marker carries selected= and skipped=, and a skip or an empty selection is rc=1. Hard-coded emulation times removed (they matched neither run; TCG wall is noise). Banner on lines 1-2. - cortexm_startup.c: HardFault/Default handlers print TAP_DSP_TESTS_FAULT with the IPSR value over semihosting and _exit(2); the marker is a FAIL_REGULAR_EXPRESSION and the test TIMEOUT drops to 600 s. MSPLIM guard also accepts __ARM_ARCH_8_1M_MAIN__ (clang's M55). irq_lock / irq_restore in house case. - Provenance: platform/README.md (copies of MuTap @ 142361b <- SampleRateTap, canonical here, duplication deliberate pending taphouse), SPDX lines in the .ld files, the inherited "Ported from RatioTap" line corrected. - Toolchains: -Wno-psabi on the C++ flags (the ~26 GCC notes per leg). - ci.yml: runs-on ubuntu-24.04; apt --no-install-recommends with newlib and the C++ runtime named; -DTAP_DSP_WERROR=ON on the embedded configure; ctest -V on the emulated step so counts and the marker are in the log; size -A alongside the Berkeley totals; checkout@v5; hosted configure passes -DTAP_DSP_BUILD_BENCH=ON for the bench PR; a guarded verbose `ctest -L parity -R default_flags` step for #24's ulp table. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZPTzNxo5Fe4EtpXXKf7Sy
Review findings A2-A12 and B3, B4, B6-B10 on #24; the #17-coupled items (bare-metal registration through the shared helper) follow in a second commit once the helper lands. - Informational target: a second private reference library at default flags replaces the link to tap::dsp_fft (which Stage 2c removes and which carries CMSIS objects on the M55); the report is one test for both precisions and --gtest_output=xml writes its RecordProperty values to parity-ulp.xml in the build tree; the test-file comment no longer claims the table is in every CI log (ctest hides passing stdout; CI runs the label with -V as its own step, Part 13). - TAP_DSP_PARITY_MAX_N defaults to 4096 when CMAKE_CROSSCOMPILING, is static_asserted to be a power of two >= 4, and sizes_up_to_65536() is renamed sweep_sizes(). MSVC /fp:precise claim qualified (VS 2022 17.0+). objdump FMA-count recipe and the Stage 2c fftsg_float.c hazard recorded in the parity file header. - Oracle: profile<Sample> carries forward_scale(n), inverse_scale(n), tolerance(n, norm2) and k_full_scale so Q15/Q31 plug in without touching the tests; the Higham comment states mu ~ few u (makewt derives half the table arithmetically), that the constant is derived with slack (measured 0.25/0.17), and why the 2-norm-per-element bound confines the DFT comparison to N <= 256; the inverse edge term goes through TwoSum; ImpulseHasFlatSpectrum renamed ImpulseIsFlatAtEverySize and the overlap with test_fft.cpp stated in the header; self-check constants derived and their measured maxima recorded. - RT guard: the header states it counts C++ operator new only (malloc and vendor-internal allocation are invisible); the GCC pragma is push/pop scoped to the replacement functions; TransformsAreNoexcept says why it exists beside the static_asserts. - signals.h: db_from_ratio dropped until a caller exists. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZPTzNxo5Fe4EtpXXKf7Sy
Fix pass for both reviews: 09a584e, CI 35268481640 all seven jobs greenPer-leg from the Review A (correctness)
Review B (process / downstream)
Also in this pass (from the coordinator, not in either review)
Not changed: 🤖 Generated with Claude Code https://claude.ai/code/session_019ZPTzNxo5Fe4EtpXXKf7Sy Generated by Claude Code |
Review findings A2-A12 and B3, B4, B6-B10 on #24; the #17-coupled items (bare-metal registration through the shared helper) follow in a second commit once the helper lands. - Informational target: a second private reference library at default flags replaces the link to tap::dsp_fft (which Stage 2c removes and which carries CMSIS objects on the M55); the report is one test for both precisions and --gtest_output=xml writes its RecordProperty values to parity-ulp.xml in the build tree; the test-file comment no longer claims the table is in every CI log (ctest hides passing stdout; CI runs the label with -V as its own step, Part 13). - TAP_DSP_PARITY_MAX_N defaults to 4096 when CMAKE_CROSSCOMPILING, is static_asserted to be a power of two >= 4, and sizes_up_to_65536() is renamed sweep_sizes(). MSVC /fp:precise claim qualified (VS 2022 17.0+). objdump FMA-count recipe and the Stage 2c fftsg_float.c hazard recorded in the parity file header. - Oracle: profile<Sample> carries forward_scale(n), inverse_scale(n), tolerance(n, norm2) and k_full_scale so Q15/Q31 plug in without touching the tests; the Higham comment states mu ~ few u (makewt derives half the table arithmetically), that the constant is derived with slack (measured 0.25/0.17), and why the 2-norm-per-element bound confines the DFT comparison to N <= 256; the inverse edge term goes through TwoSum; ImpulseHasFlatSpectrum renamed ImpulseIsFlatAtEverySize and the overlap with test_fft.cpp stated in the header; self-check constants derived and their measured maxima recorded. - RT guard: the header states it counts C++ operator new only (malloc and vendor-internal allocation are invisible); the GCC pragma is push/pop scoped to the replacement functions; TransformsAreNoexcept says why it exists beside the static_asserts. - signals.h: db_from_ratio dropped until a caller exists. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZPTzNxo5Fe4EtpXXKf7Sy
Rebased onto claude/wave1-stage1-legs. The two parity executables now go through tap_dsp_add_gtest_executable, so they are discovered hosted and one-shot under QEMU like tap_dsp_tests; the block no longer calls include(GoogleTest) or gtest_discover_tests itself. Consequences: - The 2^20 tests are compiled out (not GTEST_SKIP'ped) when TAP_DSP_PARITY_MAX_N is below 2^20, because the one-shot main counts a skip as a failed gate; the knob defaults to 4096 when CMAKE_CROSSCOMPILING and the toolchains' own cache value is honoured. - The oracle's closed-form sweep is capped by the same knob (tap_dsp_tests gets the definition), so no 65536-point buffers on the MPS2 legs. - The JUnit XML for the informational report is attached as a GTEST_OUTPUT ENVIRONMENT property via a ctest-time TEST_INCLUDE_FILES script, since the helper has no EXTRA_ARGS/PROPERTIES pass-through; hosted only. Verified on the host: GCC 13 and Clang 18 hosted (214/214, -Werror clean), -DTAP_DSP_BARE_METAL=ON host run (three one-shot markers, rc=0, selected=161/4/1, skipped=0, oracle and RT suites running under the negative filter), -DTAP_DSP_PARITY_MAX_N=4096 hosted (2^20 tests absent). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZPTzNxo5Fe4EtpXXKf7Sy
Review findings A2-A12 and B3, B4, B6-B10 on #24; the #17-coupled items (bare-metal registration through the shared helper) follow in a second commit once the helper lands. - Informational target: a second private reference library at default flags replaces the link to tap::dsp_fft (which Stage 2c removes and which carries CMSIS objects on the M55); the report is one test for both precisions and --gtest_output=xml writes its RecordProperty values to parity-ulp.xml in the build tree; the test-file comment no longer claims the table is in every CI log (ctest hides passing stdout; CI runs the label with -V as its own step, Part 13). - TAP_DSP_PARITY_MAX_N defaults to 4096 when CMAKE_CROSSCOMPILING, is static_asserted to be a power of two >= 4, and sizes_up_to_65536() is renamed sweep_sizes(). MSVC /fp:precise claim qualified (VS 2022 17.0+). objdump FMA-count recipe and the Stage 2c fftsg_float.c hazard recorded in the parity file header. - Oracle: profile<Sample> carries forward_scale(n), inverse_scale(n), tolerance(n, norm2) and k_full_scale so Q15/Q31 plug in without touching the tests; the Higham comment states mu ~ few u (makewt derives half the table arithmetically), that the constant is derived with slack (measured 0.25/0.17), and why the 2-norm-per-element bound confines the DFT comparison to N <= 256; the inverse edge term goes through TwoSum; ImpulseHasFlatSpectrum renamed ImpulseIsFlatAtEverySize and the overlap with test_fft.cpp stated in the header; self-check constants derived and their measured maxima recorded. - RT guard: the header states it counts C++ operator new only (malloc and vendor-internal allocation are invisible); the GCC pragma is push/pop scoped to the replacement functions; TransformsAreNoexcept says why it exists beside the static_asserts. - signals.h: db_from_ratio dropped until a caller exists. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZPTzNxo5Fe4EtpXXKf7Sy
Rebased onto claude/wave1-stage1-legs. The two parity executables now go through tap_dsp_add_gtest_executable, so they are discovered hosted and one-shot under QEMU like tap_dsp_tests; the block no longer calls include(GoogleTest) or gtest_discover_tests itself. Consequences: - The 2^20 tests are compiled out (not GTEST_SKIP'ped) when TAP_DSP_PARITY_MAX_N is below 2^20, because the one-shot main counts a skip as a failed gate; the knob defaults to 4096 when CMAKE_CROSSCOMPILING and the toolchains' own cache value is honoured. - The oracle's closed-form sweep is capped by the same knob (tap_dsp_tests gets the definition), so no 65536-point buffers on the MPS2 legs. - The JUnit XML for the informational report is attached as a GTEST_OUTPUT ENVIRONMENT property via a ctest-time TEST_INCLUDE_FILES script, since the helper has no EXTRA_ARGS/PROPERTIES pass-through; hosted only. Verified on the host: GCC 13 and Clang 18 hosted (214/214, -Werror clean), -DTAP_DSP_BARE_METAL=ON host run (three one-shot markers, rc=0, selected=161/4/1, skipped=0, oracle and RT suites running under the negative filter), -DTAP_DSP_PARITY_MAX_N=4096 hosted (2^20 tests absent). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZPTzNxo5Fe4EtpXXKf7Sy
#24) * Add the Stage 2a FFT test suite: Ooura parity gate, independent oracle, RT guard Written first, against the vendored C, so the wave-2 port works test-first (docs/audit-fft-and-code-smells.md, Part 3 Stage 2a and Part 9). - tests/test_fft_parity_ooura.cpp + target tap_dsp_fft_parity: memcmp bit identity of basic_real_fft<double|float> against raw rdft/rdft_f from a private reference build of fftsg.c/fftsg_float.c, both sides compiled with -ffp-contract=off (MSVC: nothing), forward and inverse, N = 4..65536 plus 2^20, on five materials. ooura_ref and engine_under_test are each re-pointable in one place. TAP_DSP_PARITY_MAX_N (default 2^20) caps the sizes for emulated targets. A second target, tap_dsp_fft_parity_default_flags, prints the measured max-ulp deviation per N at default flags and never fails (Part 6, N3). - tests/test_fft_oracle.cpp: closed-form vectors (impulse, DC, Nyquist, on-bin cosine/sine with the +i convention, two-tone) and their unnormalized inverses, to a tolerance derived from Higham's FFT bound (4 * eps * log2 N * ||y||_2, measured 0.25/0.17 of it for double/float); plus a double-double (TwoSum/TwoProd) compensated DFT for N <= 256, with a profile<Sample> extension point for the fixed-point stage. - tests/test_fft_rt.cpp: static_assert(noexcept) on the four transforms and size queries; a global operator new/delete counting guard proving the four calls allocate nothing at N = 512 and 4096, on the first call after construction and in steady state; copy and copy-assignment bit-identical. - tests/support/signals.h: one xorshift32, random_signal, tone (exact modular phase reduction) and db_from_ratio for the new files. - tests/CMakeLists.txt: additive, delimited block at the end. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZPTzNxo5Fe4EtpXXKf7Sy * Address the wave-1 reviews on the Stage 2a suite (host-side items) Review findings A2-A12 and B3, B4, B6-B10 on #24; the #17-coupled items (bare-metal registration through the shared helper) follow in a second commit once the helper lands. - Informational target: a second private reference library at default flags replaces the link to tap::dsp_fft (which Stage 2c removes and which carries CMSIS objects on the M55); the report is one test for both precisions and --gtest_output=xml writes its RecordProperty values to parity-ulp.xml in the build tree; the test-file comment no longer claims the table is in every CI log (ctest hides passing stdout; CI runs the label with -V as its own step, Part 13). - TAP_DSP_PARITY_MAX_N defaults to 4096 when CMAKE_CROSSCOMPILING, is static_asserted to be a power of two >= 4, and sizes_up_to_65536() is renamed sweep_sizes(). MSVC /fp:precise claim qualified (VS 2022 17.0+). objdump FMA-count recipe and the Stage 2c fftsg_float.c hazard recorded in the parity file header. - Oracle: profile<Sample> carries forward_scale(n), inverse_scale(n), tolerance(n, norm2) and k_full_scale so Q15/Q31 plug in without touching the tests; the Higham comment states mu ~ few u (makewt derives half the table arithmetically), that the constant is derived with slack (measured 0.25/0.17), and why the 2-norm-per-element bound confines the DFT comparison to N <= 256; the inverse edge term goes through TwoSum; ImpulseHasFlatSpectrum renamed ImpulseIsFlatAtEverySize and the overlap with test_fft.cpp stated in the header; self-check constants derived and their measured maxima recorded. - RT guard: the header states it counts C++ operator new only (malloc and vendor-internal allocation are invisible); the GCC pragma is push/pop scoped to the replacement functions; TransformsAreNoexcept says why it exists beside the static_asserts. - signals.h: db_from_ratio dropped until a caller exists. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZPTzNxo5Fe4EtpXXKf7Sy * Register the parity targets through the bare-metal helper (#17 coupling) Rebased onto claude/wave1-stage1-legs. The two parity executables now go through tap_dsp_add_gtest_executable, so they are discovered hosted and one-shot under QEMU like tap_dsp_tests; the block no longer calls include(GoogleTest) or gtest_discover_tests itself. Consequences: - The 2^20 tests are compiled out (not GTEST_SKIP'ped) when TAP_DSP_PARITY_MAX_N is below 2^20, because the one-shot main counts a skip as a failed gate; the knob defaults to 4096 when CMAKE_CROSSCOMPILING and the toolchains' own cache value is honoured. - The oracle's closed-form sweep is capped by the same knob (tap_dsp_tests gets the definition), so no 65536-point buffers on the MPS2 legs. - The JUnit XML for the informational report is attached as a GTEST_OUTPUT ENVIRONMENT property via a ctest-time TEST_INCLUDE_FILES script, since the helper has no EXTRA_ARGS/PROPERTIES pass-through; hosted only. Verified on the host: GCC 13 and Clang 18 hosted (214/214, -Werror clean), -DTAP_DSP_BARE_METAL=ON host run (three one-shot markers, rc=0, selected=161/4/1, skipped=0, oracle and RT suites running under the negative filter), -DTAP_DSP_PARITY_MAX_N=4096 hosted (2^20 tests absent). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZPTzNxo5Fe4EtpXXKf7Sy * Confine the float oracle sweeps to the CMSIS backend's 32..4096 on the M55 The first QEMU run of the rebased suite HardFaulted in fft_oracle_test/0.ImpulseIsFlatAtEverySize at N = 4 on the cortex-m55 leg, the one leg where basic_real_fft<float> routes through CMSIS-DSP: arm_rfft_fast_init_f32 accepts only 32..4096 and fft.h's wrapper does not check its return status, so fft.h's ">= 4" contract is undefined behaviour there below 32 (the existing battery starts at 64, which is why it never showed). The oracle's profile<Sample> now carries the engine's size range (k_min_n / k_max_n; 32..4096 for float under TAP_DSP_FFT_CMSIS, 4..2^20 otherwise) and every sweep is built from it. The wrapper defect itself is recorded for the fft.h owner, not papered over here. Also: the informational table printed "zu" for N on newlib-nano; %lu now. Hosted GCC 13 / Clang 18: 214/214, -Werror clean; host one-shot mode: three markers rc=0, selected=161/4/1, skipped=0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZPTzNxo5Fe4EtpXXKf7Sy --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
What this changes
Stage 1 (DspTap side) of
docs/audit-fft-and-code-smells.md, Part 10, amended per Part 13 after the two hostile reviews: the embedded CI legs become real. Four bare-metal jobs —cortex-m4-softfp,cortex-m4f,cortex-m33,cortex-m55— cross-compile the test battery withgcc-arm-none-eabi, run it underqemu-system-armwithctest -V, and printarm-none-eabi-size(Berkeley and-A) of the test binary. They replace the compile-onlybuild-embedded-m55job.platform/:cortexm_startup.c(MuTap'sarmv8m_startup.c@142361b, MSPLIM write guarded by__ARM_ARCH_8M_MAIN__ || __ARM_ARCH_8_1M_MAIN__, and a fault path:HardFault_Handler/Default_HandlerprintTAP_DSP_TESTS_FAULT <what> ipsr=<n>over semihosting and_exit(2)),mps2_an505.ldandmps3_an547.ldcopied from MuTap, a newmps2_an386.ldfor QEMU's Cortex-M4 board, SPDX lines on all three scripts, andplatform/README.mdrecording provenance (SampleRateTap → RatioTap → MuTap → here), that this copy is the canonical one from the first consumer pin, and that the duplication is deliberate pending the taphouse consolidation.cmake/: newarm-cortex-m4-mps2.cmake(cache variableTAP_DSP_M4_FPU: OFF →-mcpu=cortex-m4 -mfloat-abi=soft, ON →-mfpu=fpv4-sp-d16 -mfloat-abi=hard; emulatorqemu-system-arm -M mps2-an386), newarm-cortex-m33-mps2.cmake(as MuTap's), the existing M55 file repointed atplatform/. All setTAP_DSP_BARE_METAL ON(renamed fromMUTAP_BARE_METAL), add-Wno-psabito the C++ flags, and defaultTAP_DSP_PARITY_MAX_Nto4096as a cache entry for Stage 2a tests, written first: Ooura parity gate, independent oracle, real-time guard #24's parity target; the M4/M33 files pinTAP_DSP_FFT_CMSIS OFFbefore the rootoption()is evaluated.tests/CMakeLists.txt:include(GoogleTest)hoisted, and one registration helper for every test executable, documented in the file header:tap_dsp_add_gtest_executable(<target> SOURCES <src>... [MAIN_FILTER <gtest filter>] [LABELS <label>...])— hosted: linksGTest::gtest_main+gtest_discover_tests;TAP_DSP_BARE_METAL: linksGTest::gtest+bare_metal_main.cpp, passesMAIN_FILTERasTAP_DSP_BARE_METAL_FILTER, registers oneadd_test(<target>_emulated)withPASS_REGULAR_EXPRESSION "TAP_DSP_TESTS_COMPLETE rc=0 ",FAIL_REGULAR_EXPRESSION "\[ FAILED \];TAP_DSP_TESTS_FAULT",TIMEOUT 600.tap_dsp_testsuses it.tests/bare_metal_main.cpp: a negative filter (default"*"), so every suite compiled into a test executable runs on target unless excluded by name inMAIN_FILTER; each exclusion is a written budget decision next to the source list (double psola/pvoc/yin/log_mel/nn, six Kaiser design searches, MultitoneAnalysis). The marker isTAP_DSP_TESTS_COMPLETE rc=<n> selected=<n> skipped=<n>; an empty selection or anyGTEST_SKIPisrc=1..github/workflows/ci.yml:runs-on: ubuntu-24.04(pinned, matchingbench.yml);apt-get --no-install-recommends gcc-arm-none-eabi libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib qemu-system-arm; embedded configure with-DTAP_DSP_WERROR=ON;ctest -Von the emulated step;actions/checkout@v5; hosted configure passes-DTAP_DSP_BUILD_BENCH=ONfor Stage 1b: FFT benchmark scenarios and the instruction-count ratchet scaffold #21 (inert until it lands); a guarded verbosectest -L parity -R default_flagsstep for Stage 2a tests, written first: Ooura parity gate, independent oracle, real-time guard #24's ulp table.Why
The plan's fixed-point gates (Stage 3) and the
.text/instruction-count ratchets (Stage 1b) need legs that actually execute on Cortex-M targets; the previous M55 job linked platform files that only exist in MuTap and built the library with tests off. The reviews then showed the first cut was not integrable: #24 adds test executables of its own,gtest_discover_testscannot run under the emulator, a positive filter would have silently dropped every suite #24/#22/#19 add, and a passingctestswallowed the only evidence the legs produce. The helper, the negative filter,-Vand the fault path close those;TAP_DSP_PARITY_MAX_N=4096on the legs is the plan's "parity at N ≤ 4096".Verification
Hosted:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DTAP_DSP_WERROR=ON && cmake --build build -j8 && ctest→ 160/160, unchanged.Host one-shot mode (
-DTAP_DSP_BARE_METAL=ON, Linux gtest without pthreads and with the threeGTEST_HAS_*defines):Running 114 tests from 25 test suites,TAP_DSP_TESTS_COMPLETE rc=0 selected=114 skipped=0— the same 114 the positive filter selected, so the negative list is an exact inversion today.Scratch merge with Stage 2a tests, written first: Ooura parity gate, independent oracle, real-time guard #24 (
4ddff35onto09a584e), Stage 2a tests, written first: Ooura parity gate, independent oracle, real-time guard #24's block converted to the helper as posted on Stage 2a tests, written first: Ooura parity gate, independent oracle, real-time guard #24: host one-shottap_dsp_tests_emulated161/0,tap_dsp_fft_parity_emulated4/0,tap_dsp_fft_parity_default_flags_emulated2/0; hosted 215/215,-L parity= 8 tests, 38ulplines under-V.CI, run 35268481640 at 09a584e, all seven jobs green, from the
-Vlogs (GCC 13.2.115:13.2.rel1-2, QEMU 8.2.2, newlib 4.4.0, MinSizeRel,-Werror, 0 warnings, 0-Wpsabinotes):Runningtext.text.ARM.extab+exidx.data.bss.heap(NOLOAD)rc=0 selected=114 skipped=0rc=0 selected=114 skipped=0rc=0 selected=114 skipped=0rc=0 selected=114 skipped=0Wall time across the three runs so far moved 52–95 s on the identical M4 soft-float binary: TCG noise, a budget signal only.
clang-format
--dry-run --Werrorand clang-tidy ontests/bare_metal_main.cpp, and clang-format plus a hostgcc -fsyntax-only -Wall -Wextra -Wconversion -Wshadowpass onplatform/cortexm_startup.c, were local: neither file is in the hosted compile database the Style workflow tidies (bare_metal_main.cppcompiles only underTAP_DSP_BARE_METAL; the startup file is compiled from the cross link line).Not exercised: the fault path itself. No leg faulted, so
TAP_DSP_TESTS_FAULT→_exit(2)→FAIL_REGULAR_EXPRESSIONis proven only by reading (semihostingwriteon fd 2 and_exitare whatinitialise_monitor_handles/librdimon already provide for the normal path). A deliberate-fault build would prove it; not done here.No Arm toolchain or QEMU in the development container; everything on-target is CI's evidence.
Notes for the reviewer
rc=1on target, itsLargeTransform*tests (whichGTEST_SKIPbelow 2^20) are kept off the legs byMAIN_FILTER "*-fft_parity_ooura.LargeTransform*". With the negative filter its oracle and RT suites run on all four legs by default; the oracle's soft-float double sweep to 65536 is the cost to watch (cap byTAP_DSP_PARITY_MAX_Nif a leg goes over budget).bench.ymlnames these toolchain files exactly and its own CI is red until this is onmain..textceilings and the size-probe object are deliberately absent (Stage 1b);size -Ais printed so the ceilings read.text/.rodataper section rather than the Berkeley fold.CLAUDE.md:69("The CMSIS-Helium backend is compile-verified undercmake/arm-cortex-m55-mps3.cmake; its runtime parity is the consuming library's job") is false once this merges — the parity suite runs on the M55 leg here.mainis off; the seven CI jobs plusdriftandclang-tidygate nothing until required..textis CMSIS tables") were unmeasured and are withdrawn; the per-sectionsize -Aoutput now in the logs is what a future comparison should cite.🤖 Generated with Claude Code
https://claude.ai/code/session_019ZPTzNxo5Fe4EtpXXKf7Sy