1use std::collections::BTreeMap;
17
18use crate::ast::stmt::{Stmt, TypeExpr};
19use crate::codegen_sva::fabric_rtl::{loop_fabric_from_source, LoopCell, Src};
20
21use super::cell::{ASrc, AnalogCell, AnalogNetlist};
22use super::control::{ControlProgram, CtrlStep};
23use super::error_budget::{outward_sum, ErrorSource, ErrorTerm, OutputBudget};
24use super::gain::{
25 propagate_bounds, quantize_weights, select_scale, src_bound_of, GainSchedule, MathBound,
26 NetBound, RangeCertificate,
27};
28use super::lower::{map_float_cell, source_without_range_contracts, MappedCell, NetRef};
29use super::target::{AnaUnplaceable, AnalogCapability, AnalogCellKind, AnalogTarget};
30use super::{assumed_ranges_in, AnaLowerError, AssumedRange};
31
32#[derive(Clone, Copy, Debug, PartialEq)]
34pub enum InitSrc {
35 Param(usize),
36 Const(f64),
37}
38
39#[derive(Clone, Debug, PartialEq)]
42pub struct AnalogIntegrator {
43 pub tick: AnalogNetlist,
44 pub outs: Vec<ASrc>,
45 pub init: Vec<InitSrc>,
46 pub frames: usize,
47 pub result_state: usize,
48 pub param_ranges: Vec<AssumedRange>,
50 pub control: ControlProgram,
51}
52
53pub fn analog_integrator_from_source(
55 src: &str,
56 fn_name: &str,
57 target: &AnalogTarget,
58) -> Result<AnalogIntegrator, AnaLowerError> {
59 let param_ranges = check_integrator_contracts(src, fn_name)?;
60
61 let twin = source_without_range_contracts(src);
62 let lf = loop_fabric_from_source(&twin, fn_name);
63
64 if lf.seq_input.is_some() || lf.seq_heap_ptr.is_some() {
65 return Err(AnaLowerError::UnloweredOp(
66 "a streamed loop belongs to the reduction lane".to_string(),
67 ));
68 }
69 if !lf.epilogue.is_empty() || !lf.prologue_heap.is_empty() || !lf.invariants.is_empty() {
70 return Err(AnaLowerError::UnloweredOp(
71 "loop epilogue/prologue/invariants are not yet integrated".to_string(),
72 ));
73 }
74 let frames = match (lf.lo, lf.hi) {
75 (Src::Const(lo), Src::Const(hi)) if hi >= lo => (hi - lo + 1) as usize,
76 _ => {
77 return Err(AnaLowerError::UnloweredOp(
78 "the trip count must be a compile-time constant (the frame schedule is static)"
79 .to_string(),
80 ))
81 }
82 };
83 let n_state = lf.n_state;
84
85 let mut init = Vec::with_capacity(n_state);
87 for s in &lf.init {
88 init.push(match s {
89 Src::In(k) => InitSrc::Param(*k),
90 Src::Const(bits) => InitSrc::Const(f64::from_bits(*bits as u64)),
91 Src::Val(_) => {
92 return Err(AnaLowerError::UnloweredOp(
93 "a computed initial state is not yet integrated".to_string(),
94 ))
95 }
96 });
97 }
98 for (s, i) in init.iter().enumerate() {
99 if let InitSrc::Param(k) = i {
100 if *k >= param_ranges.len() {
101 return Err(AnaLowerError::UnboundedInput(format!(
102 "state {s} initializes from parameter {k}, which has no declared range"
103 )));
104 }
105 }
106 }
107
108 for cell in &lf.body {
111 let illegal = |s: &Src| matches!(s, Src::In(k) if *k >= n_state);
112 let bad = match cell {
113 LoopCell::Arith { a, b, .. }
114 | LoopCell::Func { a, b, .. }
115 | LoopCell::VectorArith { a, b, .. } => illegal(a) || illegal(b),
116 LoopCell::Select { ca, cb, t, e, .. } => {
117 illegal(ca) || illegal(cb) || illegal(t) || illegal(e)
118 }
119 _ => true, };
121 if bad {
122 return Err(AnaLowerError::UnloweredOp(
123 "the induction variable / memory used inside an integrator body".to_string(),
124 ));
125 }
126 }
127 let mut map: BTreeMap<usize, NetRef> = BTreeMap::new();
128 let mut cells: Vec<AnalogCell> = Vec::new();
129 let resolve_with = |map: &BTreeMap<usize, NetRef>, s: &Src| -> ASrc {
130 match s {
131 Src::In(k) => ASrc::In(*k), Src::Const(bits) => ASrc::Const(f64::from_bits(*bits as u64)),
133 Src::Val(v) => match map.get(v) {
134 Some(NetRef::Cell(c)) => ASrc::Val(*c),
135 Some(NetRef::Const(f)) => ASrc::Const(*f),
136 None => ASrc::Val(usize::MAX),
137 },
138 }
139 };
140 for (i, cell) in lf.body.iter().enumerate() {
141 let resolve = |s: &Src| resolve_with(&map, s);
142 let mapped = match map_float_cell(cell, &resolve)? {
143 MappedCell::Tile(t) => NetRef::Cell(push(&mut cells, t)),
144 MappedCell::Tiles(ts) => NetRef::Cell(super::lower::push_tiles(&mut cells, ts)),
145 MappedCell::Fold(v) => NetRef::Const(v),
146 };
147 map.insert(i, mapped);
148 }
149 let outs: Vec<ASrc> = lf.next.iter().map(|s| resolve_with(&map, s)).collect();
150 if outs.iter().any(|o| matches!(o, ASrc::Val(usize::MAX))) {
151 return Err(AnaLowerError::UnloweredOp(
152 "a state update references an unlowered value".to_string(),
153 ));
154 }
155 let tick = AnalogNetlist {
156 n_in: n_state,
157 cells,
158 out: outs[lf.result_state],
159 };
160
161 let t_frame_s = target.settle_ns.0 * 1.0e-9;
164 for cell in &tick.cells {
165 if let AnalogCell::GainConst { k, .. } = cell {
166 if *k != 0.0 {
167 target
168 .require_integrator_rate(k.abs() / t_frame_s)
169 .map_err(AnaLowerError::Unplaceable)?;
170 }
171 }
172 }
173
174 gate_integrator_on_target(&tick, n_state, target)?;
175
176 let control = ControlProgram {
177 frame: vec![CtrlStep::Settle, CtrlStep::SampleHold],
179 frames,
180 epilogue: vec![CtrlStep::ReadAdc { ch: 0 }],
181 tick_ns: target.settle_ns.0,
182 };
183 Ok(AnalogIntegrator {
184 tick,
185 outs,
186 init,
187 frames,
188 result_state: lf.result_state,
189 param_ranges,
190 control,
191 })
192}
193
194fn push(cells: &mut Vec<AnalogCell>, c: AnalogCell) -> usize {
195 cells.push(c);
196 cells.len() - 1
197}
198
199fn check_integrator_contracts(
202 src: &str,
203 fn_name: &str,
204) -> Result<Vec<AssumedRange>, AnaLowerError> {
205 crate::ui_bridge::with_parsed_program(src, |parsed, interner| {
206 let (stmts, _types, _pol) = match parsed {
207 Ok(t) => t,
208 Err(advice) => {
209 return Err(AnaLowerError::UnloweredOp(format!(
210 "parse failed: {advice}"
211 )))
212 }
213 };
214 let Some((params, body)) = stmts.iter().find_map(|s| match s {
215 Stmt::FunctionDef {
216 name, params, body, ..
217 } if interner.resolve(*name) == fn_name => Some((params, *body)),
218 _ => None,
219 }) else {
220 return Err(AnaLowerError::UnloweredOp(format!(
221 "function `{fn_name}` not found"
222 )));
223 };
224 let is_float = |t: &TypeExpr<'_>| {
225 matches!(t, TypeExpr::Primitive(s) | TypeExpr::Named(s)
226 if matches!(interner.resolve(*s), "Float" | "Real"))
227 };
228 if !params.iter().all(|(_, t)| is_float(t)) || params.is_empty() {
229 return Err(AnaLowerError::NoAnalogRegion);
230 }
231 let ranges = assumed_ranges_in(body, interner);
232 let mut out = Vec::with_capacity(params.len());
233 for (p, _) in params.iter() {
234 let pname = interner.resolve(*p);
235 let Some(r) = ranges.iter().find(|r| r.var == pname) else {
236 return Err(AnaLowerError::UnboundedInput(format!(
237 "parameter `{pname}` has no declared range (`Assume {pname} is between A and B.`)"
238 )));
239 };
240 out.push(r.clone());
241 }
242 Ok(out)
243 })
244}
245
246fn gate_integrator_on_target(
247 tick: &AnalogNetlist,
248 n_state: usize,
249 target: &AnalogTarget,
250) -> Result<(), AnaLowerError> {
251 let mut need = vec![
252 AnalogCapability::Cell(AnalogCellKind::Integrate),
253 AnalogCapability::Cell(AnalogCellKind::SampleHold),
254 AnalogCapability::DacChannels(n_state.max(1)),
255 AnalogCapability::AdcChannels(1),
256 ];
257 for cell in &tick.cells {
258 need.push(AnalogCapability::Cell(cell.kind()));
259 }
260 for cap in need {
261 if !target.provides(cap) {
262 return Err(AnaLowerError::Unplaceable(
263 AnaUnplaceable::MissingCapability {
264 needed: cap,
265 target: target.name,
266 },
267 ));
268 }
269 }
270 let n_mul = tick
271 .cells
272 .iter()
273 .filter(|c| matches!(c, AnalogCell::Mul { .. }))
274 .count();
275 if n_mul > 0 && !target.provides(AnalogCapability::MulTiles(n_mul)) {
276 return Err(AnaLowerError::Unplaceable(
277 AnaUnplaceable::MissingCapability {
278 needed: AnalogCapability::MulTiles(n_mul),
279 target: target.name,
280 },
281 ));
282 }
283 if tick.cells.len() > target.rows * target.cols {
284 return Err(AnaLowerError::Unplaceable(AnaUnplaceable::TooSmall {
285 needed_cells: tick.cells.len(),
286 target_rows: target.rows,
287 target_cols: target.cols,
288 target: target.name,
289 }));
290 }
291 Ok(())
292}
293
294fn step(cells_src: &AnalogNetlist, outs: &[ASrc], state: &[f64]) -> Vec<f64> {
296 let mut vals = Vec::with_capacity(cells_src.cells.len());
297 for cell in &cells_src.cells {
298 let v = cell.eval(state, &vals);
299 vals.push(v);
300 }
301 outs.iter().map(|o| o.eval(state, &vals)).collect()
302}
303
304fn init_state(r: &AnalogIntegrator, params: &[f64]) -> Vec<f64> {
305 r.init
306 .iter()
307 .map(|i| match i {
308 InitSrc::Param(k) => params[*k],
309 InitSrc::Const(c) => *c,
310 })
311 .collect()
312}
313
314pub fn eval_analog_integrated(r: &AnalogIntegrator, params: &[f64]) -> f64 {
316 let mut state = init_state(r, params);
317 for _ in 0..r.frames {
318 state = step(&r.tick, &r.outs, &state);
319 }
320 state[r.result_state]
321}
322
323pub fn eval_analog_integrated_quantized(
326 r: &AnalogIntegrator,
327 schedule: &GainSchedule,
328 target: &AnalogTarget,
329 params: &[f64],
330) -> f64 {
331 let grid = super::eval::ConverterGrid::of(schedule, target);
332 let mut state: Vec<f64> = init_state(r, params)
333 .into_iter()
334 .zip(&r.init)
335 .map(|(v, i)| match i {
336 InitSrc::Param(_) => grid.dac_q(v),
337 InitSrc::Const(_) => v, })
339 .collect();
340 for _ in 0..r.frames {
341 state = step(&schedule.quantized, &r.outs, &state);
342 }
343 grid.adc_q(state[r.result_state])
344}
345
346pub fn integrator_scale_pass(
349 r: &AnalogIntegrator,
350 target: &AnalogTarget,
351) -> Result<(GainSchedule, RangeCertificate), AnaLowerError> {
352 let quantized = quantize_weights(&r.tick, target)?;
353 let mut state: Vec<MathBound> = r
354 .init
355 .iter()
356 .map(|i| match i {
357 InitSrc::Param(k) => MathBound {
358 lo: r.param_ranges[*k].lo,
359 hi: r.param_ranges[*k].hi,
360 },
361 InitSrc::Const(c) => MathBound { lo: *c, hi: *c },
362 })
363 .collect();
364 let mut state_hulls = state.clone();
365 let mut hulls: Vec<MathBound> = vec![
366 MathBound {
367 lo: f64::INFINITY,
368 hi: f64::NEG_INFINITY
369 };
370 quantized.cells.len()
371 ];
372 for _ in 0..r.frames {
373 let bounds = propagate_bounds(&quantized.cells, &state)?;
374 for (h, b) in hulls.iter_mut().zip(&bounds) {
375 h.lo = h.lo.min(b.lo);
376 h.hi = h.hi.max(b.hi);
377 }
378 let next: Vec<MathBound> = r
379 .outs
380 .iter()
381 .map(|o| src_bound_of(o, &state, &bounds))
382 .collect();
383 for (h, b) in state_hulls.iter_mut().zip(&next) {
384 h.lo = h.lo.min(b.lo);
385 h.hi = h.hi.max(b.hi);
386 }
387 if next.iter().any(|b| !(b.lo.is_finite() && b.hi.is_finite())) {
388 return Err(AnaLowerError::UnloweredOp(
389 "state range diverges over the declared frame count".to_string(),
390 ));
391 }
392 state = next;
393 }
394 for h in hulls.iter_mut() {
395 if h.lo > h.hi {
396 *h = MathBound { lo: 0.0, hi: 0.0 };
397 }
398 }
399
400 let has_mul = quantized
401 .cells
402 .iter()
403 .any(|c| matches!(c, AnalogCell::Mul { .. }));
404 let input_bounds = state_hulls;
405 let all: Vec<MathBound> = input_bounds.iter().chain(hulls.iter()).copied().collect();
406 let scale_log2 = select_scale(&all, has_mul, target)?;
407 let s = f64::powi(2.0, scale_log2);
408 let per_net = hulls
409 .iter()
410 .map(|b| NetBound {
411 math: *b,
412 volt_lo: (b.lo * s).next_down(),
413 volt_hi: (b.hi * s).next_up(),
414 })
415 .collect();
416 let per_net_scale_log2 = vec![scale_log2; quantized.cells.len()];
417 let schedule = GainSchedule {
418 scale_log2,
419 out_descale_log2: -scale_log2,
420 mul_vref_v: s,
421 quantized,
422 per_net_scale_log2,
423 };
424 let cert = RangeCertificate {
425 input_bounds,
426 per_net,
427 rail_half_span_v: target.half_span_v(),
428 noise_floor_v: 0.0,
429 min_full_scale_over_noise: 1.0,
430 };
431 Ok((schedule, cert))
432}
433
434pub fn integrator_error_budget(
438 r: &AnalogIntegrator,
439 cert: &RangeCertificate,
440 schedule: &GainSchedule,
441 target: &AnalogTarget,
442) -> OutputBudget {
443 use super::error_budget::{eval_rounding_guard, half_lsb, out_lanes, propagate_eps_pass};
444 let dac_eps = half_lsb(cert, schedule, target.dac_bits);
445 let adc_eps = half_lsb(cert, schedule, target.adc_bits);
446 let bound_of = |src: &ASrc| -> MathBound {
447 match src {
448 ASrc::In(k) => cert.input_bounds[*k],
449 ASrc::Val(v) => cert.per_net[*v].math,
450 ASrc::Const(c) => MathBound { lo: *c, hi: *c },
451 }
452 };
453 let mut state_eps: Vec<Vec<f64>> = r
455 .init
456 .iter()
457 .map(|i| match i {
458 InitSrc::Param(_) => vec![dac_eps, 0.0, 0.0],
459 InitSrc::Const(_) => vec![0.0; 3],
460 })
461 .collect();
462 for _ in 0..r.frames {
463 let carried = state_eps.clone();
464 let input_eps = |src: &ASrc, lane: usize| -> f64 {
465 match src {
466 ASrc::In(k) => carried[*k][lane],
467 _ => 0.0,
468 }
469 };
470 let input_total = |src: &ASrc| -> f64 {
471 match src {
472 ASrc::In(k) => outward_sum(carried[*k].iter().copied()),
473 _ => 0.0,
474 }
475 };
476 let pass = propagate_eps_pass(
477 &r.tick.cells,
478 &schedule.quantized.cells,
479 &bound_of,
480 3,
481 1,
482 2,
483 &input_eps,
484 &input_total,
485 );
486 state_eps = r
487 .outs
488 .iter()
489 .map(|o| out_lanes(&pass, o, 3, &input_eps))
490 .collect();
491 }
492
493 let out_eps = &state_eps[r.result_state];
494 let mut terms = Vec::new();
495 if out_eps[0] > 0.0 {
496 terms.push(ErrorTerm {
497 eps_math: out_eps[0],
498 source: ErrorSource::DacQuant {
499 input: 0,
500 bits: target.dac_bits,
501 },
502 });
503 }
504 if out_eps[1] > 0.0 {
505 terms.push(ErrorTerm {
506 eps_math: out_eps[1],
507 source: ErrorSource::WeightQuant,
508 });
509 }
510 if out_eps[2] > 0.0 {
511 terms.push(ErrorTerm {
512 eps_math: out_eps[2],
513 source: ErrorSource::MulInteraction,
514 });
515 }
516 terms.push(ErrorTerm {
517 eps_math: adc_eps,
518 source: ErrorSource::AdcQuant {
519 bits: target.adc_bits,
520 },
521 });
522 terms.push(ErrorTerm {
523 eps_math: eval_rounding_guard(cert, r.frames * schedule.quantized.cells.len()),
524 source: ErrorSource::EvalRounding,
525 });
526 let eps_math = outward_sum(terms.iter().map(|t| t.eps_math));
527 OutputBudget { eps_math, terms }
528}