spectre/
blocks.rs

1//! Spectre-specific blocks for use in testbenches.
2
3use arcstr::ArcStr;
4use rust_decimal::Decimal;
5use scir::ParamValue;
6use serde::{Deserialize, Serialize};
7use std::path::PathBuf;
8use substrate::block::Block;
9use substrate::schematic::{CellBuilder, PrimitiveBinding, Schematic};
10use substrate::simulation::waveform::{TimeWaveform, Waveform};
11use substrate::types::{Array, InOut, Io, Signal, TwoTerminalIo};
12
13use crate::{Primitive, Spectre};
14
15/// Data associated with a pulse [`Vsource`].
16#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, PartialEq, Eq)]
17pub struct Pulse {
18    /// The zero value of the pulse.
19    pub val0: Decimal,
20    /// The one value of the pulse.
21    pub val1: Decimal,
22    /// The period of the pulse.
23    pub period: Option<Decimal>,
24    /// Rise time.
25    pub rise: Option<Decimal>,
26    /// Fall time.
27    pub fall: Option<Decimal>,
28    /// The pulse width.
29    pub width: Option<Decimal>,
30    /// Waveform delay.
31    pub delay: Option<Decimal>,
32}
33
34/// A voltage source.
35#[derive(Serialize, Deserialize, Debug, Clone, Hash, PartialEq, Eq)]
36pub enum Vsource {
37    /// A dc voltage source.
38    Dc(Decimal),
39    /// An AC small-signal current source.
40    Ac(AcSource),
41    /// A pulse voltage source.
42    Pulse(Pulse),
43    /// A piecewise linear source
44    Pwl(Waveform<Decimal>),
45}
46
47impl Vsource {
48    /// Creates a new DC voltage source.
49    #[inline]
50    pub fn dc(value: Decimal) -> Self {
51        Self::Dc(value)
52    }
53
54    /// Creates a new pulse voltage source.
55    #[inline]
56    pub fn pulse(value: Pulse) -> Self {
57        Self::Pulse(value)
58    }
59
60    /// Creates a new piecewise linear voltage source.
61    #[inline]
62    pub fn pwl(value: Waveform<Decimal>) -> Self {
63        Self::Pwl(value)
64    }
65}
66
67impl Block for Vsource {
68    type Io = TwoTerminalIo;
69
70    fn name(&self) -> arcstr::ArcStr {
71        // `vsource` is a reserved Spectre keyword,
72        // so we call this block `uservsource`.
73        arcstr::format!("uservsource")
74    }
75    fn io(&self) -> Self::Io {
76        Default::default()
77    }
78}
79
80impl Schematic for Vsource {
81    type Schema = Spectre;
82    type NestedData = ();
83
84    fn schematic(
85        &self,
86        io: &substrate::types::schematic::IoNodeBundle<Self>,
87        cell: &mut CellBuilder<<Self as Schematic>::Schema>,
88    ) -> substrate::error::Result<Self::NestedData> {
89        use arcstr::literal;
90        let mut params = Vec::new();
91        match self {
92            Vsource::Dc(dc) => {
93                params.push((literal!("type"), ParamValue::String(literal!("dc"))));
94                params.push((literal!("dc"), ParamValue::Numeric(*dc)));
95            }
96            Vsource::Pulse(pulse) => {
97                params.push((literal!("type"), ParamValue::String(literal!("pulse"))));
98                params.push((literal!("val0"), ParamValue::Numeric(pulse.val0)));
99                params.push((literal!("val1"), ParamValue::Numeric(pulse.val1)));
100                if let Some(period) = pulse.period {
101                    params.push((literal!("period"), ParamValue::Numeric(period)));
102                }
103                if let Some(rise) = pulse.rise {
104                    params.push((literal!("rise"), ParamValue::Numeric(rise)));
105                }
106                if let Some(fall) = pulse.fall {
107                    params.push((literal!("fall"), ParamValue::Numeric(fall)));
108                }
109                if let Some(width) = pulse.width {
110                    params.push((literal!("width"), ParamValue::Numeric(width)));
111                }
112                if let Some(delay) = pulse.delay {
113                    params.push((literal!("delay"), ParamValue::Numeric(delay)));
114                }
115            }
116            Vsource::Pwl(waveform) => {
117                let mut pwl = String::new();
118                pwl.push('[');
119                for (i, pt) in waveform.values().enumerate() {
120                    use std::fmt::Write;
121                    if i != 0 {
122                        pwl.push(' ');
123                    }
124                    write!(&mut pwl, "{} {}", pt.t(), pt.x()).unwrap();
125                }
126                pwl.push(']');
127                params.push((literal!("type"), ParamValue::String(literal!("pwl"))));
128                params.push((literal!("wave"), ParamValue::String(pwl.into())));
129            }
130            Vsource::Ac(ac) => {
131                params.push((literal!("type"), ParamValue::String(literal!("dc"))));
132                params.push((literal!("dc"), ParamValue::Numeric(ac.dc)));
133                params.push((literal!("mag"), ParamValue::Numeric(ac.mag)));
134                params.push((literal!("phase"), ParamValue::Numeric(ac.phase)));
135            }
136        };
137
138        let mut prim = PrimitiveBinding::new(Primitive::RawInstance {
139            cell: arcstr::literal!("vsource"),
140            ports: vec!["p".into(), "n".into()],
141            params,
142        });
143        prim.connect("p", io.p);
144        prim.connect("n", io.n);
145        cell.set_primitive(prim);
146        Ok(())
147    }
148}
149
150/// An AC source.
151#[derive(Serialize, Deserialize, Default, Debug, Copy, Clone, Hash, PartialEq, Eq)]
152pub struct AcSource {
153    /// The DC value.
154    pub dc: Decimal,
155    /// The magnitude.
156    pub mag: Decimal,
157    /// The phase, **in degrees*.
158    pub phase: Decimal,
159}
160
161/// A current source.
162///
163/// Positive current is drawn from the `p` node and enters the `n` node.
164#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, PartialEq, Eq)]
165pub enum Isource {
166    /// A DC current source.
167    Dc(Decimal),
168    /// An AC small-signal current source.
169    Ac(AcSource),
170    /// A pulse current source.
171    Pulse(Pulse),
172}
173
174impl Isource {
175    /// Creates a new DC current source.
176    #[inline]
177    pub fn dc(value: Decimal) -> Self {
178        Self::Dc(value)
179    }
180
181    /// Creates a new pulse current source.
182    #[inline]
183    pub fn pulse(value: Pulse) -> Self {
184        Self::Pulse(value)
185    }
186
187    /// Creates a new AC current source.
188    #[inline]
189    pub fn ac(value: AcSource) -> Self {
190        Self::Ac(value)
191    }
192}
193
194impl Block for Isource {
195    type Io = TwoTerminalIo;
196
197    fn name(&self) -> arcstr::ArcStr {
198        // `isource` is a reserved Spectre keyword,
199        // so we call this block `userisource`.
200        arcstr::format!("userisource")
201    }
202    fn io(&self) -> Self::Io {
203        Default::default()
204    }
205}
206
207impl Schematic for Isource {
208    type Schema = Spectre;
209    type NestedData = ();
210
211    fn schematic(
212        &self,
213        io: &substrate::types::schematic::IoNodeBundle<Self>,
214        cell: &mut CellBuilder<<Self as Schematic>::Schema>,
215    ) -> substrate::error::Result<Self::NestedData> {
216        use arcstr::literal;
217        let mut params = Vec::new();
218        match self {
219            Isource::Dc(dc) => {
220                params.push((literal!("type"), ParamValue::String(literal!("dc"))));
221                params.push((literal!("dc"), ParamValue::Numeric(*dc)));
222            }
223            Isource::Pulse(pulse) => {
224                params.push((literal!("type"), ParamValue::String(literal!("pulse"))));
225                params.push((literal!("val0"), ParamValue::Numeric(pulse.val0)));
226                params.push((literal!("val1"), ParamValue::Numeric(pulse.val1)));
227                if let Some(period) = pulse.period {
228                    params.push((literal!("period"), ParamValue::Numeric(period)));
229                }
230                if let Some(rise) = pulse.rise {
231                    params.push((literal!("rise"), ParamValue::Numeric(rise)));
232                }
233                if let Some(fall) = pulse.fall {
234                    params.push((literal!("fall"), ParamValue::Numeric(fall)));
235                }
236                if let Some(width) = pulse.width {
237                    params.push((literal!("width"), ParamValue::Numeric(width)));
238                }
239                if let Some(delay) = pulse.delay {
240                    params.push((literal!("delay"), ParamValue::Numeric(delay)));
241                }
242            }
243            Isource::Ac(ac) => {
244                params.push((literal!("type"), ParamValue::String(literal!("dc"))));
245                params.push((literal!("dc"), ParamValue::Numeric(ac.dc)));
246                params.push((literal!("mag"), ParamValue::Numeric(ac.mag)));
247                params.push((literal!("phase"), ParamValue::Numeric(ac.phase)));
248            }
249        };
250
251        let mut prim = PrimitiveBinding::new(Primitive::RawInstance {
252            cell: arcstr::literal!("isource"),
253            ports: vec!["p".into(), "n".into()],
254            params,
255        });
256        prim.connect("p", io.p);
257        prim.connect("n", io.n);
258        cell.set_primitive(prim);
259        Ok(())
260    }
261}
262
263/// A current probe.
264#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, PartialEq, Eq)]
265pub struct Iprobe;
266
267impl Block for Iprobe {
268    type Io = TwoTerminalIo;
269
270    fn name(&self) -> arcstr::ArcStr {
271        // `iprobe` is a reserved Spectre keyword,
272        // so we call this block `useriprobe`.
273        arcstr::format!("useriprobe")
274    }
275    fn io(&self) -> Self::Io {
276        Default::default()
277    }
278}
279
280impl Schematic for Iprobe {
281    type Schema = Spectre;
282    type NestedData = ();
283    fn schematic(
284        &self,
285        io: &substrate::types::schematic::IoNodeBundle<Self>,
286        cell: &mut CellBuilder<<Self as Schematic>::Schema>,
287    ) -> substrate::error::Result<Self::NestedData> {
288        let mut prim = PrimitiveBinding::new(Primitive::RawInstance {
289            cell: arcstr::literal!("iprobe"),
290            ports: vec!["in".into(), "out".into()],
291            params: Vec::new(),
292        });
293        prim.connect("in", io.p);
294        prim.connect("out", io.n);
295        cell.set_primitive(prim);
296        Ok(())
297    }
298}
299
300/// An n-port black box.
301#[derive(Serialize, Deserialize, Debug, Clone, Hash, PartialEq, Eq)]
302pub struct Nport {
303    parameter_file: PathBuf,
304    ports: usize,
305}
306
307impl Nport {
308    /// Creates a new n-port with the given parameter file and number of ports.
309    pub fn new(ports: usize, parameter_file: impl Into<PathBuf>) -> Self {
310        Self {
311            parameter_file: parameter_file.into(),
312            ports,
313        }
314    }
315}
316
317/// The interface of an [`Nport`].
318#[derive(Clone, Debug, Io)]
319pub struct NportIo {
320    /// The ports.
321    ///
322    /// Each port contains two signals: a p terminal and an n terminal.
323    pub ports: Array<TwoTerminalIo>,
324}
325
326impl Block for Nport {
327    type Io = NportIo;
328
329    fn name(&self) -> arcstr::ArcStr {
330        // `nport` is a reserved Spectre keyword,
331        // so we call this block `usernport`.
332        arcstr::format!("usernport")
333    }
334    fn io(&self) -> Self::Io {
335        NportIo {
336            ports: Array::new(self.ports, Default::default()),
337        }
338    }
339}
340
341impl Schematic for Nport {
342    type Schema = Spectre;
343    type NestedData = ();
344    fn schematic(
345        &self,
346        io: &substrate::types::schematic::IoNodeBundle<Self>,
347        cell: &mut CellBuilder<<Self as Schematic>::Schema>,
348    ) -> substrate::error::Result<Self::NestedData> {
349        let mut prim = PrimitiveBinding::new(Primitive::RawInstance {
350            cell: arcstr::literal!("nport"),
351            ports: (1..=self.ports)
352                .flat_map(|i| [arcstr::format!("t{i}"), arcstr::format!("b{i}")])
353                .collect(),
354            params: Vec::from_iter([(
355                arcstr::literal!("file"),
356                ParamValue::String(arcstr::format!("{:?}", self.parameter_file)),
357            )]),
358        });
359        for i in 0..self.ports {
360            prim.connect(arcstr::format!("t{}", i + 1), io.ports[i].p);
361            prim.connect(arcstr::format!("b{}", i + 1), io.ports[i].n);
362        }
363        cell.set_primitive(prim);
364        Ok(())
365    }
366}
367
368/// An ideal 2-terminal resistor.
369#[derive(Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
370pub struct Resistor {
371    /// The resistor value.
372    value: Decimal,
373}
374impl Resistor {
375    /// Create a new resistor with the given value.
376    #[inline]
377    pub fn new(value: impl Into<Decimal>) -> Self {
378        Self {
379            value: value.into(),
380        }
381    }
382
383    /// The value of the resistor.
384    #[inline]
385    pub fn value(&self) -> Decimal {
386        self.value
387    }
388}
389impl Block for Resistor {
390    type Io = TwoTerminalIo;
391
392    fn name(&self) -> ArcStr {
393        arcstr::format!("ideal_resistor_{}", self.value)
394    }
395
396    fn io(&self) -> Self::Io {
397        Default::default()
398    }
399}
400
401impl Schematic for Resistor {
402    type Schema = Spectre;
403    type NestedData = ();
404    fn schematic(
405        &self,
406        io: &substrate::types::schematic::IoNodeBundle<Self>,
407        cell: &mut CellBuilder<<Self as Schematic>::Schema>,
408    ) -> substrate::error::Result<Self::NestedData> {
409        let mut prim = PrimitiveBinding::new(Primitive::RawInstance {
410            cell: arcstr::literal!("resistor"),
411            ports: vec![arcstr::literal!("1"), arcstr::literal!("2")],
412            params: Vec::from_iter([(arcstr::literal!("r"), ParamValue::Numeric(self.value()))]),
413        });
414        prim.connect("1", io.p);
415        prim.connect("2", io.n);
416        cell.set_primitive(prim);
417        Ok(())
418    }
419}
420
421/// An ideal 2-terminal capacitor.
422#[derive(Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
423pub struct Capacitor {
424    /// The resistor value.
425    value: Decimal,
426}
427
428impl Capacitor {
429    /// Create a new capacitor with the given value.
430    #[inline]
431    pub fn new(value: impl Into<Decimal>) -> Self {
432        Self {
433            value: value.into(),
434        }
435    }
436
437    /// The value of the capacitor.
438    #[inline]
439    pub fn value(&self) -> Decimal {
440        self.value
441    }
442}
443
444impl Block for Capacitor {
445    type Io = TwoTerminalIo;
446
447    fn name(&self) -> ArcStr {
448        arcstr::format!("capacitor_{}", self.value)
449    }
450
451    fn io(&self) -> Self::Io {
452        Default::default()
453    }
454}
455
456impl Schematic for Capacitor {
457    type Schema = Spectre;
458    type NestedData = ();
459    fn schematic(
460        &self,
461        io: &substrate::types::schematic::IoNodeBundle<Self>,
462        cell: &mut CellBuilder<<Self as Schematic>::Schema>,
463    ) -> substrate::error::Result<Self::NestedData> {
464        let mut prim = PrimitiveBinding::new(Primitive::RawInstance {
465            cell: arcstr::literal!("capacitor"),
466            ports: vec![arcstr::literal!("1"), arcstr::literal!("2")],
467            params: Vec::from_iter([(arcstr::literal!("c"), ParamValue::Numeric(self.value()))]),
468        });
469        prim.connect("1", io.p);
470        prim.connect("2", io.n);
471        cell.set_primitive(prim);
472        Ok(())
473    }
474}
475
476/// An instance with a pre-defined cell.
477#[derive(Debug, Clone, PartialEq, Hash, Eq, Serialize, Deserialize)]
478pub struct RawInstance {
479    /// The name of the underlying cell.
480    pub cell: ArcStr,
481    /// The name of the ports of the underlying cell.
482    pub ports: Vec<ArcStr>,
483    /// The parameters to pass to the instance.
484    pub params: Vec<(ArcStr, ParamValue)>,
485}
486
487impl RawInstance {
488    /// Create a new raw instance with the given parameters.
489    #[inline]
490    pub fn with_params(
491        cell: ArcStr,
492        ports: Vec<ArcStr>,
493        params: impl Into<Vec<(ArcStr, ParamValue)>>,
494    ) -> Self {
495        Self {
496            cell,
497            ports,
498            params: params.into(),
499        }
500    }
501    /// Create a new raw instance with no parameters.
502    #[inline]
503    pub fn new(cell: ArcStr, ports: Vec<ArcStr>) -> Self {
504        Self {
505            cell,
506            ports,
507            params: Vec::new(),
508        }
509    }
510}
511impl Block for RawInstance {
512    type Io = InOut<Array<Signal>>;
513
514    fn name(&self) -> ArcStr {
515        arcstr::format!("raw_instance_{}", self.cell)
516    }
517
518    fn io(&self) -> Self::Io {
519        InOut(Array::new(self.ports.len(), Default::default()))
520    }
521}
522
523impl Schematic for RawInstance {
524    type Schema = Spectre;
525    type NestedData = ();
526    fn schematic(
527        &self,
528        io: &substrate::types::schematic::IoNodeBundle<Self>,
529        cell: &mut CellBuilder<<Self as Schematic>::Schema>,
530    ) -> substrate::error::Result<Self::NestedData> {
531        let mut prim = PrimitiveBinding::new(Primitive::RawInstance {
532            cell: self.cell.clone(),
533            ports: self.ports.clone(),
534            params: self.params.clone(),
535        });
536        for (i, port) in self.ports.iter().enumerate() {
537            prim.connect(port, io[i]);
538        }
539        cell.set_primitive(prim);
540        Ok(())
541    }
542}