# Physical CPU0 heartbeat: host preparation only Prepared 2026-09-26 America/Chicago; no board release was run for this file. The working tree was `impl/small-core-heartbeat-20260926` at base `ba23c967`. `bash tools/test-small-core-heartbeat.sh` passed on the host. With `SMALL_CORE_OUTPUT_DIR=/tmp/k230-small-core-heartbeat-host`, the generated 80-byte `heartbeat.bin` had SHA-256 `11d675ac6a4993c5ea820603a59778993487a478d07b1e30d3e9545ae22ba133`. The temporary output is a host artifact, not a committed firmware image. ## Source-backed physical path The pinned `k230_linux_sdk` revision is `1104236db4d1e47873bd68924f912747b820228c` (`nix/k230-sdk-src.nix`). `nix/uboot-k230.nix` copies that U-Boot overlay and uses `k230_canmv_v3_defconfig`. In its `board/canaan/common/sdk_autoconf.h:16`, `CONFIG_LINUX_RUN_CORE_ID` is 1. `k230_spl.c:150` loads U-Boot; `k230_img.c:276-285` releases physical CPU1 into that U-Boot image and parks physical CPU0 in `wfi`. Thus the normal U-Boot prompt is on physical CPU1, supported by source and the later RVV/256 KiB L2 Linux observation. The same overlay's `arch/riscv/cpu/k230/cpu.c:125-166` defines `boot_baremetal 0`, which writes physical CPU0's vector at `0x91102100`, asserts/deasserts `CPU0_RST_CTL` at `0x91101004`, and flushes the specified image range. The built U-Boot ELF in `/nix/store/zzz867rp8drrwkhibj0rlqc04sk9g7f8-uboot-k230_canmv_v3_defconfig-riscv64-unknown-linux-gnu-2022.10` contains that command (`nm` and a string search). The vendor default environment and this project's `firmware/stage1/tdisplay.env:29` load a physical CPU0 bare-metal image at `0x07000000` before that command. This grounds the scratch load address, not a claim that the new payload has run. The 80-byte program loads at `0x07000000`. It writes CPU0's read-only `mhartid` and `misa` CSRs at `0x07002008` and `0x07002010`, and emits the same monotonic 64-bit counter at `0x07002000`, `0x07003000`, and `0x07004000`. Those three output pages lie within the vendor's CPU0 bare-metal scratch range and outside the actual 80-byte file. The program issues `fence rw,rw` and Canaan U-Boot's documented `l2cache.ciall` encoding (`cache.c:158-161`, `0x0170000b`) after each update. Each output page is read **once** from the CPU1 U-Boot prompt, separated in time, to avoid relying on a second read of a line CPU1 may retain in its own cache. This is a bounded diagnostic protocol, not a demonstrated CPU-to-CPU coherency contract. `llvm-objdump` and `readelf` confirmed entry `0x07000000`, the output symbol at `0x07002000`, scalar RV64 ELF attributes without V, CSR reads, the three stores, and the L2 maintenance word. The host check proves only layout and instruction selection. It does not prove that CPU0 starts, that its L2 flush publishes to CPU1, or that the board's U-Boot reads those pages freshly. The live Linux memory snapshot collected by the coordinator at `2026-09-27T03:34:24Z` (`/tmp/k230-core-memory-result.txt`) describes 1 GiB at `0x00000000..0x3fffffff`, with `0x10000000..0x103fffff` reserved for the framebuffer and no second-core reservation. It does not reserve these probe pages for Linux; this first test stays at the U-Boot prompt and ends in a reset. A Linux-coexistence candidate must use a separate DT reservation and read-only observer before it can boot. `python3 -m unittest discover -s tests -p test_ums_target.py` passed 16 host tests. It proves the UMS helper's refusal guards, not that a disposable card or a recovery rehearsal was available during this preparation.