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:

  1. Determine what pads are present

  2. Determine which pad is the first pad, possibly based on some filter provided by the user.

  3. Determine the location of pad 1.

  4. 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:

  1. decompose - Useful tool for unpacking

  2. visit - Recursively search through a circuit graph for elements matching a given type. Provides metadata for locating these elements in the tree.

  3. extract - Similar to visit but 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:

  1. 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 attribute p[1].

  2. This example uses visit to 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:

  1. RefPath(('p', Item(1))

  2. RefPath(('p', Item(2))

  3. 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:

  1. "p[1]"

  2. "p[2]"

  3. "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:

GroundPlane

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.