UCI NOP-sled fencing for turbo speed support - #20
Merged
Conversation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UCI banner: ULTIMATE 64 ELITE (UCI) → UCI NETWORKING
Insert 16 NOPs after every STA to UCI_CONTROL/UCI_CMD_DATA via a uci_fence macro defined in uci_regs.inc. At 48 MHz, 16 NOPs = 32 cycles = ~0.67us, giving the FPGA time to latch each write. At 1 MHz the overhead is 32us per write -- negligible for networking. Unlike the LDA UCI_STATUS fence approach, NOPs cannot interfere with the UCI state machine. 11 fence sites total: 8 in uci_cmd.s (uci_abort, uci_begin_cmd, uci_put_byte, uci_push_wait, uci_check_err CLR_ERR, uci_drain_resp NEXT_DATA, uci_drain_status NEXT_DATA, uci_ack) and 3 in net.s (hostname write loop, null terminator, send data loop). Test results on U64E hardware: - 1 MHz: PASS (HTTP 200, body "HELLO FROM TEST SERVER") - 48 MHz: FAIL (write-side fencing alone is insufficient; read-side timing also needs work at turbo -- follow-up needed) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three categories of changes for turbo-speed UCI register access: 1. Read-side fencing: uci_fence (48 NOPs) after every LDA from $DF1C-$DF1F — UCI_STATUS, UCI_ID, UCI_RESP_DATA, UCI_STATUS_DATA. Without this, reads return stale/glitched values at 8+ MHz. 2. Post-PUSH_CMD settle delay: 255-iteration delay loop in uci_push_wait so the FPGA has time to latch the command and assert CMD_BUSY before the CPU starts polling. At 48 MHz the original uci_fence alone was only 2 us — the FPGA needs ~27 us. 3. 16-bit spin-wait in uci_read_resp_bytes: DATA_AV may not be set immediately after push_wait returns (e.g. TCP_CONNECT waits for a full network round-trip). The old code bailed on the first DATA_AV=0; the new code spins up to 65536 iterations (~150 ms at 48 MHz) before giving up. Also bumped uci_fence from 16 to 48 NOPs (0.67 us -> 2 us at 48 MHz) and converted two short branches to JMPs to fix range errors caused by the larger fence expansions. Verified: both BACKEND=uci and default ip65 builds succeed. 1 MHz baseline HTTP test passes. 48 MHz testing blocked on U64 power cycle — to be verified after device recovery. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The FPGA needs ~38 µs of wall-clock time between UCI register accesses. A 48-NOP sled (2 µs at 48 MHz) was far too short, and even a 256-NOP sled (10.7 µs at 48 MHz, the max that fits in the code segment) was insufficient. Replace the NOP sled with a nested delay loop (OUTER=5, INNER=100, ~2525 cycles = ~52 µs at 48 MHz, ~2.5 ms at 1 MHz). Binary search found the minimum at OUTER=3 INNER=122 (~38.4 µs); the chosen values provide 35% margin. Verified passing at both 1 MHz and 48 MHz on U64E hardware. Also convert all branches that span a fence expansion to JMP trampolines, since even the 14-byte delay loop can exceed the 8-bit branch range in tight loops. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update CLAUDE.md to reflect that UCI networking works at 48 MHz turbo via a nested delay-loop fence (~52us per UCI register access). All four test scenarios pass on real U64E: 22B and 1460B responses at both 1 MHz and 48 MHz. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5 tasks
JC-000 added a commit
that referenced
this pull request
May 6, 2026
…#31) * chore(integration): phase 0 — pre-seed sibling allowlists + helper scripts Adds tools/integration/ helpers that downstream integration-phase agents will call. Also pre-seeds the allowlists of the three sibling crypto repos (c64-x25519, c64-ChaCha20-Poly1305, c64-nist-curves) so cross-repo agent dispatches no longer stall on bash-approval prompts. New scripts: - allowlist_preseed.sh / _merge_allow.py — merge allow + deny patterns into each sibling's .claude/settings.local.json (dedupe against existing; preserve order; append new at the end). - worktree_up.sh / worktree_merge.sh — wrap git worktree add and --no-ff merge-with-tests for Phase C parallel work. - build_x25519.sh / build_chacha20poly1305.sh / build_nistcurves.sh — stub placeholders; Phase C agents fill them in. - run_vice_suite.sh — one-arg wrapper around tools/run_all_tests.py honoring C64_SKIP_BUILD. - run_u64_suite.sh — intentional refuse-to-run; U64E runs must be dispatched via a live Phase F agent per feedback_never_inline_u64e. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(crypto): Phase C.0 — REU-overlay infra + cfg rework Adds shared crypto infrastructure (zp_canon.inc, reu_layout.inc, crypto_swap.s, mul_tables.s, crypto_init.s stub, zp_define.mk) that Phases C.1-.3 will populate with the sibling libs. cfg reshaped for the REU-overlay design per plan: - UCI: CRYPTO_RESIDENT \$6000-\$BFFF (24 KB), CRYPTO_OVERLAY \$4100-\$5FFF (7.5 KB) active. - ip65: CRYPTO_RESIDENT \$6000-\$BFFF (24 KB). CRYPTO_OVERLAY deferred until Phase C.1 lands an overlay image — ip65 can't shrink CRYPTO_RESIDENT below 24 KB without Profile B + sibling CODE savings that don't exist yet. No sibling libs linked yet; in-tree src/crypto/*.s unchanged. Both backends build; run_all_tests.py --skip-slow passes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(crypto): Phase C.1 — x25519 as REU overlay (UCI backend) Adds libs/x25519/ submodule (pinned to cd9a663) + integration build script tools/integration/build_x25519.sh that splits the c64-x25519 sibling sources into three segments when assembled into build/lib/x25519.a: OVERLAY_X25519 runtime code (fe25519/x25519/mul_8x8 body) loaded at boot into the CRYPTO_OVERLAY slot ($4200-$5FFF under UCI). Actual size 3484 B of the 7680 B budget (45%). CRYPTO_INIT_CODE boot-only routines (x25519_reu_mul_init, x25519_sqtab_init) placed in the pre-SQTAB half of CRYPTO_RESIDENT so they survive overlay swaps. DATA sibling's page-aligned RW buffers and constant lookup tables, placed below $A000. Under BACKEND=uci the Makefile filters out src/crypto/x25519.s + src/crypto/fe25519.s, defines USE_X25519_SIBLING, and links build/lib/x25519.a. ip65 continues using the in-tree sources until Phase C.2 frees enough CRYPTO_RESIDENT headroom. cfg/c64-https-uci.cfg splits CRYPTO_RESIDENT around a 1 KB hole at $7800-$7BFF reserved for the sibling's hardcoded sqtab_lo/hi tables (built at boot by `x25519_sqtab_init`). New DATA + CRYPTO_INIT_CODE segments flow into the split halves. src/crypto/shared/crypto_init.s now drives x25519_sqtab_init + x25519_reu_mul_init + crypto_overlay_stash_x25519 (DMAs the overlay image from $4200 to REU bank 2 offset $0100 so future P-256 / P-384 swaps can restore it). reu_layout.inc bumps overlay offsets to $0100 to dodge the 64-byte zero buffer at bank 2 offset $0000 used by `reu_clear_wide`. src/crypto/x25519_aliases.s aliases the legacy `fe_*` names to the sibling's `fe25519_*` exports for test-harness compatibility. fe_mul and fe_sqr are trampolines that append fe25519_reduce_final because the sibling intentionally skips final reduction in mul/sqr. Verification: - `make BACKEND=uci` clean build, OVERLAY_X25519 = 3484 B (≤ 8192 B). - CRYPTO_RESIDENT total fill 98.2% (23126 / 23552 B). - `make BACKEND=ip65` clean build, no regression. - `BACKEND=uci python3 tools/test_x25519.py` — 71/71 pass. - `BACKEND=ip65 python3 tools/test_x25519.py` — 71/71 pass (baseline). - `python3 tools/test_entropy.py` under ip65 — 7/7 pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(crypto): Phase C.2 — relocate aux crypto code to LOADER_OVERFLOW Rescopes Phase C.2: the original ccp sibling integration overflows CRYPTO_RESIDENT by ~20 KB (measurement, not estimate) and is abandoned — in-tree chacha20/poly1305/aead/word32 stay permanently. Instead, reorganize memory to open headroom for Phase C.3: - New CRYPTO_AUX_CODE segment for SHA-256, HMAC-DRBG, ecdsa_verify dispatcher (always-resident, addressable from anywhere). - CRYPTO_AUX_CODE + TLS_CODE now load into NET_CODE (LOADER_OVERFLOW neighborhood) under UCI. Under ip65, only the CRYPTO_AUX_CODE rename happens — it still loads into CRYPTO_RESIDENT because ip65's NET_CODE is already 88% full with the ip65 blob and cannot absorb either TLS_CODE (~1.8 KB) or CRYPTO_AUX_CODE (~3.1 KB). UCI fill deltas: - NET_CODE: 23.4% → 83.3% (absorbed 1.8 KB TLS_CODE + 3.1 KB CRYPTO_AUX_CODE) - CRYPTO_RESIDENT ($6000-$77FF, 6 KB): 100% → 70.8% (TLS_CODE left; ~1.8 KB reclaimed) - CRYPTO_RESIDENT_2 ($7C00-$BFFF, 17 KB): 97.8% → 80.1% (~3.1 KB reclaimed) ip65 fill deltas: unchanged (CRYPTO_AUX_CODE routes to CRYPTO_RESIDENT, same region as before). Backend divergence documented in both cfg files. No test regressions: 251/253 passing on both backends (the 2 chacha20/poly1305 failures pre-date this commit and are unrelated). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(crypto): Phase C.3 — nist-curves P-384 primitives (build artifacts) Adds the libs/nistcurves/ submodule (pinned at 1e72e6c) and the build plumbing for a narrow P-384 integration under BACKEND=uci: - build/lib/nistcurves-p384.a emits OVERLAY_P384 containing ONLY the three variable-base primitives that TLS would consume: ec_point_double_384, ec_point_add_384, ec_jacobian_to_affine_384 plus their fp384/mod384 helpers. Fixed-base ec_scalar_mul_384 + ec_precompute_384 and the Lim-Lee anchor tables are sed-stripped from the sibling sources since they're unused here and would bloat the overlay. Size: 5,879 B (≤ 7,680 B ceiling). - src/crypto/p384_force_link.s forces ld65 to pull the archive members via a 6-byte reference table in CRYPTO_AUX_CODE. - tools/test_p384_symbols.py exercises the primitives against NIST P-384 reference values computed from the generator (2G, 3G, 17G, Jacobian->affine). P-256 ECDSA stays in-tree under both backends pending upstream work on variable-base scalar-mult and big-endian I/O (JC-000/c64-nist-curves#17). The USE_NISTCURVES_P384 Makefile gate is present but commented: with both OVERLAY_X25519 (3.4 KB) and OVERLAY_P384 (5.7 KB) active, ld65 stacks them sequentially in CRYPTO_OVERLAY ($4200-$5FFF, 7.5 KB) and overflows by 1.6 KB. Architecturally max(x25519,p384)=5.7 KB fits the slot, but the cfg needs `run=CRYPTO_OVERLAY, load=<staging>` for one overlay plus a contiguous file-backed staging region to hold the displaced image. That restructure exceeds Phase C.3's stop-on-overflow hard rule and is deferred pending supervisor OK. Uncomment the two CA65FLAGS/SIBLING_LIB_ARCHIVES lines in Makefile once the cfg is extended to enable the integration end-to-end. ip65 and UCI builds both succeed with the gate commented (p384_force_link.s is inert when USE_NISTCURVES_P384 is undefined). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(crypto): Phase C.3b — P-384 as external overlay image (no PRG link) Previous Phase C.3 landed a P-384 archive but couldn't link it alongside x25519 due to ld65's load= segment concatenation (two segments with load=CRYPTO_OVERLAY stack rather than overlay). New approach: P-384 is smoke-test-only, so keep it out of the production PRG entirely. A new cfg/p384-overlay.cfg + build script tools/integration/build_nistcurves_p384_bin.sh link the archive standalone to extract: build/lib/overlay-p384.bin (8 KB raw OVERLAY_P384 image, padded with $00) build/labels-p384.txt (VICE labels for P-384 symbols + DATA buffers at $C000-$C635 inside TCP_BUF) Makefile target `p384-overlay` produces both; the main PRG build under BACKEND=uci is unchanged (production PRG still does NOT link nistcurves-p384.a — the USE_NISTCURVES_P384 gate stays commented). Production PRG size: 47105 B before and after. tools/test_p384_symbols.py now: - Skips cleanly under BACKEND=ip65. - Uploads the 8 KB image to REU bank 2 offset $4100 in two 4 KB halves staged via TCP_BUF ($C000-$CFFF — networking is off), using a 55-byte SEI/LDA/STA trampoline injected at $0340 that reads DMA params from $0380. - Calls crypto_swap_to_p384 — REU→$4200 DMA, then restores REU registers ($DF02/$DF03=$6600, $DF07/$DF08=512, $DF0A=0) to the "mul-row FETCH config" that x25519's reu_fetch_mul_row expects (fp_mul_384 / fp_sqr_384 only overwrite $DF05 / $DF06 / $DF01). - Exercises ec_point_double_384 / ec_point_add_384 / ec_jacobian_to_affine_384 against NIST P-384 generator vectors. Known issue: fp_mul_384 verifies correctly (2*3=6 smoke-tested) but fp_sqr_384 hangs on any nonzero input in this standalone link, causing ec_point_double_384 (which calls ec_sqrp_384 -> fp_sqr_384) to time out on Test 1. The test infrastructure (overlay upload, swap, REU restore, ZP/fp_src wiring, readback) is verified working via the fp_mul_384 path; the root cause of the fp_sqr hang has not been identified yet — full details in the module docstring. Build artifacts: - Main UCI PRG: 47105 B (unchanged). - overlay-p384.bin: 8192 B. - labels-p384.txt: 96 entries covering ec_point_double_384, ec_point_add_384, ec_jacobian_to_affine_384, fp_* helpers, ec384_p1/p2/p3 buffers, ec384_affine_x/y, etc. - x25519 non-regression: tools/test_x25519.py passes 71/71. * test(crypto): Phase E — crypto_init regression test Boot-only sanity check that crypto_init seeds: - Shared sqtab tables (sqtab_lo, sqtab_hi at $7800/$7A00) - x25519 REU mul-row stash (row 42 spot-check via DMA pull-back to C64 $C000) - x25519 overlay correctly paged into live slot at $4200-$5FFF, with idempotent re-swap Narrowed from the original plan: ccp Shoup + nist-curves P-256/P-384 precompute samples were dropped because those integrations were deferred (Profile A ccp too large; P-256 blocked on upstream variable-base scalar-mult; P-384 is external-image-only). UCI runs the three checks; ip65 skips cleanly (no sibling libs). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(exports): promote cc20_data_ptr, cc20_remain, zp_ptr to labels.txt Phase F surfaced two unit-test failures under both backends: test_crypto.py and test_http.py were FATAL on `cc20_data_ptr` and `zp_ptr` lookups, cascading to a later failure on `cc20_remain`. All three symbols are equates in src/constants.inc without any `.export` directive, so they never reached build/labels.txt and Labels.from_file() couldn't resolve them. Promote the three here in src/exports.s, following the same pattern Phase C.1 used for fe_src1/fe_src2/fe_dst. Baseline improvement: run_all_tests.py --skip-slow goes from 251/253 to 253/253 under ip65. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(tools/uci): defensive skip of non-C labels in _load_labels() Phase C.1 added REU_OVERLAY_{X25519,P256,P384} labels at offsets $020100/$022100/$024100. ld65 emits these as "al XXXXXX .name" (no "C:" prefix) because they're outside the 16-bit C64 code space. The hand-rolled _load_labels() parsers in tools/uci/test_https_local.py and tools/uci/bench_ecdsa_u64e.py crashed with ValueError on these lines (split(":", 1) on "022100" returns a 1-element list). Skip non-C lines — the UCI harness scripts only need CPU addresses and the REU offsets aren't useful for them anyway. Upstream c64-test-harness Labels.from_file() got the proper fix in JC-000/c64-test-harness#62 (0.12.3) — parses both forms into one map. A follow-up can migrate these scripts to use the upstream parser once Labels grows a public iteration API. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(tools/uci): migrate _load_labels() to c64_test_harness.Labels Replaces the 8-line hand-rolled parser with a single Labels.from_file() call in both tools/uci/test_https_local.py and tools/uci/bench_ecdsa_u64e.py. This is now possible because the upstream Labels class gained: - JC-000/c64-test-harness#62: parse non-C-prefixed label lines ("al XXXXXX .name") for REU offsets and other >16-bit addresses. - JC-000/c64-test-harness#64: implement collections.abc.Mapping so dict(labels), .items(), .keys(), .values(), iteration all work. Behavioral change: _load_labels() now returns REU_OVERLAY_* entries too (1183 labels vs ~1180 before). Callers looking up by name are unaffected; any code iterating over every label gets three extra keys that it didn't previously see. No such iteration callers exist in either script today. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(submodules): bump nist-curves + switch URLs to GitHub HTTPS libs/nistcurves 1e72e6c → dfdfb59 picks up: - PR #19 (JC-000/c64-nist-curves): variable-base ec_scalar_mul_var for both P-256 and P-384, fp_mod_mul_n / fp_mod_mul_n_384, packaged ecdsa_verify_256 / ecdsa_verify_384 (160/240-byte BE structs, C=0 valid / C=1 invalid), fp_reverse32/48 BE↔LE helpers. Closes issue #17. - PR #20: reu_fetch_mul_row relocated from main.s into mul_8x8.s so standalone-overlay links (like c64-https) reach it. Closes issue #18 (fp_sqr_384 hang). Also switch libs/x25519 and libs/nistcurves .gitmodules URLs from local file paths to HTTPS — recent git releases reject "file://" transports for submodule fetches (CVE-2022-39253 mitigation), which was blocking `git submodule update --remote`. Integration of the new ECDSA API lands in a follow-up phase. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(phase-f): narrow x25519 TLS hang to Montgomery ladder Root-cause investigation under BACKEND=uci. Progressive `tls_last_state` probing (reverted) bisected the hang to the `jsr x25519_scalarmult` call inside `tls_ecdh_generate_keypair`: $82 reached (pre tls_ecdh_generate_keypair) $91 reached (post privkey-copy) $92/$93 reached (post fe25519_copy + pre clamp) $94 reached (pre x25519_scalarmult) $9E never seen (post return) x25519_scalarmult in isolation (tools/test_x25519.py) still passes, so the defect is stateful: something in the boot or early-handshake path corrupts state between x25519's init and its first TLS invocation. Three leads documented (ZP overlap from legacy sqtab_init at src/boot.s:211, REU register state, IRQ racing ZP) plus a small recommended experiment: gate the legacy sqtab_init call the same way reu_mul_init is already gated under USE_X25519_SIBLING. No functional code changes — diagnostic instrumentation was reverted after capturing the bisection. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * revert(crypto): roll back x25519 REU overlay integration (C.1) Phase F validation showed x25519_scalarmult hangs deterministically in TLS context under BACKEND=uci at 48 MHz — scalarmult enters and never returns (tls_last_state=$94 pre-jsr, never reaches $9E post). Standalone tools/test_x25519.py passes 71/71, so the defect is stateful / context-dependent. Rolling back libs/x25519 submodule + overlay machinery while keeping the rest of Phase C (shared infra in Phase C.0, aux code relocation in C.2, P-384 external smoke test in C.3b, submodule URL fix, Labels migration). In-tree src/crypto/{fe25519,x25519}.s return as the active x25519 implementation under both backends. Specific changes: - Remove -D USE_X25519_SIBLING=1 and the build/lib/x25519.a rule from Makefile; drop the x25519.s + fe25519.s filter-out. - cfg/c64-https-uci.cfg: revert CRYPTO_RESIDENT to a single contiguous $6000-$BFFF region; drop the $7800-$7BFF sqtab hole, CRYPTO_RESIDENT_2 split, X25519_SQTAB region, OVERLAY_X25519 segment, and DATA segment declarations. CRYPTO_OVERLAY region stays for the external P-384 smoke test. - cfg/c64-https-ip65.cfg: drop the OVERLAY_X25519 placeholder. - src/crypto/shared/crypto_init.s: simplify back to a single mul_tables_init dispatch, drop the .ifdef USE_X25519_SIBLING branch (REU mul init + overlay stash). - src/crypto/shared/crypto_swap.s: drop crypto_swap_to_x25519 (remaining dispatcher serves the P-384 external smoke test only). - src/crypto/shared/reu_layout.inc: drop REU_OVERLAY_X25519 equate. - src/boot.s: drop all .ifndef USE_X25519_SIBLING guards — reu_mul_init, the REU table inits, and their BSS come back unconditionally. - src/data.s: drop all .ifndef USE_X25519_SIBLING guards — fe_p, x25_basepoint, mul38_lo/hi_tab, mul_dma_lo/hi, sqtab_lo/hi, fe_tmp1..4, x25_scalar/u/result/ladder state, mul_cached_a/_src2_buf come back unconditionally. - src/crypto/poly1305.s: drop .ifndef guard around poly_prod_lo/hi, mul_8x8 exports (these are back in-tree). - src/crypto/x25519_aliases.s: delete (alias layer no longer needed). - tools/integration/build_x25519.sh: delete (sibling overlay builder). - tools/test_crypto_init.py: delete (Phase E regression test was overlay-specific; shared sqtab coverage is retained in tools/test_crypto.py). - libs/x25519/ submodule + .gitmodules entry: remove. Verification: - make BACKEND=ip65: clean build. - make BACKEND=uci: clean build. - C64_SKIP_BUILD=1 python3 tools/run_all_tests.py --skip-slow: 253/253. Filed upstream follow-up: JC-000/c64-x25519#33. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(crypto): Phase C.4 — sibling P-256 ECDSA (c64-nist-curves) Replaces in-tree ecdsa_{points,fp,mod,curve}.s with the sibling's ecdsa_verify_256 (JC-000/c64-nist-curves PR #19 — variable-base scalar-mult, mod-order multiply, packaged ECDSA verify, BE-struct I/O with fp_reverse32 helper). src/crypto/ecdsa_verify.s collapses from the ~673 B in-tree verifier body into a ~234 B thin TLS dispatcher that banks ROM out, dispatches on ecdsa_curve_id (P-384 still stubbed), hands the 160-byte packed r|s|h|Qx|Qy BE struct to ecdsa_verify_256, and forwards its C flag. src/data.s lays ecdsa_sig_r/s + ecdsa_hash + ecdsa_pubkey_x/y out as five contiguous 32-byte BE fields so the dispatcher can pass ecdsa_sig_r as the struct base with no explicit pack step. Sibling code lives in CRYPTO_CODE (fp256 + mod256 + points256 + ecdsa256), CRYPTO_RODATA (curve256 constants), and CRYPTO_BSS (data buffers). ~7.3 KB of always-resident code + ~1.3 KB of BSS; no overlay mechanism. The dispatcher provides an ec_scalar_mul shim that redirects u1*G (which the sibling's ecdsa_verify_256 dispatches to a Lim-Lee 8-way fixed-base comb) to ec_scalar_mul_var with G pre-loaded as the base point, avoiding the 16 KB REU bank-2 precompute table that the comb would otherwise need at boot. Shared mul infrastructure (mul_cached_a, mul_src2_buf, mul_dma_lo/hi, sqtab_lo/hi, mul_8x8, reu_fetch_mul_row, poly_prod_lo/hi) is reused from the in-tree src/data.s + src/crypto/poly1305.s + src/boot.s; the sibling's copies are excluded from the archive to avoid collisions. src/data.s grows mul_src2_buf from 32 to 35 bytes so the sibling fp256's 4x-unrolled mul over-read into j=32..34 reads zeros. REU DMA register equates (reu_reu_hi/bank/command) are promoted to linker- visible exports via src/exports.s so fp256's .import resolves. Memory-fit work: the sibling + dispatcher pushed CRYPTO_RESIDENT over its 24 KB budget under BOTH backends. Under UCI we added a new CRYPTO_AUX_CODE2 segment (carrying only hmac_drbg) alongside the existing CRYPTO_AUX_CODE; both route to NET_CODE under UCI so Phase C.2's aux-code-in-NET_CODE arrangement is preserved. Under ip65 NET_BSS is split into the blob-owned $4000-$4F8B region and a NET_BSS_TAIL slack region at $4F8C-$5FFF ($1074 B); TLS_CODE + sha256's CRYPTO_AUX_CODE relocate there while hmac_drbg's CRYPTO_AUX_CODE2 rides the NET_CODE tail. BSS reclaim: removed six now-dead ecdsa scratch buffers (ecdsa_verify_tmp, ev_u1/u2/point_save and their P-384 twins, ~432 B) from src/data.s and pruned unused sibling result registers (fp_r1/r2/r3 + fp_tmp2/3/4 + fp_inv_iter, ~194 B) from data_p256_raw.s. Validated: - run_all_tests.py --skip-slow: 253/253 - test_x509.py (ECDSA full sweep): 11/11, valid P-256 verify under VICE warp in ~30 s - tools/uci/test_https_local.py on U64E at 48 MHz: PASS, body "HELLO FROM TLS SERVER" decrypted, handshake progress=0x03 → progress=0x05 sentinel in 81.9 s wall-clock (~3 s under the 82 s Phase F ECDSA bench baseline) In-tree ecdsa_{curve,fp,mod,points}.s remain on disk but are filtered out of CRYPTO_SRCS_ALL in the Makefile; Phase G will delete them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(phase-f): trace capture analysis — bus-tap undersampling Captured before Phase C.4 landed. Explains why trace.bin couldn't pinpoint the x25519 hang loop (~48:1 undersampling at 48 MHz). Retained for future diagnosis attempts on JC-000/c64-x25519#33. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(phase-g): delete orphan in-tree ECDSA primitives Phase C.4 stopped linking the in-tree P-256 primitives (ecdsa_{curve,fp,mod,points}.s) in favour of the sibling c64-nist-curves integration via build/lib/nistcurves-p256.a. The files remained on disk for reference but have been unused since 06bf475. Delete them now, along with the legacy ACME-era P-384 .asm stubs (ecdsa_*_384.asm) which were never assembled under ca65. The corresponding Makefile filter-out list becomes a no-op and is simplified back to a plain wildcard. Verified: both `make BACKEND=ip65` and `make BACKEND=uci` build clean after deletion. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(phase-g): update CLAUDE.md for Phase C.4 end state - Crypto ABI: point P-256 at libs/nistcurves sibling via ecdsa_verify_256 / ec_scalar_mul_var; note X25519 stays in-tree pending JC-000/c64-x25519#33; note ChaCha20-Poly1305 stays in-tree permanently. - MEMORY requirements: REU banks 0-1 no longer used by shipped build (x25519 overlay rolled back); banks 4-7 reserved for P-384 external overlay smoke test. Note crypto_init currently bootstraps mul_tables_init only. - P-384 entry: sibling primitives buildable via `make p384-overlay` (Phase C.3b), but the target has a pre-existing unresolved- symbol bug (ec_base384_x/y in points384_raw.s); TLS-level P-384 verify still stubbed. - End-to-end HTTPS status: handshake wall-clock now 81.9 s on U64E 48 MHz under the sibling P-256 path. - Summary of recent fixes: add bullet 11 for Phase C.4 (06bf475). - Known issues: add X25519 overlay deferred (#33) and `make p384-overlay` unresolved-symbol bug. - ECDSA benchmark: note 81.9 s handshake wall-clock post-Phase-C.4; speedup path becomes a libs/nistcurves submodule bump. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
UCI register fencing for turbo speed support (1-48 MHz verified on U64E hardware).
The Ultimate 64 Elite's UCI FPGA needs ~38us between register accesses regardless of CPU clock speed. At stock 1 MHz this is naturally satisfied. At turbo speeds (4-48 MHz) the CPU outruns the FPGA, causing double-latched writes and stale reads that corrupt the UCI command protocol.
Fix: A nested delay-loop macro (
uci_fenceinuci_regs.inc) inserted after every read/write to UCI registers ($DF1C-$DF1F). Parameters: UCI_FENCE_OUTER=5, UCI_FENCE_INNER=100, providing ~2525 cycles (~52us at 48 MHz, 35% margin). 14 bytes per fence site, 24 sites total.Test results (real U64E at 192.168.1.81)
Files changed
src/net/uci/uci_regs.inc—uci_fencemacro with configurable OUTER/INNER constantssrc/net/uci/uci_cmd.s— fences after all UCI register accesses in shared primitivessrc/net/uci/net.s— fences after direct UCI register accesses in net adapterCLAUDE.md— document fence approach and 48 MHz supportOriginally posted by @JC-000 on 2026-04-16