spectre/
lib.rs

1//! Spectre plugin for Substrate.
2#![warn(missing_docs)]
3
4use std::collections::HashMap;
5use std::fmt::{Display, Formatter};
6use std::io::Write;
7#[cfg(any(unix, target_os = "redox"))]
8use std::os::unix::prelude::PermissionsExt;
9use std::path::{Path, PathBuf};
10use std::process::Stdio;
11use std::sync::Arc;
12
13use crate::analysis::ac::Ac;
14use crate::analysis::montecarlo;
15use crate::analysis::montecarlo::MonteCarlo;
16
17use analysis::dc::DcOp;
18use analysis::tran;
19use analysis::tran::Tran;
20use analysis::{Sweep, ac, dc};
21use arcstr::ArcStr;
22use cache::CacheableWithState;
23use cache::error::TryInnerError;
24use error::*;
25use indexmap::{IndexMap, IndexSet};
26use itertools::Itertools;
27use lazy_static::lazy_static;
28use num::complex::Complex64;
29use psfparser::analysis::ac::AcData;
30use psfparser::analysis::dc::DcData;
31use psfparser::analysis::transient::TransientData;
32use regex::Regex;
33use rust_decimal::Decimal;
34use scir::netlist::ConvertibleNetlister;
35use scir::schema::{FromSchema, NoSchema, NoSchemaError};
36use scir::{
37    Library, NamedSliceOne, NetlistLibConversion, ParamValue, SignalInfo, Slice, SliceOnePath,
38};
39use serde::{Deserialize, Serialize};
40use spice::netlist::{
41    HasSpiceLikeNetlist, Include, NetlistKind, NetlistOptions, NetlisterInstance, RenameGround,
42};
43use spice::{BlackboxContents, BlackboxElement, Spice};
44use substrate::context::Installation;
45use substrate::execute::{ExecOpts, Executor, LogOutput};
46use substrate::schematic::conv::ConvertedNodePath;
47use substrate::schematic::schema::Schema;
48use substrate::simulation::options::ic::InitialCondition;
49use substrate::simulation::options::{SimOption, Temperature, ic};
50use substrate::simulation::{SimulationContext, Simulator, SupportedBy};
51use substrate::types::schematic::NodePath;
52use templates::{RunScriptContext, write_run_script};
53use type_dispatch::impl_dispatch;
54
55pub mod analysis;
56pub mod blocks;
57pub mod error;
58pub(crate) mod templates;
59#[cfg(test)]
60mod tests;
61
62/// Spectre primitives.
63#[derive(Debug, Clone)]
64pub enum Primitive {
65    /// A raw instance with an associated cell.
66    RawInstance {
67        /// The associated cell.
68        cell: ArcStr,
69        /// The ordered ports of the instance.
70        ports: Vec<ArcStr>,
71        /// Parameters associated with the instance.
72        params: Vec<(ArcStr, ParamValue)>,
73    },
74    /// A raw instance with an associated IBIS model.
75    ///
76    /// Parameters are not supported.
77    IbisInstance {
78        /// The name of the associated component.
79        component: ArcStr,
80        /// The path to the IBIS model.
81        model: PathBuf,
82        /// The ordered ports of the instance.
83        ports: Vec<ArcStr>,
84    },
85    /// A raw instance with an associated cell represented in SPF format.
86    ///
87    /// Parameters are not supported.
88    SpfInstance {
89        /// The name of the associated cell.
90        cell: ArcStr,
91        /// The path to the SPF netlist.
92        netlist: PathBuf,
93        /// The ordered ports of the instance.
94        ports: Vec<ArcStr>,
95    },
96    /// An instance with blackboxed contents.
97    BlackboxInstance {
98        /// The contents of the cell.
99        contents: BlackboxContents,
100    },
101    /// A SPICE primitive.
102    ///
103    /// Integrated using `simulator lang=spice`.
104    Spice(spice::Primitive),
105}
106
107/// Spectre error presets.
108#[derive(
109    Copy, Clone, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize,
110)]
111pub enum ErrPreset {
112    /// Liberal.
113    Liberal,
114    /// Moderate.
115    #[default]
116    Moderate,
117    /// Conservative.
118    Conservative,
119}
120
121impl Display for ErrPreset {
122    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123        match *self {
124            Self::Liberal => write!(f, "liberal"),
125            Self::Moderate => write!(f, "moderate"),
126            Self::Conservative => write!(f, "conservative"),
127        }
128    }
129}
130
131/// A signal referenced by a save/ic Spectre statement.
132#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
133pub enum SimSignal {
134    /// A raw string to follow "save/ic".
135    Raw(ArcStr),
136    /// A SCIR signal path representing a node whose voltage should be referenced.
137    ScirVoltage(SliceOnePath),
138    /// A SCIR signal path representing a terminal whose current should be referenced.
139    ScirCurrent(SliceOnePath),
140
141    /// An instance path followed by a raw tail path.
142    InstanceTail(InstanceTail),
143}
144
145/// An instance path followed by a raw tail path.
146#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
147pub struct InstanceTail {
148    /// The path to the instance.
149    pub instance: scir::InstancePath,
150    /// The raw tail string.
151    pub tail: ArcStr,
152}
153
154impl<T: Into<ArcStr>> From<T> for SimSignal {
155    fn from(value: T) -> Self {
156        Self::Raw(value.into())
157    }
158}
159
160impl From<InstanceTail> for SimSignal {
161    fn from(value: InstanceTail) -> Self {
162        Self::InstanceTail(value)
163    }
164}
165
166impl SimSignal {
167    /// Creates a new [`SimSignal`].
168    pub fn new(path: impl Into<ArcStr>) -> Self {
169        Self::from(path)
170    }
171
172    pub(crate) fn to_string(&self, lib: &Library<Spectre>, conv: &NetlistLibConversion) -> ArcStr {
173        match self {
174            SimSignal::Raw(raw) => raw.clone(),
175            SimSignal::ScirCurrent(scir) => {
176                ArcStr::from(Spectre::node_current_path(lib, conv, scir))
177            }
178            SimSignal::ScirVoltage(scir) => {
179                ArcStr::from(Spectre::node_voltage_path(lib, conv, scir))
180            }
181            SimSignal::InstanceTail(itail) => {
182                let ipath = Spectre::instance_path(lib, conv, &itail.instance);
183                arcstr::format!("{}.{}", ipath, itail.tail)
184            }
185        }
186    }
187}
188
189/// Spectre simulator global configuration.
190#[derive(Debug, Clone, Default)]
191pub struct Spectre {}
192
193/// Spectre per-simulation options.
194///
195/// A single simulation contains zero or more analyses.
196#[derive(Debug, Clone, Default)]
197pub struct Options {
198    includes: IndexSet<Include>,
199    saves: IndexMap<SimSignal, u64>,
200    ics: IndexMap<SimSignal, Decimal>,
201    next_save_key: u64,
202    /// The simulation temperature.
203    temp: Option<Decimal>,
204    save: Option<SaveOption>,
205    /// Override the default Spectre flags.
206    override_flags: Option<String>,
207}
208
209/// The allowed values of the `save` option.
210#[derive(Copy, Clone, Debug, Default)]
211pub enum SaveOption {
212    /// All signals.
213    All,
214    /// All signals up to `nestlvl` deep in the subcircuit hierarchy.
215    Lvl,
216    /// All public signals.
217    ///
218    /// Excludes certain currents and internal nodes.
219    AllPub,
220    /// All public signals up to `nestlvl` deep in the subcircuit hierarchy.
221    ///
222    /// Excludes certain currents and internal nodes.
223    LvlPub,
224    /// Save only selected signals.
225    ///
226    /// This is the default behavior of Spectre.
227    #[default]
228    Selected,
229    /// Save no signals.
230    None,
231}
232
233impl SaveOption {
234    /// The Spectre string corresponding to this [`SaveOption`].
235    fn as_str(&self) -> &'static str {
236        match *self {
237            SaveOption::All => "all",
238            SaveOption::Lvl => "lvl",
239            SaveOption::AllPub => "allpub",
240            SaveOption::LvlPub => "lvlpub",
241            SaveOption::Selected => "selected",
242            SaveOption::None => "none",
243        }
244    }
245}
246
247impl Display for SaveOption {
248    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
249        f.write_str(self.as_str())
250    }
251}
252
253impl Options {
254    /// Include the given file.
255    pub fn include(&mut self, path: impl Into<PathBuf>) {
256        self.includes.insert(Include::new(path));
257    }
258    /// Include the given section of a file.
259    pub fn include_section(&mut self, path: impl Into<PathBuf>, section: impl Into<ArcStr>) {
260        self.includes.insert(Include::new(path).section(section));
261    }
262
263    fn save_inner(&mut self, save: impl Into<SimSignal>) -> u64 {
264        let save = save.into();
265
266        if let Some(key) = self.saves.get(&save) {
267            *key
268        } else {
269            let save_key = self.next_save_key;
270            self.next_save_key += 1;
271            self.saves.insert(save, save_key);
272            save_key
273        }
274    }
275
276    fn set_ic_inner(&mut self, key: impl Into<SimSignal>, value: Decimal) {
277        self.ics.insert(key.into(), value);
278    }
279
280    /// Marks a transient voltage to be saved in all transient analyses.
281    pub fn save_tran_voltage(&mut self, save: impl Into<SimSignal>) -> tran::VoltageSaveKey {
282        tran::VoltageSaveKey(self.save_inner(save))
283    }
284
285    /// Marks a transient current to be saved in all transient analyses.
286    pub fn save_tran_current(&mut self, save: impl Into<SimSignal>) -> tran::CurrentSaveKey {
287        tran::CurrentSaveKey(vec![self.save_inner(save)])
288    }
289
290    /// Marks an AC voltage to be saved in all AC analyses.
291    pub fn save_ac_voltage(&mut self, save: impl Into<SimSignal>) -> ac::VoltageSaveKey {
292        ac::VoltageSaveKey(self.save_inner(save))
293    }
294
295    /// Marks an AC current to be saved in all AC analyses.
296    pub fn save_ac_current(&mut self, save: impl Into<SimSignal>) -> ac::CurrentSaveKey {
297        ac::CurrentSaveKey(vec![self.save_inner(save)])
298    }
299
300    /// Marks a DC voltage to be saved in all DC analyses.
301    pub fn save_dc_voltage(&mut self, save: impl Into<SimSignal>) -> dc::VoltageSaveKey {
302        dc::VoltageSaveKey(self.save_inner(save))
303    }
304
305    /// Marks a DC current to be saved in all DC analyses.
306    pub fn save_dc_current(&mut self, save: impl Into<SimSignal>) -> dc::CurrentSaveKey {
307        dc::CurrentSaveKey(vec![self.save_inner(save)])
308    }
309
310    /// Set the simulation temperature.
311    pub fn set_temp(&mut self, temp: Decimal) {
312        self.temp = Some(temp);
313    }
314
315    /// Set the `save` option.
316    pub fn save(&mut self, save: SaveOption) {
317        self.save = Some(save);
318    }
319
320    /// Sets the flags used to invoke Spectre.
321    ///
322    /// Overrides the default set of flags.
323    pub fn set_flags(&mut self, flags: impl Into<String>) {
324        self.override_flags = Some(flags.into());
325    }
326}
327
328impl SimOption<Spectre> for Temperature {
329    fn set_option(
330        self,
331        opts: &mut <Spectre as Simulator>::Options,
332        _ctx: &SimulationContext<Spectre>,
333    ) {
334        opts.set_temp(*self)
335    }
336}
337
338#[impl_dispatch({&str; &String; ArcStr; String; SimSignal})]
339impl<K> SimOption<Spectre> for InitialCondition<K, ic::Voltage> {
340    fn set_option(
341        self,
342        opts: &mut <Spectre as Simulator>::Options,
343        _ctx: &SimulationContext<Spectre>,
344    ) {
345        opts.set_ic_inner(self.path, *self.value);
346    }
347}
348
349impl SimOption<Spectre> for InitialCondition<&SliceOnePath, ic::Voltage> {
350    fn set_option(
351        self,
352        opts: &mut <Spectre as Simulator>::Options,
353        _ctx: &SimulationContext<Spectre>,
354    ) {
355        opts.set_ic_inner(SimSignal::ScirVoltage(self.path.clone()), *self.value);
356    }
357}
358
359impl SimOption<Spectre> for InitialCondition<&ConvertedNodePath, ic::Voltage> {
360    fn set_option(
361        self,
362        opts: &mut <Spectre as Simulator>::Options,
363        _ctx: &SimulationContext<Spectre>,
364    ) {
365        opts.set_ic_inner(
366            SimSignal::ScirVoltage(match self.path {
367                ConvertedNodePath::Cell(path) => path.clone(),
368                ConvertedNodePath::Primitive {
369                    instances, port, ..
370                } => SliceOnePath::new(instances.clone(), NamedSliceOne::new(port.clone())),
371            }),
372            *self.value,
373        );
374    }
375}
376
377impl SimOption<Spectre> for InitialCondition<&NodePath, ic::Voltage> {
378    fn set_option(
379        self,
380        opts: &mut <Spectre as Simulator>::Options,
381        ctx: &SimulationContext<Spectre>,
382    ) {
383        InitialCondition {
384            path: ctx.lib.convert_node_path(self.path).unwrap(),
385            value: self.value,
386        }
387        .set_option(opts, ctx)
388    }
389}
390
391#[impl_dispatch({SliceOnePath; ConvertedNodePath; NodePath})]
392impl<T> SimOption<Spectre> for InitialCondition<T, ic::Voltage> {
393    fn set_option(
394        self,
395        opts: &mut <Spectre as Simulator>::Options,
396        ctx: &SimulationContext<Spectre>,
397    ) {
398        InitialCondition {
399            path: &self.path,
400            value: self.value,
401        }
402        .set_option(opts, ctx)
403    }
404}
405
406#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)]
407struct CachedSim {
408    simulation_netlist: Vec<u8>,
409}
410
411struct CachedSimState {
412    input: Vec<Input>,
413    netlist: PathBuf,
414    output_path: PathBuf,
415    log: PathBuf,
416    stdout_path: PathBuf,
417    run_script: PathBuf,
418    work_dir: PathBuf,
419    executor: Arc<dyn Executor>,
420    /// Override the default Spectre flags.
421    override_flags: Option<String>,
422}
423
424#[derive(Debug, Clone, Serialize, Deserialize)]
425enum CachedData {
426    Tran(HashMap<String, Vec<f64>>),
427    Ac {
428        freq: Vec<f64>,
429        signals: HashMap<String, Vec<Complex64>>,
430    },
431    DcOp(HashMap<String, f64>),
432    // The outer vec has length `numruns`.
433    // The inner vec length equals the length of the inner analysis.
434    MonteCarlo(Vec<Vec<CachedData>>),
435}
436
437impl CachedData {
438    fn into_output(
439        self,
440        ctx: &SimulationContext<Spectre>,
441        conv: &NetlistLibConversion,
442        saves: &IndexMap<SimSignal, u64>,
443    ) -> Output {
444        match self {
445            CachedData::Tran(mut raw_values) => tran::Output {
446                time: Arc::new(raw_values.remove("time").unwrap()),
447                raw_values: raw_values
448                    .into_iter()
449                    .map(|(k, v)| (ArcStr::from(k), Arc::new(v)))
450                    .collect(),
451                saved_values: saves
452                    .iter()
453                    .map(|(k, v)| (*v, k.to_string(&ctx.lib.scir, conv)))
454                    .collect(),
455            }
456            .into(),
457            CachedData::Ac { freq, signals } => ac::Output {
458                freq: Arc::new(freq),
459                raw_values: signals
460                    .into_iter()
461                    .map(|(k, v)| (ArcStr::from(k), Arc::new(v)))
462                    .collect(),
463                saved_values: saves
464                    .iter()
465                    .map(|(k, v)| (*v, k.to_string(&ctx.lib.scir, conv)))
466                    .collect(),
467            }
468            .into(),
469            CachedData::DcOp(values) => dc::OpOutput {
470                raw_values: values
471                    .into_iter()
472                    .map(|(k, v)| (ArcStr::from(k), v))
473                    .collect(),
474                saved_values: saves
475                    .iter()
476                    .map(|(k, v)| (*v, k.to_string(&ctx.lib.scir, conv)))
477                    .collect(),
478            }
479            .into(),
480            CachedData::MonteCarlo(data) => Output::MonteCarlo(montecarlo::Output(
481                data.into_iter()
482                    .map(|data| {
483                        data.into_iter()
484                            .map(|d| d.into_output(ctx, conv, saves))
485                            .collect()
486                    })
487                    .collect(),
488            )),
489        }
490    }
491}
492
493impl CacheableWithState<CachedSimState> for CachedSim {
494    type Output = Vec<CachedData>;
495    type Error = Arc<Error>;
496
497    fn generate_with_state(
498        &self,
499        state: CachedSimState,
500    ) -> std::result::Result<Self::Output, Self::Error> {
501        let inner = || -> Result<Self::Output> {
502            let CachedSimState {
503                input,
504                netlist,
505                output_path,
506                stdout_path,
507                log,
508                run_script,
509                work_dir,
510                executor,
511                override_flags,
512            } = state;
513            write_run_script(
514                RunScriptContext {
515                    netlist: &netlist,
516                    raw_output_path: &output_path,
517                    log_path: &log,
518                    bashrc: None,
519                    format: "psfbin",
520                    flags: override_flags.as_deref().unwrap_or("++aps +mt"),
521                },
522                &run_script,
523            )?;
524
525            let mut perms = std::fs::metadata(&run_script)?.permissions();
526            #[cfg(any(unix, target_os = "redox"))]
527            perms.set_mode(0o744);
528            std::fs::set_permissions(&run_script, perms)?;
529
530            let mut command = std::process::Command::new("/bin/bash");
531            command
532                .arg(&run_script)
533                .current_dir(&work_dir)
534                .stdin(Stdio::null());
535            executor
536                .execute(
537                    command,
538                    ExecOpts {
539                        logs: LogOutput::File(stdout_path),
540                        ..Default::default()
541                    },
542                )
543                .map_err(|_| Error::SpectreError)?;
544
545            let mut raw_outputs = Vec::with_capacity(input.len());
546
547            for (i, input) in input.iter().enumerate() {
548                raw_outputs.push(parse_analysis(
549                    &output_path,
550                    &subanalysis_name("analysis", i),
551                    input,
552                )?);
553            }
554            Ok(raw_outputs)
555        };
556        inner().map_err(Arc::new)
557    }
558}
559
560impl ConvertibleNetlister<Spectre> for Spectre {
561    type Error = std::io::Error;
562    type Options<'a> = NetlistOptions<'a>;
563
564    fn write_scir_netlist<W: Write>(
565        &self,
566        lib: &Library<Spectre>,
567        out: &mut W,
568        opts: Self::Options<'_>,
569    ) -> std::result::Result<NetlistLibConversion, Self::Error> {
570        NetlisterInstance::new(self, lib, out, opts).export()
571    }
572}
573
574impl Spectre {
575    fn simulate(
576        &self,
577        ctx: &SimulationContext<Self>,
578        options: Options,
579        input: Vec<Input>,
580    ) -> Result<Vec<Output>> {
581        std::fs::create_dir_all(&ctx.work_dir)?;
582        let netlist = ctx.work_dir.join("netlist.scs");
583        let mut f = std::fs::File::create(&netlist)?;
584        let mut w = Vec::new();
585
586        let includes = options.includes.into_iter().collect::<Vec<_>>();
587        let saves = options.saves.keys().cloned().collect::<Vec<_>>();
588        let ics = options
589            .ics
590            .iter()
591            .map(|(k, v)| (k.clone(), *v))
592            .collect::<Vec<_>>();
593
594        let conv = self.write_scir_netlist(
595            &ctx.lib.scir,
596            &mut w,
597            NetlistOptions::new(
598                NetlistKind::Testbench(RenameGround::Yes("0".into())),
599                &includes,
600            ),
601        )?;
602
603        writeln!(w)?;
604        if let Some(temp) = options.temp {
605            writeln!(w, "settemp1 options temp={}", temp)?;
606        }
607        for save in saves {
608            writeln!(w, "save {}", save.to_string(&ctx.lib.scir, &conv))?;
609        }
610        if let Some(save) = options.save {
611            writeln!(w, "setsave1 options save={}", save)?;
612        }
613        for (k, v) in ics {
614            writeln!(w, "ic {}={}", k.to_string(&ctx.lib.scir, &conv), v)?;
615        }
616
617        writeln!(w)?;
618        for (i, an) in input.iter().enumerate() {
619            an.netlist(&mut w, &subanalysis_name("analysis", i))?;
620            writeln!(w)?;
621        }
622        f.write_all(&w)?;
623
624        let output_path = ctx.work_dir.join("psf");
625        let log = ctx.work_dir.join("spectre.log");
626        let stdout_path = ctx.work_dir.join("spectre.out");
627        let run_script = ctx.work_dir.join("simulate.sh");
628        let work_dir = ctx.work_dir.clone();
629        let executor = ctx.ctx.executor.clone();
630
631        let raw_outputs = ctx
632            .ctx
633            .cache
634            .get_with_state(
635                "spectre.simulation.outputs",
636                CachedSim {
637                    simulation_netlist: w,
638                },
639                CachedSimState {
640                    input,
641                    netlist,
642                    output_path,
643                    stdout_path,
644                    log,
645                    run_script,
646                    work_dir,
647                    executor,
648                    override_flags: options.override_flags.clone(),
649                },
650            )
651            .try_inner()
652            .map_err(|e| match e {
653                TryInnerError::CacheError(e) => Error::Caching(e),
654                TryInnerError::GeneratorError(e) => Error::Generator(e.clone()),
655            })?
656            .clone();
657
658        let conv = Arc::new(conv);
659        let outputs = raw_outputs
660            .into_iter()
661            .map(|raw_values| raw_values.into_output(ctx, &conv, &options.saves))
662            .collect();
663
664        Ok(outputs)
665    }
666
667    /// Escapes the given identifier to be Spectre-compatible.
668    pub fn escape_identifier(node_name: &str) -> String {
669        // The name "0" is reserved, as it represents global ground.
670        // To prevent nodes from being accidentally connected to global ground,
671        // we rename 0 to x0, x0 to xx0, xx0 to xxx0, etc.
672        lazy_static! {
673            static ref RE: Regex = Regex::new("^(x*)0$").unwrap();
674        }
675        if let Some(caps) = RE.captures(node_name) {
676            let xs = caps.get(1).unwrap();
677            return format!("x{}0", xs.as_str());
678        }
679
680        let mut escaped_name = String::new();
681        for c in node_name.chars() {
682            if c.is_alphanumeric() || c == '_' {
683                escaped_name.push(c);
684            } else {
685                escaped_name.push('\\');
686                escaped_name.push(c);
687            }
688        }
689        escaped_name
690    }
691
692    /// Converts a [`scir::InstancePath`] to a Spectre path string corresponding to
693    /// the associated instance.
694    pub fn instance_path(
695        lib: &Library<Spectre>,
696        conv: &NetlistLibConversion,
697        path: &scir::InstancePath,
698    ) -> String {
699        lib.convert_instance_path_with_conv(conv, path.clone())
700            .join(".")
701    }
702
703    /// Converts a [`SliceOnePath`] to a Spectre path string corresponding to the associated
704    /// node voltage.
705    pub fn node_voltage_path(
706        lib: &Library<Spectre>,
707        conv: &NetlistLibConversion,
708        path: &SliceOnePath,
709    ) -> String {
710        lib.convert_slice_one_path_with_conv(conv, path.clone(), |name, index| {
711            let name = Spectre::escape_identifier(name);
712            if let Some(index) = index {
713                arcstr::format!("{}\\[{}\\]", name, index)
714            } else {
715                name.into()
716            }
717        })
718        .join(".")
719    }
720
721    /// Converts a [`SliceOnePath`] to a Spectre path string corresponding to the associated
722    /// terminal current.
723    pub fn node_current_path(
724        lib: &Library<Spectre>,
725        conv: &NetlistLibConversion,
726        path: &SliceOnePath,
727    ) -> String {
728        let mut named_path =
729            lib.convert_slice_one_path_with_conv(conv, path.clone(), |name, index| {
730                let name = Spectre::escape_identifier(name);
731                if let Some(index) = index {
732                    arcstr::format!("{}\\[{}\\]", name, index)
733                } else {
734                    name.into()
735                }
736            });
737        let signal = named_path.pop().unwrap();
738        let mut str_path = named_path.join(".");
739        str_path.push(':');
740        str_path.push_str(&signal);
741        str_path
742    }
743}
744
745impl scir::schema::Schema for Spectre {
746    type Primitive = Primitive;
747}
748
749impl FromSchema<NoSchema> for Spectre {
750    type Error = NoSchemaError;
751
752    fn convert_primitive(
753        _primitive: <NoSchema as Schema>::Primitive,
754    ) -> std::result::Result<<Self as Schema>::Primitive, Self::Error> {
755        Err(NoSchemaError)
756    }
757
758    fn convert_instance(
759        _instance: &mut scir::Instance,
760        _primitive: &<NoSchema as Schema>::Primitive,
761    ) -> std::result::Result<(), Self::Error> {
762        Err(NoSchemaError)
763    }
764}
765
766/// An error converting to/from the [`Spectre`] schema.
767#[derive(Debug, Clone, Copy)]
768pub enum SpectreConvError {
769    /// A primitive that is not supported by the target schema was encountered.
770    UnsupportedPrimitive,
771    /// A primitive is missing a required parameter.
772    MissingParameter,
773    /// A primitive has an extra parameter.
774    ExtraParameter,
775    /// A primitive has an invalid value for a certain parameter.
776    InvalidParameter,
777    /// A primitive has an invalid port.
778    InvalidPort,
779}
780
781impl FromSchema<Spice> for Spectre {
782    type Error = SpectreConvError;
783
784    fn convert_primitive(
785        primitive: <Spice as Schema>::Primitive,
786    ) -> std::result::Result<<Self as Schema>::Primitive, Self::Error> {
787        Ok(Primitive::Spice(primitive))
788    }
789
790    fn convert_instance(
791        _instance: &mut scir::Instance,
792        _primitive: &<Spice as Schema>::Primitive,
793    ) -> std::result::Result<(), Self::Error> {
794        Ok(())
795    }
796}
797
798impl Installation for Spectre {}
799
800impl Simulator for Spectre {
801    type Schema = Spectre;
802    type Input = Input;
803    type Options = Options;
804    type Output = Output;
805    type Error = Error;
806
807    fn simulate_inputs(
808        &self,
809        config: &substrate::simulation::SimulationContext<Self>,
810        options: Self::Options,
811        input: Vec<Self::Input>,
812    ) -> Result<Vec<Self::Output>> {
813        self.simulate(config, options, input)
814    }
815}
816
817/// Inputs directly supported by Spectre.
818#[derive(Debug, Clone, Serialize, Deserialize)]
819pub enum Input {
820    /// Transient simulation input.
821    Tran(Tran),
822    /// AC simulation input.
823    Ac(Ac),
824    /// A DC operating point input.
825    DcOp(DcOp),
826    /// A Monte Carlo input.
827    MonteCarlo(MonteCarlo<Vec<Input>>),
828}
829
830impl From<Tran> for Input {
831    fn from(value: Tran) -> Self {
832        Self::Tran(value)
833    }
834}
835
836impl From<Ac> for Input {
837    fn from(value: Ac) -> Self {
838        Self::Ac(value)
839    }
840}
841
842impl From<DcOp> for Input {
843    fn from(value: DcOp) -> Self {
844        Self::DcOp(value)
845    }
846}
847
848impl<A: SupportedBy<Spectre>> From<MonteCarlo<A>> for Input {
849    fn from(value: MonteCarlo<A>) -> Self {
850        Self::MonteCarlo(value.into())
851    }
852}
853
854/// Outputs directly produced by Spectre.
855#[derive(Debug, Clone)]
856pub enum Output {
857    /// Transient simulation output.
858    Tran(tran::Output),
859    /// AC simulation output.
860    Ac(ac::Output),
861    /// DC operating point simulation output.
862    DcOp(analysis::dc::OpOutput),
863    /// Monte Carlo simulation output.
864    MonteCarlo(montecarlo::Output<Vec<Output>>),
865}
866
867impl From<tran::Output> for Output {
868    fn from(value: tran::Output) -> Self {
869        Self::Tran(value)
870    }
871}
872
873impl From<ac::Output> for Output {
874    fn from(value: ac::Output) -> Self {
875        Self::Ac(value)
876    }
877}
878
879impl From<analysis::dc::OpOutput> for Output {
880    fn from(value: analysis::dc::OpOutput) -> Self {
881        Self::DcOp(value)
882    }
883}
884
885impl TryFrom<Output> for tran::Output {
886    type Error = Error;
887    fn try_from(value: Output) -> Result<Self> {
888        match value {
889            Output::Tran(t) => Ok(t),
890            _ => Err(Error::SpectreError),
891        }
892    }
893}
894
895impl TryFrom<Output> for ac::Output {
896    type Error = Error;
897    fn try_from(value: Output) -> Result<Self> {
898        match value {
899            Output::Ac(ac) => Ok(ac),
900            _ => Err(Error::SpectreError),
901        }
902    }
903}
904
905impl TryFrom<Output> for analysis::dc::OpOutput {
906    type Error = Error;
907    fn try_from(value: Output) -> Result<Self> {
908        match value {
909            Output::DcOp(dcop) => Ok(dcop),
910            _ => Err(Error::SpectreError),
911        }
912    }
913}
914
915impl From<montecarlo::Output<Vec<Output>>> for Output {
916    fn from(value: montecarlo::Output<Vec<Output>>) -> Self {
917        Self::MonteCarlo(value)
918    }
919}
920
921impl TryFrom<Output> for montecarlo::Output<Vec<Output>> {
922    type Error = Error;
923    fn try_from(value: Output) -> Result<Self> {
924        match value {
925            Output::MonteCarlo(mc) => Ok(mc),
926            _ => Err(Error::SpectreError),
927        }
928    }
929}
930
931impl Input {
932    fn netlist<W: Write>(&self, out: &mut W, name: &str) -> Result<()> {
933        write!(out, "{name} ")?;
934        match self {
935            Self::Tran(t) => t.netlist(out),
936            Input::Ac(ac) => ac.netlist(out),
937            Input::DcOp(dcop) => dcop.netlist(out),
938            Self::MonteCarlo(mc) => mc.netlist(out, name),
939        }
940    }
941}
942
943impl Tran {
944    fn netlist<W: Write>(&self, out: &mut W) -> Result<()> {
945        write!(out, "tran stop={}", self.stop)?;
946        if let Some(ref start) = self.start {
947            write!(out, " start={start}")?;
948        }
949        if let Some(errpreset) = self.errpreset {
950            write!(out, " errpreset={errpreset}")?;
951        }
952        if let Some(noisefmax) = self.noise_fmax {
953            write!(out, " noisefmax={noisefmax}")?;
954        }
955        if let Some(noisefmin) = self.noise_fmin {
956            write!(out, " noisefmin={noisefmin}")?;
957        }
958        Ok(())
959    }
960}
961
962impl Ac {
963    fn netlist<W: Write>(&self, out: &mut W) -> Result<()> {
964        write!(out, "ac start={} stop={}", self.start, self.stop)?;
965        match self.sweep {
966            Sweep::Linear(pts) => write!(out, " lin={pts}")?,
967            Sweep::Logarithmic(pts) => write!(out, " log={pts}")?,
968            Sweep::Decade(pts) => write!(out, " dec={pts}")?,
969        };
970        Ok(())
971    }
972}
973
974impl DcOp {
975    fn netlist<W: Write>(&self, out: &mut W) -> Result<()> {
976        write!(out, "dc")?;
977        Ok(())
978    }
979}
980
981fn subanalysis_name(prefix: &str, idx: usize) -> String {
982    format!("{prefix}_{idx}")
983}
984
985fn parse_analysis(output_dir: &Path, name: &str, analysis: &Input) -> Result<CachedData> {
986    Ok(if let Input::MonteCarlo(analysis) = analysis {
987        let mut data = Vec::new();
988        for iter in 1..analysis.numruns + 1 {
989            let mut mc_data = Vec::new();
990            for i in 0..analysis.analysis.len() {
991                // FIXME: loops should be swapped
992                let new_name = subanalysis_name(&format!("{}-{:0>3}_{}", name, iter, name), i);
993                mc_data.push(parse_analysis(
994                    output_dir,
995                    &new_name,
996                    &analysis.analysis[i],
997                )?)
998            }
999            data.push(mc_data);
1000        }
1001        CachedData::MonteCarlo(data)
1002    } else {
1003        let file_name = match analysis {
1004            Input::Tran(_) => {
1005                format!("{name}.tran.tran")
1006            }
1007            Input::Ac(_) => format!("{name}.ac"),
1008            Input::DcOp(_) => format!("{name}.dc"),
1009            Input::MonteCarlo(_) => unreachable!(),
1010        };
1011        let psf_path = output_dir.join(file_name);
1012        let psf = std::fs::read(psf_path)?;
1013        let ast = psfparser::binary::parse(&psf).map_err(|_| Error::Parse)?;
1014
1015        match analysis {
1016            Input::Tran(_) => {
1017                let values = TransientData::from_binary(ast).signals;
1018                CachedData::Tran(values)
1019            }
1020            Input::Ac(_) => {
1021                let values = AcData::from_binary(ast);
1022                CachedData::Ac {
1023                    freq: values.freq,
1024                    signals: values.signals,
1025                }
1026            }
1027            Input::DcOp(_) => {
1028                let values = DcData::from_binary(ast).unwrap_op().signals;
1029                CachedData::DcOp(values)
1030            }
1031            Input::MonteCarlo(_) => {
1032                unreachable!()
1033            }
1034        }
1035    })
1036}
1037
1038impl MonteCarlo<Vec<Input>> {
1039    fn netlist<W: Write>(&self, out: &mut W, name: &str) -> Result<()> {
1040        write!(
1041            out,
1042            "montecarlo variations={} numruns={} savefamilyplots=yes",
1043            self.variations, self.numruns
1044        )?;
1045        if let Some(seed) = self.seed {
1046            write!(out, " seed={seed}")?;
1047        }
1048        if let Some(firstrun) = self.firstrun {
1049            write!(out, " firstrun={firstrun}")?;
1050        }
1051        write!(out, " {{")?;
1052
1053        for (i, an) in self.analysis.iter().enumerate() {
1054            let name = subanalysis_name(name, i);
1055            write!(out, "\n\t")?;
1056            an.netlist(out, &name)?;
1057        }
1058        write!(out, "\n}}")?;
1059
1060        Ok(())
1061    }
1062}
1063
1064impl HasSpiceLikeNetlist for Spectre {
1065    fn write_prelude<W: Write>(&self, out: &mut W, lib: &Library<Spectre>) -> std::io::Result<()> {
1066        writeln!(out, "// Substrate Spectre library\n")?;
1067        writeln!(out, "simulator lang=spectre\n")?;
1068        writeln!(out, "// This is a generated file.")?;
1069        writeln!(
1070            out,
1071            "// Be careful when editing manually: this file may be overwritten.\n"
1072        )?;
1073        writeln!(out, "global 0\n")?;
1074
1075        // find all unique IBIS models and include them
1076        let ibis = lib
1077            .primitives()
1078            .filter_map(|p| {
1079                if let Primitive::IbisInstance { model, .. } = p.1 {
1080                    Some(model.clone())
1081                } else {
1082                    None
1083                }
1084            })
1085            .collect::<IndexSet<_>>();
1086        for ibis_path in ibis.iter() {
1087            writeln!(out, "ibis_include {:?}", ibis_path)?;
1088        }
1089
1090        // find all unique spf netlists and include them
1091        let spfs = lib
1092            .primitives()
1093            .filter_map(|p| {
1094                if let Primitive::SpfInstance { netlist, .. } = p.1 {
1095                    Some(netlist.clone())
1096                } else {
1097                    None
1098                }
1099            })
1100            .collect::<IndexSet<_>>();
1101        for spf_path in spfs.iter() {
1102            writeln!(out, "dspf_include {:?}", spf_path)?;
1103        }
1104
1105        // find all unique SPICE netlists and include them
1106        let includes = lib
1107            .primitives()
1108            .filter_map(|p| {
1109                if let Primitive::Spice(spice::Primitive::RawInstanceWithInclude {
1110                    netlist, ..
1111                }) = p.1
1112                {
1113                    Some(netlist.clone())
1114                } else {
1115                    None
1116                }
1117            })
1118            .collect::<IndexSet<_>>();
1119        for include in includes.iter() {
1120            writeln!(out, "include {:?}", include)?;
1121        }
1122
1123        Ok(())
1124    }
1125
1126    fn write_include<W: Write>(&self, out: &mut W, include: &Include) -> std::io::Result<()> {
1127        if let Some(section) = &include.section {
1128            write!(out, "include {:?} section={}", include.path, section)?;
1129        } else {
1130            write!(out, "include {:?}", include.path)?;
1131        }
1132        Ok(())
1133    }
1134
1135    fn write_start_subckt<W: Write>(
1136        &self,
1137        out: &mut W,
1138        name: &ArcStr,
1139        ports: &[&SignalInfo],
1140    ) -> std::io::Result<()> {
1141        write!(out, "subckt {} (", name)?;
1142        for sig in ports {
1143            if let Some(width) = sig.width {
1144                for i in 0..width {
1145                    write!(out, " {}\\[{}\\]", Spectre::escape_identifier(&sig.name), i)?;
1146                }
1147            } else {
1148                write!(out, " {}", Spectre::escape_identifier(&sig.name))?;
1149            }
1150        }
1151        write!(out, " )")?;
1152        Ok(())
1153    }
1154
1155    fn write_end_subckt<W: Write>(&self, out: &mut W, name: &ArcStr) -> std::io::Result<()> {
1156        write!(out, "ends {}", name)
1157    }
1158
1159    fn write_instance<W: Write>(
1160        &self,
1161        out: &mut W,
1162        name: &ArcStr,
1163        connections: Vec<ArcStr>,
1164        child: &ArcStr,
1165    ) -> std::io::Result<ArcStr> {
1166        let name = ArcStr::from(Spectre::escape_identifier(&format!("x{}", name)));
1167        write!(out, "{} (", name)?;
1168
1169        for connection in connections {
1170            write!(out, " {}", connection)?;
1171        }
1172
1173        write!(out, " ) {}", child)?;
1174
1175        Ok(name)
1176    }
1177
1178    fn write_primitive_inst<W: Write>(
1179        &self,
1180        out: &mut W,
1181        name: &ArcStr,
1182        mut connections: HashMap<ArcStr, Vec<ArcStr>>,
1183        primitive: &<Self as Schema>::Primitive,
1184    ) -> std::io::Result<ArcStr> {
1185        Ok(match primitive {
1186            Primitive::RawInstance {
1187                cell,
1188                ports,
1189                params,
1190            } => {
1191                let connections = ports
1192                    .iter()
1193                    .flat_map(|port| connections.remove(port).unwrap_or_else(|| panic!("raw instance `{name}` must connect to all ports; missing connection to port `{port}`")))
1194                    .collect();
1195                let name = self.write_instance(out, name, connections, cell)?;
1196                for (key, value) in params.iter().sorted_by_key(|(key, _)| key) {
1197                    write!(out, " {key}={value}")?;
1198                }
1199                name
1200            }
1201            Primitive::BlackboxInstance { contents } => {
1202                // TODO: See if there is a way to translate the name based on the
1203                // contents, or make documentation explaining that blackbox instances
1204                // cannot be addressed by path.
1205                for elem in &contents.elems {
1206                    match elem {
1207                        BlackboxElement::InstanceName => write!(out, "{}", name)?,
1208                        BlackboxElement::RawString(s) => write!(out, "{}", s)?,
1209                        BlackboxElement::Port(p) => {
1210                            for part in connections.get(p).unwrap() {
1211                                write!(out, "{}", part)?
1212                            }
1213                        }
1214                    }
1215                }
1216                name.clone()
1217            }
1218            Primitive::Spice(p) => {
1219                writeln!(out, "simulator lang=spice")?;
1220                let name = Spice.write_primitive_inst(out, name, connections, p)?;
1221                writeln!(out, "simulator lang=spectre")?;
1222                name
1223            }
1224            Primitive::IbisInstance {
1225                component, ports, ..
1226            } => {
1227                let connections = ports
1228                    .iter()
1229                    .flat_map(|port| connections.remove(port).unwrap())
1230                    .collect();
1231                self.write_instance(
1232                    out,
1233                    name,
1234                    connections,
1235                    &arcstr::format!("{}_ibis", component),
1236                )?
1237            }
1238            Primitive::SpfInstance { cell, ports, .. } => {
1239                let connections = ports
1240                    .iter()
1241                    .flat_map(|port| connections.remove(port).unwrap())
1242                    .collect();
1243                self.write_instance(out, name, connections, cell)?
1244            }
1245        })
1246    }
1247
1248    fn write_slice<W: Write>(
1249        &self,
1250        out: &mut W,
1251        slice: Slice,
1252        info: &SignalInfo,
1253    ) -> std::io::Result<()> {
1254        let name = Spectre::escape_identifier(&info.name);
1255        if let Some(range) = slice.range() {
1256            for i in range.indices() {
1257                write!(out, "{}\\[{}\\]", &name, i)?;
1258            }
1259        } else {
1260            write!(out, "{}", &name)?;
1261        }
1262        Ok(())
1263    }
1264}