Component Style Guide
This is a style guide for users creating their own DeviceLayout.jl components.
For basic Julia style, see the style guide in the Julia manual. Contributions to Julia projects should conform to local conventions, which are often established by a choice of either Blue or SciML style. A good way for projects to short-circuit basic style questions is automatic formatting using Runic.jl or JuliaFormatter.jl. For example, DeviceLayout.jl generally follows Blue style, with simple conventions enforced via JuliaFormatter using a julia-format job in GitHub CI together with the script in scripts/format.jl.
Many topics below, like parameter naming conventions, are important for creating a predictable codebase that can be understood without reading code or inspecting GDSII files. Other topics, like how to choose parameterization for components, can also have direct benefits in device design. The ExamplePDK is meant to demonstrate good style in this sense, but it does not yet follow all recommendations here.
Parameter naming
Use descriptive and meaningful names that clearly communicate a parameter’s purpose and function. Parameters should be self-documenting, allowing developers to understand their role without examining implementation details.
- Good:
claw_trace - Bad:
d2(not descriptive),claw_width(which width?)
- Good:
Maintain consistency with local standards. If there is no established style convention for your project or team, maintain consistency in your own contributions throughout your component, project, package, and/or PDK. Once you establish a naming pattern, apply it uniformly to create a predictable and maintainable codebase.
Prioritize readability over brevity. Choose names that are pronounceable and immediately understandable, even if they’re longer. Autocomplete makes length matter less.
Good:
island_widthBad:
isl_wParameter names (as with variable names in general) should also be long and descriptive enough that they can be uniquely identified in full-text search
- Bad:
p, pa, path - Good:
feedline_path - Use names that are extremely concise only in very narrow scopes, like few-line functions or for loops (e.g.
for i in eachindex(array))
- Bad:
Hierarchical naming
Use hierarchical naming with underscores to group related parameters and establish clear relationships.
Parameters should be in
snake_caseDescriptors should be in order of increasing specificity, typically
<feature>_<dimension>When there is ambiguity, add more feature-specific prefixes
Rationale:
- Logical grouping: Parameters are grouped by feature when sorted alphabetically
- Autocomplete friendly: Typing the feature name shows related parameters
- Hierarchical clarity: The feature is the primary concept, with dimension as a qualifier
# Good: Clear hierarchy and relationships
island_outer_radius = 135μm
island_inner_radius = [80, 80, 80, 80, 80]μm
island_ground_gap = 15μm
island_coupler_gap = 15μm
claw_inner_gap = 5μm
claw_outer_gap = 20μm
claw_trace = 10μm
feedline_style = Paths.CPW(10μm, 6μm)
feedline_length = 300μm
feedline_bridge = nothing
# Avoid: Flat naming without clear relationships
outer_radius = 135μm
inner_gap = 5μm
coupler_gap = 15μm
island_gap = 15μm
width = 10μmFeature naming
Decide what each bit of a component is called and use that name consistently in prefixes and documentation. Names should be descriptive and reflect the physical shape or functional purpose. Some common ones in ExamplePDK:
- General shapes:
island,claw,shield,star_tip,cutout(shape in a negative layer into which positive shapes are placed) - Path shapes:
meander,bend,taper,tap(path ‘tee’d off of another line),snake(two opposite turns in a row) - Functional elements:
xy,z,junction,coupler,termination,feedline(a signal-carrying path addressing a component)
When naming a feature, ask:
- Do existing components have a similar feature? If so, what is it called? Unless that name clearly violates style rules, use that.
- Do existing components use the name I want with a different meaning? If so, choose a different name.
- Is this name ambiguous, or will someone looking at a drawing understand exactly which feature it refers to?
Dimensional suffixes in 2D
Use suffixes rather than prefixes to indicate dimension: feature_width rather than width_feature. Reach for the following common suffixes first:
- For rectangular features, use
_x_lengthand_y_length - For linear features, use
_length(primary/extensible dimension) and_width(cross-sectional thickness along length) - For circular features, use
_radius - For CPW-like features, use
_traceand_gapor (especially if you’re actually drawing it as a Path)feature_style = Paths.CPW(trace, gap)
In more detail:
_length: The primary dimensional extent, as in the longest dimension, the length of aPath, the dimension along which the feature would be “extended” from one end, or the dimension along the feature’s “main” axis (e.g., a symmetry axis)_width: The dimension perpendicular to_length_x_lengthor_y_length: For rectangular or non-Path-like features, extent along the x axis or y axis in the reference frame of the component_radius: The radius of a curve or circle_offset: Linear displacement from a reference position (e.g., centered or aligned)Align.above(feature_polygon, reference; offset=feature_offset))- If offsetting in both dimensions, use a
Point
_bias: Distance to grow a positive shape from each edge (e.g., foroffset(feature_polygon, feature_bias))_pitch: Center-center distance of repeated features (e.g. bridges on a CPW)_gap: Edge-edge width of negative space between two features (e.g., width of non-metallized region between edges of metal features)_spacing: Edge-edge distance in contexts with more than two features (e.g., spacing between edges of bump bonds)_trace: The width of a metal strip_overlap: How far something extends into another feature/region_rounding: Rounding radius of a corner- relative or absolute is determined by unitless/unitful; components should use a type annotation to require one or the other
- Example:
island_rounding::Float64if the component is designed to use rounding relative to edge length, orisland_rounding::DeviceLayout.Lengthfor an absolute rounding radius
Rotational suffixes:
_direction: Direction of a path or similar feature, counterclockwise from the positive x axis_angle: Angular extent_rotation: Counterclockwise rotation from a reference direction
Avoid:
_thickness, _height, _depth(more appropriate for 3D)_distance(ambiguous without additional qualifiers like_center_centeror_edge_edge, but acceptable if offset, pitch, gap, spacing don’t apply)_extentwith respect to Paths: usually you want trace, gap, and length, but if you do use extent, it must be the distance from center line to outer edge of style; that is,trace/2forPaths.Traceandtrace/2 + gapforPaths.CPW(corresponds toPaths.extent()function)_extentin other contexts: Use sparingly to describe dimensions not well captured by the above terms, for example bounding box dimensions of a feature with radial symmetry like the star island (e.g.,island_x_extentfeels more natural thanisland_x_length)—but usually that’s not going to be a parameter unless it’s specifically subject to design intent or constraints_shift(prefer_offsetfor linear displacement, unless it can be confused with curve or polygon offsetting)_diameter(prefer_radius)
Suffixes in 3D
_height: Height above a substrate surface_thickness: Thickness of an extrusion_depth: Thickness of a subtracted or downward extrusion (etch/trench depth)_flipchip_gap: Distance between substrate surfaces on chips facing each other
Composite components
For parameters passed through to subcomponents:
- If the parameter is inherited by multiple subcomponents, use the same name as in the subcomponents
- If the parameter is used for one specific subcomponent, add prefix for the subcomponent
- Use
SchematicDrivenLayout.filter_parametersto get a collection of either kind of shared parameters
If you have a large number of parameters to pass through to a subcomponent, or want to maintain flexibility over parameters that are not usually important, use a
templatesNamedTuple parameter- For each subcomponent, the
templatesNamedTuple contains an instance of the subcomponent type (with the subcomponent name as the key) that provides defaults - Parameters you’d often want to override or reconcile with other parameters are set at the CompositeComponent parameter level
- For each subcomponent, the
Example of template usage in _build_subcomponents:
@component jj = comp.templates.jj begin
lead_length = comp.island_ground_gap / 2 # Override specific params
# Other template parameters are preserved
endExample with both filtering and templates:
@compdef struct MyCompositeComponent <: CompositeComponent
templates = (;
subcomp1=MySubComponent(; name="subcomp1"),
subcomp2=MySubComponent(; name="subcomp2")
)
subcomp1_width = 2mm
length = 2mm
end
@compdef struct MySubComponent <: Component
width = 1mm
length = 2mm
end
function SchematicDrivenLayout._build_subcomponents(cc::MyCompositeComponent)
# Matching with no prefix: (; length=...)
shared_params = filter_parameters(MySubComponent, cc)
# Matching with prefix: (; width=...)
subcomp1_overrides = filter_parameters(cc.templates.subcomp1, cc)
@component subcomp1 = cc.templates.subcomp1(; subcomp1_overrides..., shared_params...)
@component subcomp2 = cc.templates.subcomp2(; shared_params...)
return (subcomp1, subcomp2)
endOther special cases
Boolean parameters: use past participles or adjectives, e.g.
roundedinstead ofround,rounding, oris_rounded- (although for rounding specifically you would just set
feature_rounding = 0.0to turn it off, with or without units depending on whether the component uses relative or absolute rounding)
- (although for rounding specifically you would just set
Angles:
- Use degrees for the default
- Positive angles are counterclockwise
Arrays/counting:
Count starting with 1
Use suffix
_countfor the number of something (notn_...ornum_...)Parameter arrays should be ordered consistently, but the order may depend on context
Generic circular arrangement: Counterclockwise from the x-axis
Chip ports: Conform to your packaging convention based on how the chip is placed in a package (IC packages are usually numbered counterclockwise from left end of bottom edge;
ExamplePDK.ChipTemplatesports are numbered clockwise from left end of top edge)Grids: Matrix with [Row, Column] starting with [1, 1] in the upper left (maps to Julia matrix literal)
Pairs (input/output, start/end):
input_andoutput_or similar descriptors even if it’s arbitrary which is which;_0_and_1_if they correspond top0andp1as in the start and end points of a Path; tuple otherwise- For two or three things that aren’t input/output pairs, you can use an array/tuple or
_1_, _2_, _3_, but don’t use the latter if you have multiple numbered collections with unrelated counts
- For two or three things that aren’t input/output pairs, you can use an array/tuple or
Parameterization
What parameters should you use to describe the component in the first place? The guiding considerations, in order of priority:
Geometric independence: Each parameter should control a single, distinct geometric property without creating unintended dependencies
- Good:
island_width,island_ground_gap - Bad:
island_width,transmon_total_width, derivingisland_ground_gap = (transmon_total_width - island_width)/2- Explanation:
transmon_total_widthnow controls the gap, but the gap also appears at the end of the transmon length
- Explanation:
- Good:
total_pathlength,feature_positiondescribing how far along a component's path a feature is placed - Bad:
total_pathlength,feature_position_pathlength_ratiospecifying the ratio of position to total path length- Explanation: Changing the total pathlength changes the position of the feature, which may be physically motivated but will cause headaches (especially if there is a hook on the feature)
- Good:
Design independence: “Tuning parameters”—the set of parameters that are varied in practice to obtain target design properties—should each approximately independently affect one design property
Bad:
coupling_lengthandmeander_lengthfor an inductively-coupled transmission line resonator, such that increasing the coupling length lowers the frequency directly by increasing the total lengthGood:
coupling_lengthandtotal_length, such that increasing the coupling length only affects resonator frequency by increasing external loadingThis can conflict with geometric independence, which takes priority:
- Bad:
coupling_pad_widthandeffective_lengthparameter taking into account estimated capacitive loading of coupling pad, so thatcoupling_pad_widthchanges the total path length in order to leave frequency roughly fixed (this differs from thetotal_lengthexample above becauseeffective_lengthis not a single, distinct geometric property) - Good: Separate parameters for transmission line resonator length and coupling pad size, even though both affect resonant frequency
- Bad:
Design intent preservation: Parameters should reflect engineering requirements rather than arbitrary geometric relationships—they should capture the “why” behind dimensional choices, not just the “what”
Bad: A meander parameterized by segment lengths and number of turns
Good: A meander parameterized by total length and available footprint
Parameters should still describe geometric properties, not functional properties:
- Bad: Meander
delaythat controls total path length based on wave propagation delay (calculated how?) - Good: Meander
total_lengththat controls length of path directly - Bad: Two degenerate parameters—“total effective length”, which is varied to control the frequency, and “assumed extra effective length” that accounts for bends, bridges, and coupling capacitances—so that you can have a parameter approximately inversely proportional to frequency with as little offset as possible
- Good: Make target frequency a device-level parameter rather than a component parameter, from which you compute the required path length based on data
- Bad: Meander
Constraint expression: It should be easy and natural to describe design rules or constraints on parameters
Bad: Two features overlap if some complex combination of parameters is negative, creating an invalid geometry
Good: A
feature1_feature2_gapparameter that must be nonnegativeBad: Drawing the entire geometry to calculate a footprint or distance between hooks that will be constrained in a device
Good: A parameter that controls a footprint dimension or hook position directly, eliminating a parameter that doesn’t carry design intent
There should also be no ‘holes’ in feasible parameter space; that is, parameterization allows constraints to define a convex region in parameter space where designs are feasible
The preceding considerations take priority
Bad: A meander parameterized by straight segment length and number of turns, even though there is a constraint that the length must be positive
Good: A meander parameterized by total length and available footprint, with a helper function to compute the straight segment length
# Good: Clear constraint with documented intent @compdef struct ExampleMeanderResonator <: Component bbox_x_length = 1.25mm # Bounding box width (path enters at origin along x axis) bend_radius = 0.05mm # Bend radius # ... other parameters... end # Must be positive function _straight_segment_length(res::ExampleMeanderResonator) return res.bbox_x_length - 2 * res.bend_radius end
Some specific guidelines:
Components are parameterized by geometry, not physical properties—trace and gap, not impedance
- The relationship between geometry and target property is not necessarily fixed—you might change a material, get new data, or improve your simulations
- If you know a relationship between geometry and target property, use the property as a device-level parameter from which you derive the geometric component parameter based on a documented data source (like Josephson energy to junction width)
Geometries should not have any “magic numbers” (literal numeric lengths that appear in geometry code)—these should be made into parameters with descriptive names
Don’t be shy about adding parameters, but don’t prematurely create additional control knobs you’re not sure you’ll need—once you can describe your desired geometries without magic numbers, stop
Bridges/crossovers/other "decorations": Make a
MyDecorationcomponent type and use an instance as a parameter, with components across the device with the same decoration using the same instance- Ensures fabrication requirements are consistently met and updated across the device, and also reduces redundant rendering/memory usage
- The decoration Component doesn’t have to go in a schematic—it can be attached to a path like any coordinate system
- The default should be
nothing, to encourage defining decorations at the device level and sharing them across components
Choose parameters so that there are as few parameters that affect hook position as possible
- The hook should be easy to find directly from parameters, without building complex geometry first
- In particular, “tuning parameters” should not change hook positions
- If changing one hook position can’t be avoided, consider whether another hook position should change along with it to more easily express a simple “distance between hooks” constraint and to keep the entire floorplan from shifting when the parameter is tuned
Avoid “flags” that change how the component is rendered for different purposes like simulation or artwork—use
OptionalStyle(e.g., with the helper functionsnot_simulated,only_simulated,only_solidmodel) or metadata together with your rendering target to customize behaviorOrientation/reference frames
- If components always have a particular global orientation on chip, the local orientation should be the same
- A Path-like component should otherwise generally have the path starting at the origin facing along the positive x-axis (
α0=0°)
Type annotations are generally optional, except where there is ambiguity about what kind of thing the parameter is
- When annotating a length, use a floating point type like
::typeof(1.0nm)(orμmormmif that’s how you want the unit printed) - Rounding: While rounding in DeviceLayout can be either relative (unitless) or absolute (unitful), a given component is likely designed for only one or the other. In that case, use a
::Float64or a length annotation
- When annotating a length, use a floating point type like
A parameter should be the appropriate Julia or DeviceLayout.jl type for the kind of thing it is; it is not required to be a single real value, even when it is subject to tuning or optimization
For a
Path-based feature, usefeature_style=Paths.CPW(trace, gap)rather thanfeature_traceandfeature_gapOptimization should use transformed parameters anyway
- Example: In the single transmon optimization example, we optimize over
xwithcap_length = (1 / x[1]^2) * 620μmandtotal_length=(1 / x[2]) * 5000μm, so that frequencies are approximately linear inxand both elements are around 1 - Example: When optimizing
feature1_feature2_offset::Pointwith a scale of 1μm, optimize overxwithfeature1_feature2_offset = Point(x[1], x[2]) * 1μm
- Example: In the single transmon optimization example, we optimize over
Component documentation
Documentation and definitions should follow existing templates used by generate_component_definition and generate_component_package.
At least one kind of diagram showing key features and parameters is required:
- Annotated ASCII diagram in the docstring
- Annotated drawing of rendered component in HTML docs
Docstrings
Docstrings should follow the templates in Component.jlt or CompositeComponent.jlt in DeviceLayout.jl/templates. You can customize these for your PDK by adding modified copies to a templates/ subfolder of your PDK root folder. These templates are used when a component definition is created with SchematicDrivenLayout.generate_component_definition or SchematicDrivenLayout.generate_component_package.
For a non-composite Component, the docstring looks like this:
"""
struct {{{compname}}} <: Component
<One-line description of component.>
<Optional: Longer description of component>
<Optional: Annotated ASCII illustration>
# Parameters
- `name`: Name of component
- `<p>`: <Parameter description>
- ...
# Hooks
- `<hook1>`: Description
- ...
"""
@compdef struct {{{compname}}} <: Component
# ... parameters...
endA CompositeComponent docstring also enumerates its subcomponents with links to their docstrings.
Any ambiguous parameters or hooks should be documented well enough that they can be understood without reading the code or inspecting GDS files. For example, if names of parameters or hooks use x/y, left/right, compass directions like north, or other terms relative to a reference frame, the reference frame or axes need to be clearly documented.
HTML docs
Docs should start with the template Component.mdt in DeviceLayout.jl/templates. You can customize this for your PDK by adding a modified copy to a templates/ subfolder of your PDK root folder. This template is automatically used by SchematicDrivenLayout.generate_component_package.
The template shows all docstrings in the component package, then runs an @example block that creates a default component instance (with hooks shown as labeled arrows), saves it to SVG, and displays it. Additional components added to a package should add a similar example.
Additionally, if there is no ASCII drawing in the docstring, add an image of the component manually annotated with parameters and hooks:
- Use a drawing app (PowerPoint works surprisingly well) to annotate a PNG/SVG of the component geometry with parameters and hooks
- Put the resulting image in
docs/src/assets/mycomponent_annotated.jpg - Include that file in the
.mddocumentation file:
```@raw html
<img src="../assets/mycomponent_annotated.jpg"/>
```Geometry methods
Organize _geometry! methods with clear sections and consistent patterns. For example:
function SchematicDrivenLayout._geometry!(cs::CoordinateSystem, comp::MyComponent)
# 1. Extract parameters used in this method body (use destructuring for readability)
(; param1, param2, param3) = comp
# 2. Create primary geometry
main_shape = _create_main_geometry(comp)
# 3. Create secondary features
cutouts = _create_cutouts(comp)
# 4. Apply operations (boolean, rounding, etc.)
final_shape = _apply_operations(main_shape, cutouts)
# 5. Place geometry with appropriate layers and mesh sizing
place!(cs, MeshSized(2 * critical_dimension(comp))(final_shape), METAL_NEGATIVE)
return cs
endExtract the parameters you need for a function at the top of that function using destructuring
Explicitly assign derived parameters with descriptive names:
param_total = param_1 + param_2 + param3rather than just using(param_1 + param_2 + param_3)in other expressions- Important derived parameters should be calculated using functions available to package users
Extract complex geometry creation into helper functions
- Helper functions should generally be private, start with an underscore, and take the component as input with the appropriate type annotation, e.g.
_paths(comp::MyComponent)
- Helper functions should generally be private, start with an underscore, and take the component as input with the appropriate type annotation, e.g.
Internal coordinate systems should always use
uniquename(to ensure different names across instances)Use bounding-box alignment methods for alignment rather than manually calculating dimensions
Use
OptionalStyle(e.g., with the helper functionsnot_simulated,only_simulated,only_solidmodel) or metadata to allow customizable rendering based on your rendering target
Hooks methods
Use descriptive names that indicate connection purpose or geometry:
- Hooks should usually be named after the component that gets attached there—a qubit would have a
readouthook and a readout resonator aqubithook - Hooks can also be named after the geometric feature they mark, for example if it’s used more in the sense of geometric alignment than for circuit/schematic connectivity, or if multiple kinds of components can be attached
- Use
p0andp1for start/end or in/out (consistent withPaths)
- Hooks should usually be named after the component that gets attached there—a qubit would have a
Write angles in degrees
Use a leading semicolon when returning a literal NamedTuple to avoid bugs when you have a single hook
- Incorrect:
return (claw = clawhook)assigns the valueclawhookto the variableclawand returns it - Correct:
return (; claw = clawhook)returns the NamedTuple you want - Do this even when you have multiple hooks, for safety against copying the code and deleting all but one
- Incorrect:
Avoid doing heavy computation inside
hooks—it is a sign that your parameterization needs improvementFor components using hooks based on paths, derive hooks from path geometry:
function SchematicDrivenLayout.hooks(comp::MyPathBasedComponent) path = _path(comp) # Reuse path creation logic # Extend path for hook positioning if needed straight!(path, comp.connection_distance, Paths.NoRender()) return hooks(path) # Use path's natural hooks end
Component and package creation and versioning
See the page on Process Design Kits (PDKs) for general recommendations on creating and organizing a DeviceLayout.jl PDK. For component packages within your PDK:
Use
SchematicDrivenLayout.generate_component_packageto generate component packages and documentation based on a template- Fill out the docstring templates with parameter/hook/subcomponent descriptions
- Add an ASCII diagram to the docstring if it's simple enough, and if not, create an annotated image for the HTML docs
- You can also use
generate_component_definitionto create only the definition file, or copy-paste fromDeviceLayout.jl/templatesto do it manually
New components can be defined locally in a design project (that is, non-package code for creating a specific device), but should be put in a package if they are reused
- If you're using the component in more than one design project, then put it in a package
- For example, if a component is being copied from design project to design project, then it should be packaged
New components should generally go in separate packages
Components go in the same package if and only if they should always be versioned together
Example: Shunt and series interdigital capacitors both go in InterdigitalCapacitors—they have identical dependencies, share most of their code, and no one should ever have a reason to use v1.1 of the series capacitor with v1.2 of the shunt capacitor
Example: A parallel plate capacitor would not go in the same package as interdigital capacitors
Example: A lumped element resonator that combines an interdigital capacitor with some lumped inductor would not go in the same package as either interdigital capacitor (or inductor), since it has more dependencies than either alone
- Otherwise, a user of only the interdigital capacitor would have to version their other dependencies to be compatible with the correct version of the lumped inductor
@variantand@composite_variantshould be used sparingly in package code- Prefer to explicitly factor out shared code that is reused between related components
@variantis more appropriate for conveniently making small, ad-hoc modifications in design projects- If your variant combines a base component with other functional structures, consider making a
CompositeComponentinstead
Components types and packages, like all types and packages, should be in
CamelCaseComponent packages should be named according to the category of physical structure(s) they implement, as specifically as reasonably possible
- Bad:
Readoutcontaining all your readout resonators and Purcell filters - Good:
ClawedMeanderResonatorscontaining readout resonators and Purcell filters based on CPW meanders with claw-shaped coupling capacitors
- Bad:
Component package names should usually be plural
- Examples:
StarTransmons,ClawCapacitors,ChipTemplates - Rare exceptions:
FlipChipIntegration(a package with various components that are part of a "system")
- Examples:
Component packages can contain multiple components if those components should always be versioned together (e.g., they share code or always form a composite component together)
- Bad:
Capacitorspackage containing both interdigital and claw capacitors - Good:
InterdigitalCapacitorsandClawCapacitorspackages, each containing series and parallel versions with shared helper functions to create the IDC or claw shapes
- Bad:
Names should avoid obscure abbreviations, numbers, and subjective descriptions
- Abbreviations that any quantum or electrical engineer should know are acceptable (“CPW”)
- Bad:
LongSideSp80GapNegative - Good:
ImpedanceMatchedCPWLauncher
Components should not be named greedily—don’t call a component “Qubit,” because the name is too valuable. Examples:
- Bad:
RoundQubitis a bad name (is it a circle? an ellipse? did you mean concentric?) - Bad:
PurcellReadoutis too generic—is it the readout resonator, filter, or both? Is the physical structure a meander, hairpin, hanger, spiral? - Better:
MeanderPurcellReadoutResonatorandMeanderPurcellFilter - Better: Define one component type
BareMeanderResonatorwithout external coupling elements, then define aMeanderFilteredReadoutResonatorcomposite component combining two of those with additional components for coupling
- Bad:
If a name has more than three "parts" (component class, shape, and one modifier) then there is likely a problem with the scope of the component
Component and PDK packages must follow Semantic Versioning, like any Julia package
A design should be able to update components within major versions and get a clean XOR with the old version (except for changes that are bugfixes)
- XOR should be clean across the entire design space, not just for particular designs or defaults
For example, changes to default parameters count as breaking
Component and PDK packages must keep a changelog
- For breaking changes, a migration guide with detailed steps for each change that needs to be made when upgrading major version should be included in the documentation
- The migration guide should also be linked or (if brief) included in the changelog
Component composition
What do you choose to be a component in the first place? Where do you draw the boundaries between parts of composite components?
As a layout abstraction, AbstractComponent is a parameterized geometry with default parameters and special attachment points (hooks). The interface also provides a few other conveniences, like the default_parameters function, the ability to use a component instance as a template constructor (e.g., to locally customize defaults), and (if defined with @compdef) storing the geometry after it is first computed.
Meanwhile, AbstractComponent does not have necessarily satisfy any functional/physical contract—for example, hooks do not have to be electrical "pins" or "ports". However, a component will be used as a schematic-level abstraction, allowing us to assign meaning to its presence in a device and to its connections to other components.
You should not create a Component for every unit of reusable parameterized geometry. You should instead create a function that returns a GeometryEntity or two or places them in a coordinate system (for examples, see the shape library) when most of the following are true of your parameterized geometry:
- It has only one or two shapes
- It can be useful in various layers
- It has relatively few parameters, most of which have no natural defaults
- It has no special attachment/alignment points (bounding-box alignment methods are sufficient)
- It has no independent physical meaning
Anything more complicated or meaningful is a candidate for a Component.
Choose definitions for components or composite components that help you take advantage of the schematic level of abstraction. For example, you may have different small elements that can be swapped in and out of a larger structure, like junction types in a transmon; in that case, you would organize the transmon as a composite component with an island subcomponent and a choice of junction subcomponents. The ExamplePDK transmons accomplish this with a template parameter that can be either a single junction or a SQUID component.
As another example, you might want to be able to build simulation models for individual components or small groups of components based on subgraphs of your schematic graph. In that case, at least for components at the top level in a schematic, something like one of the following should be approximately true:
- It has some independent physical or functional meaning
- It is a minimal unit for a useful simulation
- It can be associated with an RF network with ports, which could be cascaded with other components' RF networks based on schematic connections
- It can be associated with a netlist, such that netlist nodes could be identified and elements (like capacitances and inductances) quantified, and then composed with other components' netlists based on schematic connections
The appropriate conceptual boundaries between components will depend on your device and on your simulation workflow. In analog RF devices, where crosstalk and parasitic couplings significantly complicate component abstraction boundaries, there is not always an obvious or unique correct choice.