1#![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#[derive(Debug, Clone)]
63pub enum Primitive {
64 RawInstance {
66 cell: ArcStr,
68 ports: Vec<ArcStr>,
70 params: Vec<(ArcStr, ParamValue)>,
72 },
73 IbisInstance {
77 component: ArcStr,
79 model: PathBuf,
81 ports: Vec<ArcStr>,
83 },
84 SpfInstance {
88 cell: ArcStr,
90 netlist: PathBuf,
92 ports: Vec<ArcStr>,
94 },
95 BlackboxInstance {
97 contents: BlackboxContents,
99 },
100 Spice(spice::Primitive),
104}
105
106#[derive(
108 Copy, Clone, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize,
109)]
110pub enum ErrPreset {
111 Liberal,
113 #[default]
115 Moderate,
116 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#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
132pub enum SimSignal {
133 Raw(ArcStr),
135 ScirVoltage(SliceOnePath),
137 ScirCurrent(SliceOnePath),
139
140 InstanceTail(InstanceTail),
142}
143
144#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
146pub struct InstanceTail {
147 pub instance: scir::InstancePath,
149 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 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#[derive(Debug, Clone, Default)]
190pub struct Spectre {}
191
192#[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 temp: Option<Decimal>,
203 save: Option<SaveOption>,
204 override_flags: Option<String>,
206}
207
208#[derive(Copy, Clone, Debug, Default)]
210pub enum SaveOption {
211 All,
213 Lvl,
215 AllPub,
219 LvlPub,
223 #[default]
227 Selected,
228 None,
230}
231
232impl SaveOption {
233 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 pub fn include(&mut self, path: impl Into<PathBuf>) {
255 self.includes.insert(Include::new(path));
256 }
257 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 pub fn save_tran_voltage(&mut self, save: impl Into<SimSignal>) -> tran::VoltageSaveKey {
281 tran::VoltageSaveKey(self.save_inner(save))
282 }
283
284 pub fn save_tran_current(&mut self, save: impl Into<SimSignal>) -> tran::CurrentSaveKey {
286 tran::CurrentSaveKey(vec![self.save_inner(save)])
287 }
288
289 pub fn save_ac_voltage(&mut self, save: impl Into<SimSignal>) -> ac::VoltageSaveKey {
291 ac::VoltageSaveKey(self.save_inner(save))
292 }
293
294 pub fn save_ac_current(&mut self, save: impl Into<SimSignal>) -> ac::CurrentSaveKey {
296 ac::CurrentSaveKey(vec![self.save_inner(save)])
297 }
298
299 pub fn save_dc_voltage(&mut self, save: impl Into<SimSignal>) -> dc::VoltageSaveKey {
301 dc::VoltageSaveKey(self.save_inner(save))
302 }
303
304 pub fn save_dc_current(&mut self, save: impl Into<SimSignal>) -> dc::CurrentSaveKey {
306 dc::CurrentSaveKey(vec![self.save_inner(save)])
307 }
308
309 pub fn set_temp(&mut self, temp: Decimal) {
311 self.temp = Some(temp);
312 }
313
314 pub fn save(&mut self, save: SaveOption) {
316 self.save = Some(save);
317 }
318
319 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_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 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 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 pub fn escape_identifier(node_name: &str) -> String {
674 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 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 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 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#[derive(Debug, Clone, Copy)]
773pub enum SpectreConvError {
774 UnsupportedPrimitive,
776 MissingParameter,
778 ExtraParameter,
780 InvalidParameter,
782 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#[derive(Debug, Clone, Serialize, Deserialize)]
824pub enum Input {
825 Tran(Tran),
827 Ac(Ac),
829 DcOp(DcOp),
831 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#[derive(Debug, Clone)]
861pub enum Output {
862 Tran(tran::Output),
864 Ac(ac::Output),
866 DcOp(analysis::dc::OpOutput),
868 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 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 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 for ibis_path in ibis.iter().sorted() {
1093 writeln!(out, "ibis_include {:?}", ibis_path)?;
1094 }
1095
1096 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 for spf_path in spfs.iter().sorted() {
1109 writeln!(out, "dspf_include {:?}", spf_path)?;
1110 }
1111
1112 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 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 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}