Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
279 changes: 167 additions & 112 deletions docs/osc-servo-transport.md

Large diffs are not rendered by default.

25 changes: 24 additions & 1 deletion firmware/lib/drivers/src/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,28 @@
/// Trim chain-pair pipeline counters: where do BURST-food pairs die between
/// the break stamp and a window verdict? One field per exit of
/// [`ServoBus::on_drift_break`]'s decision ladder, in ladder order.
///
/// Wire format for the debug-link dump: 26 little-endian 32-bit words in
/// field order, 104 bytes; `verdict_err`, `poll_ppm` and the four `tw_*`
/// fields are signed (`struct` format `<15IiI3IiI4i`).
#[repr(C)]
pub struct TrimProbe {
/// Drift stamps taken (fresh break wakes with a break byte newest).
/// Break stamps taken: one per verified frame with a stamp latched for
/// its break, however many frames a wake resolved.
pub stamps: u32,
/// Frames verified: `stamps + unstamped`.
pub frames: u32,
/// Frames verified with no stamp latched for their break: the detector
/// raised no overflow for it (or a stale skip ran the ring dry).
pub unstamped: u32,
/// Stamps skipped as older than the previous stamp plus the ring
/// distance allows: orphans (a re-fire, a CRC-failed frame's break).
pub stale: u32,
/// Stamps dropped by a CAL ruler mark or a rescue.
pub cleared: u32,
/// Break services whose resolver drive hit its per-wake frame bound:
/// the ladder lagged the wire by more than `FRAMES_PER_WAKE` frames.
pub bound_hits: u32,
/// Stamp with no predecessor (first after boot/restart) -- no pair.
pub no_prev: u32,
/// Pair rejected: NO verified frame between the stamps (a coalesced or
Expand Down Expand Up @@ -58,6 +76,11 @@ pub struct TrimProbe {
impl TrimProbe {
pub const ZERO: Self = Self {
stamps: 0,
frames: 0,
unstamped: 0,
stale: 0,
cleared: 0,
bound_hits: 0,
no_prev: 0,
span_none: 0,
span_many: 0,
Expand Down
49 changes: 31 additions & 18 deletions firmware/lib/drivers/src/bus/clock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ struct CalRun {
span: u32,
}

/// What the wire delivered between two break-wake stamps (sec 9.3): the drift
/// What the wire delivered between two break stamps (sec 9.3): the drift
/// tracker pairs the stamps only around exactly ONE CRC-verified SILENT
/// instruction -- the one frame shape whose break-to-break span is
/// host-clocked end to end. Anything solicited puts a responder's
Expand All @@ -68,7 +68,7 @@ pub struct ClockTracker {
pub(super) pending_cal: Option<(u16, u8)>,
cal: Option<CalRun>,
cal_ready: bool,
// Differential drift tracker (sec 9.3): the last break-wake stamp + ring
// Differential drift tracker (sec 9.3): the last break stamp + ring
// cursor, the one silent instruction verified since it, the seam
// baseline (mean pair error of the first window after a restart - the
// CAL anchor), and the current window.
Expand Down Expand Up @@ -171,26 +171,23 @@ impl ClockTracker {
self.pending_cal = None;
}

/// Drift chain-pair (sec 9.3): adjacent break-wake stamps bracketing one
/// Drift chain-pair (sec 9.3): adjacent break stamps bracketing one
/// silent verified instruction measure `seam + drift*span` -- the host's
/// queuing seam is unknown but stationary, so the mean pair error over
/// the first window after a CAL IS the seam (baseline), and every later
/// window reads its shift from that one anchor. Anything constant -
/// seam, FE latch offset, entry-path residue - dies in the subtraction;
/// only changes survive, and the sanity band catches the non-thermal
/// ones. The steps this tracker applies stay IN the measurement: a
/// window reads the clock's residual against the anchor, never an
/// increment against the tracker's own last decision, so window noise
/// cannot integrate into a walk - a step a noisy window took, the next
/// window reads back out (DES pin `noisy_silent_flood_holds_the_cal_anchor`;
/// seam, detector latch offset - dies in the subtraction; only changes
/// survive, and the sanity band catches the non-thermal ones. The
/// steps this tracker applies stay IN the measurement: a window reads
/// the clock's residual against the anchor, never an increment against
/// the tracker's own last decision, so window noise cannot integrate
/// into a walk - a step a noisy window took, the next window reads
/// back out (DES pin `noisy_silent_flood_holds_the_cal_anchor`;
/// re-baselining per decision walked the bench trim -1 -> +13 on plain
/// flood food).
pub fn on_drift_break(&mut self, now: u32, cursor: u16, len: usize, tpb: u32) {
// The same break again (a re-fired wake): its stamp stands.
if matches!(self.drift_prev, Some((_, c)) if c == cursor) {
return;
}
let prev = self.drift_prev.replace((now, cursor));
/// flood food). `stamp` is the break detector's hardware latch
/// (transport sec 8): a wake's entry lag never enters a pair.
pub fn on_drift_break(&mut self, stamp: u32, cursor: u16, len: usize, tpb: u32) {
let prev = self.drift_prev.replace((stamp, cursor));
let seen = core::mem::replace(&mut self.drift_seen, VerifiedSpan::None);
crate::bench::trim_probe(|p| p.stamps += 1);
let (t1, c1) = match prev {
Expand Down Expand Up @@ -225,7 +222,7 @@ impl ClockTracker {
return;
}
let span = (footprint as u32).wrapping_mul(tpb);
let err = now.wrapping_sub(t1).wrapping_sub(span) as i32;
let err = stamp.wrapping_sub(t1).wrapping_sub(span) as i32;
if err.unsigned_abs() > span >> TRIM_GATE_SHIFT {
crate::bench::trim_probe(|p| p.gated += 1);
return; // a real pause (inter-chain gap), not a seam
Expand Down Expand Up @@ -261,6 +258,22 @@ impl ClockTracker {
self.drift_win_n = 0;
}

/// The earliest tick a stamp for the break at `cursor` can carry: the
/// previous stamp plus the ring distance between the two breaks in wire
/// time, less the gate (the wire delivers no byte in under a byte-time,
/// and a legal clock offset stays inside the gate). A stamp before it
/// belongs to no break at `cursor` - an orphan (a parked low's re-fire,
/// a CRC-failed frame's break) for the caller to skip. None without a
/// previous stamp: nothing bounds the first.
pub fn stamp_floor(&self, cursor: u16, len: usize, tpb: u32) -> Option<u32> {
let (t1, c1) = self.drift_prev?;
if len == 0 {
return None;
}
let span = (ring_wrap(cursor as usize + len - c1 as usize, len) as u32).wrapping_mul(tpb);
Some(t1.wrapping_add(span - (span >> TRIM_GATE_SHIFT)))
}

/// The drift pair is still open for its one verified frame -- the shape
/// classification is only worth computing for that first frame.
pub fn pair_open(&self) -> bool {
Expand Down
27 changes: 0 additions & 27 deletions firmware/lib/drivers/src/bus/framer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,33 +255,6 @@ impl Framer {
self.hunting
}

/// The frame at the anchor, if it is whole in the ring. Nothing else
/// moves: a hunt, a partial or an empty ladder is left to
/// [`Self::resolve`].
pub fn settle(&mut self, ring: &[u8], cursor: u16, now: u32) -> Option<FrameSpan> {
let len = ring.len();
if len == 0 {
return None;
}
let received = dist(cursor, self.anchor, len);
let a = self.anchor as usize;
if received < HEADER_SPAN_BYTES || ring[a] != BREAK_RING_BYTE {
return None;
}
let len_byte = ring[ring_wrap(a + 2, len)];
let footprint = osc_protocol::wire::footprint(len_byte) as u16;
if len_byte < 3 || received < footprint {
return None;
}
self.anchor = ring_wrap(a + footprint as usize, len) as u16;
self.frontier = Frontier::idle();
Some(FrameSpan {
anchor: a as u16,
footprint,
packet_end: now,
})
}

/// The next frame has begun: its break byte is ringed at the anchor
/// (position from the stream, with every earlier frame settled).
/// Returns the index just past that byte - a break's drift stamp
Expand Down
Loading
Loading