KDI v0.1.0 — frame format 2 (self-describing container)
Frame format 2. decode needs no descriptor: every length, lane identity, element width and cadence is on the wire.
⚠️ Not yet emitted by gateware. No shipping bitstream produces format 2, and the device leaves theclean_framecapability bit clear — so a host that requires the clean frame will correctly refuse to bind to today's hardware. The format is published ahead of the emitter, on purpose, so that it is fixed before anything binds to it. The control plane in this descriptor is hardware-verified.
The container
32 B header magic | format=2 | flags | timestamp u48 | frame_words | layout |
hdr_words | n_sections | run_id | contract_rev | desc_words
descriptors n × 16 B, stride taken FROM THE WIRE so it can grow again
lane ids Σ n_lanes × u16 · bodies element-major (1/16/32/64-bit) · CRC-32 trailer
magicis a resync anchor only, never a validity test. Validity is CRC + declared length + timestamp continuity. Host-side that is one call:zlib.crc32(frame) == 0x2144DF1C.- A new module type is a new
kind, skipped bysection_words— additive, no decoder change, no version bump. An unknown format is skippable too, byframe_words:magic,formatandframe_wordsare frozen at their offsets for every future format. - Cadence is an exact rational (
tick_num/tick_den; 30 kHz =10000/3). Rate is never normative in this contract — the timebase is, so a hardware rate change needs no contract change. - One shared 48-bit timebase at 10 ns, sampled per frame, epoch per run paired with
run_id. Aligning two streams is exact integer subtraction — no sync channel, no per-stream linear fit. The frame-to-frame delta jitters (3333/3334 at 30 kHz) and there is deliberately no constant-delta invariant, because at 30 kS/s one is not implementable. - Digital input is bit-packed: 16 lines cost one word, every bit named by its own lane id — no host-side slot→bit formula.
to_deviceis reserved (declared, unimplemented) — the container is direction-agnostic.
Testable in both directions
vectors/golden_frame.json now ships 7 negative frames alongside the good one, each with the normative reason token it must be rejected with (crc_err, reserved_bits, tick_sane, timestamp_top16, desc_words, section_words). A decoder can be proven to reject what it must, not merely accept what it should.
Known limitation, published deliberately
Digital-in levels are sampled at the end of the frame that carries them, so they trail their own timestamp by up to one frame period, and the offset varies with the enabled lane count. Derived from RTL, not bench-measured. A future emitter latches at frame start and this note tightens.
Breaking
Format 1 frames no longer decode. Safe now because nothing has bound. kdi: stays 0.1 — it is the command-set version, and no command changed.
Conformance: 12/12 pass. Source: keyvast-fpga@d567775.