Design Introspection#
Introspection is the ability to interrogate an object in a running program and then determine
important characteristics of that object. For instance, we might want to know what type an
object is – eg, str, float, etc. It can also be used to identify the value of attributes
or other structural aspects of an object.
In JITX, introspection of the circuit graph is an important feature for programmatic design of circuits. By allowing the user to introspect the running design, we allow for a wide range of behavior that would other wise be impossible. For example, consider an activity like decorating the first pad of a land pattern with a silkscreen marker. It might consist of the following steps:
Determine what pads are present
Determine which pad is the first pad, possibly based on some filter provided by the user.
Determine the location of pad 1.
Determime design rule values for minimum silkscreen feature size and soldermask minimum sliver
In order to prevent hard-coded values, we may need to introspect at various levels in the design to determine the necessary information for programmatically generating features of a land pattern, component, or circuit.
Tree Introspeection Tools#
The jitx.inspect library provides multiple standard tools for introspection:
visit - Recursively search through a circuit graph for elements matching a given type. Provides metadata for locating these elements in the tree.
extract - Similar to
visitbut just provides the elements directly without metadata.
Example - Finding Pad 1#
Motivated by the exercise above, here is an example of finding pad 1 for a landpattern. To set the stage for this example here are a few points of note:
This example uses the SOIC generator for its landpattern. This generator produces pads on attributes named
p[1],p[2], etc. So the first pad is the pad on attributep[1].This example uses
visitto introspect the pads of the landpattern.
from jitx.component import Component
from jitx.net import Port
from jitx.toleranced import Toleranced
from jitx.inspect import visit, extract
from jitxlib.symbols.box import BoxSymbol
from jitxlib.landpatterns.generators.soic import SOIC, SOIC_DEFAULT_LEAD_PROFILE
class NE555(Component):
GND,TRIG,OUT,RESET,CONT,THRESH,DISCH,VCC = [Port() for _ in range(8)]
lp = SOIC(num_leads = 8)
.lead_profile(SOIC_DEFAULT_LEAD_PROFILE)
.narrow(Toleranced.min_max(4.81, 5.0))
symb = BoxSymbol()
def get_first_pad(self) -> Pad:
pds = list(extract(self.lp, Pad)) # Returns a generator
return pds[0]
Note that I needed to convert the generator created by visit into a list in order to index into it. You will also notice
that the result is a tuple, of which we’ve thrown away the first element - we will return to that first element in a moment.
This first pass at making a function to find the first pad works but it is making a rather large assumption. It is assuming that the pads are going to be defined in pad id order. In other words, that the generate first defines p[1], then p[2], and so on. The
current generator does this - but we can’t guarantee that for all landpatterns that this will be the case. Further, what if we
wanted to find a pad B[13] in a Full Matrix BGA - just indexing into a flattened list may be both error prone and inefficient.
Here is another way to implement the same function:
def get_first_pad(self) -> Pad:
pds = visit(self.lp, Pad)
for elems in pds:
trace, pd = elems
if trace.path == "p[1]":
return pd
raise RuntimeError("No Pad with index = 1 found.")
The trace object is a tool for identifying where in the design tree the visited object, in this case pd:Pad, can be found. It includes an object path of type RefPath. The RefPath object is a Sequence (ie, tuple-like) that provides the objects that must be traversed to arrive at the object in question.
In the case of this landpattern’s Pad objects, we would see the following RefPath sequence:
RefPath(('p', Item(1))RefPath(('p', Item(2))RefPath(('p', Item(3))…
The Item element is used to represent the index (or key) into a collection, like a dict or list. Notice that this example shows the second element as ordered numerically - but this isn’t guaranteed.
The string representation for a RefPath matches the python accessor syntax. For example:
"p[1]""p[2]""p[3]"…
This is why we can match trace.path on "p[1]" to find the first pad of the landpattern. This will work for finding arbitrary pads in a BGA or other landpattern as well.
Access the current object#
The tools described in the previous sections were focused on exploring the current node and its children. We can think of this as looking down the design tree. However, there is another common introspection task which requires us to look up the design tree. For this
application we will commonly use the current object.
from jitx import current
from jitx.component import Component
class SomeComponent(Component):
def __init__(self):
# Retrieve a 50 ohm single-ended routing structure
se50 = current.substrate.routing_structure(50.0)
# Retrieve fabrication constraint information
fab_constraint = current.substrate.constraints
clearance = fab_constraints.min_silk_solder_mask_space
# Retrieve the board shape
sh = current.design.board.shape
Here we show a few examples of using the current object to access the context of design level objects in the design tree. This
ends up being a very useful way to write application agnostic code.
Example: Board Shaped Pour#
One common feature to add to a design is a ground plane on a particular layer or set of layers. Here is an example:
class TopLevel(Circuit):
def __init__(self):
self.GND = Net("GND")
# ... Other circuit connections here ...
sh = current.design.board.shape
self.GNDPour = Pour(sh, layer=3, isolate=0.1, rank=1)
self.GND += self.GNDPour
In this code, we introspect the design to grab the shape of the current board. Then we can use this shape to create a ground plane on the bottom side of the board. This ends up looking like this:

Here we see that a ground plane was placed on layer 3 (green, bounded by the board edge). The via is necessary for the ground plane to appear, otherwise the backend will assume that the pour is not connected to anything and remove it.