plumbob
FRL link training state machine for HDMI 2.1.
plumbob implements the Fixed Rate Link (FRL) training procedure defined in the HDMI 2.1
specification. It owns the four-phase state machine — configuration, readiness polling,
initiation polling, and the LTP pattern loop — and defines the ScdcClient interface its
SCDC implementation must satisfy. Callers supply an ScdcClient and an HdmiPhy, call
train_at_rate for each rate to attempt, and handle the TrainingOutcome.
Rate fallback policy, SCDC register decoding, and PHY vendor sequences are all out of scope: plumbob implements the spec, not the strategy around it.
Usage
[]
= "0.1"
Implement ScdcClient against your SCDC transport:
use ;
Construct an FrlTrainer and step down through rates until one succeeds:
use HdmiForumFrl;
use ;
let mut trainer = new;
let config = default;
let rates = ;
for rate in rates
For a complete worked example with simulated SCDC and PHY backends, see
examples/simulate.
Training procedure
train_at_rate runs all four phases for a single rate attempt and returns a typed terminal
result. FallbackRequired means the link did not converge at this rate; TrainingError
means a hard I/O failure from the SCDC client or PHY. The two are kept distinct so a caller
diagnosing a failure knows whether it came from the protocol or the bus.
flowchart TD
A["train_at_rate(rate, config)"]
B["Phase 1\nwrite Config_0 · set_frl_rate"]
C["Phase 2\npoll flt_ready"]
D["Phase 3\npoll frl_start"]
E["Phase 4\nLTP loop · drive patterns"]
F(["Success { achieved_rate }"])
G(["FallbackRequired"])
H(["TrainingError"])
A --> B --> C
C -- asserted --> D
C -- timeout --> G
D -- asserted --> E
D -- timeout --> G
E -- "ltp_req == None" --> F
E -- timeout --> G
B & C & D & E -- I/O error --> H
Phase 1 writes the target rate and FFE level count to Config_0 and configures the PHY lanes for this rate.
Phase 2 polls until the sink asserts flt_ready, signalling it has completed internal
preparation at the requested rate. Times out after TrainingConfig::flt_ready_timeout
iterations.
Phase 3 polls until the sink asserts frl_start, signalling it is ready for the LTP
loop to begin. Times out after TrainingConfig::frl_start_timeout iterations.
Phase 4 drives the sink-requested LTP pattern on the PHY lanes on each iteration until
ltp_req reaches None (all lanes satisfied), or until TrainingConfig::ltp_timeout
iterations have elapsed.
Each timeout value is an exact iteration count: a value of N means at most N poll attempts
are made before the phase gives up. The inter-poll delay is the implementer's
responsibility and belongs inside the ScdcClient methods.
Diagnostics
Enable the alloc feature to get train_at_rate_traced, which returns a TrainingTrace
alongside the outcome. The trace records the rate, the TrainingConfig in force, and an
ordered TrainingEvent log covering the full attempt:
let = trainer.train_at_rate_traced?;
println!;
for event in &trace.events
A complete successful trace looks like:
RateConfigured { rate: Rate9Gbps3Lanes, ffe_levels: Ffe0 }
FltReadyReceived { after_iterations: 5 }
FrlStartReceived { after_iterations: 12 }
LtpPatternRequested { pattern: Lfsr0 }
LtpPatternRequested { pattern: Lfsr2 }
AllLanesSatisfied { after_iterations: 47 }
A trace that timed out in phase 4 — the sink requested patterns but lanes failed to lock:
RateConfigured { rate: Rate12Gbps4Lanes, ffe_levels: Ffe0 }
FltReadyReceived { after_iterations: 4 }
FrlStartReceived { after_iterations: 3 }
LtpPatternRequested { pattern: Lfsr1 }
LtpPatternRequested { pattern: Lfsr3 }
LtpLoopTimeout { iterations_elapsed: 1000 }
LtpPatternRequested is emitted only on transitions, so a sink that holds the same pattern
for many iterations produces one event, not one per poll. Each timeout event names the phase
it came from — a phase 2 timeout means the sink did not complete internal preparation at
this rate; a phase 4 timeout points toward signal integrity or equalization.
Features
| Feature | Default | Description |
|---|---|---|
std |
no | Implies alloc; no additional API surface |
alloc |
no | Enables TrainingTrace, TrainingEvent, and train_at_rate_traced |
No features are enabled by default. The bare crate provides the full four-phase state machine without an allocator.
no_std builds
plumbob declares #![no_std] throughout.
Bare no_std (no features) — the complete training state machine is available.
FrlTrainer, TrainingConfig, TrainingOutcome, TrainingError, and all owned protocol
types (LtpReq, FfeLevels, TrainingStatus, CedCounters) are stack-allocated. No heap
use anywhere in the training loop. This tier covers bare-metal and firmware targets.
no_std + alloc — adds TrainingTrace and train_at_rate_traced:
= { = "0.1", = ["alloc"] }
std — implies alloc:
= { = "0.1", = ["std"] }
Stack position
plumbob sits between the SCDC/PHY implementations and the integration layer that
orchestrates rate selection and fallback. It defines one interface (ScdcClient) and
implements one (LinkTrainer) from the layer above.
flowchart LR
dt["display-types"]
hal["hdmi-hal"]
culvert["culvert"]
plumbob["plumbob"]
integration["integration layer"]
dt --> plumbob
hal --> plumbob
culvert -->|"implements ScdcClient"| plumbob
plumbob -->|"implements LinkTrainer"| integration
plumbob does not depend on culvert. The relationship runs the other way: culvert
implements plumbob::ScdcClient for Scdc<T>, gated behind a plumbob cargo feature.
Any crate that implements ScdcClient is substitutable.
Out of scope
- Rate fallback policy —
train_at_ratereturnsFallbackRequired; the caller decides the retry sequence and the fallback-to-TMDS threshold. - SCDC register decoding — plumbob reads typed values from
ScdcClientand does not decode raw register bytes or know SCDC register addresses. - PHY vendor sequences — plumbob calls
HdmiPhy::set_frl_rateandsend_ltp; the register sequences for lane reconfiguration live in platform PHY backends. - Timing — plumbob is synchronous and poll-based. Inter-poll delay is implicit in the
transport; the iteration limits in
TrainingConfigare the only timeout mechanism. - TMDS link setup — plumbob handles FRL training only.
Documentation
doc/architecture.md— role, scope, interface boundaries, design principles, and the async roadmap
License
Licensed under the Mozilla Public License 2.0.