spectre/
lib.rs

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