Skip to content

UCI handshake: DATA_ACC is an accept, not a per-byte advance — three defects it causes #144

Description

@JC-000

Filing so the UCI adapters can align. c64-wireguard found and fixed three handshake defects that all trace to one misunderstanding of the Command Interface. c64-https's adapter shares the ancestry, so it likely shares some of these.

Verified against Gideon's own 6502 client, the Register API doc, and the FPGA source; the fixes are hardware-verified on a U64 Elite (fw 3.15).

The misunderstanding

We treated control bit 1 ($02, which we call UCI_CTRL_NEXT_DATA) as "advance to the next byte". It is not. It is DATA_ACC — the data-accept handshake that ends a transfer. Register API v1.1 §2.4.1, verbatim:

"DATA_ACC: Writing a '1' to this register bit tells the communication layer that all data from the Ultimate was accepted. … Writing to this bit also causes the transfer of the data/status queues to be aborted and reset. Thus, the data response and status response queues will be empty after writing this bit."

Reads advance the pointers by themselvescommand_protocol.vhd, on the C64 read strobe:

when c_cif_slot_response =>  response_pointer <= response_pointer + 1;
when c_cif_slot_status   =>  status_pointer   <= status_pointer   + 1;

The canonical sequence (Gideon's software/6502/unsorted/uci_wedge.s, and every wrapper in the reference C lib) is:

push command -> read response until DATA_AV clears -> read status until STAT_AV clears -> ONE DATA_ACC

Nothing is written to $DF1C between the two reads. Both queues are live simultaneously in the data state, and the single accept releases both.

The three defects

1. Per-byte accept flushes the queue. Our drain loops read a byte then pulsed $02. Because that resets both queues, we captured exactly one byte and discarded the rest. It presented as "the firmware only sends one status character".

2. The accept is mandatory on every transaction — including writes. It returns the state machine to idle. Without it the next PUSH_CMD hits else error_busy <= '1' and is silently dropped. Worse, command-byte writes are not state-gated: a rejected command still advances command_pointer, and only the Ultimate resets it — so one missing accept corrupts every subsequent command. In our tree SOCKET_WRITE returns no response payload, so its path skipped the response drain and its only accept was hiding inside the status drain; removing the per-byte pulse silently removed it. Worth checking your write path specifically.

3. Status read after the accept reads nothing. On most of our paths the response drain ran first and its pulse destroyed the status line before anything read it. Status must be read before the accept.

What we changed

  • Removed per-byte $02 writes from both drain loops; reads auto-advance.
  • Exactly one uci_ack per transaction, on every exit path (12 paths, 11 sites).
  • Status drained (and captured) before the accept.
  • Added the missing accept to the SOCKET_WRITE path.

Hardware-verified: repeated UDP echo round-trips pass, 893-byte reads arrive whole, and the status line now reads in full — 04,DATAGRAM TRUNCATED: 1420 where before we saw a single 0.

Two things worth knowing

The comment is the trap. Ours said UCI_CTRL_NEXT_DATA = $02 ; advance response data FIFO — wrong on both counts, and it propagated. Check uci_regs.inc for the same wording.

A too-long command is silently corrupted. The command buffer has the identical end-of-buffer guard as the response buffer: writing more than 896 command bytes overwrites byte 895 repeatedly and command_length saturates, with no rejection. Relevant to any chunked SOCKET_WRITE.

Also: multi-block reads just landed upstream

GideonZ/1541ultimate#806 (merged 2026-08-27) returns UDP datagrams up to 1472 bytes by splitting the reply across Data More blocks. The gateware always supported this; only the software side was missing. If c64-https ever grows a UDP path, the read cap is no longer 893.

Cross-referenced from GideonZ/1541ultimate#802 and c64-wireguard#46.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions