Reorganize source tree: kernel/, v3/, v4/ split and board infrastructure
Source tree reorganization: - Move StarForth v3 engine to v3/ (src/, include/, Makefile) - Move kernel to kernel/ (src/, include/, linker/, Makefile) - Create v4/ skeleton for F18-ISA golden model (DECOMPOSITION.md, JUSTIFICATION.md) - Move FABRIC-0..4.md to docs/fabric/ - Move ONTOLOGY.md and ROADMAP.md to docs/ Board infrastructure: - Add boards/ser5/, boards/raspi/, boards/milkv/, boards/zynq7020/ - Each board has board.mk (ISA, CPU flags, boot recipe) and README.md - Root Makefile becomes thin dispatcher: boot_image, all, clean, docs take TARGET - make boot_image TARGET=SER5|RASPI|MILKV builds one GPT/MBR image per board - ZYNQ7020 target exists but stops with clear error (ARMv7 port not built yet) - scripts/mkdiskimage.sh builds disk images for all boards Docs pipeline: - docs/book/ with LaTeX master (main.tex) and Makefile - pandoc converts Markdown to LaTeX at build time - Two Lua filters: table-widths.lua (wide tables wrap), code-breaks.lua (inline code breaks) - make docs builds single PDF (754 pages, 0 missing characters) - make docs TARGET=<board> adds board appendix - build/docs/<book|board>/meta.tex stamps git commit into PDF Bug fixes: - 42 include paths that only worked by accident now use correct relative paths - clang-18 hardcode replaced with configurable CC variable (fixed aarch64 build) - Pi 5: kernel_2712.img linked at 0x80000, .bss zeroed, memory reserved - Doxyfile, .clang-tidy, README.md, Kconfig paths updated Verified: - Hosted v3 build passes 1012 tests, 0 failures - SER5 image boots in QEMU (OVMF), POST passes, K exact (65536 = Q48_ONE) - Milk-V image boots in QEMU (OpenSBI + U-Boot + bootefi), POST passes - make clean TARGET=<board> removes only that board and its ISA objects - make all builds all boards, hosted v3, and docs in one run Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* heartbeat.c - Shared M5 heartbeat / TIME-TRUST engine (punch-list item 0.8)
|
||||
*
|
||||
* One implementation of heartbeat_init()/heartbeat_ticks()/heartbeat_trust()/
|
||||
* heartbeat_state(), shared by all three architectures. Each architecture's
|
||||
* timer.c contributes only heartbeat_read_counter() — the one thing that is
|
||||
* genuinely per-ISA (rdtsc / rdtime / CNTPCT_EL0).
|
||||
*
|
||||
* Top half / bottom half split (FABRIC-0.md §25.1 item 0.8, per the GAP-A1
|
||||
* ruling in §16.4/§18.4): heartbeat_tick() is called from interrupt context
|
||||
* and does nothing but read the counter, bump TIME-TICKS, and latch a
|
||||
* pending sample. heartbeat_service() runs on the mainline (the REPL idle
|
||||
* loop) and does the window/variance/trust work. Neither one feeds patron
|
||||
* state — the engine stays on the virtual tick per §18.4, unchanged by this
|
||||
* file.
|
||||
*
|
||||
* Adaptive re-arm period (FABRIC-0.md §26, ruled 2026-08-03): Loop #7
|
||||
* (vm_runtime.c) computes an execution-derived stable/volatile signal and
|
||||
* calls heartbeat_set_adaptive_period_ns() with it, rescaled to this file's
|
||||
* kernel-appropriate base (10 ms, matching the 100 Hz rate item 0.1-0.7
|
||||
* configured) rather than the hosted 10 µs HEARTBEAT_TICK_NS base. Each
|
||||
* architecture's re-arm function reads heartbeat_next_period_ns() and
|
||||
* converts it to raw counter units instead of using a fixed constant.
|
||||
* Single writer (mainline, via vm_tick()'s Loop #7 site), single reader
|
||||
* (the ISR's re-arm call) — no lock needed, per §21.1's finding that
|
||||
* nothing here is actually concurrent on one hart.
|
||||
*/
|
||||
|
||||
#include "starkernel/timer.h"
|
||||
#include "starkernel/capsule_vm_switch_signal.h" /* FABRIC-3.md §XXVIII Stage 3 */
|
||||
|
||||
#if defined(ARCH_AMD64)
|
||||
#define HEARTBEAT_HAS_VARIANCE 1
|
||||
#else
|
||||
#define HEARTBEAT_HAS_VARIANCE 0
|
||||
#endif
|
||||
|
||||
/* Kernel-side adaptive-period base: 10 ms, matching the 100 Hz hardware
|
||||
* rate established throughout items 0.1-0.7. Deliberately NOT
|
||||
* HEARTBEAT_TICK_NS (include/starforth_config.h) -- that constant is the
|
||||
* hosted pthread-worker's 10 µs base and is three orders of magnitude too
|
||||
* fast for a bare-metal ISR period (FABRIC-0.md §26.3). Loop #7's decision
|
||||
* logic is reused unmodified; only the base it scales differs. */
|
||||
#define HEARTBEAT_BASE_PERIOD_NS 10000000ULL
|
||||
|
||||
static TimeTrustState g_heartbeat;
|
||||
|
||||
/* Top/bottom half handoff. */
|
||||
static volatile uint64_t g_pending_counter;
|
||||
static volatile int g_pending_valid;
|
||||
|
||||
/* Adaptive re-arm period, clamped to the same [1/4x, 4x] band Loop #7
|
||||
* itself enforces on tick_target_ns, so a caller cannot runaway the ISR
|
||||
* rate even if it forwarded an unclamped value. */
|
||||
static volatile uint64_t g_adaptive_period_ns = HEARTBEAT_BASE_PERIOD_NS;
|
||||
|
||||
#if HEARTBEAT_HAS_VARIANCE
|
||||
|
||||
/**
|
||||
* @brief Push a signed delta value into the heartbeat rolling window.
|
||||
*/
|
||||
static void window_push(TimeWindow *w, int64_t delta)
|
||||
{
|
||||
w->deltas[w->pos] = delta;
|
||||
w->pos = (w->pos + 1) % TIME_WINDOW_SIZE;
|
||||
if (w->count < TIME_WINDOW_SIZE) {
|
||||
w->count++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compute the relative variance of heartbeat deltas as a Q48.16 value.
|
||||
*/
|
||||
static q48_16_t window_variance_q48(const TimeWindow *w, uint64_t expected_delta)
|
||||
{
|
||||
if (w->count < 2 || expected_delta == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t sum = 0;
|
||||
for (uint32_t i = 0; i < w->count; i++) {
|
||||
sum += w->deltas[i];
|
||||
}
|
||||
int64_t mean = sum / (int64_t)w->count;
|
||||
|
||||
uint64_t sum_sq = 0;
|
||||
for (uint32_t i = 0; i < w->count; i++) {
|
||||
int64_t diff = w->deltas[i] - mean;
|
||||
if (diff > 0x7FFFFFFF) diff = 0x7FFFFFFF;
|
||||
if (diff < -0x7FFFFFFF) diff = -0x7FFFFFFF;
|
||||
sum_sq += (uint64_t)(diff * diff);
|
||||
}
|
||||
uint64_t var_tsc = sum_sq / w->count;
|
||||
|
||||
uint64_t exp_sq = expected_delta;
|
||||
if (exp_sq > 0xFFFFFFFF) {
|
||||
var_tsc >>= 16;
|
||||
exp_sq >>= 8;
|
||||
}
|
||||
exp_sq = exp_sq * exp_sq;
|
||||
if (exp_sq == 0) return 0;
|
||||
|
||||
if (var_tsc > 0x0000FFFFFFFFFFFFULL) {
|
||||
var_tsc = 0x0000FFFFFFFFFFFFULL;
|
||||
}
|
||||
|
||||
return (var_tsc << 16) / exp_sq;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Derive TIME-TRUST from a Q48.16 relative variance value.
|
||||
*/
|
||||
static q48_16_t variance_to_trust(q48_16_t variance)
|
||||
{
|
||||
q48_16_t denom = q48_add(Q48_ONE, variance);
|
||||
if (denom == 0) {
|
||||
return Q48_ONE;
|
||||
}
|
||||
return q48_div(Q48_ONE, denom);
|
||||
}
|
||||
|
||||
#endif /* HEARTBEAT_HAS_VARIANCE */
|
||||
|
||||
/**
|
||||
* @brief Initialise the M5 heartbeat / TIME-TRUST subsystem.
|
||||
*
|
||||
* Shared across all three architectures -- the fallback and calibration
|
||||
* formula never differed between them, only what heartbeat_service() does
|
||||
* with the resulting window did.
|
||||
*/
|
||||
void heartbeat_init(uint64_t tsc_hz, uint64_t tick_hz)
|
||||
{
|
||||
g_heartbeat.ticks = 0;
|
||||
g_heartbeat.last_tsc = 0;
|
||||
g_heartbeat.total_samples = 0;
|
||||
g_heartbeat.variance = 0;
|
||||
g_heartbeat.trust = Q48_ONE;
|
||||
|
||||
g_heartbeat.window.pos = 0;
|
||||
g_heartbeat.window.count = 0;
|
||||
for (int i = 0; i < TIME_WINDOW_SIZE; i++) {
|
||||
g_heartbeat.window.deltas[i] = 0;
|
||||
}
|
||||
|
||||
g_heartbeat.expected_delta = (tick_hz > 0 && tsc_hz > 0)
|
||||
? (tsc_hz / tick_hz) : 10000000ULL;
|
||||
|
||||
g_pending_valid = 0;
|
||||
g_adaptive_period_ns = HEARTBEAT_BASE_PERIOD_NS;
|
||||
}
|
||||
|
||||
void heartbeat_tick(void)
|
||||
{
|
||||
g_pending_counter = heartbeat_read_counter();
|
||||
g_heartbeat.ticks++;
|
||||
g_pending_valid = 1;
|
||||
|
||||
/* FABRIC-3.md §XXVIII, Stage 3 (2026-09-13): the one deliberate,
|
||||
* documented amendment to this file's own top-half/bottom-half
|
||||
* discipline above. sk_vm_switch_signal_tick() reaches into VM
|
||||
* scheduling state from real ISR context -- something this file's
|
||||
* header comment previously said nothing here did. It stays cheap
|
||||
* (bounded iteration over a small slot table, no locking needed for
|
||||
* the same reason nothing else here needs it: single hart, nothing
|
||||
* concurrent with this ISR while it runs) and it never itself
|
||||
* switches anything -- it only ever sets a flag, consumed later at a
|
||||
* safe cooperative checkpoint on the mainline
|
||||
* (execute_colon_word()). The GAP-A1 split itself is not overturned:
|
||||
* everything else in this function is exactly as minimal as before. */
|
||||
sk_vm_switch_signal_tick();
|
||||
}
|
||||
|
||||
void heartbeat_service(void)
|
||||
{
|
||||
if (!g_pending_valid) {
|
||||
return;
|
||||
}
|
||||
uint64_t now = g_pending_counter;
|
||||
g_pending_valid = 0;
|
||||
|
||||
TimeTrustState *s = &g_heartbeat;
|
||||
s->total_samples++;
|
||||
|
||||
if (s->total_samples == 1) {
|
||||
s->last_tsc = now;
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t actual_delta = now - s->last_tsc;
|
||||
s->last_tsc = now;
|
||||
int64_t deviation = (int64_t)actual_delta - (int64_t)s->expected_delta;
|
||||
|
||||
#if HEARTBEAT_HAS_VARIANCE
|
||||
window_push(&s->window, deviation);
|
||||
s->variance = window_variance_q48(&s->window, s->expected_delta);
|
||||
s->trust = variance_to_trust(s->variance);
|
||||
#else
|
||||
/* RISC-V `time` / AArch64 CNTPCT_EL0 are architecturally invariant
|
||||
* counters -- no statistical quality estimate needed (matches the
|
||||
* per-arch rationale this file replaces). */
|
||||
(void)deviation;
|
||||
s->trust = Q48_ONE;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint64_t heartbeat_ticks(void)
|
||||
{
|
||||
return g_heartbeat.ticks;
|
||||
}
|
||||
|
||||
time_trust_t heartbeat_trust(void)
|
||||
{
|
||||
return g_heartbeat.trust;
|
||||
}
|
||||
|
||||
const TimeTrustState *heartbeat_state(void)
|
||||
{
|
||||
return &g_heartbeat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the adaptive re-arm period (FABRIC-0.md §26).
|
||||
*
|
||||
* Called from vm_runtime.c's Loop #7 site on the mainline execution path
|
||||
* (never interrupt context) with a value already rescaled to this file's
|
||||
* HEARTBEAT_BASE_PERIOD_NS. Clamped defensively to the same [1/4x, 4x]
|
||||
* band Loop #7 itself enforces around its own base, so a caller forwarding
|
||||
* an unclamped or wrongly-scaled value cannot run the ISR away.
|
||||
*
|
||||
* @param ns Desired period in nanoseconds for the next re-arm.
|
||||
*/
|
||||
void heartbeat_set_adaptive_period_ns(uint64_t ns)
|
||||
{
|
||||
uint64_t lo = HEARTBEAT_BASE_PERIOD_NS / 4;
|
||||
uint64_t hi = HEARTBEAT_BASE_PERIOD_NS * 4;
|
||||
if (ns < lo) ns = lo;
|
||||
if (ns > hi) ns = hi;
|
||||
g_adaptive_period_ns = ns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read the period the next hardware re-arm should use.
|
||||
*
|
||||
* Called from interrupt context by each architecture's re-arm function
|
||||
* (apic_timer_rearm(), riscv64_timer_rearm(), the AArch64 equivalent) in
|
||||
* place of the fixed constant those functions used before item 0.8.
|
||||
*
|
||||
* @return Current adaptive period in nanoseconds.
|
||||
*/
|
||||
uint64_t heartbeat_next_period_ns(void)
|
||||
{
|
||||
return g_adaptive_period_ns;
|
||||
}
|
||||
Reference in New Issue
Block a user