1use super::cell::{ASrc, AnalogCell, AnalogNetlist};
18use super::target::{AnaUnplaceable, AnalogTarget};
19use super::{AnaLowerError, AssumedRange};
20
21#[derive(Clone, Copy, Debug, PartialEq)]
23pub struct MathBound {
24 pub lo: f64,
25 pub hi: f64,
26}
27
28impl MathBound {
29 fn outward(self) -> MathBound {
30 MathBound {
31 lo: self.lo.next_down(),
32 hi: self.hi.next_up(),
33 }
34 }
35 fn add(self, o: MathBound) -> MathBound {
36 MathBound {
37 lo: self.lo + o.lo,
38 hi: self.hi + o.hi,
39 }
40 .outward()
41 }
42 fn scale(self, w: f64) -> MathBound {
43 let (a, b) = (w * self.lo, w * self.hi);
44 MathBound {
45 lo: a.min(b),
46 hi: a.max(b),
47 }
48 .outward()
49 }
50 fn mul(self, o: MathBound) -> MathBound {
51 let ps = [
52 self.lo * o.lo,
53 self.lo * o.hi,
54 self.hi * o.lo,
55 self.hi * o.hi,
56 ];
57 MathBound {
58 lo: ps.iter().copied().fold(f64::INFINITY, f64::min),
59 hi: ps.iter().copied().fold(f64::NEG_INFINITY, f64::max),
60 }
61 .outward()
62 }
63 pub fn max_abs(self) -> f64 {
64 self.lo.abs().max(self.hi.abs())
65 }
66}
67
68#[derive(Clone, Debug, PartialEq)]
72pub struct GainSchedule {
73 pub scale_log2: i32,
75 pub out_descale_log2: i32,
77 pub mul_vref_v: f64,
81 pub quantized: AnalogNetlist,
87 pub per_net_scale_log2: Vec<i32>,
92}
93
94impl GainSchedule {
95 pub fn realized_math_cells(&self) -> Vec<AnalogCell> {
100 let e_io = self.scale_log2;
101 let e = &self.per_net_scale_log2;
102 let src_e = |s: &ASrc| -> i32 {
103 match s {
104 ASrc::Val(v) => e[*v],
105 _ => e_io,
106 }
107 };
108 self.quantized
109 .cells
110 .iter()
111 .enumerate()
112 .map(|(i, cell)| match cell {
113 AnalogCell::Sum { inputs, weights } => AnalogCell::Sum {
114 inputs: inputs.clone(),
115 weights: inputs
116 .iter()
117 .zip(weights)
118 .map(|(x, w)| w * f64::powi(2.0, src_e(x) - e[i]))
119 .collect(),
120 },
121 AnalogCell::GainConst { a, k } => AnalogCell::GainConst {
122 a: *a,
123 k: k * f64::powi(2.0, src_e(a) - e[i]),
124 },
125 AnalogCell::Mul { a, b } => AnalogCell::Mul { a: *a, b: *b },
126 AnalogCell::Select { c, t, f } => AnalogCell::Select {
127 c: *c,
128 t: *t,
129 f: *f,
130 },
131 AnalogCell::Exp { a } => AnalogCell::Exp { a: *a },
132 AnalogCell::Log { a } => AnalogCell::Log { a: *a },
133 AnalogCell::Tanh { a } => AnalogCell::Tanh { a: *a },
134 AnalogCell::Sqrt { a } => AnalogCell::Sqrt { a: *a },
135 AnalogCell::Div { a, b } => AnalogCell::Div { a: *a, b: *b },
136 })
137 .collect()
138 }
139}
140
141#[derive(Clone, Debug, PartialEq)]
143pub struct NetBound {
144 pub math: MathBound,
145 pub volt_lo: f64,
146 pub volt_hi: f64,
147}
148
149#[derive(Clone, Debug, PartialEq)]
153pub struct RangeCertificate {
154 pub input_bounds: Vec<MathBound>,
156 pub per_net: Vec<NetBound>,
158 pub rail_half_span_v: f64,
160 pub noise_floor_v: f64,
162 pub min_full_scale_over_noise: f64,
164}
165
166pub fn scale_pass(
170 netlist: &AnalogNetlist,
171 ranges: &[AssumedRange],
172 target: &AnalogTarget,
173) -> Result<(GainSchedule, RangeCertificate), AnaLowerError> {
174 assert_eq!(
175 ranges.len(),
176 netlist.n_in,
177 "one declared range per plane input"
178 );
179 let input_bounds: Vec<MathBound> = ranges
180 .iter()
181 .map(|r| MathBound { lo: r.lo, hi: r.hi })
182 .collect();
183 for b in &input_bounds {
184 if !(b.lo.is_finite() && b.hi.is_finite() && b.lo <= b.hi) {
185 return Err(AnaLowerError::UnboundedInput(format!(
186 "declared range [{}, {}] is not a finite interval",
187 b.lo, b.hi
188 )));
189 }
190 }
191
192 let quantized = quantize_weights(netlist, target)?;
196
197 let bounds = propagate_bounds(&quantized.cells, &input_bounds)?;
199
200 let half_span = target.half_span_v();
205 let has_mul = netlist
206 .cells
207 .iter()
208 .any(|c| matches!(c, AnalogCell::Mul { .. }));
209 let has_translinear = netlist.cells.iter().any(|c| {
210 matches!(
211 c,
212 AnalogCell::Exp { .. }
213 | AnalogCell::Log { .. }
214 | AnalogCell::Tanh { .. }
215 | AnalogCell::Sqrt { .. }
216 | AnalogCell::Div { .. }
217 )
218 });
219 let all_bounds: Vec<MathBound> = input_bounds.iter().chain(bounds.iter()).copied().collect();
220 let scale_log2 = match select_scale_pinned(&all_bounds, has_mul, target, has_translinear) {
221 Ok(e) => e,
222 Err(uniform_refusal) => {
225 if has_translinear {
226 return Err(uniform_refusal);
227 }
228 return per_net_scale_pass(&input_bounds, &quantized, &bounds, target)
229 .map_err(|_| uniform_refusal);
230 }
231 };
232 let s = f64::powi(2.0, scale_log2);
233
234 let noise_floor_v = 0.0; let min_ratio = 1.0;
239 let mut per_net = Vec::with_capacity(bounds.len());
240 for b in &bounds {
241 let (volt_lo, volt_hi) = ((b.lo * s).next_down(), (b.hi * s).next_up());
242 if volt_lo < -half_span || volt_hi > half_span {
243 let needed_db = 20.0 * (b.max_abs() * s / half_span).log10();
244 return Err(AnaLowerError::Unplaceable(
245 AnaUnplaceable::DynamicRangeExceeded {
246 net: per_net.len(),
247 needed_db: needed_db.max(0.0),
248 available_db: 0.0,
249 target: target.name,
250 },
251 ));
252 }
253 per_net.push(NetBound {
254 math: *b,
255 volt_lo,
256 volt_hi,
257 });
258 }
259
260 let per_net_scale_log2 = vec![scale_log2; quantized.cells.len()];
261 let schedule = GainSchedule {
262 scale_log2,
263 out_descale_log2: -scale_log2,
264 mul_vref_v: s,
265 quantized,
266 per_net_scale_log2,
267 };
268 let cert = RangeCertificate {
269 input_bounds,
270 per_net,
271 rail_half_span_v: half_span,
272 noise_floor_v,
273 min_full_scale_over_noise: min_ratio,
274 };
275 Ok((schedule, cert))
276}
277
278pub(crate) fn propagate_bounds(
281 cells: &[AnalogCell],
282 input_bounds: &[MathBound],
283) -> Result<Vec<MathBound>, AnaLowerError> {
284 let src_bound = |s: &ASrc, vals: &[MathBound]| -> MathBound {
285 match s {
286 ASrc::In(k) => input_bounds[*k],
287 ASrc::Val(v) => vals[*v],
288 ASrc::Const(c) => MathBound { lo: *c, hi: *c },
289 }
290 };
291 let mut bounds: Vec<MathBound> = Vec::with_capacity(cells.len());
292 for cell in cells {
293 let b = match cell {
294 AnalogCell::Sum { inputs, weights } => {
295 let mut acc = MathBound { lo: 0.0, hi: 0.0 };
296 for (x, w) in inputs.iter().zip(weights) {
297 acc = acc.add(src_bound(x, &bounds).scale(*w));
298 }
299 acc
300 }
301 AnalogCell::Mul { a, b } => src_bound(a, &bounds).mul(src_bound(b, &bounds)),
302 AnalogCell::GainConst { a, k } => src_bound(a, &bounds).scale(*k),
303 AnalogCell::Select { c, t, f } => {
304 let cb = src_bound(c, &bounds);
307 let (tb, fb) = (src_bound(t, &bounds), src_bound(f, &bounds));
308 if cb.hi < 0.0 {
309 tb
310 } else if cb.lo >= 0.0 {
311 fb
312 } else {
313 MathBound {
314 lo: tb.lo.min(fb.lo),
315 hi: tb.hi.max(fb.hi),
316 }
317 .outward()
318 }
319 }
320 AnalogCell::Exp { a } => {
322 let ab = src_bound(a, &bounds);
323 MathBound {
324 lo: ab.lo.exp(),
325 hi: ab.hi.exp(),
326 }
327 .outward()
328 }
329 AnalogCell::Log { a } => {
330 let ab = src_bound(a, &bounds);
331 if ab.lo <= 0.0 {
332 return Err(AnaLowerError::UnloweredOp(format!(
333 "log domain: operand box [{}, {}] touches the nonpositive axis",
334 ab.lo, ab.hi
335 )));
336 }
337 MathBound {
338 lo: ab.lo.ln(),
339 hi: ab.hi.ln(),
340 }
341 .outward()
342 }
343 AnalogCell::Tanh { a } => {
344 let ab = src_bound(a, &bounds);
345 MathBound {
346 lo: ab.lo.tanh(),
347 hi: ab.hi.tanh(),
348 }
349 .outward()
350 }
351 AnalogCell::Sqrt { a } => {
352 let ab = src_bound(a, &bounds);
353 if ab.lo <= 0.0 {
354 return Err(AnaLowerError::UnloweredOp(format!(
355 "sqrt domain: operand box [{}, {}] touches zero — the error slope \
356 diverges (the budget's Lipschitz factor needs lo > 0)",
357 ab.lo, ab.hi
358 )));
359 }
360 MathBound {
361 lo: ab.lo.sqrt(),
362 hi: ab.hi.sqrt(),
363 }
364 .outward()
365 }
366 AnalogCell::Div { a, b } => {
367 let (ab, bb) = (src_bound(a, &bounds), src_bound(b, &bounds));
368 if bb.lo <= 0.0 && bb.hi >= 0.0 {
369 return Err(AnaLowerError::Unplaceable(
370 AnaUnplaceable::DivisorRangeSpansZero {
371 net: bounds.len(),
372 lo: bb.lo,
373 hi: bb.hi,
374 },
375 ));
376 }
377 let ps = [ab.lo / bb.lo, ab.lo / bb.hi, ab.hi / bb.lo, ab.hi / bb.hi];
378 MathBound {
379 lo: ps.iter().copied().fold(f64::INFINITY, f64::min),
380 hi: ps.iter().copied().fold(f64::NEG_INFINITY, f64::max),
381 }
382 .outward()
383 }
384 };
385 if !(b.lo.is_finite() && b.hi.is_finite()) {
386 return Err(AnaLowerError::UnloweredOp(
387 "range propagation overflowed — the declared ranges do not bound this dataflow"
388 .to_string(),
389 ));
390 }
391 bounds.push(b);
392 }
393 Ok(bounds)
394}
395
396pub(crate) fn src_bound_of(
398 s: &ASrc,
399 input_bounds: &[MathBound],
400 bounds: &[MathBound],
401) -> MathBound {
402 match s {
403 ASrc::In(k) => input_bounds[*k],
404 ASrc::Val(v) => bounds[*v],
405 ASrc::Const(c) => MathBound { lo: *c, hi: *c },
406 }
407}
408
409pub(crate) fn select_scale_pinned(
414 all_bounds: &[MathBound],
415 has_mul: bool,
416 target: &AnalogTarget,
417 pin_unit_scale: bool,
418) -> Result<i32, AnaLowerError> {
419 if pin_unit_scale {
420 let half_span = target.half_span_v();
421 let (lo_e, hi_e) = target.scale_log2_range;
422 let fits0 = all_bounds
423 .iter()
424 .all(|b| b.lo.next_down() >= -half_span && b.hi.next_up() <= half_span);
425 if lo_e <= 0 && 0 <= hi_e && fits0 && !has_mul {
426 return Ok(0);
427 }
428 if has_mul {
429 if target.mul_vref_options_v.iter().any(|v| v.0 == 1.0) && fits0 {
432 return Ok(0);
433 }
434 return Err(AnaLowerError::Unplaceable(
435 AnaUnplaceable::MulReferenceUnrealizable {
436 needed_v: 1.0,
437 options_v: target.mul_vref_options_v.iter().map(|v| v.0).collect(),
438 target: target.name,
439 },
440 ));
441 }
442 return Err(AnaLowerError::Unplaceable(
443 AnaUnplaceable::DynamicRangeExceeded {
444 net: 0,
445 needed_db: 0.0,
446 available_db: 0.0,
447 target: target.name,
448 },
449 ));
450 }
451 select_scale(all_bounds, has_mul, target)
452}
453
454pub(crate) fn select_scale(
455 all_bounds: &[MathBound],
456 has_mul: bool,
457 target: &AnalogTarget,
458) -> Result<i32, AnaLowerError> {
459 let half_span = target.half_span_v();
460 let global_max = all_bounds
461 .iter()
462 .map(|b| b.max_abs())
463 .fold(0.0_f64, f64::max);
464 let fits = |e: i32| -> bool {
465 let s = f64::powi(2.0, e);
466 all_bounds
467 .iter()
468 .all(|b| (b.lo * s).next_down() >= -half_span && (b.hi * s).next_up() <= half_span)
469 };
470 let (lo_e, hi_e) = target.scale_log2_range;
471 if has_mul {
472 let mut opts: Vec<f64> = target.mul_vref_options_v.iter().map(|v| v.0).collect();
473 opts.sort_by(|a, b| b.partial_cmp(a).unwrap());
474 for v in &opts {
475 let e = v.log2();
476 if e.fract() != 0.0 {
477 continue; }
479 let e = e as i32;
480 if e >= lo_e && e <= hi_e && (global_max == 0.0 || fits(e)) {
481 return Ok(e);
482 }
483 }
484 let needed = if global_max == 0.0 {
485 half_span
486 } else {
487 half_span / global_max
488 };
489 Err(AnaLowerError::Unplaceable(
490 AnaUnplaceable::MulReferenceUnrealizable {
491 needed_v: needed,
492 options_v: target.mul_vref_options_v.iter().map(|v| v.0).collect(),
493 target: target.name,
494 },
495 ))
496 } else {
497 let mut e = if global_max == 0.0 {
498 0
499 } else {
500 ((half_span / global_max).log2().floor() as i32).clamp(lo_e, hi_e)
501 }
502 .min(hi_e);
503 while global_max > 0.0 && !fits(e) && e > lo_e {
504 e -= 1;
505 }
506 if global_max > 0.0 && !fits(e) {
507 return Err(AnaLowerError::Unplaceable(
508 AnaUnplaceable::DynamicRangeExceeded {
509 net: 0,
510 needed_db: 20.0
511 * (global_max * f64::powi(2.0, lo_e) / half_span)
512 .log10()
513 .max(0.0),
514 available_db: 0.0,
515 target: target.name,
516 },
517 ));
518 }
519 Ok(e)
520 }
521}
522
523pub(crate) fn quantize_weights(
527 netlist: &AnalogNetlist,
528 target: &AnalogTarget,
529) -> Result<AnalogNetlist, AnaLowerError> {
530 let lsb = target.weight_lsb();
531 let snap = |w: f64| -> f64 { (w / lsb).round() * lsb };
532 let gate = |wq: f64| -> Result<(), AnaLowerError> {
533 if wq != 0.0 {
534 target
535 .require_gain_ratio(wq.abs())
536 .map_err(AnaLowerError::Unplaceable)?;
537 }
538 Ok(())
539 };
540 let mut cells = Vec::with_capacity(netlist.cells.len());
541 for cell in &netlist.cells {
542 cells.push(match cell {
543 AnalogCell::Sum { inputs, weights } => {
544 if inputs.len() > target.sum_fanin {
545 return Err(AnaLowerError::Unplaceable(
546 AnaUnplaceable::SumFaninExceeded {
547 needed: inputs.len(),
548 max: target.sum_fanin,
549 target: target.name,
550 },
551 ));
552 }
553 let wq: Vec<f64> = weights.iter().map(|w| snap(*w)).collect();
554 for w in &wq {
555 gate(*w)?;
556 }
557 AnalogCell::Sum {
558 inputs: inputs.clone(),
559 weights: wq,
560 }
561 }
562 AnalogCell::GainConst { a, k } => {
563 let kq = snap(*k);
564 gate(kq)?;
565 AnalogCell::GainConst { a: *a, k: kq }
566 }
567 AnalogCell::Mul { a, b } => AnalogCell::Mul { a: *a, b: *b },
568 AnalogCell::Select { c, t, f } => AnalogCell::Select {
569 c: *c,
570 t: *t,
571 f: *f,
572 },
573 AnalogCell::Exp { a } => AnalogCell::Exp { a: *a },
574 AnalogCell::Log { a } => AnalogCell::Log { a: *a },
575 AnalogCell::Tanh { a } => AnalogCell::Tanh { a: *a },
576 AnalogCell::Sqrt { a } => AnalogCell::Sqrt { a: *a },
577 AnalogCell::Div { a, b } => AnalogCell::Div { a: *a, b: *b },
578 });
579 }
580 Ok(AnalogNetlist {
581 n_in: netlist.n_in,
582 cells,
583 out: netlist.out,
584 })
585}
586
587fn per_net_scale_pass(
599 input_bounds: &[MathBound],
600 quantized_math: &AnalogNetlist,
601 bounds: &[MathBound],
602 target: &AnalogTarget,
603) -> Result<(GainSchedule, RangeCertificate), AnaLowerError> {
604 let half_span = target.half_span_v();
605 let (lo_e, hi_e) = target.scale_log2_range;
606 let n = quantized_math.cells.len();
607 let fits_one = |b: &MathBound, e: i32| -> bool {
608 let s = f64::powi(2.0, e);
609 (b.lo * s).next_down() >= -half_span && (b.hi * s).next_up() <= half_span
610 };
611 let best_for = |b: &MathBound| -> Option<i32> { (lo_e..=hi_e).rev().find(|e| fits_one(b, *e)) };
612
613 let out_cell = match quantized_math.out {
615 ASrc::Val(v) => Some(v),
616 _ => None,
617 };
618 let mut io_bounds: Vec<MathBound> = input_bounds.to_vec();
619 if let Some(o) = out_cell {
620 io_bounds.push(bounds[o]);
621 }
622 let mut pinned_io = vec![false; n];
624 if let Some(o) = out_cell {
625 pinned_io[o] = true;
626 }
627 for (i, cell) in quantized_math.cells.iter().enumerate() {
628 let has_const = match cell {
629 AnalogCell::Sum { inputs, .. } => inputs.iter().any(|x| matches!(x, ASrc::Const(_))),
630 AnalogCell::GainConst { a, .. } | AnalogCell::Mul { a, .. } => {
631 matches!(a, ASrc::Const(_))
632 }
633 AnalogCell::Select { c, t, f } => [c, t, f].iter().any(|x| matches!(x, ASrc::Const(_))),
634 AnalogCell::Exp { a }
635 | AnalogCell::Log { a }
636 | AnalogCell::Tanh { a }
637 | AnalogCell::Sqrt { a } => matches!(a, ASrc::Const(_)),
638 AnalogCell::Div { a, b } => [a, b].iter().any(|x| matches!(x, ASrc::Const(_))),
639 };
640 if has_const {
641 pinned_io[i] = true;
642 }
643 if let AnalogCell::Select { t, f, .. } = cell {
646 pinned_io[i] = true;
647 for x in [t, f] {
648 if let ASrc::Val(v) = x {
649 pinned_io[*v] = true;
650 }
651 }
652 }
653 }
654 let mut mul_class = vec![false; n];
656 let mut mul_reads_input = false;
657 for (i, cell) in quantized_math.cells.iter().enumerate() {
658 if let AnalogCell::Mul { a, b } = cell {
659 mul_class[i] = true;
660 for x in [a, b] {
661 match x {
662 ASrc::Val(v) => mul_class[*v] = true,
663 ASrc::In(_) => mul_reads_input = true,
664 ASrc::Const(_) => {}
665 }
666 }
667 }
668 }
669 let has_mul = mul_class.iter().any(|m| *m);
670
671 let e_m = if has_mul {
674 let mut opts: Vec<f64> = target.mul_vref_options_v.iter().map(|v| v.0).collect();
675 opts.sort_by(|a, b| b.partial_cmp(a).unwrap());
676 let candidate = opts.iter().filter_map(|v| {
677 let e = v.log2();
678 (e.fract() == 0.0).then_some(e as i32)
679 });
680 let mut chosen = None;
681 for e in candidate {
682 if e < lo_e || e > hi_e {
683 continue;
684 }
685 let class_ok = (0..n).all(|i| !mul_class[i] || fits_one(&bounds[i], e));
686 let io_ok = !mul_reads_input || io_bounds.iter().all(|b| fits_one(b, e));
687 if class_ok && io_ok {
688 chosen = Some(e);
689 break;
690 }
691 }
692 Some(chosen.ok_or_else(|| {
693 AnaLowerError::Unplaceable(AnaUnplaceable::MulReferenceUnrealizable {
694 needed_v: half_span,
695 options_v: target.mul_vref_options_v.iter().map(|v| v.0).collect(),
696 target: target.name,
697 })
698 })?)
699 } else {
700 None
701 };
702
703 let e_io = if mul_reads_input {
706 e_m.expect("mul_reads_input implies a Mul")
707 } else {
708 io_bounds
709 .iter()
710 .try_fold(hi_e, |acc, b| best_for(b).map(|e| acc.min(e)))
711 .ok_or_else(|| dynamic_range_refusal(&io_bounds, lo_e, half_span, target))?
712 };
713
714 let mut per_net = vec![0i32; n];
716 for i in 0..n {
717 per_net[i] = if mul_class[i] {
718 let e = e_m.expect("mul class implies e_m");
719 if !fits_one(&bounds[i], e) {
720 return Err(dynamic_range_refusal(&[bounds[i]], lo_e, half_span, target));
721 }
722 e
723 } else if pinned_io[i] {
724 if !fits_one(&bounds[i], e_io) {
725 return Err(dynamic_range_refusal(&[bounds[i]], lo_e, half_span, target));
726 }
727 e_io
728 } else {
729 best_for(&bounds[i])
730 .ok_or_else(|| dynamic_range_refusal(&[bounds[i]], lo_e, half_span, target))?
731 };
732 }
733
734 let lsb = target.weight_lsb();
736 let snap = |w: f64| -> f64 { (w / lsb).round() * lsb };
737 let src_e = |x: &ASrc| -> i32 {
738 match x {
739 ASrc::Val(v) => per_net[*v],
740 _ => e_io,
741 }
742 };
743 let mut phys_cells = Vec::with_capacity(n);
744 for (i, cell) in quantized_math.cells.iter().enumerate() {
745 phys_cells.push(match cell {
746 AnalogCell::Sum { inputs, weights } => {
747 let wq: Vec<f64> = inputs
748 .iter()
749 .zip(weights)
750 .map(|(x, w)| snap(w * f64::powi(2.0, per_net[i] - src_e(x))))
751 .collect();
752 for w in &wq {
753 if *w != 0.0 {
754 target
755 .require_gain_ratio(w.abs())
756 .map_err(AnaLowerError::Unplaceable)?;
757 }
758 }
759 AnalogCell::Sum {
760 inputs: inputs.clone(),
761 weights: wq,
762 }
763 }
764 AnalogCell::GainConst { a, k } => {
765 let kq = snap(k * f64::powi(2.0, per_net[i] - src_e(a)));
766 if kq != 0.0 {
767 target
768 .require_gain_ratio(kq.abs())
769 .map_err(AnaLowerError::Unplaceable)?;
770 }
771 AnalogCell::GainConst { a: *a, k: kq }
772 }
773 AnalogCell::Mul { a, b } => AnalogCell::Mul { a: *a, b: *b },
774 AnalogCell::Select { c, t, f } => AnalogCell::Select {
775 c: *c,
776 t: *t,
777 f: *f,
778 },
779 AnalogCell::Exp { a } => AnalogCell::Exp { a: *a },
780 AnalogCell::Log { a } => AnalogCell::Log { a: *a },
781 AnalogCell::Tanh { a } => AnalogCell::Tanh { a: *a },
782 AnalogCell::Sqrt { a } => AnalogCell::Sqrt { a: *a },
783 AnalogCell::Div { a, b } => AnalogCell::Div { a: *a, b: *b },
784 });
785 }
786 let quantized = AnalogNetlist {
787 n_in: quantized_math.n_in,
788 cells: phys_cells,
789 out: quantized_math.out,
790 };
791
792 let mut per_net_bounds = Vec::with_capacity(n);
795 for (i, b) in bounds.iter().enumerate() {
796 let s_i = f64::powi(2.0, per_net[i]);
797 let (volt_lo, volt_hi) = ((b.lo * s_i).next_down(), (b.hi * s_i).next_up());
798 if volt_lo < -half_span || volt_hi > half_span {
799 return Err(dynamic_range_refusal(&[*b], lo_e, half_span, target));
800 }
801 per_net_bounds.push(NetBound {
802 math: *b,
803 volt_lo,
804 volt_hi,
805 });
806 }
807
808 let schedule = GainSchedule {
809 scale_log2: e_io,
810 out_descale_log2: -e_io,
811 mul_vref_v: f64::powi(2.0, e_m.unwrap_or(e_io)),
812 quantized,
813 per_net_scale_log2: per_net,
814 };
815 let cert = RangeCertificate {
816 input_bounds: input_bounds.to_vec(),
817 per_net: per_net_bounds,
818 rail_half_span_v: half_span,
819 noise_floor_v: 0.0,
820 min_full_scale_over_noise: 1.0,
821 };
822 Ok((schedule, cert))
823}
824
825fn dynamic_range_refusal(
826 bounds: &[MathBound],
827 lo_e: i32,
828 half_span: f64,
829 target: &AnalogTarget,
830) -> AnaLowerError {
831 let global_max = bounds.iter().map(|b| b.max_abs()).fold(0.0_f64, f64::max);
832 AnaLowerError::Unplaceable(AnaUnplaceable::DynamicRangeExceeded {
833 net: 0,
834 needed_db: (20.0 * (global_max * f64::powi(2.0, lo_e) / half_span).log10()).max(0.0),
835 available_db: 0.0,
836 target: target.name,
837 })
838}
839
840pub fn to_range_claim(
844 schedule: &GainSchedule,
845 cert: &RangeCertificate,
846) -> (
847 Vec<logicaffeine_proof::interval_replay::RCell>,
848 logicaffeine_proof::interval_replay::RangeClaim,
849) {
850 use logicaffeine_proof::interval_replay::{RCell, RSrc, RangeClaim};
851 let realized = schedule.realized_math_cells();
855 let conv = |s: &ASrc| match s {
856 ASrc::In(k) => RSrc::In(*k),
857 ASrc::Val(v) => RSrc::Val(*v),
858 ASrc::Const(c) => RSrc::Const(*c),
859 };
860 let cells = realized
861 .iter()
862 .map(|c| match c {
863 AnalogCell::Sum { inputs, weights } => RCell::Sum {
864 inputs: inputs.iter().map(conv).collect(),
865 weights: weights.clone(),
866 },
867 AnalogCell::Mul { a, b } => RCell::Mul {
868 a: conv(a),
869 b: conv(b),
870 },
871 AnalogCell::GainConst { a, k } => RCell::Gain { a: conv(a), k: *k },
872 AnalogCell::Select { c, t, f } => RCell::Select {
873 c: conv(c),
874 t: conv(t),
875 f: conv(f),
876 },
877 AnalogCell::Exp { a } => RCell::Exp { a: conv(a) },
878 AnalogCell::Log { a } => RCell::Log { a: conv(a) },
879 AnalogCell::Tanh { a } => RCell::Tanh { a: conv(a) },
880 AnalogCell::Sqrt { a } => RCell::Sqrt { a: conv(a) },
881 AnalogCell::Div { a, b } => RCell::Div {
882 a: conv(a),
883 b: conv(b),
884 },
885 })
886 .collect();
887 let claim = RangeClaim {
888 input_lo: cert.input_bounds.iter().map(|b| b.lo).collect(),
889 input_hi: cert.input_bounds.iter().map(|b| b.hi).collect(),
890 scale_log2: schedule.per_net_scale_log2.clone(),
891 node_lo_v: cert.per_net.iter().map(|n| n.volt_lo).collect(),
892 node_hi_v: cert.per_net.iter().map(|n| n.volt_hi).collect(),
893 rail_half_span_v: cert.rail_half_span_v,
894 noise_floor_v: cert.noise_floor_v,
895 min_full_scale_over_noise: cert.min_full_scale_over_noise,
896 };
897 (cells, claim)
898}