SI Usage Patterns#
The objects and methods discussed in Constraints are often considered the “primitives” of the JITX Signal Integrity (SI) system. These primitives must be strung together in a particular way to achieve the desired results. To make this process easier, we’ve developed various higher level constructs to support differential pairs and other more complex protocols.
Signal Constraint Base Class#
The SignalConstraint object is a base class for defining a modular set of constraints that apply to a particular protocol. This base class has a type parameter that defines the bundle type that a particular signal constraint applies to. This type parameter allows the language server to confirm that you, the user, are using this constraint correctly.
The easiest example to look at is the DiffPairConstraint. Here is an excerpt:
class DiffPairConstraint(SignalConstraint[DiffPair]):
"""Basic signal constraint for a differential pair signal topology.
...
"""
def __init__(
self,
skew: Toleranced,
loss: float,
structure: DifferentialRoutingStructure | None = None,
):
...
Notice that the parent class for DiffPairConstraint is SignalConstraint[DiffPair]. This is indicating that this type only applies to topologies of the type DiffPair.
The DiffPairConstraint object is a helpful abstraction for applying all of the necessary constraints for a typical differential pair. This includes:
skew- Constrains the time delay between thepandnsignals of theDiffPairbundle.loss- Maximum acceptable loss from endpoint to endpoint.structure- Applies a medium constraint for this diff pair.
This constraint doesn’t handle any inter-topology constraints - it only handles the constraints that apply to a differential pair. Deriving from this class would allow us to add additional constraints - such as a timing delay constraint - for custom applications.
Here is a simple example:
def __init__(self):
...
diff_rs = current.substrate.differential_routing_structure(100.0)
self.ck_cst = DiffPairConstraint(
skew=Toleranced(0, 1e-12), # Seconds
loss=12.0, # dB
structure=diff_rs
)
self.ck = src.CK + dst.CK
self.ck_cst.constrain(src.CK, dst.CK)
The main pieces are:
Create the
DiffPairConstraintobject and attach it to the circuit viaself.ck_cst = ...Connect the two ends with an ordinary net. The
+operator on twoDiffPairports connectsptopandnton.Apply the
self.ck_cstconstraint by naming the two ends of the path.Notice that
src.CKanddst.CKmust be the same port type,DiffPair.
As with any constraint, the measurement appears once the pair is routed, and any part the pair passes through needs a pin model. Signal Integrity Constraints explains both.
Custom SignalConstraint Example#
To create a constraint for a custom protocol, subclass SignalConstraint[T] where T is the port bundle type. Here is an example for a serial link with TX and RX differential pairs:
from dataclasses import dataclass
from jitx import current
from jitx.net import DiffPair, Port
from jitx.si import (
DiffPairConstraint,
DifferentialRoutingStructure,
SignalConstraint,
)
from jitx.toleranced import Toleranced
class SerialLink(Port):
"""Port bundle for a serial link with TX and RX differential pairs."""
tx = DiffPair()
rx = DiffPair()
@dataclass(frozen=True)
class SerialStandard:
"""Parameters for a serial link standard."""
skew: Toleranced = Toleranced(0, 5e-12) # max 5ps intra-pair skew
loss: float = 12.0 # max 12dB insertion loss
impedance: Toleranced = Toleranced(100, 10) # 100 ohm +/- 10%
class SerialLinkConstraint(SignalConstraint["SerialLink"]):
"""Applies differential pair constraints to both TX and RX paths."""
def __init__(
self,
standard: SerialStandard,
structure: DifferentialRoutingStructure | None = None,
):
super().__init__()
if structure is None:
structure = current.substrate.differential_routing_structure(
standard.impedance
)
self._dp_cst = DiffPairConstraint(
skew=standard.skew,
loss=standard.loss,
structure=structure,
)
def constrain(self, src: SerialLink, dst: SerialLink):
# TX-to-RX crossover: src transmits, dst receives (and vice versa)
self._dp_cst.constrain(src.tx, dst.rx)
self._dp_cst.constrain(dst.tx, src.rx)
Usage in a circuit:
import jitx
class MyCircuit(jitx.Circuit):
def __init__(self):
...
std = SerialStandard()
self.link_cst = SerialLinkConstraint(std)
# Connect each transmitter to the far receiver
self.links = [
self.fpga.serial.tx + self.phy.serial.rx,
self.phy.serial.tx + self.fpga.serial.rx,
]
# Apply constraints to both directions at once
self.link_cst.constrain(self.fpga.serial, self.phy.serial)
The SignalConstraint pattern allows you to encapsulate all the constraints for a protocol into a single reusable class. The type parameter (SerialLink in this case) enables the language server to verify that the constraint is applied to the correct port types.
More Complex Protocols#
This model extends to more complex protocols. For example, consider the RGMII Protocol Constraint. This constraint type is defined as an internal type to the RGMII bundle type. The RGMII.Standard.Constraint encapsulates the constraints for an RGMII interface and makes constraint application a single method call. This allows for highly re-usable constraints.
Like DiffPairConstraint, its constrain method builds its Topology objects from the ends you pass it, so it works on ordinary nets. We call constrain(src, dst) with the two ends throughout this page. The base class also has a constrain_topology helper, which calls constrain(src, dst) immediately. Before it does, it moves an end that sits at one end of a >> chain already in the circuit to the far end of that chain. On plain nets there is no chain to follow, so the two calls are equivalent.
The last page in this series, Daisy-Chain Topologies, covers signals that must visit their loads in a fixed order.