logicaffeine_compile/codegen_ana/control.rs
1//! The digital control plane's frame schedule — DAC/ADC strobing around the analog plane.
2//!
3//! The mixed-signal division of labor: the DIGITAL side validates each word against its declared
4//! range (the `Assume` contract), drives the DAC, waits out the settling budget, strobes the
5//! sample-and-hold (the ZOH boundary), and reads the ADC. The analog side only ever settles.
6//! A streamed reduction runs one FRAME per element with the accumulator resident in the S&H
7//! between frames — the element stream is quantized per tick, the accumulator never is.
8
9/// One step of the control plane's schedule.
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub enum CtrlStep {
12 /// Drive DAC channel `ch` with the next validated word.
13 LoadDac { ch: usize },
14 /// Wait the target's DC settling budget.
15 Settle,
16 /// Strobe the sample-and-hold — commit this frame's result into analog state.
17 SampleHold,
18 /// Read ADC channel `ch` into a digital word.
19 ReadAdc { ch: usize },
20 /// Swap every tile's chopper polarity (the O5 scheduled transform): the SAME dataflow
21 /// runs a second frame with input/output commutation reversed, so each frozen per-die
22 /// offset enters negated and cancels EXACTLY in the two-frame average. What survives is
23 /// intra-pair drift (`ChopResidual`, modeled) and noise (fresh per frame — averaging
24 /// buys the √2).
25 ChopSwap,
26}
27
28/// A frame-based control program: `frame` repeats `frames` times, then `epilogue` runs once.
29#[derive(Clone, Debug, PartialEq)]
30pub struct ControlProgram {
31 pub frame: Vec<CtrlStep>,
32 pub frames: usize,
33 pub epilogue: Vec<CtrlStep>,
34 /// Wall-clock per frame in nanoseconds (the target's settling budget).
35 pub tick_ns: f64,
36}
37
38impl ControlProgram {
39 /// Total scheduled steps (frames × frame length + epilogue).
40 pub fn total_steps(&self) -> usize {
41 self.frames * self.frame.len() + self.epilogue.len()
42 }
43
44 /// The chopped twin of this schedule: every frame runs TWICE around a `ChopSwap`, and
45 /// the digital side averages the pair. Purely a schedule transform — the dataflow, the
46 /// netlist, and the certificates are untouched.
47 pub fn chopped(&self) -> ControlProgram {
48 let mut frame = self.frame.clone();
49 frame.push(CtrlStep::ChopSwap);
50 frame.extend(self.frame.iter().copied());
51 frame.push(CtrlStep::ChopSwap);
52 ControlProgram {
53 frame,
54 frames: self.frames,
55 epilogue: self.epilogue.clone(),
56 tick_ns: self.tick_ns,
57 }
58 }
59}