logicaffeine_compile/codegen_ana/
spice_emit.rs1use super::cell::{ASrc, AnalogCell};
12use super::gain::GainSchedule;
13use super::target::AnalogTarget;
14
15pub const SPICE_RELTOL: f64 = 1.0e-4;
18pub const SPICE_ABSTOL: f64 = 1.0e-12;
19pub const SPICE_VNTOL: f64 = 1.0e-9;
20
21pub fn emit_spice_dc(
24 schedule: &GainSchedule,
25 target: &AnalogTarget,
26 inputs: &[f64],
27 out_path: &str,
28) -> String {
29 let netlist = &schedule.quantized;
30 assert_eq!(inputs.len(), netlist.n_in);
31 let s = f64::powi(2.0, schedule.scale_log2);
32 let mut o = String::new();
33 o.push_str("* codegen_ana behavioral DC netlist (controlled-source macromodel)\n");
34 o.push_str(&format!(
35 "* target: {} | block scale 2^{} V/math | Vref {} V\n",
36 target.name, schedule.scale_log2, schedule.mul_vref_v
37 ));
38 o.push_str(&format!(
39 ".options reltol={SPICE_RELTOL} abstol={SPICE_ABSTOL} vntol={SPICE_VNTOL}\n"
40 ));
41 for (k, x) in inputs.iter().enumerate() {
42 o.push_str(&format!("VIN{k} in{k} 0 DC {}\n", x * s));
43 }
44 let node = |src: &ASrc| -> String {
45 match src {
46 ASrc::In(k) => format!("in{k}"),
47 ASrc::Val(v) => format!("n{v}"),
48 ASrc::Const(c) => format!("({})", c * s), }
50 };
51 let volt = |src: &ASrc| -> String {
52 match src {
53 ASrc::Const(c) => format!("({})", c * s),
54 other => format!("V({})", node(other)),
55 }
56 };
57 for (i, cell) in netlist.cells.iter().enumerate() {
58 match cell {
59 AnalogCell::Exp { a } => {
60 o.push_str(&format!("BEXP{i} n{i} 0 V = exp({})\n", volt(a)));
61 }
62 AnalogCell::Log { a } => {
63 o.push_str(&format!("BLOG{i} n{i} 0 V = ln({})\n", volt(a)));
64 }
65 AnalogCell::Tanh { a } => {
66 o.push_str(&format!("BTANH{i} n{i} 0 V = tanh({})\n", volt(a)));
67 }
68 AnalogCell::Sqrt { a } => {
69 o.push_str(&format!("BSQRT{i} n{i} 0 V = sqrt({})\n", volt(a)));
70 }
71 AnalogCell::Div { a, b } => {
72 o.push_str(&format!(
73 "BDIV{i} n{i} 0 V = ({}) / ({})\n",
74 volt(a),
75 volt(b)
76 ));
77 }
78 AnalogCell::Select { c, t, f } => {
79 o.push_str(&format!(
80 "BSEL{i} n{i} 0 V = (({}) < 0) ? ({}) : ({})\n",
81 volt(c),
82 volt(t),
83 volt(f)
84 ));
85 }
86 AnalogCell::Sum {
87 inputs: xs,
88 weights,
89 } => {
90 let terms: Vec<String> = xs
91 .iter()
92 .zip(weights)
93 .map(|(x, w)| format!("({w})*{}", volt(x)))
94 .collect();
95 o.push_str(&format!("BSUM{i} n{i} 0 V = {}\n", terms.join(" + ")));
96 }
97 AnalogCell::Mul { a, b } => {
98 o.push_str(&format!(
99 "BMUL{i} n{i} 0 V = {}*{}/({})\n",
100 volt(a),
101 volt(b),
102 schedule.mul_vref_v
103 ));
104 }
105 AnalogCell::GainConst { a, k } => {
106 o.push_str(&format!("BGAIN{i} n{i} 0 V = ({k})*{}\n", volt(a)));
107 }
108 }
109 }
110 let out_node = node(&netlist.out);
111 o.push_str(".op\n.control\nrun\n");
112 o.push_str(&format!("wrdata {out_path} v({out_node})\n"));
113 o.push_str("quit\n.endc\n.end\n");
114 o
115}