Repository navigation
Description
Claim
PLAUSIBLE ONLY — needs hardware to confirm or refute. Filing so it is not
lost, not asserting it is a live bug.
net_tcp_send has three exit paths that return C=1 without ever reaching
uci_ack, i.e. without the DATA_ACC that returns the FPGA's state machine to
idle. Per the mechanism in #144, a transaction left un-accepted makes the next
PUSH_CMD hit else error_busy <= '1' and be silently dropped — which is
$86 on the $DF1C status bit, a code CLAUDE.md records as having been seen
in the field on otherwise-passing runs.
Verified against origin/master at dc06095, src/net/uci/net.s:
| exit | lines | reached when |
|---|---|---|
@sb_push's bail |
:817-818 |
uci_push_wait timed out waiting for the SOCKET_WRITE response |
@sb_err_drain_to |
:832-834 |
uci_drain_resp/uci_drain_status timed out on the UCI_ERR_SEND_FAIL path |
@sb_ok_drain_to |
:852-854 |
the same two drains timed out on the success path |
All three are wall-clock-timeout paths with net_last_error already set to
UCI_ERR_WAIT_TIMEOUT ($89), so they are honest failures — the question is
only what state they leave the device in for the next command.
The recovery primitive exists and is not wired to any of them:
$ grep -n 'jsr uci_abort' src/net/uci/net.s
107: jsr uci_abort
One call site, in net_init. Nothing on an error path calls it, so the only
thing that clears the state machine after a mid-transaction bail is the next
boot.
Why this is not a #144 reopen
#144's three named defects are all closed on master and I verified each
independently before closing it (see
#144 (comment)): the
per-byte pulse is gone, all 11 uci_ack sites run
drain_resp -> drain_status -> ack in that order, and the SOCKET_WRITE path
has its own accept on both its completing exits. This is a fourth thing,
about timeout paths rather than about queue semantics, and it is defensible as
designed — a wedged FPGA may not answer an accept either, and issuing one into
an unknown state is not obviously safer than not.
What would settle it
Hardware, and it is cheap to look for because the symptom is already
instrumented:
- Supports the claim: after any run that records
$89 UCI_ERR_WAIT_TIMEOUT
on a send, the next transaction reports$86. CLAUDE.md notes$86has
exactly one setter incommand_protocol.vhd(thePUSH_CMD-while-not-idle
branch), so it always means our push was rejected — the two appearing in that
order would be the signature. - Refutes it:
$89on a send with no$86following, or$86observed on
runs with no preceding send timeout.
CLAUDE.md's note that $86 has been seen on passing runs (github.com HTTP 200,
2026-08-28) is the reason this is worth checking rather than dismissing: a
passing run that carries one recovered from a real rejected push, and this is
a candidate source for it.
If confirmed, the fix is presumably jsr uci_abort before the sec; rts on
the two drain-timeout exits (@sb_err_drain_to, @sb_ok_drain_to) and
possibly on @sb_push's. uci_abort already writes ABORT and burns a settle
delay, so it is the intended primitive; it just has one caller.
No hardware was available to this session.
Found by adversarial review while confirming #144's closure.
Activity
rigs: take the device lock through the shared budget, all 13 sites
Merge pull request #223 from JC-000/fix/194-data-acc-timeout-exits
fix(uci): wait for ABORT's reset; abort on every connect/close timeout (