From 5f2c10e5a6a6ef8a9fa5857ec0a1d0333358d094 Mon Sep 17 00:00:00 2001 From: JC-000 <3798556+JC-000@users.noreply.github.com> Date: Mon, 20 Jul 2026 08:32:55 -0500 Subject: [PATCH 1/3] feat(crypto): nistcurves v0.5.0 + USE_NISTCURVES_ONCHIP turbo profile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump libs/nistcurves v0.3.0 -> v0.5.0 and wire the FP_ONCHIP_MUL turbo profile (upstream issue #69) behind USE_NISTCURVES_ONCHIP=1: - wrapper: profile arg (reu|onchip); dynamic member list (v0.5.0 renamed/added archive members); onchip mode rebuilds mul_8x8_onchip.o with the SHARED_* consumer defines + LIB_SHARED_SQTAB_BASE=$BC00 via a generated glue TU (upstream's SHARED_CT_MUL_8X8 x FP_ONCHIP_MUL combo lacks .imports — composition bridge, no libs/ source patch) - poly1305.s: under the flag, provide the c64-lib-contract §8.3 canonical ct_mul_8x8 (+ SMC bake sites) with the legacy A=a/X=b mul_8x8 kept as a thin shim; default build byte-path unchanged - data.s: yield sqtab_lo/hi to the lib's $BC00 equates under the flag (aligned same-size placeholder keeps TABLES_BSS layout; post-link Makefile check asserts sqtab_reserved==$BC00) - boot.s: skip the ~128 KB reu_mul_init population + yield the reu_fetch_mul_row export under the flag (onchip verify issues no REU row DMA; boot obligation shrinks to sqtab_init) - cfg/uci: LIB_NISTCURVES_MUL_CODE -> CRYPTO_OVERLAY (onchip fp256 growth overflows CRYPTO_HOT by 59 B otherwise); Makefile guards the flag against X25519_SIBLING + both overlay-embed flags Correctness: VICE KAT oracle 3/3 PASS against the onchip UCI PRG (CAVP SigVer vectors). Default UCI build: byte-identical behavior, 0 warnings. KNOWN: plain ip65 does not link at either pin (BSS overflows CRYPTO_COLD_SHADOW by 1662 B @ v0.3.0, 1406 B @ v0.5.0) — pre-existing on master since PR #55, improved-but-not-fixed by this bump; tracked at c64-nist-curves#54. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01UZmMaxyyykvYbCvJAc4QVW --- Makefile | 39 ++++- cfg/c64-https-uci.cfg | 7 +- libs/nistcurves | 2 +- src/boot.s | 13 ++ src/crypto/poly1305.s | 63 ++++++++ src/data.s | 13 ++ tools/integration/build_nistcurves_p256.sh | 169 ++++++++++++--------- 7 files changed, 233 insertions(+), 73 deletions(-) diff --git a/Makefile b/Makefile index 52a2364..f044dc6 100644 --- a/Makefile +++ b/Makefile @@ -61,7 +61,29 @@ UCI_SRCS := src/net/uci/net.s src/net/uci/uci_cmd.s # external overlay image (see below), not linked into the main PRG. # Phase C.4 adds nistcurves-p256.a which IS linked in, always-resident, # for BOTH backends (replaces the in-tree ecdsa_{curve,fp,mod,points}.s). +# +# USE_NISTCURVES_ONCHIP=1 (issue #69 / nistcurves v0.5.0): swap in the +# FP_ONCHIP_MUL turbo-profile archive — fp_mul/fp_sqr generate multiply +# rows on-chip instead of REU DMA row fetches, removing the ~1 MHz-anchored +# DMA floor on turbo hosts (crossover ~30 MHz; see CLAUDE.md "Why turbo +# stops paying"). Gates: data.s yields sqtab to the lib's $BC00 equates, +# poly1305.s provides the §8.3 canonical ct_mul_8x8 + SMC bake sites, +# boot.s skips reu_mul_init + yields the reu_fetch_mul_row export. +ifeq ($(USE_NISTCURVES_ONCHIP),1) +ifeq ($(USE_X25519_SIBLING),1) +$(error USE_NISTCURVES_ONCHIP and USE_X25519_SIBLING are mutually exclusive for now: both archives export reu_fetch_mul_row) +endif +ifeq ($(USE_OVERLAY_P384_EMBED),1) +$(error USE_NISTCURVES_ONCHIP places LIB_NISTCURVES_MUL_CODE in CRYPTO_OVERLAY - mutually exclusive with USE_OVERLAY_P384_EMBED) +endif +ifeq ($(EMBED_P256_OVERLAY),1) +$(error USE_NISTCURVES_ONCHIP places LIB_NISTCURVES_MUL_CODE in CRYPTO_OVERLAY - mutually exclusive with EMBED_P256_OVERLAY) +endif +SIBLING_LIB_ARCHIVES := build/lib/nistcurves-p256-onchip.a +CA65FLAGS += -D USE_NISTCURVES_ONCHIP=1 +else SIBLING_LIB_ARCHIVES := build/lib/nistcurves-p256.a +endif # Phase C.5 (USE_X25519_SIBLING=1): c64-x25519 v0.4.0 sibling, always-resident, # replaces in-tree fe25519.s + x25519.s + X25519 buffers in src/data.s. @@ -226,6 +248,14 @@ $(PRG): $(PRG_DEPS) # Rewrite ca65 label format `al XXXXXX .name` -> VICE format `al C:XXXX .name` # so the c64-test-harness Labels.from_file() reader can parse it. sed -i '' 's/^al 00\([0-9a-fA-F]\{4\}\) /al C:\1 /' $(LABELS) +ifeq ($(USE_NISTCURVES_ONCHIP),1) + # Onchip-profile invariant: the sibling's sqtab_lo/hi equates are + # BAKED to $$BC00/$$BE00 (LIB_SHARED_SQTAB_BASE in the wrapper). + # data.s's sqtab_reserved placeholder must still land exactly there — + # any TABLES_BSS layout drift silently corrupts every multiply. + @grep -q '^al C:BC00 \.sqtab_reserved' $(LABELS) || \ + { echo 'ERROR: sqtab_reserved is not at $$BC00 — TABLES_BSS layout drifted; realign LIB_SHARED_SQTAB_BASE in tools/integration/build_nistcurves_p256.sh'; exit 1; } +endif # Phase 5 Fix D: $(LABELS) is normally a side-effect of the $(PRG) # link recipe; we don't add an explicit rule. The overlay-bin rule @@ -267,7 +297,14 @@ build/lib/nistcurves-p384-sha384.a build/lib/nistcurves-p384-curve.a: # for the 160-byte BE struct packing that bridges TLS to the sibling. build/lib/nistcurves-p256.a: @mkdir -p build/lib - bash tools/integration/build_nistcurves_p256.sh + bash tools/integration/build_nistcurves_p256.sh reu + +# Onchip turbo-profile variant (issue #69). Same wrapper, onchip mode: +# builds upstream lib-p256-verify-onchip and rebuilds mul_8x8_onchip.o +# with the SHARED_* consumer defines + LIB_SHARED_SQTAB_BASE=$BC00. +build/lib/nistcurves-p256-onchip.a: + @mkdir -p build/lib + bash tools/integration/build_nistcurves_p256.sh onchip # Phase C.5: c64-x25519 v0.4.0 X25519 archive — replaces the in-tree # fe25519.s + x25519.s + X25519 buffer declarations in src/data.s when diff --git a/cfg/c64-https-uci.cfg b/cfg/c64-https-uci.cfg index d25005a..1d4b2df 100644 --- a/cfg/c64-https-uci.cfg +++ b/cfg/c64-https-uci.cfg @@ -255,7 +255,12 @@ SEGMENTS { # zero bytes today. LIB_NISTCURVES_BSS: load = CRYPTO_COLD_SHADOW, type = bss, optional = yes; LIB_NISTCURVES_TABLES: load = CRYPTO_COLD_SHADOW, type = bss, optional = yes, align = $100; - LIB_NISTCURVES_MUL_CODE: load = CRYPTO_HOT, type = ro, optional = yes; + # Onchip profile (issue #69): og_common + sqtab equates. Placed in + # CRYPTO_OVERLAY (free under USE_NISTCURVES_ONCHIP — the Makefile + # forbids combining it with the P-384/P-256 overlay flags, whose + # runtime swap-ins would clobber resident code here) because the + # onchip fp256 growth overflows CRYPTO_HOT by ~59 B otherwise. + LIB_NISTCURVES_MUL_CODE: load = CRYPTO_OVERLAY, type = ro, optional = yes; # libs/nistcurves P-384 segments — routed via the OVERLAY_P384_CURVE # bin staging (DMA'd to REU bank 7 at boot, paged into the live # overlay slot on a P-384 handshake). Mirrors the pre-contract diff --git a/libs/nistcurves b/libs/nistcurves index b67de54..fd2248d 160000 --- a/libs/nistcurves +++ b/libs/nistcurves @@ -1 +1 @@ -Subproject commit b67de54520bb4193b073d9703d5c85adcf29f505 +Subproject commit fd2248dafd3fbc3d50028da882d550287c3230fb diff --git a/src/boot.s b/src/boot.s index 172ebb3..fe12ed9 100644 --- a/src/boot.s +++ b/src/boot.s @@ -21,8 +21,14 @@ .import reu_mul_init .else .export reu_mul_init + ; Under USE_NISTCURVES_ONCHIP the sibling's rebuilt + ; mul_8x8_onchip.o exports reu_fetch_mul_row unconditionally + ; (upstream has no guard on it) — yield ours to avoid the + ; ld65 duplicate. The in-tree routine body stays for local use. + .ifndef USE_NISTCURVES_ONCHIP .export reu_fetch_mul_row .endif + .endif ; ---- exports: Phase 3 P-384 overlay REU stash ---- .export reu_p384_overlay_init @@ -268,7 +274,14 @@ start: lda $01 and #%11111110 sta $01 + .ifndef USE_NISTCURVES_ONCHIP jsr reu_mul_init + .else + ; Onchip profile (issue #69): fp_mul generates rows on-chip via + ; og_common/ct_mul_8x8 — REU banks 0/1 are never fetched, so the + ; ~128 KB population pass is skipped. Boot obligation for the + ; sibling shrinks to sqtab_init (called below via the shared path). + .endif ; Phase 3: stash both P-384 split overlay images in REU banks 6 ; and 7 from the .incbin'd staging blocks at $4200 and $E000. diff --git a/src/crypto/poly1305.s b/src/crypto/poly1305.s index 7a4c99d..64882b6 100644 --- a/src/crypto/poly1305.s +++ b/src/crypto/poly1305.s @@ -201,6 +201,68 @@ sqtab_init: ; Uses identity: a*b = sqtab[a+b] - sqtab[|a-b|] ; Clobbers: A, X, Y ; ============================================================================= +.ifdef USE_NISTCURVES_ONCHIP +; --- c64-lib-contract §8.3 canonical body (issue #69 integration) --- +; The sibling's FP_ONCHIP_MUL row generator (og_common, rebuilt with +; SHARED_CT_MUL_8X8) imports ct_mul_8x8 + the SMC bake sites from the +; consumer. Body copied verbatim from libs/nistcurves/src/mul_8x8.s +; (the §8.3 reference copy). Convention: caller bakes `a` into +; smc_sum_a_imm+1 / smc_diff_a_imm+1 once per row, passes b in Y. +; The legacy in-tree convention (A=a, X=b, re-baked per call) is kept +; as the thin `mul_8x8` shim for poly1305/fe25519 call sites. +.export ct_mul_8x8 +.export smc_sum_a_imm, smc_diff_a_imm + +mul_8x8: ; legacy shim: A=a, X=b + sta smc_sum_a_imm+1 ; bake a (per call — legacy sites only) + sta smc_diff_a_imm+1 + txa + tay ; Y = b + ; fall through into ct_mul_8x8 + +ct_mul_8x8: + ; --- Compute sum = a + b and SMC-patch the two abs,x hi bytes --- + tya ; A = b + clc +smc_sum_a_imm: + adc #$00 ; SMC imm = a; A = (a+b).lo, C = sum-page bit + tax ; X = (a+b) & $FF + lda #>sqtab_lo + adc #0 ; sum-page carry folded into hi byte + sta smc_lo_addr+2 ; patch sqtab_lo abs,x hi byte + adc #(>sqtab_hi - >sqtab_lo) ; C=0 after prior adc #0, so += 2 + sta smc_hi_addr+2 ; patch sqtab_hi abs,x hi byte + + ; --- Branchless |a-b| -> Y (sign-mask flip-and-negate) --- + tya ; A = b + sec +smc_diff_a_imm: + sbc #$00 ; SMC imm = a; A = b-a, C=1 iff b>=a + sta ct_diff_raw + lda #$00 + sbc #$00 ; C=1: $00; C=0: $FF (sign mask) + sta ct_sign_mask + eor ct_diff_raw ; raw XOR mask + sec + sbc ct_sign_mask ; + (-mask): +0 if b>=a, +1 if b&2; exit 2;; esac + # --- Paths --- PROJECT_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" LIB_DIR="$PROJECT_ROOT/libs/nistcurves" LIB_SRC="$LIB_DIR/src" LIB_BUILD="$LIB_DIR/build" -STAGING="$PROJECT_ROOT/build/lib/nistcurves_p256_staging" OUT_DIR="$PROJECT_ROOT/build/lib" -ARCHIVE="$OUT_DIR/nistcurves-p256.a" -SIZES="$OUT_DIR/nistcurves-p256.sizes.txt" +if [ "$PROFILE" = "onchip" ]; then + UPSTREAM_TARGET="lib-p256-verify-onchip" + UPSTREAM_ARCHIVE="$LIB_BUILD/lib/nistcurves-p256-verify-onchip.a" + STAGING="$PROJECT_ROOT/build/lib/nistcurves_p256_onchip_staging" + ARCHIVE="$OUT_DIR/nistcurves-p256-onchip.a" + SIZES="$OUT_DIR/nistcurves-p256-onchip.sizes.txt" +else + UPSTREAM_TARGET="lib-p256-verify" + UPSTREAM_ARCHIVE="$LIB_BUILD/lib/nistcurves-p256-verify.a" + STAGING="$PROJECT_ROOT/build/lib/nistcurves_p256_staging" + ARCHIVE="$OUT_DIR/nistcurves-p256.a" + SIZES="$OUT_DIR/nistcurves-p256.sizes.txt" +fi CA65="${CA65:-ca65}" AR65="${AR65:-ar65}" @@ -63,27 +86,14 @@ ZP_OVERRIDES=( '-D' 'fp_mul_j=$3a' ) -# --- 1. Build upstream's lib-p256-verify archive --- +# --- 1. Build upstream's verify archive --- # Upstream's Makefile builds every module with the same recipe (no per-file -# CA65FLAGS hook), so we cannot pass -D overrides via `make CA65=...` here: -# the override would land on every .s, including fp256.s which only -# `.importzp`s the slots and would error on a redefinition. We therefore -# build upstream with its defaults, then rebuild zp_config.o ourselves with -# the overrides below. -# -# Note on c64-lib-contract SPEC §8.1: nistcurves v0.3.0's `mul_8x8.s` is -# the only TU that references sqtab_lo / sqtab_hi (via the local -# `.ifndef LIB_SHARED_SQTAB_BASE` equate in that file). Step 4 below -# drops `mul_8x8.o` from the archive entirely — c64-https provides the -# canonical `sqtab_lo` / `sqtab_hi` via src/data.s and the population -# init via src/crypto/poly1305.s::sqtab_init. So no LIB_SHARED_SQTAB_BASE -# / SHARED_SQTAB_INIT override is needed at the nistcurves Makefile -# invocation — the upstream default baked into mul_8x8.o is discarded -# before it reaches the link. -echo "[p256] building libs/nistcurves lib-p256-verify (upstream defaults)..." -make -s -C "$LIB_DIR" lib-p256-verify >/dev/null +# CA65FLAGS hook), so we cannot pass -D overrides via `make CA65=...` here. +# We build upstream with its defaults, then rebuild the TUs that need +# consumer overrides (zp_config.o always; mul_8x8_onchip.o under onchip). +echo "[p256/$PROFILE] building libs/nistcurves $UPSTREAM_TARGET (upstream defaults)..." +make -s -C "$LIB_DIR" "$UPSTREAM_TARGET" >/dev/null -UPSTREAM_ARCHIVE="$LIB_BUILD/lib/nistcurves-p256-verify.a" if [ ! -f "$UPSTREAM_ARCHIVE" ]; then echo "ERROR: upstream archive missing: $UPSTREAM_ARCHIVE" >&2 exit 1 @@ -96,9 +106,6 @@ cp "$UPSTREAM_ARCHIVE" "$STAGING/upstream.a" (cd "$STAGING" && "$AR65" x upstream.a $( "$AR65" t upstream.a )) # --- 3. Rebuild zp_config.o with c64-https overrides --- -# `.ifndef`-guarded slots in src/zp_config.s let -D flags win cleanly. -# The .exportzp declarations propagate the override values to every -# `.importzp` site via the link. "$CA65" \ --cpu 6502 \ -g \ @@ -107,39 +114,61 @@ cp "$UPSTREAM_ARCHIVE" "$STAGING/upstream.a" -o "$STAGING/zp_config.o" \ "$LIB_SRC/zp_config.s" -# --- 4. Drop conflicting members --- -# mul_8x8.o: exports mul_8x8, sqtab_init, poly_prod_lo/hi, sqtab_lo/hi, -# reu_fetch_mul_row. c64-https's src/crypto/poly1305.s already -# exports these — including upstream's copy causes ld65 dup-sym. -# data_shared.o: exports mul_cached_a, mul_src2_buf, mul_dma_lo/hi. -# c64-https's src/data.s already exports these — same conflict. +# --- 4. Drop conflicting members / rebuild the onchip mul object --- rm -f "$STAGING/mul_8x8.o" "$STAGING/data_shared.o" +if [ "$PROFILE" = "onchip" ]; then + # Rebuild (not drop): fp256_onchip.o imports og_common/og_src_ld which + # only this TU provides. The SHARED_* defines strip everything that + # would collide with the in-tree providers (see header comment #3). + # + # Upstream gap (candidate c64-nist-curves issue): the og_common block + # references ct_mul_8x8 / smc_* / poly_prod_* as same-TU symbols, so + # SHARED_CT_MUL_8X8 alone leaves them undefined — the guard combo was + # never exercised upstream. Bridge it with a generated glue TU that + # declares the .imports and then .includes the PRISTINE library source + # (composition, not a source patch — libs/ stays untouched). + cat > "$STAGING/mul_8x8_onchip_glue.s" <<'EOF' +; generated by build_nistcurves_p256.sh (onchip profile) — do not edit +.import ct_mul_8x8 +.import smc_sum_a_imm, smc_diff_a_imm +.import poly_prod_lo, poly_prod_hi +.import mul_cached_a +.include "mul_8x8.s" +EOF + "$CA65" \ + --cpu 6502 \ + -g \ + -I "$LIB_SRC" \ + -D FP_ONCHIP_MUL=1 \ + -D SHARED_CT_MUL_8X8=1 \ + -D SHARED_SQTAB_INIT=1 \ + -D 'LIB_SHARED_SQTAB_BASE=$BC00' \ + -o "$STAGING/mul_8x8_onchip.o" \ + "$STAGING/mul_8x8_onchip_glue.s" +else + rm -f "$STAGING/mul_8x8_onchip.o" +fi # --- 5. Re-archive into c64-https's expected location --- -# Order matches upstream's lib-p256-verify recipe so labels.txt diffs -# stay readable across bumps. +# Member list is taken from the upstream archive dynamically (v0.5.0 +# renamed/added members vs v0.3.0: ecdsa256_nocomb.o, precalc_manifest.o, +# lib_manifest_onchip.o, ...) minus the dropped members above, so this +# script no longer needs touching when upstream reshuffles objects. +MEMBERS=() +for m in $( "$AR65" t "$STAGING/upstream.a" ); do + [ -f "$STAGING/$m" ] || continue # dropped members + MEMBERS+=("$STAGING/$m") +done rm -f "$ARCHIVE" -"$AR65" a "$ARCHIVE" \ - "$STAGING/lib_version.o" \ - "$STAGING/lib_manifest.o" \ - "$STAGING/zp_config.o" \ - "$STAGING/constants.o" \ - "$STAGING/reu_config.o" \ - "$STAGING/fp256.o" \ - "$STAGING/mod256.o" \ - "$STAGING/curve256.o" \ - "$STAGING/points256_core.o" \ - "$STAGING/ecdsa256.o" \ - "$STAGING/data_p256.o" +"$AR65" a "$ARCHIVE" "${MEMBERS[@]}" # --- 6. Per-source byte counts (for the supervisor's PR description) --- { - echo "# nistcurves-p256.a per-source byte counts (ca65 .o file sizes)" - for src in lib_version lib_manifest zp_config constants reu_config \ - fp256 mod256 curve256 points256_core ecdsa256 data_p256; do - if [ -f "$STAGING/$src.o" ]; then - bytes=$(wc -c < "$STAGING/$src.o") - printf '%-24s %d bytes (.o)\n' "$src" "$bytes" + echo "# $(basename "$ARCHIVE") per-source byte counts (ca65 .o file sizes)" + for m in $( "$AR65" t "$STAGING/upstream.a" ); do + if [ -f "$STAGING/$m" ]; then + bytes=$(wc -c < "$STAGING/$m") + printf '%-24s %d bytes (.o)\n' "${m%.o}" "$bytes" fi done } > "$SIZES" From b9aabca1c5f3a314feedbe5a8f9a2fc22ce2b986 Mon Sep 17 00:00:00 2001 From: JC-000 <3798556+JC-000@users.noreply.github.com> Date: Mon, 20 Jul 2026 11:56:24 -0500 Subject: [PATCH 2/3] =?UTF-8?q?fix(boot):=20retain=20reu=5Fmul=5Finit=20un?= =?UTF-8?q?der=20onchip=20profile=20=E2=80=94=20C64U=20bridge=20quirk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Controlled A/B on C64U hardware (2026-07-20): an onchip-profile boot that issues no REU DMA loses the first UCI TCP_CONNECT at the FPGA bridge (command accepted, no error bit, DATA_AV never asserts, no SYN on the wire) — 0/8 e2e attempts across the day. The identical build with reu_mul_init retained passes 3/3 (47.0-47.8 s @ 64 MHz), and an interleaved re-run of the REU-quiet build still fails 0/2 in the same window. Boot-time REU traffic evidently settles shared expansion-I/O state the UCI bridge depends on. Cost: ~1 s of boot; REU banks 0/1 population is otherwise unused under the onchip profile. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01UZmMaxyyykvYbCvJAc4QVW --- src/boot.s | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/boot.s b/src/boot.s index fe12ed9..421b3c4 100644 --- a/src/boot.s +++ b/src/boot.s @@ -274,14 +274,16 @@ start: lda $01 and #%11111110 sta $01 - .ifndef USE_NISTCURVES_ONCHIP + ; Onchip note (issue #69): fp_mul generates rows on-chip and REU + ; banks 0/1 are never fetched, so this population pass is not + ; strictly needed under USE_NISTCURVES_ONCHIP. It is RETAINED + ; under both profiles anyway: C64U hardware testing (2026-07-20) + ; showed the first UCI TCP_CONNECT after a REU-quiet boot is + ; dropped by the FPGA bridge (0/6 e2e vs 3/6 for REU-profile + ; builds on the same flaky-WiFi day) — the boot-time REU DMA + ; traffic appears to settle shared expansion-I/O state. See + ; c64-test-harness#137 experiment log. jsr reu_mul_init - .else - ; Onchip profile (issue #69): fp_mul generates rows on-chip via - ; og_common/ct_mul_8x8 — REU banks 0/1 are never fetched, so the - ; ~128 KB population pass is skipped. Boot obligation for the - ; sibling shrinks to sqtab_init (called below via the shared path). - .endif ; Phase 3: stash both P-384 split overlay images in REU banks 6 ; and 7 from the .incbin'd staging blocks at $4200 and $E000. From 0c1961757c2aecb09ddcfb661f2647b1cf823789 Mon Sep 17 00:00:00 2001 From: JC-000 <3798556+JC-000@users.noreply.github.com> Date: Mon, 20 Jul 2026 11:58:50 -0500 Subject: [PATCH 3/3] =?UTF-8?q?docs:=20turbo-profile=20campaign=20results?= =?UTF-8?q?=20=E2=80=94=20crossover=20~34=20MHz,=20e2e=2047.5=20s=20@64?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Full 4-point clock sweeps on the C64U for v0.3.0-REU / v0.5.0-REU / v0.5.0-onchip: REU floor is ~42 s (supersedes the ill-conditioned 2-point 28.4 s figure), onchip floor 2.5 s, measured crossover ~34 MHz with an in-band bracket at 32 MHz. HTTPS e2e: 59.9 s @48 / 47.5 s @64 under the onchip profile — first sub-50 s handshake. Adds the USE_NISTCURVES_ONCHIP build flag docs and the C64U REU-quiet-boot connect quirk (c64-test-harness#137). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01UZmMaxyyykvYbCvJAc4QVW --- CLAUDE.md | 77 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 24 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 52d0f5e..9036668 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,6 +38,14 @@ Variables: CRYPTO_OVERLAY slot at PRG-load (UCI; mutually exclusive with USE_X25519_SIBLING / USE_OVERLAY_P384_EMBED) + - `USE_NISTCURVES_ONCHIP=1` — link the libs/nistcurves v0.5.0 + FP_ONCHIP_MUL turbo-profile P-256 verify + archive (no REU row-fetch DMA; ~34 MHz + crossover vs the default REU profile — see + the ECDSA wall-clock section). Mutually + exclusive with USE_X25519_SIBLING and both + overlay-embed flags (MUL_CODE occupies + CRYPTO_OVERLAY). - `CA65`, `LD65` — toolchain overrides - `VICE` — override the `make run` emulator @@ -267,6 +275,15 @@ that this codebase now handles: fix when the P-384 build unblocks). - **Wider fence floor** — see the delay-loop fence section above (INNER=217 accommodates both devices). + - **REU-quiet boot drops the first TCP_CONNECT** — a PRG whose boot + issues no REU DMA (the original onchip-profile gating skipped + reu_mul_init) loses its first UCI TCP_CONNECT at the FPGA bridge: + command accepted, no error bit, DATA_AV never asserts, no SYN on + the wire (0/8 e2e attempts vs 3/3 for the identical build with + reu_mul_init retained; interleaved control confirmed). Boot-time + REU traffic evidently settles shared expansion-I/O state. boot.s + therefore retains reu_mul_init under BOTH profiles. See + c64-test-harness#137. - **Multiple network interfaces** — Ethernet AND WiFi. GET_IPADDR (iface=0) returns 0.0.0.0 on a WiFi-connected box; `net_dhcp_acquire` probes iface 0..3 and takes the first lease. @@ -549,30 +566,42 @@ measured 2026-07-19 with the INNER=217 fence and boot-at-speed flow: - 64 MHz: **64.7 s** end-to-end — first >48 MHz datapoint. The 48→64 ratio (0.89) is well short of the ideal 0.75. -**Why turbo stops paying (measured 2026-07-19):** isolated -`ecdsa_verify_256` bench (`bench_ecdsa_u64e.py`, RFC 6979 vector, n=3 -medians on the C64U) gives 53.8 s @ 48 MHz / 47.4 s @ 64 MHz. Fitting -T(f) = D + C/f to both pairs: - - CPU-scaled C speed-invariant D - ECDSA verify 1.22 Gcycles 28.4 s (53% of wall @ 48) - full HTTPS e2e 1.59 Gcycles 39.8 s - - D is self-consistent to 0.1 s from either endpoint. The 28.4 s - verify-side D matches the sibling fp_mul's REU row-fetch traffic: - each 256-bit multiply DMAs up to 32 rows x 512 B = 16 KB from REU - banks 0/1, and REU DMA runs at the stock ~1 MB/s bus rate - regardless of CPU turbo (independently evidenced by the P-384 - overlay swap: 2x7.5 KB in ~16 ms at 48 MHz = ~1.04 us/B). ~28 s - = ~27 MB of row DMA per verify at that rate. The remaining - ~11.4 s of e2e D is UCI firmware/network latency. Above ~48 MHz - the verify is majority-DMA-bound; the projected ceiling with this - fp_mul is T(inf) ~= D = 28 s no matter the clock. Getting - meaningfully faster requires cutting REU traffic in the sibling - library (fetch-free on-chip square-table mul a la c64-x25519 — - breakeven vs row DMA is ~2.5 MHz — or narrower row transfers), - tracked at - [c64-nist-curves#69](https://github.com/JC-000/c64-nist-curves/issues/69). +**Why turbo stops paying — and the fix (campaign 2026-07-20):** the +REU's DMA rate is anchored to the ~1 MHz bus clock, so fp_mul's +row fetches put a speed-invariant floor under every verify. Filed as +[c64-nist-curves#69](https://github.com/JC-000/c64-nist-curves/issues/69); +upstream shipped the `FP_ONCHIP_MUL` turbo profile in v0.5.0, consumed +here via `make BACKEND=uci USE_NISTCURVES_ONCHIP=1`. Full 4-point +clock sweeps (`bench_ecdsa_u64e.py`, RFC 6979 vector, n=2 medians, +C64U, fits T(f)=D+C/f, residuals <=4.1%): + + config 16MHz 32MHz 48MHz 64MHz D(floor) C + v0.3.0 REU 72.1 57.9 53.7 47.5 41.8 s 491 MHz*s + v0.5.0 REU 72.2 57.7 53.7 49.3 42.9 s 471 MHz*s + v0.5.0 onchip 117.5 59.6 41.2 31.0 2.5 s 1839 MHz*s + + - The REU-profile floor is ~42 s (an earlier 2-point fit said + 28.4 s — that number was ill-conditioned and is superseded; at + 64 MHz the REU verify is ~88% floor). + - v0.5.0's REU path is performance-identical to v0.3.0. + - The onchip profile ELIMINATES the floor (D = 2.5 s) at the cost + of ~3.9x the CPU work; it scales 3.79x for a 4x clock. + - **Measured crossover: ~34 MHz** — REU wins below (57.7 vs + 59.6 s at 32 MHz, in-band bracket), onchip wins above. At stock + 1 MHz REU remains ~3x faster. Ship both profiles; note these + numbers are for the no-comb verify archive (the library's + comb-PRG numbers are ~2x faster in absolute terms). + + HTTPS e2e handshake wall-clock (C64U, local listener): + + profile 48 MHz 64 MHz + v0.3.0 REU 73.0 s 64.7-65.9 s + v0.5.0 onchip 59.9 s **47.5 s** (n=3: 47.0/47.6/47.8) + + 47.5 s @ 64 MHz is the first sub-50 s handshake — still above a + typical 10-30 s internet-server window, but upstream's shape-(2) + follow-up (c64-nist-curves#71, ~8 s comb verify projected) plus + the comb archive would land the handshake around ~25-30 s. v0.3.0's hot-path code is essentially unchanged from v0.2.0; the small wall-clock improvement is within measurement noise across