1use std::collections::BTreeMap;
17
18use crate::ast::stmt::{Stmt, TypeExpr};
19use crate::codegen_sva::fabric_rtl::{decision_fabric_from_source, FuncUnit, LoopCell, Src};
20
21use super::cell::{ASrc, AnalogCell, AnalogNetlist};
22use super::target::{AnaUnplaceable, AnalogCapability, AnalogTarget};
23use super::{assumed_ranges_in, AnaLowerError};
24
25#[derive(Clone, Copy)]
28pub(crate) enum NetRef {
29 Cell(usize),
30 Const(f64),
31}
32
33pub(crate) enum MappedCell {
35 Tile(AnalogCell),
37 Tiles(Vec<AnalogCell>),
41 Fold(f64),
43}
44
45pub(crate) const PREV_TILE: usize = usize::MAX - 1;
47
48pub(crate) fn push_tiles(cells: &mut Vec<AnalogCell>, tiles: Vec<AnalogCell>) -> usize {
50 let mut last = 0usize;
51 for mut t in tiles {
52 let prev = cells.len().wrapping_sub(1);
53 let fix = |s: &mut ASrc| {
54 if matches!(s, ASrc::Val(v) if *v == PREV_TILE) {
55 *s = ASrc::Val(prev);
56 }
57 };
58 match &mut t {
59 AnalogCell::Sum { inputs, .. } => inputs.iter_mut().for_each(fix),
60 AnalogCell::Mul { a, b } => {
61 fix(a);
62 fix(b);
63 }
64 AnalogCell::GainConst { a, .. } => fix(a),
65 AnalogCell::Select { c, t, f } => {
66 fix(c);
67 fix(t);
68 fix(f);
69 }
70 AnalogCell::Exp { a }
71 | AnalogCell::Log { a }
72 | AnalogCell::Tanh { a }
73 | AnalogCell::Sqrt { a } => fix(a),
74 AnalogCell::Div { a, b } => {
75 fix(a);
76 fix(b);
77 }
78 }
79 cells.push(t);
80 last = cells.len() - 1;
81 }
82 last
83}
84
85pub(crate) fn map_float_cell(
90 cell: &LoopCell,
91 asrc: &dyn Fn(&Src) -> ASrc,
92) -> Result<MappedCell, AnaLowerError> {
93 Ok(match cell {
94 LoopCell::Func { unit: FuncUnit::FAddF, a, b } => {
95 MappedCell::Tile(AnalogCell::Sum { inputs: vec![asrc(a), asrc(b)], weights: vec![1.0, 1.0] })
96 }
97 LoopCell::Func { unit: FuncUnit::FSubF, a, b } => {
98 MappedCell::Tile(AnalogCell::Sum { inputs: vec![asrc(a), asrc(b)], weights: vec![1.0, -1.0] })
99 }
100 LoopCell::Func { unit: FuncUnit::FMulF, a, b } => {
101 let (aa, bb) = (asrc(a), asrc(b));
102 MappedCell::Tile(match (aa, bb) {
103 (ASrc::Const(k), other) | (other, ASrc::Const(k)) => AnalogCell::GainConst { a: other, k },
104 _ => AnalogCell::Mul { a: aa, b: bb },
105 })
106 }
107 LoopCell::Func { unit: FuncUnit::IntToF, a, .. } => match a {
110 Src::Const(c) => MappedCell::Fold(*c as f64),
111 _ => {
112 return Err(AnaLowerError::UnloweredOp(
113 "int→float promotion of a runtime value (no voltage representation for a digital word mid-plane)".to_string(),
114 ))
115 }
116 },
117 LoopCell::Func { unit: FuncUnit::FDivF, a, b } => {
118 MappedCell::Tile(AnalogCell::Div { a: asrc(a), b: asrc(b) })
121 }
122 LoopCell::Func { unit: FuncUnit::FSqrtF, a, .. } => {
123 MappedCell::Tile(AnalogCell::Sqrt { a: asrc(a) })
124 }
125 LoopCell::Func { unit, .. } => {
126 return Err(AnaLowerError::UnloweredOp(format!("functional unit {unit:?} has no witnessed analog tile")))
127 }
128 LoopCell::Arith { op, .. } => {
129 return Err(AnaLowerError::UnloweredOp(format!("integer op {op:?} inside an analog region")))
130 }
131 LoopCell::Select { cmp, ca, cb, t, e } => {
132 use crate::codegen_sva::fabric_rtl::CmpKind;
137 let (x, y, tt, ff) = match cmp {
138 CmpKind::Lt | CmpKind::Le => (asrc(ca), asrc(cb), asrc(t), asrc(e)),
139 CmpKind::Gt | CmpKind::Ge => (asrc(cb), asrc(ca), asrc(t), asrc(e)),
140 CmpKind::Eq | CmpKind::Ne => {
141 return Err(AnaLowerError::UnloweredOp(
142 "equality select: a continuous comparator has no measure-zero decision"
143 .to_string(),
144 ))
145 }
146 };
147 MappedCell::Tiles(vec![
148 AnalogCell::Sum { inputs: vec![x, y], weights: vec![1.0, -1.0] },
149 AnalogCell::Select { c: ASrc::Val(PREV_TILE), t: tt, f: ff },
150 ])
151 }
152 LoopCell::MemRead { .. } | LoopCell::HeapRead { .. } | LoopCell::HeapWrite { .. }
153 | LoopCell::HeapAlloc { .. } => {
154 return Err(AnaLowerError::UnloweredOp("memory/state-plane access inside an analog region".to_string()))
155 }
156 LoopCell::VectorArith { .. } => {
157 return Err(AnaLowerError::UnloweredOp("packed-SIMD op inside an analog region".to_string()))
158 }
159 })
160}
161
162pub fn analog_netlist_from_source(
170 src: &str,
171 fn_name: &str,
172 target: &AnalogTarget,
173) -> Result<AnalogNetlist, AnaLowerError> {
174 check_params(src, fn_name)?; let twin = source_without_range_contracts(src);
179 let df = decision_fabric_from_source(&twin, fn_name);
180 if df.escaped_first_return {
181 return Err(AnaLowerError::UnloweredOp(
182 "early-return branch shape: a branch continues past the first Return, and a \
183 tolerance-verified lane cannot detect a dropped branch — write the conditional \
184 as Let/Set so it if-converts"
185 .to_string(),
186 ));
187 }
188
189 let mut cells: Vec<AnalogCell> = Vec::new();
191 let mut map: BTreeMap<usize, NetRef> = BTreeMap::new();
192
193 let asrc = |s: &Src, map: &BTreeMap<usize, NetRef>| -> ASrc {
194 match s {
195 Src::In(k) => ASrc::In(*k),
196 Src::Const(bits) => ASrc::Const(f64::from_bits(*bits as u64)),
197 Src::Val(v) => match map.get(v) {
198 Some(NetRef::Cell(c)) => ASrc::Val(*c),
199 Some(NetRef::Const(f)) => ASrc::Const(*f),
200 None => ASrc::Val(usize::MAX), },
202 }
203 };
204
205 for (i, cell) in df.cells.iter().enumerate() {
206 let resolve = |s: &Src| asrc(s, &map);
207 let mapped = match map_float_cell(cell, &resolve)? {
208 MappedCell::Tile(t) => NetRef::Cell(push(&mut cells, t)),
209 MappedCell::Tiles(ts) => NetRef::Cell(push_tiles(&mut cells, ts)),
210 MappedCell::Fold(v) => NetRef::Const(v),
211 };
212 map.insert(i, mapped);
213 }
214
215 let out = asrc(&df.result, &map);
216 if matches!(out, ASrc::Val(usize::MAX)) {
217 return Err(AnaLowerError::UnloweredOp(
218 "region output references an unlowered value".to_string(),
219 ));
220 }
221
222 let netlist = AnalogNetlist {
223 n_in: df.n_in,
224 cells,
225 out,
226 };
227 gate_on_target(&netlist, target)?;
228 Ok(netlist)
229}
230
231fn push(cells: &mut Vec<AnalogCell>, c: AnalogCell) -> usize {
232 cells.push(c);
233 cells.len() - 1
234}
235
236pub(crate) fn region_param_ranges(
240 src: &str,
241 fn_name: &str,
242) -> Result<Vec<super::AssumedRange>, AnaLowerError> {
243 check_params(src, fn_name)
244}
245
246fn check_params(src: &str, fn_name: &str) -> Result<Vec<super::AssumedRange>, AnaLowerError> {
249 crate::ui_bridge::with_parsed_program(src, |parsed, interner| {
250 let (stmts, _types, _pol) = match parsed {
251 Ok(t) => t,
252 Err(advice) => {
253 return Err(AnaLowerError::UnloweredOp(format!(
254 "parse failed: {advice}"
255 )))
256 }
257 };
258 let Some((params, body)) = stmts.iter().find_map(|s| match s {
259 Stmt::FunctionDef {
260 name, params, body, ..
261 } if interner.resolve(*name) == fn_name => Some((params, *body)),
262 _ => None,
263 }) else {
264 return Err(AnaLowerError::UnloweredOp(format!(
265 "function `{fn_name}` not found"
266 )));
267 };
268
269 let is_float = |t: &TypeExpr<'_>| match t {
270 TypeExpr::Primitive(s) | TypeExpr::Named(s) => {
271 matches!(interner.resolve(*s), "Float" | "Real")
272 }
273 _ => false,
274 };
275 if !params.iter().any(|(_, t)| is_float(t)) {
276 return Err(AnaLowerError::NoAnalogRegion);
277 }
278 let ranges = assumed_ranges_in(body, interner);
280 let mut ordered = Vec::with_capacity(params.len());
281 for (p, t) in params.iter() {
282 let pname = interner.resolve(*p);
283 if !is_float(t) {
284 return Err(AnaLowerError::UnloweredOp(format!(
285 "non-float parameter `{pname}` (no voltage representation)"
286 )));
287 }
288 let Some(r) = ranges.iter().find(|r| r.var == pname) else {
289 return Err(AnaLowerError::UnboundedInput(format!(
290 "parameter `{pname}` has no declared range (`Assume {pname} is between A and B.`)"
291 )));
292 };
293 if r.lo > r.hi {
294 return Err(AnaLowerError::ContradictoryContract {
295 var: r.var.clone(),
296 lo: r.lo,
297 hi: r.hi,
298 });
299 }
300 ordered.push(r.clone());
301 }
302 Ok(ordered)
303 })
304}
305
306pub(crate) fn source_without_range_contracts(src: &str) -> String {
312 src.lines()
313 .filter(|l| !l.trim_start().starts_with("Assume "))
314 .collect::<Vec<_>>()
315 .join("\n")
316}
317
318pub(crate) fn gate_on_target(
321 netlist: &AnalogNetlist,
322 target: &AnalogTarget,
323) -> Result<(), AnaLowerError> {
324 for cell in &netlist.cells {
325 let cap = AnalogCapability::Cell(cell.kind());
326 if !target.provides(cap) {
327 return Err(AnaLowerError::Unplaceable(
328 AnaUnplaceable::MissingCapability {
329 needed: cap,
330 target: target.name,
331 },
332 ));
333 }
334 }
335 let n_mul = netlist
336 .cells
337 .iter()
338 .filter(|c| matches!(c, super::cell::AnalogCell::Mul { .. }))
339 .count();
340 if n_mul > 0 && !target.provides(AnalogCapability::MulTiles(n_mul)) {
341 return Err(AnaLowerError::Unplaceable(
342 AnaUnplaceable::MissingCapability {
343 needed: AnalogCapability::MulTiles(n_mul),
344 target: target.name,
345 },
346 ));
347 }
348 for (cap_needed, _n) in [
349 (AnalogCapability::DacChannels(netlist.n_in), netlist.n_in),
350 (AnalogCapability::AdcChannels(1), 1),
351 ] {
352 if !target.provides(cap_needed) {
353 return Err(AnaLowerError::Unplaceable(
354 AnaUnplaceable::MissingCapability {
355 needed: cap_needed,
356 target: target.name,
357 },
358 ));
359 }
360 }
361 if netlist.cells.len() > target.rows * target.cols {
362 return Err(AnaLowerError::Unplaceable(AnaUnplaceable::TooSmall {
363 needed_cells: netlist.cells.len(),
364 target_rows: target.rows,
365 target_cols: target.cols,
366 target: target.name,
367 }));
368 }
369 Ok(())
370}