Ugn grooming - #1318
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
c4256c2 to
adf98b1
Compare
92f9673 to
e723a27
Compare
A small directed, weighted graph backed by a Data.Map adjacency structure, with the Bellman-Ford shortest-path / negative-cycle machinery (potentials, relabel, negativeCycle, cycleSum). Node and weight types are generic. This is the reusable foundation for UGN grooming: deciding whether a set of UGNs is physically allowed reduces to "no negative cycle". Kept standalone (no UGN specifics) so it can be tested and reused independently.
UGNs are signed per-link clock/frame offsets (edge weights). They are physically
allowed iff the sum around every directed cycle is nonnegative, and one boot can be
relabeled onto another iff a difference-constraint system is feasible. Both reduce to
the negative-cycle / potential machinery from Bittide.Graph.Weighted.
Provides isAllowed and groomCorrection (Infeasible | Feasible{correction, frames}),
using Integer weights to avoid overflow during relaxation. Pure and demo-agnostic.
A small Wishbone peripheral with one management-unit-writable register (release_cycle, default maxBound, with a description) that takes a local counter and holds its output reset asserted until localCounter > release_cycle, then deasserts (monotonic counter, so it stays deasserted). This is how the large boot-time counter offset (the grooming relabel q, far too big for the depth-bounded elastic buffer) is realised: gating a domain's reset on a chosen cycle sets that domain's counter origin. Generic over the Wishbone width; unit test verifies the reset deasserts exactly at the written cycle.
A small Wishbone scratch peripheral with two read/write registers: a vector of n signed per-link corrections (init 0) and a valid flag (init False). The host computes the per-link grooming frame corrections and writes them here over GDB; the management-unit CPU polls valid, reads the corrections and applies them to its elastic buffers. Plain bus-accessible scratch (no fabric writes), mirroring the CaptureUgn idiom.
… the user core
Add both host-facing grooming peripherals to the generic management unit
(NmuInternalBusses 3 -> 5):
- UgnCorrections: the host writes per-link frame corrections for the MU CPU to
apply.
- TimedReset: the management unit chooses the local-counter cycle at which the
application leaves reset (the boot-offset relabel). It lives here, in the layer
around the user core, and exposes an `appReset` output.
`core` routes `appReset` into the user core via a new `UserCoreCircuit` argument. The
wire demo's user core uses it as the relabeled time base: an application counter that
starts at release and drives the programmable mux (UserCoreBusses 3 -> 2, since the
timed reset no longer sits on a user-core bus). The application counter stays in the
user core; the soft-UGN user core ignores the reset.
Host-side helpers for the grooming flow, placed beside the existing HITL utilities: GDB-based component HAL accessors (readCurrentTime, writeReleaseCycle, writeCorrections, readHardwareUgns, readAppCounter) in Utils.Gdb, and the UgnEdge conversions (ugnEdges, hardwareUgnEdges) in Utils.Ugn.
e723a27 to
9611e63
Compare
After printing the captured UGNs, the management unit polls the UgnCorrections peripheral's valid flag, then applies each link's frame correction to its elastic buffer as a single atomic wishbone write with hardware ack (rather than one frame at a time). A per-frame loop is vulnerable to concurrent Callisto adjustments between iterations, which shrink the net data_count change; a single write limits the Callisto race window to at most one adjustment, keeping the error to ±1 frame. It self-reports a success line the host waits on. The firmware half of the grooming apply path; the corrections values and the reset-release timing are computed and written by the host.
…rdware Diagnostic instrumentation to pin down why the canonical-gauge grooming breaks the XOR chain partway (nodes 0-2 pass, 3-7 fail) despite the schedule math being equivalent to main's proven generateSchedule and the frame path being exercised by an earlier green run. - UserCore: expose the application counter via a read-only Wishbone register (new AppCounterReadback device, UserCoreBusses 2 -> 3). With the app running, localCounter - app_counter == tReset for a node iff its timed-reset relabel released at the intended (relabeled) cycle. - Driver: after the app runs, read each node's app_counter + localCounter and print loc-app vs tReset, so a divergence pinpoints which node's reset did not land. This is instrumentation, not the fix; it isolates whether the failure is the Bellman-Ford reset relabel not landing on-device vs the frame corrections.
…golden UGNs for FifoSize=6 midpoint
9611e63 to
9f65e93
Compare
martijnbastiaan
left a comment
There was a problem hiding this comment.
We've run out of time on this one unfortunately. But here's all the feedback we've collected so far.
| -- SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| {- | | ||
| A small Wishbone peripheral that releases a reset at a software-chosen cycle. |
There was a problem hiding this comment.
This is a duplicate of ProgrammableMux. (Not exactly, but you know..)
| {- | | ||
| UGN grooming, expressed on top of "Bittide.Graph.Weighted". | ||
|
|
||
| UGNs (@\lambda_{i->j}@) are signed per-link clock/frame offsets, i.e. edge weights |
There was a problem hiding this comment.
The LaTeX style documentation doesn't do it for me: it neither looks good in the source code, nor does it render to anything with Haddock. I'd prefer it if it either:
- Renders properly in Haddock
- Looks good in the source through use of unicode (unicode in documentation is a-ok).
| that the UGNs changed too much. (Whether a set of UGNs is physically realisable at all — | ||
| no negative cycle — is 'Bittide.Graph.Weighted.negativeCycle'.) | ||
|
|
||
| The math (see the UGN grooming slides): with slack |
There was a problem hiding this comment.
What slides? IMO we should just drop the PDF in the repo, but we should ask @sanjaylall for permission.
| -- SPDX-FileCopyrightText: 2026 Google LLC | ||
| -- | ||
| -- SPDX-License-Identifier: Apache-2.0 |
There was a problem hiding this comment.
The commit message's body is weird: it doesn't assume the reader is familiar with UGNs, but it does just drop jargon like "difference-constraint system is feasible". Who is your target audience?
The last paragraph can be removed, it doesn't belong in the commit message body.
| This module provides 'groomCorrection': given a freshly measured @\lambda@ and a | ||
| previously stored @\lambda^{safe} = \lambda^{obs} + \varepsilon@, it finds a relabeling | ||
| @q@ and the frames to insert so the system is restored to @\lambda^{safe}@, or reports | ||
| that the UGNs changed too much. (Whether a set of UGNs is physically realisable at all — | ||
| no negative cycle — is 'Bittide.Graph.Weighted.negativeCycle'.) |
There was a problem hiding this comment.
This is way too short/jargony to be useful to the average digital designer. I think you should pull in more context on what UGN grooming is and why we're doing all this. Preferably write it yourself, not Claude. In my mind a good outline would be:
- What is UGN / UGN pair
- What differs between boots
- Why is that bad
- Why can't you just relabel
- Explain that we found a trick (+\varepsilon) which happens to get a set of equations we can easily solve using well-known algorithms.
- Mention that this is just a solution, but perhaps we'll find a "better" one in the future (e.g., one that can also remove frames).
| {- | Golden UGN graph: a full set of per-link UGNs (@λ = local - remote@) captured from a | ||
| passing CI boot of this rig, stored as a 'UgnEdge' list keyed by DNA-derived node id and | ||
| port. This is the stored reference every boot is groomed onto. | ||
|
|
||
| The UGNs carry their boot-time counter offsets; 'lambdaSafe' relabels them into a | ||
| small, non-negative gauge before use. | ||
| -} | ||
| goldenUgns :: [UgnEdge] | ||
| {- FOURMOLU_DISABLE -} | ||
| goldenUgns = canonicalizeUgn |
There was a problem hiding this comment.
Not really a fan of this approach, given that bittide is still under development. Can it be replaced by a (nightly) test that just does two wire demos -- where the second one reuses the schedule from the first one?
| -- Relabel this boot onto the stored target latencies, using the grooming | ||
| -- utilities ("Bittide.Instances.Hitl.Utils.UgnGrooming"): build UGN | ||
| -- edges from the measured counters, groom them (Bellman-Ford) onto | ||
| -- λ^safe = the target latency on both directions of every chain hop, and turn | ||
| -- the result into per-node reset releases (which remove the boot-time counter | ||
| -- offsets) and per-link frame corrections (which raise each link's latency to | ||
| -- the target). The application then runs one FIXED schedule built from the | ||
| -- target latencies -- identical every run. | ||
| currentTime <- |
There was a problem hiding this comment.
| -- Relabel this boot onto the stored target latencies, using the grooming | |
| -- utilities ("Bittide.Instances.Hitl.Utils.UgnGrooming"): build UGN | |
| -- edges from the measured counters, groom them (Bellman-Ford) onto | |
| -- λ^safe = the target latency on both directions of every chain hop, and turn | |
| -- the result into per-node reset releases (which remove the boot-time counter | |
| -- offsets) and per-link frame corrections (which raise each link's latency to | |
| -- the target). The application then runs one FIXED schedule built from the | |
| -- target latencies -- identical every run. | |
| currentTime <- | |
| -- Relabel this boot onto the stored target latencies, using the grooming | |
| -- utilities ("Bittide.Instances.Hitl.Utils.UgnGrooming"). | |
| currentTime <- |
| -- large constant. 'StartDelay' must exceed the GDB bookkeeping time, else a | ||
| -- node's reset could release before it is fully configured. |
There was a problem hiding this comment.
| -- large constant. 'StartDelay' must exceed the GDB bookkeeping time, else a | |
| -- node's reset could release before it is fully configured. | |
| -- large constant. |
This is already documented at StartDelay.
| -- This boot's measured UGN edges for the FULL graph (every node, every | ||
| -- link) -- the grooming is independent of the application and adjusts all | ||
| -- elastic buffers. | ||
| measuredEdges = hardwareUgnEdges ugnPairsTableV |
There was a problem hiding this comment.
| -- This boot's measured UGN edges for the FULL graph (every node, every | |
| -- link) -- the grooming is independent of the application and adjusts all | |
| -- elastic buffers. | |
| measuredEdges = hardwareUgnEdges ugnPairsTableV | |
| measuredEdges = hardwareUgnEdges ugnPairsTableV |
| -- Groom this boot's measured UGNs onto the stored 'lambdaSafe' (Bellman-Ford): | ||
| -- per-node reset offsets + per-link frame corrections for every edge. | ||
| -- 'appSchedule' is fixed and derived from the same 'lambdaSafe'. | ||
| relabelPlan <- case computeRelabel measuredEdges lambdaSafe of |
There was a problem hiding this comment.
| relabelPlan <- case computeRelabel measuredEdges lambdaSafe of |
This is already well-documented in the functions
What (what did you do)
Implements UGN grooming for the wire demo: restoring a freshly-booted bittide system to a stored, known-good latency profile (λ^safe) so the application's fixed schedule lands identically every boot, regardless of boot-time counter offsets.
The work is built bottom-up:
Why (context, issues, etc.)
After a boot, each node's UGNs carry arbitrary boot-time counter offsets and elastic buffers might settle at different occupancies, so a fixed application schedule can't be reused across boots. Grooming relabels every boot onto a single stored reference profile, absorbing the offsets via the timed-reset release and trimming the residual per-link drift (a few frames) inside the elastic buffer's safe range. This is the host-side groundwork for reproducible wire-demo scheduling.
Dear reviewer (anything you'd like the reviewer to pay close attention to?)
Commit-by-commit review is intended — the history is structured so each commit introduces one coherent piece and compiles on its own (graph → algorithm → peripherals → MU wiring → HITL utils → driver → firmware → readback → buffer tuning).
λ^safe is stored in the raw (non-canonicalized) gauge in the Driver.
AI disclaimer (heads-up for more than inline autocomplete)
I guided claude through the implementation of this PR based on the presentation offered by Sanjay regarding the method.
TODO
Cross-outany that do not applydocs/