substrate/schematic/
conv.rs

1//! Substrate to SCIR conversion.
2
3use std::collections::{HashMap, HashSet};
4use std::fmt::Formatter;
5
6use arcstr::ArcStr;
7use scir::{Cell, ChildId, Concat, IndexOwned, Instance, LibraryBuilder};
8use serde::{Deserialize, Serialize};
9use substrate::schematic::{ConvertedPrimitive, ScirBinding};
10use uniquify::Names;
11
12use crate::schematic::schema::Schema;
13use crate::schematic::{ConvertPrimitive, InstancePath, RawCellContents, RawCellKind};
14use crate::types::schematic::{Node, NodePath, TerminalPath};
15
16use super::{CellId, InstanceId, RawCell};
17
18/// An SCIR library with associated conversion metadata.
19pub struct RawLib<S: Schema + ?Sized> {
20    /// The SCIR library.
21    pub scir: scir::Library<S>,
22    /// Associated conversion metadata.
23    ///
24    /// Can be used to retrieve SCIR objects from their corresponding Substrate IDs.
25    pub conv: ScirLibConversion,
26}
27
28impl<S: Schema<Primitive = impl Clone>> Clone for RawLib<S> {
29    fn clone(&self) -> Self {
30        Self {
31            scir: self.scir.clone(),
32            conv: self.conv.clone(),
33        }
34    }
35}
36
37impl<S: Schema<Primitive = impl std::fmt::Debug>> std::fmt::Debug for RawLib<S> {
38    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
39        let mut builder = f.debug_struct("RawLib");
40        let _ = builder.field("scir", &self.scir);
41        let _ = builder.field("conv", &self.conv);
42        builder.finish()
43    }
44}
45
46/// Metadata associated with a conversion from a Substrate schematic to a SCIR library.
47///
48/// Provides helpers for retrieving SCIR objects from their Substrate IDs.
49#[derive(Debug, Clone)]
50pub struct ScirLibConversion {
51    /// Map from Substrate cell IDs to cell conversion metadata.
52    pub(crate) cells: HashMap<CellId, SubstrateCellConversion>,
53    /// The Substrate ID of the top cell, if there is one.
54    top: Option<CellId>,
55}
56
57impl ScirLibConversion {
58    /// Get the SCIR cell ID corresponding to the given [`RawCell`] if a corresponding cell exists.
59    ///
60    /// May return none if the given raw cell corresponds to a primitive or to a flattened cell.
61    pub fn corresponding_cell<S: Schema + ?Sized>(
62        &self,
63        cell: &RawCell<S>,
64    ) -> Option<scir::CellId> {
65        let conv = self.cells.get(&cell.id)?;
66        match conv {
67            SubstrateCellConversion::Cell(c) => c.cell_id,
68            SubstrateCellConversion::Primitive(_) => None,
69        }
70    }
71}
72
73#[derive(Debug, Clone, Default)]
74pub(crate) struct ScirLibConversionBuilder {
75    /// Map from Substrate cell IDs to cell conversion metadata.
76    pub(crate) cells: HashMap<CellId, SubstrateCellConversion>,
77    /// The Substrate ID of the top cell, if there is one.
78    top: Option<CellId>,
79}
80
81/// A path within a SCIR library corresponding to a Substrate [`NodePath`].
82#[derive(Debug, Clone)]
83pub enum ConvertedNodePath {
84    /// A path that corresponds to a node within a SCIR cell.
85    Cell(scir::SliceOnePath),
86    /// A path that corresponds to a port of a primitive.
87    Primitive {
88        /// The ID of the underlying primitive.
89        id: scir::PrimitiveId,
90        /// The instance path of the primitive instance.
91        instances: scir::InstancePath,
92        /// The port of the primitive instance.
93        port: ArcStr,
94        /// The index of the primitive port corresponding to the Substrate node.
95        index: usize,
96    },
97}
98
99impl ScirLibConversionBuilder {
100    fn new() -> Self {
101        Default::default()
102    }
103
104    fn build(self) -> ScirLibConversion {
105        ScirLibConversion {
106            cells: self.cells,
107            top: self.top,
108        }
109    }
110
111    #[inline]
112    pub(crate) fn add_cell(&mut self, id: CellId, conv: impl Into<SubstrateCellConversion>) {
113        self.cells.insert(id, conv.into());
114    }
115}
116
117impl<S: Schema> RawLib<S> {
118    fn convert_instance_path_inner<'a>(
119        &'a self,
120        top: CellId,
121        instances: impl IntoIterator<Item = &'a InstanceId>,
122    ) -> Option<(
123        scir::InstancePath,
124        SubstrateCellConversionRef<&'a ScirCellConversion, &'a ScirPrimitiveConversion>,
125    )> {
126        let mut cell = self.conv.cells.get(&top)?.as_ref();
127        let mut scir_id: scir::ChildId = self.scir.top_cell()?.into();
128
129        let mut scir_instances = scir::InstancePath::new(self.scir.top_cell()?);
130        for inst in instances {
131            let conv = cell.get_cell()?.instances.get(inst).unwrap();
132            match conv.instance.as_ref() {
133                ConvertedScirInstanceContentRef::Cell(id) => {
134                    scir_id = self.scir.cell(scir_id.into_cell()?).instance(*id).child();
135                    scir_instances.push(*id);
136                    if let Some(conv) = self.conv.cells.get(&conv.child) {
137                        cell = conv.as_ref();
138                    }
139                }
140                ConvertedScirInstanceContentRef::InlineCell(conv) => {
141                    cell = SubstrateCellConversionRef::Cell(conv);
142                }
143            }
144        }
145        Some((scir_instances, cell))
146    }
147
148    /// Converts a Substrate [`NodePath`] to a SCIR [`scir::SliceOnePath`].
149    pub fn convert_node_path(&self, path: &NodePath) -> Option<ConvertedNodePath> {
150        let (instances, cell) = self.convert_instance_path_inner(path.top, &path.instances)?;
151
152        Some(match cell {
153            SubstrateCellConversionRef::Cell(cell) => ConvertedNodePath::Cell(
154                scir::SliceOnePath::new(instances, *cell.signals.get(&path.node)?),
155            ),
156            SubstrateCellConversionRef::Primitive(p) => {
157                let (port, index) = p.ports.get(&path.node)?.first()?;
158                ConvertedNodePath::Primitive {
159                    id: p.primitive_id,
160                    instances,
161                    port: port.clone(),
162                    index: *index,
163                }
164            }
165        })
166    }
167
168    /// Convert a node in the top cell to a SCIR node path.
169    pub fn convert_node(&self, node: &Node) -> Option<ConvertedNodePath> {
170        let top = self.conv.top?;
171        let empty = Vec::new();
172        let (instances, cell) = self.convert_instance_path_inner(top, &empty)?;
173
174        Some(match cell {
175            SubstrateCellConversionRef::Cell(cell) => ConvertedNodePath::Cell(
176                scir::SliceOnePath::new(instances, *cell.signals.get(node)?),
177            ),
178            SubstrateCellConversionRef::Primitive(p) => {
179                let (port, index) = p.ports.get(node)?.first()?;
180                ConvertedNodePath::Primitive {
181                    id: p.primitive_id,
182                    instances,
183                    port: port.clone(),
184                    index: *index,
185                }
186            }
187        })
188    }
189
190    /// Converts a Substrate [`InstancePath`] to a SCIR [`scir::InstancePath`].
191    pub fn convert_instance_path(&self, path: &InstancePath) -> Option<scir::InstancePath> {
192        let (instances, _) = self.convert_instance_path_inner(path.top, &path.path)?;
193        Some(instances)
194    }
195
196    /// Converts a Substrate [`TerminalPath`] to a list of [`ConvertedNodePath`]s
197    /// associated with the terminal at that path.
198    ///
199    /// Returns [`None`] if the path is invalid. Only flattened instances will
200    /// return more than one [`ConvertedNodePath`].
201    pub fn convert_terminal_path(&self, path: &TerminalPath) -> Option<Vec<ConvertedNodePath>> {
202        let mut cell = self.conv.cells.get(&path.top)?.as_ref();
203
204        let scir_id = self.scir.top_cell()?;
205        let mut instances = scir::InstancePath::new(scir_id);
206        let mut scir_id = ChildId::Cell(scir_id);
207        let mut last_clear = false;
208        for inst in &path.instances {
209            let conv = cell.into_cell()?.instances.get(inst).unwrap();
210            match conv.instance.as_ref() {
211                ConvertedScirInstanceContentRef::Cell(id) => {
212                    scir_id = self.scir.cell(scir_id.into_cell()?).instance(*id).child();
213                    instances.push(*id);
214                    cell = self.conv.cells.get(&conv.child)?.as_ref();
215                    last_clear = false;
216                }
217                ConvertedScirInstanceContentRef::InlineCell(conv) => {
218                    cell = SubstrateCellConversionRef::Cell(conv);
219                    last_clear = true;
220                }
221            }
222        }
223
224        match cell {
225            SubstrateCellConversionRef::Cell(cell) => {
226                // If the last cell in the conversion was `Opacity::Clear`, the provided terminal is
227                // virtual and thus may correspond to more than one `scir::SignalPath`.
228                //
229                // Run DFS to find all signal paths that are directly connected to this virtual
230                // terminal.
231                let slice = *cell.signals.get(&path.node)?;
232                Some(if last_clear {
233                    let mut signals = Vec::new();
234                    self.find_connected_terminals(
235                        cell,
236                        self.scir.cell(scir_id.into_cell()?),
237                        slice,
238                        &mut instances,
239                        &mut signals,
240                    );
241                    signals
242                } else {
243                    vec![ConvertedNodePath::Cell(scir::SliceOnePath::new(
244                        instances, slice,
245                    ))]
246                })
247            }
248            SubstrateCellConversionRef::Primitive(p) => {
249                let mut out = Vec::new();
250                for (port, index) in p.ports.get(&path.node)? {
251                    out.push(ConvertedNodePath::Primitive {
252                        id: p.primitive_id,
253                        instances: instances.clone(),
254                        port: port.clone(),
255                        index: *index,
256                    });
257                }
258                Some(out)
259            }
260        }
261    }
262
263    /// Must ensure that `instances` is returned to its original value by the end of the
264    /// function call.
265    fn find_connected_terminals_in_scir_instance(
266        &self,
267        parent_cell: &scir::Cell,
268        id: scir::InstanceId,
269        slice: scir::SliceOne,
270        instances: &mut scir::InstancePath,
271        signals: &mut Vec<ConvertedNodePath>,
272    ) -> Option<()> {
273        instances.push(id);
274        let inst = parent_cell.instance(id);
275        for (name, conn) in inst.connections() {
276            let mut port_index = 0;
277            for part in conn.parts() {
278                if slice.signal() == part.signal() {
279                    let concat_index = match (slice.index(), part.range()) {
280                        (None, None) => Some(port_index),
281                        (Some(index), Some(range)) => {
282                            if range.contains(index) {
283                                Some(port_index + index - range.start())
284                            } else {
285                                None
286                            }
287                        }
288                        _ => None,
289                    };
290
291                    if let Some(concat_index) = concat_index {
292                        match inst.child() {
293                            ChildId::Cell(id) => {
294                                let child_cell = self.scir.cell(id);
295                                let port = child_cell.port(name);
296                                let port_slice = child_cell.signal(port.signal()).slice();
297                                let tail = port_slice
298                                    .slice_one()
299                                    .unwrap_or_else(|| port_slice.index(concat_index));
300                                signals.push(ConvertedNodePath::Cell(scir::SliceOnePath::new(
301                                    instances.clone(),
302                                    tail,
303                                )));
304                            }
305                            ChildId::Primitive(id) => {
306                                signals.push(ConvertedNodePath::Primitive {
307                                    id,
308                                    instances: instances.clone(),
309                                    port: name.clone(),
310                                    index: concat_index,
311                                });
312                            }
313                        }
314                    }
315                }
316                port_index += part.width();
317            }
318        }
319        instances.pop().unwrap();
320        Some(())
321    }
322
323    /// Must ensure that `instances` is returned to its original value by the end of the
324    /// function call.
325    fn find_connected_terminals(
326        &self,
327        conv: &ScirCellConversion,
328        parent_cell: &scir::Cell,
329        slice: scir::SliceOne,
330        instances: &mut scir::InstancePath,
331        signals: &mut Vec<ConvertedNodePath>,
332    ) -> Option<()> {
333        for (_, conv) in conv.instances.iter() {
334            match conv.instance.as_ref() {
335                ConvertedScirInstanceContentRef::Cell(id) => {
336                    self.find_connected_terminals_in_scir_instance(
337                        parent_cell,
338                        *id,
339                        slice,
340                        instances,
341                        signals,
342                    );
343                }
344                ConvertedScirInstanceContentRef::InlineCell(conv) => {
345                    self.find_connected_terminals(conv, parent_cell, slice, instances, signals);
346                }
347            }
348        }
349        Some(())
350    }
351}
352
353#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
354#[enumify::enumify]
355enum ConvertedScirInstanceContent<U, F> {
356    Cell(U),
357    InlineCell(F),
358}
359
360/// A converted SCIR instance.
361type ConvertedScirInstance = ConvertedScirInstanceContent<scir::InstanceId, ScirCellConversion>;
362
363#[enumify::enumify]
364#[derive(Debug, Clone)]
365pub(crate) enum SubstrateCellConversion {
366    Cell(ScirCellConversion),
367    Primitive(ScirPrimitiveConversion),
368}
369
370impl From<ScirCellConversion> for SubstrateCellConversion {
371    fn from(value: ScirCellConversion) -> Self {
372        Self::Cell(value)
373    }
374}
375
376impl From<ScirPrimitiveConversion> for SubstrateCellConversion {
377    fn from(value: ScirPrimitiveConversion) -> Self {
378        Self::Primitive(value)
379    }
380}
381
382/// Data used to map between a Substrate cell and a SCIR cell.
383///
384/// Flattened cells do not have a conversion.
385#[derive(Debug, Default, Clone)]
386pub(crate) struct ScirCellConversion {
387    /// The corresponding SCIR cell ID. [`None`] for flattened Substrate cells.
388    pub(crate) cell_id: Option<scir::CellId>,
389    /// Map Substrate nodes to SCIR signal IDs and indices.
390    pub(crate) signals: HashMap<Node, scir::SliceOne>,
391    /// Map Substrate instance IDs to SCIR instances and their underlying Substrate cell.
392    pub(crate) instances: HashMap<InstanceId, ScirInstanceConversion>,
393}
394
395/// Data used to map between a Substrate cell and a SCIR cell.
396///
397/// Flattened cells do not have a conversion.
398#[derive(Debug, Clone)]
399pub(crate) struct ScirPrimitiveConversion {
400    pub(crate) primitive_id: scir::PrimitiveId,
401    /// Map Substrate nodes to a SCIR primitive port and an index within that port.
402    pub(crate) ports: HashMap<Node, Vec<(ArcStr, usize)>>,
403}
404
405impl ScirCellConversion {
406    #[inline]
407    fn new() -> Self {
408        Self::default()
409    }
410}
411
412#[derive(Debug, Clone)]
413pub(crate) struct ScirInstanceConversion {
414    /// The Substrate cell ID of the child cell.
415    child: CellId,
416    /// The SCIR instance.
417    ///
418    /// If the instance is not inlined/flattened, this will be an opaque instance ID.
419    /// If the instance is inlined, this will be a [`ScirCellConversion`].
420    instance: ConvertedScirInstance,
421}
422
423pub(crate) struct ScirLibExportContext<S: Schema + ?Sized> {
424    lib: LibraryBuilder<S>,
425    conv: ScirLibConversionBuilder,
426    cell_names: Names<CellId>,
427}
428
429impl<S: Schema + ?Sized> Default for ScirLibExportContext<S> {
430    fn default() -> Self {
431        Self {
432            lib: LibraryBuilder::new(),
433            conv: ScirLibConversionBuilder::new(),
434            cell_names: Names::new(),
435        }
436    }
437}
438
439impl<S: Schema<Primitive = impl Clone> + ?Sized> Clone for ScirLibExportContext<S> {
440    fn clone(&self) -> Self {
441        Self {
442            lib: self.lib.clone(),
443            conv: self.conv.clone(),
444            cell_names: self.cell_names.clone(),
445        }
446    }
447}
448
449impl<S: Schema<Primitive = impl std::fmt::Debug> + ?Sized> std::fmt::Debug
450    for ScirLibExportContext<S>
451{
452    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
453        let mut builder = f.debug_struct("ScirLibExportContext");
454        let _ = builder.field("lib", &self.lib);
455        let _ = builder.field("conv", &self.conv);
456        let _ = builder.field("cell_names", &self.cell_names);
457        builder.finish()
458    }
459}
460
461impl<S: Schema + ?Sized> ScirLibExportContext<S> {
462    fn new() -> Self {
463        Self::default()
464    }
465}
466
467#[derive(Debug, Default, Clone)]
468enum FlatExport {
469    Yes(Vec<scir::SliceOne>),
470    #[default]
471    No,
472}
473
474impl FlatExport {
475    #[inline]
476    pub fn is_yes(&self) -> bool {
477        matches!(self, FlatExport::Yes(_))
478    }
479
480    #[inline]
481    pub fn is_no(&self) -> bool {
482        !self.is_yes()
483    }
484}
485
486struct ScirCellExportContext {
487    inst_idx: u64,
488    cell: scir::Cell,
489}
490
491impl ScirCellExportContext {
492    #[inline]
493    pub fn new(cell: scir::Cell) -> Self {
494        Self { inst_idx: 0, cell }
495    }
496}
497
498impl<S: Schema + ?Sized> RawCell<S> {
499    /// The name associated with the given node.
500    ///
501    /// # Panics
502    ///
503    /// Panics if the node does not exist within this cell.
504    fn node_name(&self, node: Node) -> String {
505        let node = self.roots[&node];
506        self.node_names[&node].to_string()
507    }
508    /// Export this cell and all subcells as a SCIR library.
509    ///
510    /// Returns the SCIR library and metadata for converting between SCIR and Substrate formats.
511    ///
512    /// Consider using [`export_multi_top_scir_lib`] if you need to export multiple cells
513    /// to the same SCIR library.
514    pub(crate) fn to_scir_lib(&self) -> Result<RawLib<S>, ConvError> {
515        let mut lib_ctx = ScirLibExportContext::new();
516        let scir_id = self.to_scir_cell(&mut lib_ctx)?;
517
518        if let ChildId::Cell(scir_id) = scir_id {
519            lib_ctx.lib.set_top(scir_id);
520        }
521        lib_ctx.conv.top = Some(self.id);
522
523        Ok(RawLib {
524            scir: lib_ctx.lib.build()?,
525            conv: lib_ctx.conv.build(),
526        })
527    }
528
529    /// Exports this [`RawCell`] to a SCIR cell if it has not already been exported. Should only be called
530    /// on top cells or un-flattened cells.
531    fn to_scir_cell(&self, lib_ctx: &mut ScirLibExportContext<S>) -> Result<ChildId, ConvError> {
532        if let Some(conv) = lib_ctx.conv.cells.get(&self.id) {
533            return match conv {
534                SubstrateCellConversion::Cell(c) => Ok(c.cell_id.unwrap().into()),
535                SubstrateCellConversion::Primitive(p) => Ok(p.primitive_id.into()),
536            };
537        }
538
539        let name = lib_ctx.cell_names.assign_name(self.id, &self.name);
540
541        Ok(match &self.contents {
542            RawCellContents::Cell(_) => {
543                let mut cell_ctx = ScirCellExportContext::new(Cell::new(name));
544                let mut conv =
545                    self.export_instances(lib_ctx, &mut cell_ctx, FlatExport::No, None)?;
546                let ScirCellExportContext {
547                    cell: scir_cell, ..
548                } = cell_ctx;
549
550                let id = lib_ctx.lib.merge_cell(scir_cell);
551                conv.cell_id = Some(id);
552                lib_ctx.conv.add_cell(self.id, conv);
553
554                id.into()
555            }
556            RawCellContents::Scir(ScirBinding {
557                lib,
558                cell: id,
559                port_map,
560            }) => {
561                let map = lib_ctx.lib.merge_cells((**lib).clone(), [*id]);
562                let id = map.new_cell_id(*id);
563                let mut conv = ScirCellConversion::new();
564                conv.cell_id = Some(id);
565                let cell = lib_ctx.lib.cell(id);
566
567                for port in cell.ports() {
568                    let info = cell.signal(port.signal());
569                    let nodes = &port_map.get(&info.name).unwrap_or_else(|| {
570                        panic!(
571                            "port {} not found in SCIR binding for cell {}",
572                            info.name,
573                            cell.name()
574                        )
575                    });
576
577                    for (i, node) in nodes.iter().enumerate() {
578                        conv.signals.insert(
579                            *node,
580                            if info.width.is_some() {
581                                info.slice().index(i)
582                            } else {
583                                info.slice().slice_one().unwrap()
584                            },
585                        );
586                    }
587                }
588
589                lib_ctx.conv.add_cell(self.id, conv);
590
591                id.into()
592            }
593            RawCellContents::Primitive(p) => {
594                let id = lib_ctx.lib.add_primitive(p.primitive.clone());
595                let mut ports = HashMap::new();
596                for (port, nodes) in &p.port_map {
597                    for (i, node) in nodes.iter().enumerate() {
598                        ports.entry(*node).or_insert(vec![]).push((port.clone(), i));
599                    }
600                }
601                let conv = ScirPrimitiveConversion {
602                    primitive_id: id,
603                    ports,
604                };
605                lib_ctx.conv.add_cell(self.id, conv);
606
607                id.into()
608            }
609            RawCellContents::ConvertedPrimitive(p) => {
610                let id = lib_ctx.lib.add_primitive(
611                    <ConvertedPrimitive<S> as ConvertPrimitive<S>>::convert_primitive(p)
612                        .map_err(|_| ConvError::UnsupportedPrimitive)?,
613                );
614                let mut ports = HashMap::new();
615                for (port, nodes) in p.port_map() {
616                    for (i, node) in nodes.iter().enumerate() {
617                        ports.entry(*node).or_insert(vec![]).push((port.clone(), i));
618                    }
619                }
620                let conv = ScirPrimitiveConversion {
621                    primitive_id: id,
622                    ports,
623                };
624                lib_ctx.conv.add_cell(self.id, conv);
625
626                id.into()
627            }
628        })
629    }
630    /// Exports the instances associated with `self` into the SCIR cell specified
631    /// in `cell_ctx`.
632    fn export_instances(
633        &self,
634        lib_ctx: &mut ScirLibExportContext<S>,
635        cell_ctx: &mut ScirCellExportContext,
636        flatten: FlatExport,
637        name_prefix: Option<&str>,
638    ) -> Result<ScirCellConversion, ConvError> {
639        if flatten.is_yes() {
640            assert!(
641                self.contents.is_cell(),
642                "can only flat-export Substrate cells"
643            );
644        }
645        let mut conv = ScirCellConversion::new();
646        let mut nodes = HashMap::new();
647        let mut roots_added = HashSet::new();
648        let mut names = uniquify::Names::new();
649        let prefix = name_prefix.unwrap_or("");
650
651        if let FlatExport::Yes(ref ports) = flatten {
652            // Flattened cells need to add all non-IO nodes to the enclosing cell.
653            assert_eq!(ports.len(), self.ports.len());
654            for (port, s) in self.ports.iter().zip(ports) {
655                let root = self.roots[&port.node()];
656                roots_added.insert(root);
657                nodes.insert(root, *s);
658            }
659        }
660
661        for (&src, &root) in self.roots.iter() {
662            let s = if !roots_added.contains(&root) {
663                let s = cell_ctx.cell.add_node(self.node_name(root));
664                roots_added.insert(root);
665                nodes.insert(root, s);
666                s
667            } else {
668                nodes[&root]
669            };
670            nodes.insert(src, s);
671            conv.signals.insert(src, s);
672        }
673
674        match self.contents.as_ref() {
675            RawCellKind::Scir(_)
676            | RawCellKind::Primitive(_)
677            | RawCellKind::ConvertedPrimitive(_) => {
678                // SCIR and primitive cells do not contain Substrate instances,
679                // so they cannot be flattened, and thus should not have
680                // [`RawCell::export_instances`] called on them.
681                unreachable!()
682            }
683            RawCellKind::Cell(contents) => {
684                for instance in contents.instances.iter() {
685                    let child_id: ChildId = match &instance.child.contents {
686                        RawCellContents::Primitive(_) | RawCellContents::ConvertedPrimitive(_) => {
687                            instance.child.to_scir_cell(lib_ctx)?
688                        }
689                        _ => {
690                            if instance.child.flatten {
691                                let ports = instance.connections.iter().map(|c| nodes[c]).collect();
692                                let inst_conv = instance.child.export_instances(
693                                    lib_ctx,
694                                    cell_ctx,
695                                    FlatExport::Yes(ports),
696                                    Some(&format!("{}{}_", prefix, &*instance.name)),
697                                )?;
698                                conv.instances.insert(
699                                    instance.id,
700                                    ScirInstanceConversion {
701                                        child: instance.child.id,
702                                        instance: ConvertedScirInstance::InlineCell(inst_conv),
703                                    },
704                                );
705                                continue;
706                            } else {
707                                instance.child.to_scir_cell(lib_ctx)?
708                            }
709                        }
710                    };
711                    let name = names.assign_name((), &instance.name);
712                    assert_eq!(
713                        name,
714                        instance.name,
715                        "instance name `{}` in cell `{}` is not unique",
716                        instance.name,
717                        cell_ctx.cell.name()
718                    );
719                    let mut sinst =
720                        Instance::new(arcstr::format!("{}{}", prefix, instance.name), child_id);
721                    cell_ctx.inst_idx += 1;
722
723                    assert_eq!(instance.child.ports.len(), instance.connections.len());
724
725                    let mut conns = HashMap::new();
726                    for (port, &conn) in instance.child.ports.iter().zip(&instance.connections) {
727                        conns.insert(port.node(), conn);
728                    }
729                    let port_map = match &instance.child.contents {
730                        RawCellContents::Primitive(p) => p.port_map.clone(),
731                        RawCellContents::ConvertedPrimitive(p) => p.port_map().clone(),
732                        RawCellContents::Scir(p) => p.port_map().clone(),
733                        _ => HashMap::from_iter(instance.child.ports.iter().map(|port| {
734                            (
735                                instance.child.node_name(port.node()).into(),
736                                vec![port.node()],
737                            )
738                        })),
739                    };
740                    for (port, port_nodes) in port_map {
741                        sinst.connect(
742                            port,
743                            Concat::from_iter(
744                                port_nodes.into_iter().map(|node| nodes[&conns[&node]]),
745                            ),
746                        );
747                    }
748
749                    if let RawCellContents::ConvertedPrimitive(p) = &instance.child.contents {
750                        <ConvertedPrimitive<S> as ConvertPrimitive<S>>::convert_instance(
751                            p, &mut sinst,
752                        )
753                        .map_err(|_| ConvError::UnsupportedPrimitive)?;
754                    }
755
756                    let id = cell_ctx.cell.add_instance(sinst);
757                    conv.instances.insert(
758                        instance.id,
759                        ScirInstanceConversion {
760                            child: instance.child.id,
761                            instance: ConvertedScirInstanceContent::Cell(id),
762                        },
763                    );
764                }
765            }
766        }
767
768        if flatten.is_no() {
769            for port in self.ports.iter() {
770                cell_ctx
771                    .cell
772                    .expose_port(nodes[&port.node()], port.direction());
773            }
774        }
775        Ok(conv)
776    }
777}
778
779/// The error type for Substrate functions.
780#[derive(thiserror::Error, Debug, Clone)]
781pub enum ConvError {
782    /// An error in validating the converted SCIR library.
783    #[error("error in converted SCIR library")]
784    Scir(#[from] scir::Issues),
785    /// An unsupported primitive was encountered during conversion.
786    #[error("unsupported primitive")]
787    UnsupportedPrimitive,
788}
789
790/// Export a collection of cells and all their subcells as a SCIR library.
791///
792/// Returns the SCIR library and metadata for converting between SCIR and Substrate formats.
793/// The resulting SCIR library will **not** have a top cell set.
794/// If you want a SCIR library with a known top cell, consider using [`RawCell::to_scir_lib`] instead.
795pub(crate) fn export_multi_top_scir_lib<S: Schema + ?Sized>(
796    cells: &[&RawCell<S>],
797) -> Result<RawLib<S>, ConvError> {
798    let mut lib_ctx = ScirLibExportContext::new();
799
800    for &cell in cells {
801        cell.to_scir_cell(&mut lib_ctx)?;
802    }
803
804    Ok(RawLib {
805        scir: lib_ctx.lib.build()?,
806        conv: lib_ctx.conv.build(),
807    })
808}