Skip to content

Commit 80dff5b

Browse files
JC-000claude
andauthored
land #58: C64 Ultimate support (stacked-merge follow-up) (#59)
* docs: refresh CLAUDE.md + ingestion plan for PR #46/#51-55 merged state CLAUDE.md was partially updated during the session that produced PRs #51-#55; several sections survived with pre-bump claims that no longer match reality. This commit refreshes them in one pass: - Crypto ABI: bump x25519 sibling pin v0.4.0 -> v0.6.0; mark P-256 as c64-lib-contract SPEC-aligned with the make -C libs/<X> lib-VARIANT integration pattern. - MEMORY requirements: rename CRYPTO -> CRYPTO_HOT (UCI) / CRYPTO_RESIDENT (ip65) per the W1 hot/cold split; cross-link c64-lib-contract repo + docs/library-ingestion-architecture.md. - Build vars: document USE_X25519_SIBLING + EMBED_P256_OVERLAY. - UCI Memory layout: collapse to a pointer at the post-W1 table below. - Known issues: rewrite the X25519 sibling block for v0.6.0 + the post-W1 split; replace MemoryPolicy note with the centralized VICE helper (tools/_vice_helpers.py::default_vice_config). - ECDSA P-256 wall-clock: update to 82.1 s under v0.3.0 (was 86.7 s under v0.2.0); drop the v0.2.0-era defensive-init footnote. - Memory layout: replace pre-W1 monolithic CRYPTO table with the full UCI + ip65 post-W1 layout; document the ip65 CRYPTO_COLD_SHADOW 1,662 B overflow tracked at c64-nist-curves#54. docs/library-ingestion-architecture.md gets a "Status as of 2026-05-23" appendix snapshotting which §3 library-side issues and §4 c64-https-side work items landed via PRs #51-#55 vs. are still in-flight (Task #12 / P-384 wall-clock / CI bot). No source / cfg / integration script / submodule changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(uci): C64 Ultimate support — 64 MHz e2e + WiFi DHCP + fence retune (#58) * fix(uci): widen uci_fence to keep 35% margin at 64 MHz The C64 Ultimate tops out at 64 MHz, where the old 2525-cycle fence (INNER=100, tuned for 48 MHz) shrinks to ~39.5 us — only ~4% over the FPGA's ~38 us floor. Bump UCI_FENCE_INNER to 131 (~3300 cycles): 51.6 us at 64 MHz (35.7% margin), 68.8 us at 48 MHz, ~3.3 ms at 1 MHz. Still negligible for networking at every speed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZmMaxyyykvYbCvJAc4QVW * feat(uci): interface-fallback DHCP acquire for C64 Ultimate WiFi The C64 Ultimate has Ethernet AND WiFi; GET_IPADDR(iface=0) returns 0.0.0.0 when the box is on WiFi, which the old single-shot net_dhcp_acquire surfaced as DHCP FAILED (UCI_ERR_NO_IP). Probe interface indices 0..3 and take the first non-zero lease; clean up (drain + ack) after a CMD_FAILED probe so the next one starts from idle. Clear net_last_error on success so no-lease residue from earlier probes doesn't sit next to a good acquire. Costs ~38 B in NET_CODE — boundary with NET_BSS_TAIL shifted by $40 ($1B26 -> $1B66); NET_BSS_TAIL occupancy is $625 so $75 slack remains. Verified on C64 Ultimate firmware 1.1.0 (WiFi): phase2_check PASS, IP acquired on iface 1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZmMaxyyykvYbCvJAc4QVW * fix(uci): retune uci_fence for C64 Ultimate burst floor The C64U (firmware 1.1.0, core 1.49) needs more inter-access time than the U64E's empirical ~38 us, and only under sustained CMD_DATA bursts: at 51.6 us spacing (INNER=131 @ 64 MHz) GET_IPADDR works but TCP_CONNECT's ~15-byte hostname push is silently lost (UCI_ERR_NO_SOCKET, no SYN ever leaves the box). Floor bracketed at 64 MHz: 51.6 us FAIL / 62.9 us PASS / 100 us PASS. Ship INNER=217 (5450 cycles = 85.2 us @ 64 MHz) for 35% margin over the worst-case floor; 113.5 us @ 48 MHz, ~5.5 ms @ 1 MHz — negligible for networking on both devices. E2e verified on C64U: 64 MHz PASS (64.7 s), 48 MHz PASS (73.0 s). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZmMaxyyykvYbCvJAc4QVW * test(uci): set turbo before boot — C64U runtime-switch quirk On the C64 Ultimate, a REST config CPU-speed change while the PRG is running can glitch the UCI bridge so the next pushed command is silently lost — reproduced 2x as UCI_ERR_NO_SOCKET on the first TCP_CONNECT after a 1->64 MHz switch, even with a 100 us fence (1->48 happened to survive). Move set_turbo_mhz before reset/run_prg so the machine boots at target speed and never switches mid-session; also makes boot speed deterministic instead of inheriting the previous run's config. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZmMaxyyykvYbCvJAc4QVW * docs: C64 Ultimate notes + 48/64 MHz benchmarks New bench device: C64 Ultimate Starlight (10.53.21.158, fw 1.1.0, core 1.49, NTSC, WiFi). Documents the 64 MHz speed enum, the runtime speed-switch quirk, the wider fence floor, multi-interface GET_IPADDR, REU-disabled default + set_reu() incompatibility, and the new P-256 e2e numbers: 73.0 s @ 48 MHz, 64.7 s @ 64 MHz. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZmMaxyyykvYbCvJAc4QVW * bench+docs: 48/64 MHz verify scaling analysis — REU DMA is the wall bench_ecdsa_u64e.py: the post-W1 MemoryPolicy blocks the bench's DMA writes of vector inputs into CRYPTO_BSS (now in CRYPTO_COLD_SHADOW); pass override= with a reason instead of widening the policy. Measured on C64U: ecdsa_verify_256 53.8 s @ 48 MHz / 47.4 s @ 64 MHz. T(f) = D + C/f fits both e2e and verify pairs to 0.1 s: verify D = 28.4 s speed-invariant vs C = 1.22 Gcycles CPU-scaled. D matches fp_mul's REU row-fetch volume (~27 MB/verify) at the stock ~1 MB/s DMA rate that turbo does not scale (consistent with the 16 ms overlay-swap datapoint). e2e adds ~11.4 s of UCI/network D. Conclusion documented in CLAUDE.md: >48 MHz clocks are mostly wasted on this fp_mul; the fix is library-side. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZmMaxyyykvYbCvJAc4QVW --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent bd42e6e commit 80dff5b

6 files changed

Lines changed: 184 additions & 31 deletions

File tree

‎CLAUDE.md‎

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -224,12 +224,63 @@ corrupt the UCI command protocol.
224224
**Fix:** A nested delay-loop macro `uci_fence` (defined in
225225
`src/net/uci/uci_regs.inc`) is inserted after every read/write to UCI
226226
registers `$DF1C-$DF1F`. Parameters: `UCI_FENCE_OUTER = 5`,
227-
`UCI_FENCE_INNER = 100`, yielding ~2525 cycles (~52 us at 48 MHz,
228-
35% safety margin). 14 bytes per fence site, 24 fence sites total
229-
(11 write + 13 read). At 1 MHz the same loop costs ~2.5 ms per
227+
`UCI_FENCE_INNER = 217`, yielding ~5450 cycles (85.2 us at 64 MHz —
228+
35% margin over the C64 Ultimate's empirically-bracketed floor;
229+
113.5 us at 48 MHz). 14 bytes per fence site, 24 fence sites total
230+
(11 write + 13 read). At 1 MHz the same loop costs ~5.5 ms per
230231
access — negligible for networking.
231232

232-
48 MHz turbo is fully supported and verified on real U64E hardware.
233+
The C64 Ultimate (firmware 1.1.0, core 1.49) needs MORE inter-access
234+
time than the U64E's ~38 us, and the tighter floor only bites under
235+
sustained CMD_DATA bursts: at 51.6 us spacing DHCP/GET_IPADDR works
236+
but TCP_CONNECT's ~15-byte hostname push is silently lost
237+
(UCI_ERR_NO_SOCKET, firmware never opens the socket). Floor bracketed
238+
at 64 MHz: 51.6 us FAIL / 62.9 us PASS — see the tuning matrix in
239+
`uci_regs.inc`. The U64E-era INNER=100 (52.6 us at 48 MHz) was never
240+
observed failing on U64E but sits below the C64U floor; INNER=217 is
241+
safe on both devices at every speed.
242+
243+
48 MHz turbo is fully supported and verified on real U64E hardware;
244+
64 MHz is supported and verified on the C64 Ultimate (see "C64
245+
Ultimate notes" below).
246+
247+
### C64 Ultimate notes
248+
249+
A second UCI-capable device joined the bench 2026-07-19: a **C64
250+
Ultimate "Starlight Edition"** (product "C64 Ultimate", firmware
251+
1.1.0, FPGA 122, core 1.49, NTSC mode, WiFi-connected) at
252+
10.53.21.158 — `U64_HOST=10.53.21.158`. Differences from the U64E
253+
that this codebase now handles:
254+
255+
- **64 MHz turbo** — the C64U's CPU Speed enum adds "64" (and drops
256+
" 5"). c64-test-harness's `CPU_SPEED_BY_MHZ` carries the superset.
257+
E2e verified at 64 MHz (see benchmarks below).
258+
- **Runtime speed-switch quirk** — changing CPU speed via the REST
259+
config API while the PRG is running can glitch the UCI bridge so
260+
the NEXT pushed command is silently lost (reproduced 2x as
261+
UCI_ERR_NO_SOCKET on the first TCP_CONNECT after a 1→64 switch,
262+
even with a 100 us fence; a 1→48 switch happened to survive).
263+
`tools/uci/test_https_local.py` now sets turbo BEFORE
264+
reset/run_prg so the machine boots at target speed and never
265+
switches mid-session. Mirror that pattern in new scripts
266+
(`test_https_local_p384.py` still uses the old late-switch order —
267+
fix when the P-384 build unblocks).
268+
- **Wider fence floor** — see the delay-loop fence section above
269+
(INNER=217 accommodates both devices).
270+
- **Multiple network interfaces** — Ethernet AND WiFi. GET_IPADDR
271+
(iface=0) returns 0.0.0.0 on a WiFi-connected box;
272+
`net_dhcp_acquire` probes iface 0..3 and takes the first lease.
273+
- **REU ships disabled** — fresh C64U config has `RAM Expansion
274+
Unit: Disabled`; without it sibling-nistcurves `fp_mul` silently
275+
computes garbage (same failure mode as the VICE `-reu` gotcha).
276+
Enable via REST config write. NOTE: the C64U has no `"REU"`
277+
Cartridge preset (presets list is just `[""]`), so the harness's
278+
`set_reu()` helper — which also sets `Cartridge: "REU"` — is
279+
incompatible as written; set `RAM Expansion Unit: Enabled`
280+
directly. Config writes are runtime-only (revert on power cycle).
281+
- Same UCI register map, ID byte $C9, command set, and DeviceLock /
282+
enable_uci flow as the U64E — boot_check/phase2/phase3/e2e scripts
283+
run unmodified.
233284

234285
### Memory layout under UCI
235286

@@ -488,7 +539,42 @@ Under the current `libs/nistcurves@v0.3.0` pin (post-PR #55,
488539
c64-lib-contract-aligned) the U64E 48 MHz handshake measures **82.1 s**
489540
end-to-end (verified 2026-05-20 against the local listener; the prior
490541
v0.2.0 measurement was 86.7 s, and the pre-Phase-C.4 in-tree path was
491-
~110 s). v0.3.0's hot-path code is essentially unchanged from v0.2.0;
542+
~110 s).
543+
544+
On the **C64 Ultimate** (10.53.21.158, see "C64 Ultimate notes"),
545+
measured 2026-07-19 with the INNER=217 fence and boot-at-speed flow:
546+
547+
- 48 MHz: **73.0 s** end-to-end (faster than the U64E's 82.1 s at
548+
the same clock — different FPGA core)
549+
- 64 MHz: **64.7 s** end-to-end — first >48 MHz datapoint. The
550+
48→64 ratio (0.89) is well short of the ideal 0.75.
551+
552+
**Why turbo stops paying (measured 2026-07-19):** isolated
553+
`ecdsa_verify_256` bench (`bench_ecdsa_u64e.py`, RFC 6979 vector, n=3
554+
medians on the C64U) gives 53.8 s @ 48 MHz / 47.4 s @ 64 MHz. Fitting
555+
T(f) = D + C/f to both pairs:
556+
557+
CPU-scaled C speed-invariant D
558+
ECDSA verify 1.22 Gcycles 28.4 s (53% of wall @ 48)
559+
full HTTPS e2e 1.59 Gcycles 39.8 s
560+
561+
D is self-consistent to 0.1 s from either endpoint. The 28.4 s
562+
verify-side D matches the sibling fp_mul's REU row-fetch traffic:
563+
each 256-bit multiply DMAs up to 32 rows x 512 B = 16 KB from REU
564+
banks 0/1, and REU DMA runs at the stock ~1 MB/s bus rate
565+
regardless of CPU turbo (independently evidenced by the P-384
566+
overlay swap: 2x7.5 KB in ~16 ms at 48 MHz = ~1.04 us/B). ~28 s
567+
= ~27 MB of row DMA per verify at that rate. The remaining
568+
~11.4 s of e2e D is UCI firmware/network latency. Above ~48 MHz
569+
the verify is majority-DMA-bound; the projected ceiling with this
570+
fp_mul is T(inf) ~= D = 28 s no matter the clock. Getting
571+
meaningfully faster requires cutting REU traffic in the sibling
572+
library (fetch-free on-chip square-table mul a la c64-x25519 —
573+
breakeven vs row DMA is ~2.5 MHz — or narrower row transfers),
574+
tracked at
575+
[c64-nist-curves#69](https://github.com/JC-000/c64-nist-curves/issues/69).
576+
577+
v0.3.0's hot-path code is essentially unchanged from v0.2.0;
492578
the small wall-clock improvement is within measurement noise across
493579
runs. It is fine for the local listener used by the e2e harness (600 s
494580
budget, ample headroom). Further speedups live in the sibling

‎cfg/c64-https-uci.cfg‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,16 @@ MEMORY {
7676
# but only used ~$125 — folding it into NET_BSS_TAIL frees ~370 B
7777
# for the libs/nistcurves spill-over). ip65 has had an equivalent
7878
# NET_BSS_TAIL since Phase C.4 — UCI now adopts the same pattern.
79-
NET_CODE: start = $2000, size = $1B26, file = %O, define = yes, fill = yes, fillval = $00;
79+
# Grown $40 (iface-fallback loop in net_dhcp_acquire, C64U WiFi
80+
# support) — donated from NET_BSS_TAIL, whose occupancy tops out at
81+
# $625 (LIB_NISTCURVES_P256_BSS) leaving $B5 slack pre-donation.
82+
NET_CODE: start = $2000, size = $1B66, file = %O, define = yes, fill = yes, fillval = $00;
8083
# NET_BSS_TAIL spans NET_CODE end through the start of CRYPTO_OVERLAY,
8184
# subsuming the historical UCI_BSS_REGION at $4000-$41FF (UCI_BSS now
8285
# rides on the head of this combined region). Holds 1.7 KB total —
8386
# ld65 distributes UCI_BSS (~293 B) + LIB_NISTCURVES_P256_BSS (~1.5
8487
# KB) + a stub UCI_BSS_REGION definition.
85-
NET_BSS_TAIL: start = $3B26, size = $06DA, file = %O, define = yes, fill = yes, fillval = $00;
88+
NET_BSS_TAIL: start = $3B66, size = $069A, file = %O, define = yes, fill = yes, fillval = $00;
8689
# UCI_BSS_REGION kept as a zero-size alias so any external tooling
8790
# that resolves the symbol via labels.txt still finds it (the cfg's
8891
# `define = yes` emits __UCI_BSS_REGION_START__ etc.).

‎src/net/uci/net.s‎

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -333,22 +333,36 @@ net_poll:
333333
; =============================================================================
334334
; net_dhcp_acquire — read the firmware-assigned IP via UCI GET_IPADDR
335335
;
336-
; The U64E firmware runs DHCP autonomously before the PRG is launched, so
337-
; our job is to READ the result, not to perform DHCP ourselves. Sequence:
336+
; The Ultimate firmware runs DHCP autonomously before the PRG is launched,
337+
; so our job is to READ the result, not to perform DHCP ourselves. Sequence
338+
; (per interface):
338339
;
339-
; wait_idle -> begin_cmd(NETWORK) -> put(CMD_GET_IPADDR) -> put(iface=0)
340+
; wait_idle -> begin_cmd(NETWORK) -> put(CMD_GET_IPADDR) -> put(iface)
340341
; -> push_wait -> check_err -> read 12 bytes -> drain resp
341342
; -> drain status -> ack
342343
;
344+
; Interface fallback: the U64E has a single interface (index 0), but the
345+
; C64 Ultimate has Ethernet AND WiFi — a box on WiFi returns 0.0.0.0 for
346+
; index 0. We probe indices 0..NET_DHCP_MAX_IFACE-1 and take the first
347+
; one with a non-zero lease. A CMD_FAILED on an out-of-range index is
348+
; cleaned up (drain + ack) and treated like "no lease on this interface".
349+
;
343350
; The 12-byte response layout is IP(4) + Netmask(4) + Gateway(4). We copy
344-
; the first 4 bytes into net_local_ip. If all four are zero we treat the
345-
; call as having failed (no DHCP lease) and return C=1.
351+
; the first 4 bytes into net_local_ip. If all probed interfaces yield a
352+
; zero IP we return C=1 with net_last_error = UCI_ERR_NO_IP (or
353+
; UCI_ERR_CMD_FAILED if the last probe failed at the command layer).
346354
;
347355
; Clobbers: A, X, Y
348356
; Output: C=0 on success (net_local_ip populated), C=1 on failure
349357
; (net_last_error contains the specific failure code).
350358
; =============================================================================
359+
NET_DHCP_MAX_IFACE = 4 ; probe interface indices 0..3
360+
351361
net_dhcp_acquire:
362+
lda #$00
363+
sta @iface_idx ; SMC-style local, no-ZP file convention
364+
365+
@next_iface:
352366
jsr uci_wait_idle
353367
bcs @dhcp_wait_to ; FPGA wedged — bail with C=1
354368

@@ -358,9 +372,9 @@ net_dhcp_acquire:
358372
lda #UCI_CMD_GET_IPADDR
359373
jsr uci_put_byte
360374

361-
; Interface index 0 — matches the build_get_ip helper in
362-
; c64-test-harness/src/c64_test_harness/uci_network.py.
363-
lda #$00
375+
; Interface index — 0 first (only iface on U64E; Ethernet on the
376+
; C64 Ultimate), then 1.. (C64U WiFi) until one has a lease.
377+
lda @iface_idx
364378
jsr uci_put_byte
365379

366380
jsr uci_push_wait
@@ -370,12 +384,24 @@ net_dhcp_acquire:
370384
jsr uci_check_err
371385
bcc @no_err
372386

387+
; Command failed for this interface (e.g. index out of range on
388+
; single-interface firmware). Clean up response/status state so
389+
; the next probe starts from idle, then advance.
373390
lda #UCI_ERR_CMD_FAILED
374391
sta net_last_error
392+
jsr uci_drain_resp
393+
bcs @dhcp_wait_to
394+
jsr uci_drain_status
395+
bcs @dhcp_wait_to
396+
jsr uci_ack
397+
jmp @advance
398+
375399
@dhcp_wait_to:
376400
sec
377401
rts
378402

403+
@iface_idx: .byte 0
404+
379405
@no_err:
380406
; Read the 12-byte response into uci_ipaddr_resp.
381407
lda #<uci_ipaddr_resp
@@ -403,7 +429,8 @@ net_dhcp_acquire:
403429
dex
404430
bpl @copy_ip
405431

406-
; If all four bytes are zero the firmware has no lease yet.
432+
; If all four bytes are zero this interface has no lease —
433+
; fall through to probe the next one.
407434
lda net_local_ip+0
408435
ora net_local_ip+1
409436
ora net_local_ip+2
@@ -412,10 +439,21 @@ net_dhcp_acquire:
412439

413440
lda #UCI_ERR_NO_IP
414441
sta net_last_error
415-
sec
442+
443+
@advance:
444+
inc @iface_idx
445+
lda @iface_idx
446+
cmp #NET_DHCP_MAX_IFACE
447+
bcc @next_iface
448+
sec ; every interface probed, none had a lease
416449
rts
417450

418451
@have_ip:
452+
; Clear any residue from earlier no-lease probes (e.g. iface 0's
453+
; UCI_ERR_NO_IP on a WiFi-connected C64U) so diagnostics don't
454+
; read a stale error next to a successful acquire.
455+
lda #$00
456+
sta net_last_error
419457
clc
420458
rts
421459

‎src/net/uci/uci_regs.inc‎

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,29 @@ UCI_TARGET_NETWORK = $03 ; network stack
5757
; the FPGA needs). Instead we use a nested delay loop:
5858
; total cycles ≈ OUTER * (INNER * 5 + 5)
5959
;
60-
; Tuned empirically via binary search at 48 MHz:
60+
; Tuned empirically via binary search at 48 MHz on U64E:
6161
; OUTER=3 INNER=121 (~1830 cycles, ~38 µs at 48 MHz) = FAIL
6262
; OUTER=3 INNER=122 (~1845 cycles, ~38.4 µs at 48 MHz) = PASS (minimum)
63-
; OUTER=5 INNER=100 (~2525 cycles, ~52 µs at 48 MHz) = chosen (35% margin)
63+
; OUTER=5 INNER=100 (~2525 cycles, ~52 µs at 48 MHz) = U64E-era choice
6464
;
65-
; At 1 MHz the overhead is ~2.5 ms per access — acceptable for networking.
65+
; Re-tuned at 64 MHz on the C64 Ultimate (firmware 1.1.0, core 1.49),
66+
; whose floor is HIGHER than the U64E's ~38 µs — and only bites under
67+
; sustained CMD_DATA bursts (TCP_CONNECT hostname push; short commands
68+
; like GET_IPADDR survive spacings that lose the connect command):
69+
; OUTER=5 INNER=131 (3300 cycles, 51.6 µs at 64) = FAIL (NO_SOCKET —
70+
; DHCP fine, TCP_CONNECT never latched)
71+
; OUTER=5 INNER=160 (4025 cycles, 62.9 µs at 64) = PASS
72+
; OUTER=5 INNER=255 (6400 cycles, 100.0 µs at 64) = PASS
73+
; OUTER=5 INNER=217 (5450 cycles, 85.2 µs at 64) = chosen — 35%
74+
; margin over the worst-case floor (62.9 µs);
75+
; 113.5 µs at 48 MHz, ~5.5 ms at 1 MHz
76+
;
77+
; At 1 MHz the overhead is ~5.5 ms per access — acceptable for networking.
6678
; The macro preserves A and X via the stack, costing ~14 bytes per call
6779
; site (vs 256 for the NOP sled that still wasn't enough).
6880
; =============================================================================
6981
UCI_FENCE_OUTER = 5 ; outer loop iterations
70-
UCI_FENCE_INNER = 100 ; inner loop iterations
82+
UCI_FENCE_INNER = 217 ; inner loop iterations
7183

7284
.macro uci_fence
7385
pha ; save A

‎tools/uci/bench_ecdsa_u64e.py‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -180,16 +180,20 @@ def _prepare_vector_buffers(transport: Ultimate64Transport,
180180
"""DMA-write all ecdsa_* input buffers from a JSON vector dict."""
181181
# Zero-fill first (buffers are 48 bytes each to handle P-384 hypothetical);
182182
# verify_256 reads the first 32 only but pre-zeroing is paranoid-safe.
183+
# These buffers legitimately live inside CRYPTO_COLD_SHADOW
184+
# ($A000-$BFFF) post-W1, which the MemoryPolicy reserves — override
185+
# with a reason rather than widening the policy for everyone.
186+
_OVR = "ecdsa vector input buffers (CRYPTO_BSS in CRYPTO_COLD_SHADOW)"
183187
zero48 = bytes(48)
184188
for name in ("ecdsa_hash", "ecdsa_sig_r", "ecdsa_sig_s",
185189
"ecdsa_pubkey_x", "ecdsa_pubkey_y"):
186-
transport.write_memory(labels[name], zero48)
190+
transport.write_memory(labels[name], zero48, override=_OVR)
187191

188192
# Write the 32-byte big-endian values verbatim.
189193
def _write_be32(name: str, hex_str: str) -> None:
190194
raw = bytes.fromhex(hex_str)
191195
assert len(raw) == 32, f"{name}: expected 32 bytes, got {len(raw)}"
192-
transport.write_memory(labels[name], raw)
196+
transport.write_memory(labels[name], raw, override=_OVR)
193197

194198
_write_be32("ecdsa_hash", vec["hash"])
195199
_write_be32("ecdsa_sig_r", vec["sig_r"])
@@ -198,9 +202,9 @@ def _write_be32(name: str, hex_str: str) -> None:
198202
_write_be32("ecdsa_pubkey_y", vec["pubkey_y"])
199203

200204
# Control bytes.
201-
transport.write_memory(labels["ecdsa_curve_id"], bytes([0]))
202-
transport.write_memory(labels["ecdsa_hash_len"], bytes([32]))
203-
transport.write_memory(labels["ecdsa_sig_len"], bytes([32]))
205+
transport.write_memory(labels["ecdsa_curve_id"], bytes([0]), override=_OVR)
206+
transport.write_memory(labels["ecdsa_hash_len"], bytes([32]), override=_OVR)
207+
transport.write_memory(labels["ecdsa_sig_len"], bytes([32]), override=_OVR)
204208

205209

206210
def _run_single(transport: Ultimate64Transport,

‎tools/uci/test_https_local.py‎

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,6 +1185,19 @@ def main() -> int:
11851185
)
11861186
return 3
11871187

1188+
# --- Set turbo BEFORE booting the PRG ---
1189+
# C64 Ultimate quirk (firmware 1.1.0, core 1.49): a runtime CPU
1190+
# speed change via the REST config API can glitch the UCI bridge
1191+
# so that the next pushed command is silently lost — observed as
1192+
# UCI_ERR_NO_SOCKET on the first TCP_CONNECT after a 1->64 MHz
1193+
# switch (2x reproduced; 1->48 happened to survive). Booting at
1194+
# the target speed avoids the mid-session switch entirely and
1195+
# also makes boot speed deterministic (it used to be whatever
1196+
# the previous run left in the device config).
1197+
print(f"Setting turbo to {TURBO_MHZ} MHz...")
1198+
set_turbo_mhz(client, TURBO_MHZ)
1199+
time.sleep(0.5)
1200+
11881201
print("Resetting machine...")
11891202
client.reset()
11901203
time.sleep(2.5)
@@ -1200,12 +1213,9 @@ def main() -> int:
12001213
if init_flag == 0:
12011214
print("WARNING: net_initialized is 0 — auto-init may have failed")
12021215

1203-
# --- Flip to 48 MHz turbo BEFORE we trigger the stub ---
1204-
print(f"Setting turbo to {TURBO_MHZ} MHz...")
1205-
set_turbo_mhz(client, TURBO_MHZ)
1206-
time.sleep(0.5)
1216+
# (Turbo was set before boot — see the C64U quirk note above.)
12071217

1208-
# --- Start 6510 debug-stream capture (after turbo, before trigger) ---
1218+
# --- Start 6510 debug-stream capture (before trigger) ---
12091219
if DEBUG_CAPTURE_ENABLED:
12101220
try:
12111221
set_debug_stream_mode(client, DEBUG_MODE_6510)

0 commit comments

Comments
 (0)