Substrate & Stackup#

The Substrate is a class that encapsulates the stackup, fabrication constraints, routing structures, vias, and other physical features that define the fabricatable primitives of a PCB design. This class is intended to be included at the top-level of a design and be referenced at any point in the design tree. This allows both the end-user and library developers to reference the substrate when programmatically constructing circuits and layouts.

Substrate#

If we reference the SampleSubstrate, we can see the basic structure for a substrate:


class SampleSubstrate(Substrate):
  """Sample substrate using a two layer stackup and the sample fabrication
  constraints."""

  stackup = SampleTwoLayerStackup()
  constraints = SampleFabConstraints()

  class MicroVia(Via):
    ...

  RS_50 = RoutingStructure(
    impedance = 50 * ohm,
    layers = symmetric_routing_layers(...)
  )

  DRS_100 = DifferentialRoutingStructure(
    name = "100 Ohm Differential Routing Structure",
    impedance = 100 * ohm,
    layers = symmetric_routing_layers(...)
  )

The stackup and constraints attributes are required for a substrate. The via and routing structure definitions are optional features. We can add as many via definitions as needed for our design.

The routing structures are registered with the substrate associated with a characteristic impedance. The idea is that the user can use the routing_structure and differential_routing_structure methods to lookup routing structures by impedance. In this example, we have a 50Ω single-ended and a 100Ω differential routing structure. For example:

import jitx

class SomeCircuit(jitx.Circuit):

  def __init__(self):
    rs = jitx.current.substrate.routing_structure(50.0)

The idea here is that we can abstract the details of a particular substrate from the code that needs to know about vias, routing structures, and other primitives. The routing_structure method allows us to query the substrate for the current design for an impedance controlled structure. Whether you are writing a custom circuit for this design, or are developing a general use library to be used in many designs, this provides a common way to query these necessary structures without needing to know the details about the substrate.

Stackup#

The Stackup defines the laminated layers of conductors and dielectrics that make up a PCB. This stackup defines each layers thickness and order, as well as other properties that related to the physical characteristics of each layer.

The stackup is constructed using the following Material types:

  • Conductor - The metal layers that conduct signals.

  • Dielectric - The non-conductive layers that provide isolation between layers.

We build the stackup by declaring an ordered sequence of layers inside a class derived from Stackup. Here is a simple 2-layer example:


from jitx.stackup import Dielectric, Conductor, Stackup

class LPIMask(Dielectric):
    thickness = 0.1 # mm

class FR4Core(Dielectric):
    thickness = 1.5 # mm

class Cu1oz(Conductor):
    thickness = 0.035 # mm

class Basic_Two_Layer(Stackup):
    top_solder_mask = LPIMask()
    top_copper = Cu1oz()
    core = FR4Core()
    bottom_copper = Cu1oz()
    bottom_solder_mask = LPIMask()

This results in the following stackup representation in the physical design view of the board:

Basic_2Layer

Notice that we get default names Top and Bottom for the copper layers. To customize this, we can add names for each of the Cu1oz instantiations:

    ...
    top_copper = Cu1oz(name="Tippy Top")
    core = FR4Core()
    bottom_copper = Cu1oz(name="Bottom Barrel")
    ...

Basic_2Layer_Rename

Multi-layer Stackup#

In most modern designs, you will likely need more than 2 layers. If we were to build on the previous example to create a 4-layer board, it might look something like this:


class FR4Prepreg(Dielectric):
    thickness = 0.25 # mm

class Basic_Four_Layer(Stackup):
    top_solder_mask = LPIMask()
    top_copper = Cu1oz()
    pp1 = FR4Prepreg()
    gnd_copper = Cu1oz()
    core = FR4Core()
    vdd_copper = Cu1oz()
    pp2 = FR4Prepreg()
    bottom_copper = Cu1oz()
    bottom_solder_mask = LPIMask()

Similar structure to the 2-layer board with just more layers. Now if you imagine taking this to 6, 8, or more layers, this ends up being a lot of typing. We can take advantage of the symmetry present in most PCB stackups to reduce the amount of typing we need to do:

from jitx.stackup import Dielectric, Conductor, Symmetric

class Symmetric_Four_Layer(Symmetric):

  solder_mask = LPIMask()
  ext_copper = Cu1oz()
  pp = FR4Prepreg()
  inner_copper = Cu1oz()
  core = FR4Core()

This creates a 4-layer stackup with the same layer entries as Basic_Four_Layer.

Vias#

In JITX, each design can use a finite set of via types. We must define these via types in the Substrate class.

import jitx
from jitx.layerindex import Side
from jitx.via import Via, ViaType

class SampleSubstrate(jitx.Substrate):
  """Sample substrate using a two layer stackup and the sample fabrication
  constraints."""
  ...

  class MicroVia(Via):
    start_layer = 0
    stop_layer = 1
    diameter = 0.3
    hole_diameter = 0.1
    filled = True
    tented = Side.Top
    type = ViaType.LaserDrill

  class THVia(Via):
    start_layer = Side.Top
    stop_layer = Side.Bottom
    diameter = 0.45
    hole_diameter = 0.3
    type = ViaType.MechanicalDrill

In the above definitions, the Side.Top and Side.Bottom enumerated values resolve to 0 and -1 integer values, respectiviely.

To reference a via in code, we need to reference the substrate type by name:


class SomeCircuit(jitx.Circuit):
  def __init__(self):
    # Via Type
    viaType = SampleSubstrate.THVia
    # Instantiating the Via
    self.codePlacedVia = viaType().at(2.0, 3.0)