1use arcstr::ArcStr;
4use itertools::Itertools;
5use scir::netlist::ConvertibleNetlister;
6use std::collections::{HashMap, HashSet};
7
8use std::io::{Result, Write};
9use std::path::PathBuf;
10
11use crate::{BlackboxElement, Primitive, Spice};
12use scir::schema::Schema;
13use scir::{
14 Cell, ChildId, Library, NetlistCellConversion, NetlistLibConversion, SignalInfo, Slice,
15};
16
17#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
19pub struct Include {
20 pub path: PathBuf,
22 pub section: Option<ArcStr>,
24}
25
26impl<T: Into<PathBuf>> From<T> for Include {
27 fn from(value: T) -> Self {
28 Self {
29 path: value.into(),
30 section: None,
31 }
32 }
33}
34
35impl Include {
36 pub fn new(path: impl Into<PathBuf>) -> Self {
38 Self::from(path)
39 }
40
41 pub fn section(mut self, section: impl Into<ArcStr>) -> Self {
43 self.section = Some(section.into());
44 self
45 }
46}
47
48pub trait HasSpiceLikeNetlist: Schema {
50 #[allow(unused_variables)]
54 fn write_prelude<W: Write>(&self, out: &mut W, lib: &Library<Self>) -> Result<()> {
55 Ok(())
56 }
57 fn write_include<W: Write>(&self, out: &mut W, include: &Include) -> Result<()>;
61 fn write_start_subckt<W: Write>(
65 &self,
66 out: &mut W,
67 name: &ArcStr,
68 ports: &[&SignalInfo],
69 ) -> Result<()>;
70 fn write_end_subckt<W: Write>(&self, out: &mut W, name: &ArcStr) -> Result<()>;
74 fn write_instance<W: Write>(
78 &self,
79 out: &mut W,
80 name: &ArcStr,
81 connections: Vec<ArcStr>,
82 child: &ArcStr,
83 ) -> Result<ArcStr>;
84 fn write_primitive_inst<W: Write>(
88 &self,
89 out: &mut W,
90 name: &ArcStr,
91 connections: HashMap<ArcStr, Vec<ArcStr>>,
92 primitive: &<Self as Schema>::Primitive,
93 ) -> Result<ArcStr>;
94 fn write_slice<W: Write>(&self, out: &mut W, slice: Slice, info: &SignalInfo) -> Result<()> {
98 if let Some(range) = slice.range() {
99 for i in range.indices() {
100 if i > range.start() {
101 write!(out, " ")?;
102 }
103 write!(out, "{}[{}]", &info.name, i)?;
104 }
105 } else {
106 write!(out, "{}", &info.name)?;
107 }
108 Ok(())
109 }
110 #[allow(unused_variables)]
112 fn write_postlude<W: Write>(&self, out: &mut W, lib: &Library<Self>) -> Result<()> {
113 Ok(())
114 }
115}
116
117#[derive(Clone, Debug)]
119pub enum RenameGround {
120 Yes(ArcStr),
122 No,
124}
125
126#[derive(Clone, Debug, Default)]
128#[enumify::enumify(no_as_ref, no_as_mut)]
129pub enum NetlistKind {
130 #[default]
132 Cells,
133 Testbench(RenameGround),
136}
137
138#[derive(Clone, Debug, Default)]
140pub struct NetlistOptions<'a> {
141 kind: NetlistKind,
142 includes: &'a [Include],
143}
144
145impl<'a> NetlistOptions<'a> {
146 pub fn new(kind: NetlistKind, includes: &'a [Include]) -> Self {
148 Self { kind, includes }
149 }
150}
151
152pub struct NetlisterInstance<'a, S: Schema, W> {
154 schema: &'a S,
155 lib: &'a Library<S>,
156 out: &'a mut W,
157 opts: NetlistOptions<'a>,
158}
159
160impl<'a, S: Schema, W> NetlisterInstance<'a, S, W> {
161 pub fn new(
163 schema: &'a S,
164 lib: &'a Library<S>,
165 out: &'a mut W,
166 opts: NetlistOptions<'a>,
167 ) -> Self {
168 Self {
169 schema,
170 lib,
171 out,
172 opts,
173 }
174 }
175}
176
177impl<S: HasSpiceLikeNetlist, W: Write> NetlisterInstance<'_, S, W> {
178 pub fn export(mut self) -> Result<NetlistLibConversion> {
180 let lib = self.export_library()?;
181 self.out.flush()?;
182 Ok(lib)
183 }
184
185 fn export_library(&mut self) -> Result<NetlistLibConversion> {
186 self.schema.write_prelude(self.out, self.lib)?;
187 for include in self.opts.includes {
188 self.schema.write_include(self.out, include)?;
189 writeln!(self.out)?;
190 }
191 writeln!(self.out)?;
192
193 let mut conv = NetlistLibConversion::new();
194
195 for (id, cell) in self.lib.cells() {
196 conv.cells
197 .insert(id, self.export_cell(cell, self.lib.is_top(id))?);
198 }
199
200 self.schema.write_postlude(self.out, self.lib)?;
201 Ok(conv)
202 }
203
204 fn export_cell(&mut self, cell: &Cell, is_top: bool) -> Result<NetlistCellConversion> {
205 let is_testbench_top = is_top && self.opts.kind.is_testbench();
206
207 let indent = if is_testbench_top { "" } else { " " };
208
209 let ground = match (is_testbench_top, &self.opts.kind) {
210 (true, NetlistKind::Testbench(RenameGround::Yes(replace_with))) => {
211 let msg = "testbench should have one port: ground";
212 let mut ports = cell.ports();
213 let ground = ports.next().expect(msg);
214 assert!(ports.next().is_none(), "{}", msg);
215 let ground = &cell.signal(ground.signal()).name;
216 Some((ground.clone(), replace_with.clone()))
217 }
218 _ => None,
219 };
220
221 if !is_testbench_top {
222 let ports: Vec<&SignalInfo> = cell
223 .ports()
224 .map(|port| cell.signal(port.signal()))
225 .collect();
226 self.schema
227 .write_start_subckt(self.out, cell.name(), &ports)?;
228 writeln!(self.out, "\n")?;
229 }
230
231 let mut conv = NetlistCellConversion::new();
232 for (id, inst) in cell.instances() {
233 write!(self.out, "{}", indent)?;
234 let mut connections: HashMap<_, _> = inst
235 .connections()
236 .iter()
237 .map(|(k, v)| {
238 Ok((
239 k.clone(),
240 v.parts()
241 .map(|part| self.make_slice(cell, *part, &ground))
242 .collect::<Result<Vec<_>>>()?,
243 ))
244 })
245 .collect::<Result<_>>()?;
246 let name = match inst.child() {
247 ChildId::Cell(child_id) => {
248 let child = self.lib.cell(child_id);
249 let ports = child
250 .ports()
251 .flat_map(|port| {
252 let port_name = &child.signal(port.signal()).name;
253 connections.remove(port_name).unwrap()
254 })
255 .collect::<Vec<_>>();
256 self.schema
257 .write_instance(self.out, inst.name(), ports, child.name())?
258 }
259 ChildId::Primitive(child_id) => {
260 let child = self.lib.primitive(child_id);
261 self.schema
262 .write_primitive_inst(self.out, inst.name(), connections, child)?
263 }
264 };
265 conv.instances.insert(id, name);
266 writeln!(self.out)?;
267 }
268
269 if !is_testbench_top {
270 writeln!(self.out)?;
271 self.schema.write_end_subckt(self.out, cell.name())?;
272 writeln!(self.out, "\n")?;
273 }
274 Ok(conv)
275 }
276
277 fn make_slice(
278 &mut self,
279 cell: &Cell,
280 slice: Slice,
281 rename_ground: &Option<(ArcStr, ArcStr)>,
282 ) -> Result<ArcStr> {
283 let sig_info = cell.signal(slice.signal());
284 if let Some((signal, replace_with)) = rename_ground
285 && signal == &sig_info.name
286 && slice.range().is_none()
287 {
288 return Ok(replace_with.clone());
291 }
292 let mut buf = Vec::new();
293 self.schema.write_slice(&mut buf, slice, sig_info)?;
294 Ok(ArcStr::from(std::str::from_utf8(&buf).expect(
295 "slice should only have UTF8-compatible characters",
296 )))
297 }
298}
299
300impl HasSpiceLikeNetlist for Spice {
301 fn write_prelude<W: Write>(&self, out: &mut W, lib: &Library<Self>) -> std::io::Result<()> {
302 writeln!(out, "* Substrate SPICE library")?;
303 writeln!(
304 out,
305 "* This is a generated file. Be careful when editing manually: this file may be overwritten.\n"
306 )?;
307
308 for (_, p) in lib.primitives() {
309 if let Primitive::RawInstanceWithCell {
310 cell, ports, body, ..
311 } = p
312 {
313 write!(out, ".SUBCKT {}", cell)?;
314 for port in ports {
315 write!(out, " {}", port)?;
316 }
317 writeln!(out)?;
318 writeln!(out, "{}", body)?;
319 self.write_end_subckt(out, cell)?;
320 writeln!(out)?;
321 }
322 }
323
324 let includes = lib
325 .primitives()
326 .filter_map(|p| {
327 if let Primitive::RawInstanceWithInclude { netlist, .. } = p.1 {
328 Some(netlist.clone())
329 } else {
330 None
331 }
332 })
333 .collect::<HashSet<_>>();
334 for include in includes.iter().sorted() {
336 writeln!(out, ".INCLUDE {:?}", include)?;
337 }
338
339 Ok(())
340 }
341
342 fn write_include<W: Write>(&self, out: &mut W, include: &Include) -> std::io::Result<()> {
343 if let Some(section) = &include.section {
344 write!(out, ".LIB {:?} {}", include.path, section)?;
345 } else {
346 write!(out, ".INCLUDE {:?}", include.path)?;
347 }
348 Ok(())
349 }
350
351 fn write_start_subckt<W: Write>(
352 &self,
353 out: &mut W,
354 name: &ArcStr,
355 ports: &[&SignalInfo],
356 ) -> std::io::Result<()> {
357 write!(out, ".SUBCKT {}", name)?;
358 for sig in ports {
359 if let Some(width) = sig.width {
360 for i in 0..width {
361 write!(out, " {}[{}]", sig.name, i)?;
362 }
363 } else {
364 write!(out, " {}", sig.name)?;
365 }
366 }
367 Ok(())
368 }
369
370 fn write_end_subckt<W: Write>(&self, out: &mut W, name: &ArcStr) -> std::io::Result<()> {
371 write!(out, ".ENDS {}", name)
372 }
373
374 fn write_instance<W: Write>(
375 &self,
376 out: &mut W,
377 name: &ArcStr,
378 connections: Vec<ArcStr>,
379 child: &ArcStr,
380 ) -> std::io::Result<ArcStr> {
381 let name = arcstr::format!("X{}", name);
382 write!(out, "{}", name)?;
383
384 for connection in connections {
385 write!(out, " {}", connection)?;
386 }
387
388 write!(out, " {}", child)?;
389
390 Ok(name)
391 }
392
393 fn write_primitive_inst<W: Write>(
394 &self,
395 out: &mut W,
396 name: &ArcStr,
397 mut connections: HashMap<ArcStr, Vec<ArcStr>>,
398 primitive: &<Self as Schema>::Primitive,
399 ) -> std::io::Result<ArcStr> {
400 let name = match &primitive {
401 Primitive::Res2 { value, params } => {
402 let name = arcstr::format!("R{}", name);
403 write!(out, "{}", name)?;
404 for port in ["1", "2"] {
405 for part in connections
406 .remove(port)
407 .unwrap_or_else(|| panic!("res2 instance `{name}` must connect to all ports; missing connection to port `{port}`"))
408 {
409 write!(out, " {}", part)?;
410 }
411 }
412 write!(out, " {value}")?;
413 for (key, value) in params.iter().sorted_by_key(|(key, _)| *key) {
414 write!(out, " {key}={value}")?;
415 }
416 name
417 }
418 Primitive::Cap2 { value } => {
419 let name = arcstr::format!("C{}", name);
420 write!(out, "{}", name)?;
421 for port in ["1", "2"] {
422 for part in connections.remove(port).unwrap() {
423 write!(out, " {}", part)?;
424 }
425 }
426 write!(out, " {value}")?;
427 name
428 }
429 Primitive::Diode2 {
430 model: mname,
431 params,
432 } => {
433 let name = arcstr::format!("D{}", name);
434 write!(out, "{}", name)?;
435 for port in ["1", "2"] {
436 for part in connections
437 .remove(port)
438 .unwrap_or_else(|| panic!("diode2 instance `{name}` must connect to all ports; missing connection to port `{port}`"))
439 {
440 write!(out, " {}", part)?;
441 }
442 }
443 write!(out, " {}", mname)?;
444 for (key, value) in params.iter().sorted_by_key(|(key, _)| *key) {
445 write!(out, " {key}={value}")?;
446 }
447 name
448 }
449 Primitive::Bjt {
450 model: mname,
451 params,
452 has_substrate_port,
453 } => {
454 let name = arcstr::format!("Q{}", name);
455 write!(out, "{}", name)?;
456 for &port in if *has_substrate_port {
457 ["NC", "NB", "NE", "NS"].iter()
458 } else {
459 ["NC", "NB", "NE"].iter()
460 } {
461 for part in connections.remove(port).unwrap() {
462 write!(out, " {}", part)?;
463 }
464 }
465 write!(out, " {}", mname)?;
466 for (key, value) in params.iter().sorted_by_key(|(key, _)| *key) {
467 write!(out, " {key}={value}")?;
468 }
469 name
470 }
471 Primitive::Mos {
472 model: mname,
473 params,
474 } => {
475 let name = arcstr::format!("M{}", name);
476 write!(out, "{}", name)?;
477 for port in ["D", "G", "S", "B"] {
478 for part in connections.remove(port).unwrap() {
479 write!(out, " {}", part)?;
480 }
481 }
482 write!(out, " {}", mname)?;
483 for (key, value) in params.iter().sorted_by_key(|(key, _)| *key) {
484 write!(out, " {key}={value}")?;
485 }
486 name
487 }
488 Primitive::RawInstance { cell, ports, .. }
489 | Primitive::RawInstanceWithCell { cell, ports, .. }
490 | Primitive::RawInstanceWithInclude { cell, ports, .. } => {
491 let default_params = HashMap::new();
492 let params = match &primitive {
493 Primitive::RawInstance { params, .. }
494 | Primitive::RawInstanceWithCell { params, .. } => params,
495 _ => &default_params,
496 };
497 let name = arcstr::format!("X{}", name);
498 write!(out, "{}", name)?;
499 for port in ports {
500 for part in connections.remove(port).unwrap() {
501 write!(out, " {}", part)?;
502 }
503 }
504 write!(out, " {}", cell)?;
505 for (key, value) in params.iter().sorted_by_key(|(key, _)| *key) {
506 write!(out, " {key}={value}")?;
507 }
508 name
509 }
510 Primitive::BlackboxInstance { contents } => {
511 for elem in &contents.elems {
515 match elem {
516 BlackboxElement::InstanceName => write!(out, "{}", name)?,
517 BlackboxElement::RawString(s) => write!(out, "{}", s)?,
518 BlackboxElement::Port(p) => {
519 for part in connections.get(p).unwrap() {
520 write!(out, "{}", part)?
521 }
522 }
523 }
524 }
525 name.clone()
526 }
527 };
528 writeln!(out)?;
529 Ok(name)
530 }
531}
532
533impl ConvertibleNetlister<Spice> for Spice {
534 type Error = std::io::Error;
535 type Options<'a> = NetlistOptions<'a>;
536
537 fn write_scir_netlist<W: Write>(
538 &self,
539 lib: &Library<Spice>,
540 out: &mut W,
541 opts: Self::Options<'_>,
542 ) -> std::result::Result<NetlistLibConversion, Self::Error> {
543 NetlisterInstance::new(self, lib, out, opts).export()
544 }
545}
546
547impl substrate::schematic::netlist::ConvertibleNetlister<Spice> for Spice {}