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:
rajames
2026-10-01 15:40:09 -04:00
co-authored by Junie
parent 3c709c115b
commit a8b70e88d3
630 changed files with 2646 additions and 2036 deletions
+288
View File
@@ -0,0 +1,288 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* capsule_run.c - DoE Run Logging and Parity (M7.1)
*
* Ring buffer for capsule execution logging and parity record emission.
* Freestanding - no libc dependency except for console output hooks.
*/
#include "starkernel/capsule_run.h"
#include "starkernel/capsule.h"
#ifdef __STARKERNEL__
#include "starkernel/console.h" /* parity output goes straight to the console */
#endif
/* Parity output sink.
*
* In the monolithic kernel/PE build, taking the address of a cross-translation-
* unit function (e.g. &console_puts) is resolved through an absolute
* R_X86_64_32S relocation that the PE base-relocator cannot fix up, so a stored
* console function pointer holds a garbage address and faults #GP when called.
* Direct calls to console_puts/console_putc are PC-relative and always valid, so
* the kernel parity path calls the console directly rather than via a pointer.
* The hosted build keeps the redirectable function-pointer hooks. */
#ifdef __STARKERNEL__
#define PARITY_HAVE_SINK 1
#define PARITY_EMIT(s) console_puts(s)
#else
#define PARITY_HAVE_SINK (parity_puts != 0)
#define PARITY_EMIT(s) do { if (parity_puts) parity_puts(s); } while (0)
#endif
/*===========================================================================
* Run Log Ring Buffer
*===========================================================================*/
static CapsuleRunRecord run_log[CAPSULE_MAX_RUN_RECORDS];
static uint32_t run_log_head = 0; /* Next write position */
static uint32_t run_log_count = 0; /* Total records logged */
static uint64_t run_id_counter = 0; /* Sequential run ID */
void capsule_run_log_init(void) {
run_log_head = 0;
run_log_count = 0;
run_id_counter = 0;
/* Zero the ring buffer */
for (uint32_t i = 0; i < CAPSULE_MAX_RUN_RECORDS; i++) {
run_log[i].run_id = 0;
run_log[i].vm_id = vm_uuid_none(); /* not {0,0} -- that's Hera's reserved id (item 3.8) */
run_log[i].reserved = 0;
run_log[i].capsule_id = 0;
run_log[i].capsule_hash = 0;
run_log[i].pre_dict_hash = 0;
run_log[i].post_dict_hash = 0;
run_log[i].started_ns = 0;
run_log[i].ended_ns = 0;
run_log[i].result_code = 0;
run_log[i].flags = 0;
}
}
uint64_t capsule_run_log_record(const CapsuleRunRecord *record) {
if (!record) {
return 0;
}
/* Assign run ID */
uint64_t id = ++run_id_counter;
/* Copy record to ring buffer */
CapsuleRunRecord *slot = &run_log[run_log_head];
*slot = *record;
slot->run_id = id;
/* Advance head (circular) */
run_log_head = (run_log_head + 1) % CAPSULE_MAX_RUN_RECORDS;
/* Track total count (saturates at buffer size for wrap detection) */
if (run_log_count < CAPSULE_MAX_RUN_RECORDS) {
run_log_count++;
}
return id;
}
int capsule_run_log_get(uint64_t run_id, CapsuleRunRecord *out) {
if (!out || run_id == 0 || run_id > run_id_counter) {
return -1;
}
/* Search ring buffer for matching run_id */
for (uint32_t i = 0; i < CAPSULE_MAX_RUN_RECORDS; i++) {
if (run_log[i].run_id == run_id) {
*out = run_log[i];
return 0;
}
}
return -1; /* Not found (may have been overwritten) */
}
uint32_t capsule_run_log_count(void) {
return run_log_count;
}
/*===========================================================================
* Parity Logging
*
* These functions emit structured parity records to the console/serial.
* Format is designed for deterministic verification and debugging.
*
* Output goes through a console hook that can be redirected to:
* - Serial port (kernel mode)
* - stdout (hosted mode)
* - Debug port 0x402 (QEMU)
*===========================================================================*/
/* Console output hook - to be set by platform layer */
static void (*parity_putc)(char c) = 0;
static void (*parity_puts)(const char *s) = 0;
void capsule_parity_set_output(void (*putc_fn)(char), void (*puts_fn)(const char *)) {
parity_putc = putc_fn;
parity_puts = puts_fn;
}
/* Helper: output hex value */
static void parity_put_hex64(uint64_t val) {
static const char hex[] = "0123456789abcdef";
char buf[19]; /* "0x" + 16 hex + null */
buf[0] = '0';
buf[1] = 'x';
for (int i = 15; i >= 0; i--) {
buf[2 + (15 - i)] = hex[(val >> (i * 4)) & 0xF];
}
buf[18] = '\0';
PARITY_EMIT(buf);
}
/* item 3.8: VMUuid printed as its two hex64 halves, hyphen-separated --
* reuses parity_put_hex64 rather than a new hex-formatting routine. */
static void parity_put_uuid(VMUuid id) {
parity_put_hex64(id.hi);
PARITY_EMIT("-");
parity_put_hex64(id.lo);
}
static void parity_put_u32(uint32_t val) {
char buf[12];
int i = 11;
buf[i--] = '\0';
if (val == 0) {
buf[i--] = '0';
} else {
while (val > 0 && i >= 0) {
buf[i--] = '0' + (val % 10);
val /= 10;
}
}
PARITY_EMIT(&buf[i + 1]);
}
static void parity_put_u64(uint64_t val) {
char buf[21];
int i = 20;
buf[i--] = '\0';
if (val == 0) {
buf[i--] = '0';
} else {
while (val > 0 && i >= 0) {
buf[i--] = '0' + (val % 10);
val /= 10;
}
}
PARITY_EMIT(&buf[i + 1]);
}
void capsule_parity_log_birth(
VMUuid vm_id,
uint64_t capsule_id,
uint64_t capsule_hash,
uint64_t dict_hash)
{
if (!PARITY_HAVE_SINK) return;
PARITY_EMIT("PARITY:BIRTH vm_id=");
parity_put_uuid(vm_id);
PARITY_EMIT(" capsule_id=");
parity_put_hex64(capsule_id);
PARITY_EMIT(" mode=p capsule_hash=");
parity_put_hex64(capsule_hash);
PARITY_EMIT(" dict_hash=");
parity_put_hex64(dict_hash);
PARITY_EMIT("\n");
}
void capsule_parity_log_birth_failed(
VMUuid vm_id,
uint64_t capsule_id,
CapsuleRunResult error,
uint64_t partial_dict_hash)
{
if (!PARITY_HAVE_SINK) return;
PARITY_EMIT("PARITY:BIRTH_FAILED vm_id=");
parity_put_uuid(vm_id);
PARITY_EMIT(" capsule_id=");
parity_put_hex64(capsule_id);
PARITY_EMIT(" error=");
parity_put_u32((uint32_t)error);
PARITY_EMIT(" partial_dict_hash=");
parity_put_hex64(partial_dict_hash);
PARITY_EMIT("\n");
}
void capsule_parity_log_run(
VMUuid vm_id,
uint64_t run_id,
uint64_t capsule_id,
uint64_t pre_dict_hash,
uint64_t post_dict_hash)
{
if (!PARITY_HAVE_SINK) return;
PARITY_EMIT("PARITY:RUN vm_id=");
parity_put_uuid(vm_id);
PARITY_EMIT(" run_id=");
parity_put_u64(run_id);
PARITY_EMIT(" capsule_id=");
parity_put_hex64(capsule_id);
PARITY_EMIT(" mode=e pre_dict=");
parity_put_hex64(pre_dict_hash);
PARITY_EMIT(" post_dict=");
parity_put_hex64(post_dict_hash);
PARITY_EMIT("\n");
}
/*===========================================================================
* Mama Init Parity
*===========================================================================*/
void capsule_parity_log_mama_init(
uint64_t capsule_id,
uint64_t capsule_hash,
uint64_t dict_hash)
{
if (!PARITY_HAVE_SINK) return;
PARITY_EMIT("PARITY:MAMA_INIT capsule_id=");
parity_put_hex64(capsule_id);
PARITY_EMIT(" mode=m capsule_hash=");
parity_put_hex64(capsule_hash);
PARITY_EMIT(" dict_hash=");
parity_put_hex64(dict_hash);
PARITY_EMIT("\n");
}
void capsule_parity_log_kill(VMUuid vm_id, const char *name)
{
if (!PARITY_HAVE_SINK) return;
PARITY_EMIT("PARITY:KILL vm_id=");
parity_put_uuid(vm_id);
PARITY_EMIT(" name=");
if (name) PARITY_EMIT(name);
PARITY_EMIT("\n");
}