circuit module#

Circuits#

This module provides the Circuit class, which is the primary modularization object in JITX for creating hierarchical designs with ports, components, and subcircuits.

class Circuit[source]#

Bases: Open, Positionable

The Circuit is JITX’s primary modularization object. The main function of a Circuit is instantiate ports that can be seen as an external interface, instantiate subcircuits or components, as well as net ports of these elements together.

Ports, components, and other circuits can be created directly in the class for convenience, and will be instantiated separately as instance attributes for each created circuit instance. It’s perfectly valid to add elements to self in the __init__ method, and in fact, any type of coding logic, such as a parameterized circuit, would need to go into an __init__ method, as it’s not possible to execute that properly in the class context.

All elements of the circuit needs to be reachable from the circuit in some way, it is not sufficient to merely create a component, it must be assigned to member field in the circuit in some way. It can be added to a container (such as a list, or a dictionary) that is assigned to the circuit object, not every component needs have its own attribute. The same is true for nets.

A general purpose += operator is provided to add elements to the circuit that do not need an assigned name. They’ll all be gathered into a private list that is not accessible from the outside. Note that if elements that have their name displayed somewhere (such as nets in the schematic) are added to this list, they will still be displayed, but their name may look confusing. For most objects it’s advisable to either assign them to a field or add them to a list or mapping which are rendered in a sensible way.

>>> class MyCircuit(Circuit):
...     # assume FancyComponent has an `n` and a `p` port. JITX's instantiation
...     # mechanism will create a new instance of FancyComponent for each
...     # instance of MyCircuit
...     comp = FancyComponent()
...     diffp = DiffPair()
...
...     def __init__(self):
...         self.nets = [
...             diffp.n + comp.n,
...             diffp.p + comp.p,
...         ]
in_bom: bool | None = None#

Whether the components within this circuit are in the bill of materials. If unset, defers to the parent Circuit’s in_bom attribute. If there is no parent circuit, defaults to True.

soldered: bool | None = None#

Whether the components within this circuit are soldered on the board. If unset, defers to the parent Circuit’s soldered attribute. If there is no parent circuit, defaults to True.

schematic_x_out: bool | None = None#

Whether the components within this circuit are marked with a red X in the schematic. If unset, defers to the parent Circuit’s schematic_x_out attribute. If there is no parent circuit, defaults to False.

transform: Placement | None = Placement(Transform((0, 0), 0, (1, 1)), 0)#

The placement of this circuit relative to the parent circuit.

require(Bundle, /, *, count=None, restrictions=None)[source]#

Require a port bundle to be provided a subcircuit, or, alternatively, by this circuit as a self-provide.

Overloads:
  • self, Bundle (T | type[T]), restrictions (Callable[[T], Mapping[Port, Callable[[Port], Any]]] | None) → T

  • self, Bundle (T | type[T]), count (int), restrictions (Callable[[T], Mapping[Port, Callable[[Port], Any]]] | None) → Sequence[T]

Parameters:
Return type:

T | Sequence

Note that the returned port instance is a placeholder port for the provided port that can be netted with other ports, but should not be added to this circuit as a member field, as it’s not a port that this circuit itself exposes.

Parameters:
Returns:

The required port bundle instance.

Return type:

T | Sequence

>>> class MyCircuit(Circuit):
...     subcircuit = MySubCircuit()
...     my_signal_port = DiffPair()
...     def __init__(self):
...         diffpair = self.subcircuit.require(DiffPair)
...         self.signal_net = self.my_signal_port + diffpair
place(instance, placement, /, *, on=Side.Top, relative_to=None)[source]#

Place a descendant component or circuit on the board relative to this circuit’s frame of reference. It is different from at() in that it does not modify the instance’s actual placement in its parents frame of reference, but instead adds a placement request for a child circuit or component. If the instance is introspected before placement occurs, the placement will not be reflected in the instance’s Trace transform.

Overloads:
  • self, instance (T), placement (Placement), relative_to (Component | Circuit | None) → T

  • self, instance (T), point (Point), on (Side), relative_to (Component | Circuit | None) → T

  • self, instance (T), transform (Transform), on (Side), relative_to (Component | Circuit | None) → T

Parameters:
Return type:

T

Note that circuits have a default placement of (0, 0) on top, which is to allow for elements inside the circuit to be placed relative to the design origin. If the circuit should have a free-floating frame of reference that components are placed relative to, the circuit should either be given a placement using at() or explicitly set to be free floating using circuit.at(floating=True). Note that if the circuit is free floating and components inside the circuit are not placed, it is ambiguous which frame of reference is modified when the components are placed.

>>> class MyCircuit(Circuit):
...     def __init__(self):
...         self.component = MyComponent()
...         self.place(self.component, Transform.rotate(90))
annotate(text, *, normalize=True)[source]#

Add a schematic annotation.

Parameters:
  • text (str) – Markdown formatted text to add as an annotation.

  • normalize – Whether to normalize the indentation, this is on by default, and is useful to allow natural indentation of multiline strings.

Return type:

None

>>> class MyCircuit(Circuit):
...     def __init__(self):
...         self.annotate("Hello, world!")
at(x=None, y=None, /, *, on=Side.Top, rotate=0, floating=False)[source]#

Place the circuit on the board relative to its parent’s frame of reference.

Overloads:
  • self, point (Point), on (Side), rotate (float) → Self

  • self, xform (Transform | Placement), on (Side) → Self

  • self, x (float), y (float), on (Side), rotate (float) → Self

  • self, floating (Literal[True]) → Self

Parameters:
  • x (Placement | Transform | tuple[float | int, float | int] | float | None) – x-value, transform, or placement to adopt.

  • y (float | None) – y-value if x is an x-value. This argument is only valid in that context.

  • on (Side) – If set to bottom, this object will be placed on the “opposite” side from its frame of reference. This means if the frame of reference is on the bottom of the board, setting this to “bottom” will actually put the object back on top.

  • rotate (float) – Rotation in degrees to apply to the object. Only applicable if not supplying a transform or placement.

  • floating (bool) – If set to True, no other arguments are valid, and will allow this circuit to be free floating, subject to interactive placement.

Returns:

The circuit itself, for method chaining.

Return type:

Self

class Annotation(text)[source]#

Bases: object

A text entity in the schematic. Typically not used directly, but rather through the convenience method annotate() which also normalizes the indentation.

Parameters:

text (str)

text: str#
class SchematicGroup(elem=None, /, *elems)[source]#

Bases: Structurable

A schematic group defines elements that will be placed together under a single logical grouping in the schematic. The group’s name is derived from the name of the instance attribute used to define the SchematicGroup object.

>>> class MyCircuit(Circuit):
...     comp = MyComponent()
...     def __init__(self):
...         # Add 'comp' to the schematic group named 'my_group'
...         self.my_group = SchematicGroup(self.comp)
Parameters:
class CurrentCircuit(circuit, postprocessing=<factory>)[source]#

Bases: Context

The current circuit being processed. Should not be used directly, but rather accessed through jitx.current.circuit instead.

>>> def get_ports() -> list[Port]:
...    circuit = jitx.current.circuit
...    ports = extract(circuit, Port)
...    return list(ports)
Parameters:
circuit: Circuit#
postprocessing: list[Callable[[Circuit], None]]#
postprocess(func)[source]#
Parameters:

func (Callable[[Circuit], Any])

class InstancePlacement(instance, placement, relative_to=None)[source]#

Bases: Critical

A placement of a component or circuit relative to another component or circuit.

These are created by the place() method, and do not need to be created manually.

Parameters:
instance: ReferenceType[Component | Circuit]#

The component or circuit to place.

placement: Placement#

The placement of the component or circuit.

relative_to: ReferenceType[Component | Circuit] | None = None#

The circuit or component to place relative to. If not provided, the placement is relative to the circuit’s frame of reference.

class Route(source, destination, layer, sketch=None)[source]#

Bases: Structural, Ref

A code based route between two ports/pads

Parameters:
class Sketch(start, turns_or_end, end=None, /)[source]#

Bases: object

Specify the sketch of a code based Route.

A sketch is a hint for the routing engine: a start and end terminal with an ordered list of turns in between.

Parameters:
class Terminal(point)[source]#

Bases: object

One end of a Route.Sketch.

Parameters:

point (Point)

point: Point#

The point at which this end of the sketch attaches.

start: Terminal#

The terminal where the sketch begins.

turns: Sequence[Point]#

Intermediate waypoints the route should follow.

end: Terminal#

The terminal where the sketch ends.

class Trace(shapes)[source]#

Bases: object

Computed output shape(s) of a route. In the most common case, this a single ArcPolyline shape, but could be a sequence of multiple shapes.

Parameters:

shapes (Sequence[Shape])

shapes: Sequence[Shape]#
traces: Sequence[Trace] | None = None#

Traces generated from this route. Traces are the actual copper pieces generated by the router, and will only be available once the design has been evaluated and captured by the runtime. For normal routes this is a single element, but for diff pairs there will be two entries in this list.

Note that it’s generally advisable to use the query api to look for relevant elements if, for example, only interested in the copper shapes of things, instead of inspecting these fields directly, unless explicitly the shapes of a particular route is needed.

derived: Sequence[Feature | Pour] | None = None#

Derived objects generated from this route. For most routes this will be unset, but for routing structure driven routes this could contain elements such as Soldermask openings or KeepOuts.

source: Port | Pad | Via | RouteConnectionEndpoint#

One of the end points of the route. In general, routes are not directional.

destination: Port | Pad | Via | RouteConnectionEndpoint#

The other end point of the route. In general, routes are not directional.

layer: int#

The layer to route on

sketch: Sketch | None#

An optional hint for the routing engine to follow