1use std::collections::{HashMap, HashSet};
8
9use crate::parser::shorts::ShortPropagator;
10use crate::{ComponentValue, Primitive, Spice};
11use arcstr::ArcStr;
12use lazy_static::lazy_static;
13use num_traits::Pow;
14use regex::Regex;
15use rust_decimal::Decimal;
16use rust_decimal::prelude::One;
17use scir::ParamValue;
18
19use thiserror::Error;
20use unicase::UniCase;
21
22use super::{Ast, Component, DeviceValue, Elem, Node, Subckt, Substr};
23
24pub type SubcktName = Substr;
26
27pub type ConvResult<T> = std::result::Result<T, ConvError>;
29
30#[derive(Debug, Error)]
32pub enum ConvError {
33 #[error("an instance of subcircuit `{0}` exists, but no definition was provided")]
35 MissingSubckt(Substr),
36 #[error(
38 "incorrect (missing/extra) connections for instance {inst} of cell `{child}` (in cell `{parent}`)"
39 )]
40 IncorrectConnections {
41 inst: Substr,
43 child: Substr,
45 parent: Substr,
47 },
48 #[error("invalid literal: `{0}`")]
49 InvalidLiteral(Substr),
51 #[error("cannot export a blackboxed subcircuit")]
53 ExportBlackbox,
54 #[error("netlist conversion produced SCIR containing errors: {0}")]
56 InvalidScir(Box<scir::Issues>),
57 #[error(
61 "parameters for instance {inst} of cell `{child}` (in cell `{parent}`) are not allowed because `{child}` was not blackboxed"
62 )]
63 UnsupportedParams {
64 inst: Substr,
66 child: Substr,
68 parent: Substr,
70 },
71}
72
73pub struct ScirConverter<'a> {
78 ast: &'a Ast,
79 lib: scir::LibraryBuilder<Spice>,
80 blackbox_cells: HashSet<Substr>,
81 subckts: HashMap<SubcktName, &'a Subckt>,
82 ids: HashMap<SubcktName, scir::CellId>,
83}
84
85impl<'a> ScirConverter<'a> {
86 pub fn new(ast: &'a Ast) -> Self {
88 Self {
89 ast,
90 lib: scir::LibraryBuilder::new(),
91 blackbox_cells: Default::default(),
92 subckts: Default::default(),
93 ids: Default::default(),
94 }
95 }
96
97 pub fn blackbox(&mut self, cell_name: impl Into<Substr>) {
99 self.blackbox_cells.insert(cell_name.into());
100 }
101
102 pub fn convert(mut self) -> ConvResult<scir::Library<Spice>> {
104 self.subckts = map_subckts(self.ast);
105 let subckts = self.subckts.values().copied().collect::<Vec<_>>();
106 let mut shorts = ShortPropagator::analyze(self.ast, &self.blackbox_cells);
107 for subckt in subckts {
108 match self.convert_subckt(subckt, &mut shorts) {
109 Ok(_) | Err(ConvError::ExportBlackbox) => (),
112 Err(e) => return Err(e),
113 };
114 }
115 let lib = self
116 .lib
117 .build()
118 .map_err(|issues| ConvError::InvalidScir(Box::new(issues)))?;
119 Ok(lib)
120 }
121
122 fn convert_subckt(
123 &mut self,
124 subckt: &Subckt,
125 shorts: &mut ShortPropagator,
126 ) -> ConvResult<scir::CellId> {
127 if let Some(&id) = self.ids.get(&subckt.name) {
128 return Ok(id);
129 }
130
131 if self.blackbox_cells.contains(&subckt.name) {
132 return Err(ConvError::ExportBlackbox);
133 }
134
135 let parent_name = subckt.name.clone();
136
137 let mut cell = scir::Cell::new(ArcStr::from(subckt.name.as_str()));
138 let mut nodes: HashMap<Substr, scir::SliceOne> = HashMap::new();
139 let mut local_shorts = shorts.get_cell(&parent_name).clone();
141 let mut node = |name: &Node, cell: &mut scir::Cell| {
142 let name = local_shorts.root(name);
143 if let Some(&node) = nodes.get(&name) {
144 return node;
145 }
146 let id = cell.add_node(name.as_str());
147 nodes.insert(name.clone(), id);
148 id
149 };
150
151 for component in subckt.components.iter() {
152 match component {
153 Component::Mos(mos) => {
154 let model = ArcStr::from(mos.model.as_str());
155 let params = mos
156 .params
157 .iter()
158 .map(|(k, v)| {
159 Ok((
160 UniCase::new(ArcStr::from(k.as_str())),
161 match substr_as_numeric_lit(v) {
162 Ok(v) => ParamValue::Numeric(v),
163 Err(_) => ParamValue::String(v.to_string().into()),
164 },
165 ))
166 })
167 .collect::<ConvResult<HashMap<_, _>>>()?;
168 let id = self.lib.add_primitive(Primitive::Mos { model, params });
170 let mut sinst = scir::Instance::new(&mos.name[1..], id);
171 sinst.connect("D", node(&mos.d, &mut cell));
172 sinst.connect("G", node(&mos.g, &mut cell));
173 sinst.connect("S", node(&mos.s, &mut cell));
174 sinst.connect("B", node(&mos.b, &mut cell));
175 cell.add_instance(sinst);
176 }
177 Component::Diode(diode) => {
178 let model = ArcStr::from(diode.model.as_str());
179 let params = diode
180 .params
181 .iter()
182 .map(|(k, v)| {
183 Ok((
184 UniCase::new(ArcStr::from(k.as_str())),
185 match substr_as_numeric_lit(v) {
186 Ok(v) => ParamValue::Numeric(v),
187 Err(_) => ParamValue::String(v.to_string().into()),
188 },
189 ))
190 })
191 .collect::<ConvResult<HashMap<_, _>>>()?;
192 let id = self.lib.add_primitive(Primitive::Diode2 { model, params });
194 let mut sinst = scir::Instance::new(&diode.name[1..], id);
195 sinst.connect("1", node(&diode.pos, &mut cell));
196 sinst.connect("2", node(&diode.neg, &mut cell));
197 cell.add_instance(sinst);
198 }
199 Component::Bjt(bjt) => {
200 let model = ArcStr::from(bjt.model.as_str());
201 let params = bjt
202 .params
203 .iter()
204 .map(|(k, v)| {
205 Ok((
206 UniCase::new(ArcStr::from(k.as_str())),
207 match substr_as_numeric_lit(v) {
208 Ok(v) => ParamValue::Numeric(v),
209 Err(_) => ParamValue::String(v.to_string().into()),
210 },
211 ))
212 })
213 .collect::<ConvResult<HashMap<_, _>>>()?;
214 let id = self.lib.add_primitive(Primitive::Bjt {
216 model,
217 params,
218 has_substrate_port: bjt.substrate.is_some(),
219 });
220 let mut sinst = scir::Instance::new(&bjt.name[1..], id);
221 sinst.connect("NC", node(&bjt.collector, &mut cell));
222 sinst.connect("NB", node(&bjt.base, &mut cell));
223 sinst.connect("NE", node(&bjt.emitter, &mut cell));
224 if let Some(substrate) = &bjt.substrate {
225 sinst.connect("NS", node(substrate, &mut cell));
226 }
227 cell.add_instance(sinst);
228 }
229 Component::Res(res) => {
230 let value = match &res.value {
231 DeviceValue::Value(value) => {
232 ComponentValue::Fixed(substr_as_numeric_lit(value)?)
233 }
234 DeviceValue::Model(model) => {
235 ComponentValue::Model(ArcStr::from(model.as_str()))
236 }
237 };
238 let params = res
239 .params
240 .iter()
241 .map(|(k, v)| {
242 Ok((
243 UniCase::new(ArcStr::from(k.as_str())),
244 match substr_as_numeric_lit(v) {
245 Ok(v) => ParamValue::Numeric(v),
246 Err(_) => ParamValue::String(v.to_string().into()),
247 },
248 ))
249 })
250 .collect::<ConvResult<HashMap<_, _>>>()?;
251 let id = self.lib.add_primitive(Primitive::Res2 { value, params });
252 let mut sinst = scir::Instance::new(&res.name[1..], id);
253 sinst.connect("1", node(&res.pos, &mut cell));
254 sinst.connect("2", node(&res.neg, &mut cell));
255 cell.add_instance(sinst);
256 }
257 Component::Cap(cap) => {
258 let id = self.lib.add_primitive(Primitive::Cap2 {
259 value: substr_as_numeric_lit(&cap.value)?,
260 });
261 let mut sinst = scir::Instance::new(&cap.name[1..], id);
262 sinst.connect("1", node(&cap.pos, &mut cell));
263 sinst.connect("2", node(&cap.neg, &mut cell));
264 cell.add_instance(sinst);
265 }
266 Component::Instance(inst) => {
267 let blackbox = self.blackbox_cells.contains(&inst.child);
268 if let (false, Some(subckt)) = (blackbox, self.subckts.get(&inst.child)) {
269 if !inst.params.values.is_empty() {
271 return Err(ConvError::UnsupportedParams {
272 inst: inst.name.clone(),
273 child: subckt.name.clone(),
274 parent: parent_name.clone(),
275 });
276 }
277
278 let id = self.convert_subckt(subckt, shorts)?;
279 let mut sinst = scir::Instance::new(&inst.name[1..], id);
280 let subckt = self
281 .subckts
282 .get(&inst.child)
283 .ok_or_else(|| ConvError::MissingSubckt(inst.child.clone()))?;
284
285 if subckt.ports.len() != inst.ports.len() {
286 return Err(ConvError::IncorrectConnections {
287 inst: inst.name.clone(),
288 child: subckt.name.clone(),
289 parent: parent_name.clone(),
290 });
291 }
292
293 let cshorts = shorts.get_cell(&inst.child);
294 for (cport, iport) in subckt.ports.iter().zip(inst.ports.iter()) {
295 if cshorts.root(cport) == *cport {
297 sinst.connect(cport.as_str(), node(iport, &mut cell));
298 }
299 }
300
301 if !inst.params.values.is_empty() {
302 return Err(ConvError::IncorrectConnections {
303 inst: inst.name.clone(),
304 child: subckt.name.clone(),
305 parent: parent_name.clone(),
306 });
307 }
308
309 cell.add_instance(sinst);
310 } else {
311 let child = ArcStr::from(inst.child.as_str());
312 let params = inst
313 .params
314 .iter()
315 .map(|(k, v)| {
316 Ok((
317 UniCase::new(ArcStr::from(k.as_str())),
318 match substr_as_numeric_lit(v) {
319 Ok(v) => ParamValue::Numeric(v),
320 Err(_) => ParamValue::String(v.to_string().into()),
321 },
322 ))
323 })
324 .collect::<ConvResult<HashMap<_, _>>>()?;
325 let ports: Vec<_> = (0..inst.ports.len())
326 .map(|i| arcstr::format!("{}", i + 1))
327 .collect();
328 let id = self.lib.add_primitive(Primitive::RawInstance {
329 cell: child,
330 ports: ports.clone(),
331 params,
332 });
333 let mut sinst = scir::Instance::new(&inst.name[1..], id);
334 for (cport, iport) in ports.iter().zip(inst.ports.iter()) {
335 sinst.connect(cport, node(iport, &mut cell));
336 }
337 cell.add_instance(sinst);
338 }
339 }
340 };
341 }
342
343 for port in subckt.ports.iter() {
344 let port = node(port, &mut cell);
345 cell.expose_port(port, Default::default());
348 }
349
350 let id = self.lib.add_cell(cell);
351 self.ids.insert(subckt.name.clone(), id);
352 Ok(id)
353 }
354}
355
356lazy_static! {
357 static ref NUMERIC_LITERAL_REGEX: Regex =
358 Regex::new(r"^(-?[0-9]+\.?[0-9]*)((t|g|x|meg|k|m|u|n|p|f)|(e(-?[0-9]+\.?[0-9]*)))?$")
359 .expect("failed to compile numeric literal regex");
360}
361
362pub(crate) fn convert_str_to_numeric_lit(s: &str) -> Option<Decimal> {
363 let caps = NUMERIC_LITERAL_REGEX.captures(s)?;
364 let num: Decimal = caps.get(1)?.as_str().parse().ok()?;
365 let multiplier = caps
366 .get(3)
367 .and_then(|s| {
368 Decimal::from_scientific(match s.as_str().to_lowercase().as_str() {
369 "t" => "1e12",
370 "g" => "1e9",
371 "x" | "meg" => "1e6",
372 "k" => "1e3",
373 "m" => "1e-3",
374 "u" => "1e-6",
375 "n" => "1e-9",
376 "p" => "1e-12",
377 "f" => "1e-15",
378 _ => "1e0",
379 })
380 .ok()
381 })
382 .or_else(|| {
383 caps.get(5)
384 .and_then(|s| s.as_str().parse().ok())
385 .map(|exp: Decimal| Decimal::TEN.pow(exp))
386 })
387 .unwrap_or_else(Decimal::one);
388
389 Some(num * multiplier)
390}
391
392pub(crate) fn str_as_numeric_lit(s: &str) -> std::result::Result<Decimal, ()> {
393 convert_str_to_numeric_lit(s).ok_or(())
394}
395fn substr_as_numeric_lit(s: &Substr) -> ConvResult<Decimal> {
396 str_as_numeric_lit(s).map_err(|_| ConvError::InvalidLiteral(s.clone()))
397}
398
399pub(crate) fn map_subckts(ast: &Ast) -> HashMap<SubcktName, &Subckt> {
400 let mut subckts = HashMap::new();
401 for elem in ast.elems.iter() {
402 match elem {
403 Elem::Subckt(s) => {
404 if subckts.insert(s.name.clone(), s).is_some() {
405 tracing::warn!(name=%s.name, "Duplicate subcircuits: found two subcircuits with the same name. The last one found will be used.");
406 }
407 }
408 _ => continue,
409 }
410 }
411 subckts
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417
418 #[test]
419 fn numeric_literal_regex() {
420 assert!(str_as_numeric_lit("2").is_ok());
421 assert!(str_as_numeric_lit("1.23").is_ok());
422 assert!(str_as_numeric_lit("-2").is_ok());
423 assert!(str_as_numeric_lit("-1.23").is_ok());
424 assert!(str_as_numeric_lit("0.0175668f").is_ok());
425 assert!(str_as_numeric_lit("8.88268e-19").is_ok());
426 assert!(str_as_numeric_lit("-0.0175668f").is_ok());
427 assert!(str_as_numeric_lit("-8.88268e-19").is_ok());
428 assert!(str_as_numeric_lit("8.88268e19").is_ok());
429 assert!(str_as_numeric_lit("-8.88268e19").is_ok());
430 }
431}