jitx-interconnect-constraints/SKILL.md
This skill should be used when the user asks about "topology" (>> operator), "timing constraints", "skew matching", "insertion loss limits", "differential pairs", DiffPair bundles, protocol constraints (PCIe, USB, DisplayPort, RGMII, Ethernet, DDR), pin models (BridgingPinModel, TerminatingPinModel), "reference planes", "length matching", "impedance-controlled routing", or SignalConstraint definitions. Covers TopologyNet, Constrain, ConstrainDiffPair, ConstrainReferenceDifference, DiffPairConstraint, ReferencePlanes, and protocol-specific constraint patterns.
npx skillsauth add jitx-inc/jitx-skills jitx-interconnect-constraintsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Apply signal integrity constraints to signal topologies in JITX Python designs. This skill bridges jitx-circuit-builder (wiring with +) and jitx-substrate-modeler (routing structures) by teaching how to create SI-aware topologies with >> and constrain them.
Environment setup is handled by the base jitx skill. Ensure it has been invoked first.
# Core SI imports
from jitx.si import (
Constrain,
ConstrainDiffPair,
ConstrainReferenceDifference,
DiffPairConstraint,
SignalConstraint,
PinModel,
BridgingPinModel,
TerminatingPinModel,
ReferencePlanes,
RoutingStructure,
DifferentialRoutingStructure,
symmetric_routing_layers,
)
# Topology and ports
from jitx.net import DiffPair, Port, TopologyNet, Topology
# Common bundles
from jitx.common import LanePair
# Core framework
from jitx import Circuit, Net, current
from jitx.toleranced import Toleranced
from jitx.units import ohm
These DO NOT EXIST — never import:
jitx.topology, jitx.constraints.si, jitx.diffpair, jitx.signal,
jitx.si.TopologyNet (it is in jitx.net), jitxlib.si, jitxlib.constraints
Key locations:
DiffPair and TopologyNet are in jitx.netTopology is in jitx.net (re-exported from jitx.si)jitx.sijitx.si (also used in substrate definitions)For complete API signatures, see jitx.si API docs.
+ vs >>The most important concept for SI-constrained designs:
# + creates a Net — electrical connection only, NO SI awareness
# The routing engine can route this any way it wants
self.power_net = self.ic.VCC + self.cap.p1
# >> creates a TopologyNet — ordered signal path WITH SI awareness
# The routing engine knows the signal order and can apply constraints
self += self.driver.out >> self.receiver.inp
Storage rules (same as for + nets):
# CORRECT — store on self with +=
self += self.src >> self.dst
# CORRECT — store as named attribute
self.my_topo = self.src >> self.dst
# CORRECT — store in a list on self
self.topos = [self.src >> self.dst]
# WRONG — topology created but not stored, silently dropped!
self.src >> self.dst # BAD — lost!
When to use which:
+ — Power rails, ground connections, control signals without SI requirements>> — Any signal with timing, skew, loss, or impedance constraints (clocks, data buses, differential pairs, RF signals)>># >> defines the ordered signal path
self += self.driver.out >> self.receiver.inp
# Topology(begin, end) is a DESCRIPTOR — it identifies the path, does not create it
topo = Topology(self.driver.out, self.receiver.inp)
# Get routing structure from substrate (defined in jitx-substrate-modeler)
rs50 = current.substrate.routing_structure(50.0)
# Apply with ReferencePlanes context
self.GND = Net(name="GND")
with ReferencePlanes(self.GND):
self.cst = Constrain(topo).insertion_loss(1.0).structure(rs50)
Constrain# Insertion loss — max dB
Constrain(topo).insertion_loss(3.0)
# Timing — max propagation delay (seconds)
Constrain(topo).timing(500e-12)
# Timing — min and max delay window
Constrain(topo).timing(high=500e-12, low=100e-12)
# Routing structure assignment
Constrain(topo).structure(rs50)
# Chaining (all constraints applied together)
self.cst = (
Constrain(topo)
.insertion_loss(1.0)
.timing(500e-12)
.structure(rs50)
)
# Constrain accepts a sequence of Topology objects
topos = [Topology(s, d) for s, d in zip(src_pins, dst_pins)]
self.cst = Constrain(topos).insertion_loss(3.0).structure(rs50)
from jitx.net import DiffPair
class MyComponent(jitx.Component):
data = DiffPair() # has .p (positive) and .n (negative) sub-ports
# Create diff pair topology — >> connects both .p and .n
self += self.src.data >> self.dst.data
# Identify and constrain
topo = Topology(self.src.data, self.dst.data)
drs100 = current.substrate.differential_routing_structure(100.0)
self.dp_cst = (
ConstrainDiffPair(topo)
.timing_difference(5e-12) # max 5ps intra-pair skew (P-to-N)
.insertion_loss(3.0) # max loss per line
.structure(drs100) # 100 ohm differential routing structure
)
For applying the same constraint to multiple differential pairs:
from jitx.si import DiffPairConstraint
# Create once, use many times
dp_cst = DiffPairConstraint(
skew=Toleranced(0, 5e-12), # max 5ps intra-pair skew
loss=3.0, # max 3dB insertion loss
structure=drs100, # routing structure
)
# Apply to each diff pair
self += self.src.tx >> self.dst.rx
dp_cst.constrain(self.src.tx, self.dst.rx)
self += self.src.clk >> self.dst.clk
dp_cst.constrain(self.src.clk, self.dst.clk)
from jitx.common import LanePair
class MySerialPort(Port):
lane = LanePair() # has .TX (DiffPair) and .RX (DiffPair)
Match multiple data signals to a reference clock or strobe:
# Create topologies for clock and data
self += self.src.clk >> self.dst.clk
clk_topo = Topology(self.src.clk, self.dst.clk)
data_topos = []
for i in range(8):
self += self.src.data[i] >> self.dst.data[i]
data_topos.append(Topology(self.src.data[i], self.dst.data[i]))
# Constrain all data signals relative to clock
# "All data signals must arrive within +/- 11ps of the clock"
self.bus_skew = (
ConstrainReferenceDifference(
guide=clk_topo, # reference signal
topologies=data_topos, # signals to match
).timing_difference(Toleranced(0, 11e-12))
)
Key concept: The guide is the reference signal. All topologies are constrained to arrive within the specified timing window relative to the guide.
This pattern is used for:
Pin models characterize signal propagation through components for SI analysis.
Applied to active component pins where the signal terminates:
class MyIC(jitx.Component):
TX_P = Port()
TX_N = Port()
# Silicon-to-pad delay and loss
pm_txp = TerminatingPinModel(TX_P, delay=15e-12, loss=0.2)
pm_txn = TerminatingPinModel(TX_N, delay=15e-12, loss=0.2)
Applied to passive components that a signal passes through (AC coupling caps, series resistors, common-mode chokes):
class BlockingCapacitor(jitx.Component):
p1 = Port()
p2 = Port()
# Signal passes through with this delay and loss
pin_model = BridgingPinModel(p1, p2, delay=6e-12, loss=0.5)
Define a subcircuit with ports that a topology chains through:
class ACCoupler(Circuit):
"""Single-ended AC coupling subcircuit."""
A = Port()
B = Port()
def __init__(self):
self.cap = BlockingCapacitor() # has BridgingPinModel
self += self.A >> self.cap.p1
self += self.cap.p2 >> self.B
Differential pair version:
class DiffPairCoupler(Circuit):
"""AC coupling for a differential pair."""
A = DiffPair()
B = DiffPair()
def __init__(self, capacitance=100e-9):
self.cap_p = BlockingCapacitor(capacitance)
self.cap_n = BlockingCapacitor(capacitance)
# Topology chains through the caps
self.topo_p1 = self.A.p >> self.cap_p.p1
self.topo_p2 = self.cap_p.p2 >> self.B.p
self.topo_n1 = self.A.n >> self.cap_n.p1
self.topo_n2 = self.cap_n.p2 >> self.B.n
The outer circuit connects to the subcircuit's ports with >>. The topology chains through the subcircuit's internal >> connections automatically:
class MyDesign(Circuit):
def __init__(self):
self.driver = MyDriver()
self.recv = MyReceiver()
self.coupler = ACCoupler()
# Topology chains: driver -> coupler.A -> [internal] -> coupler.B -> recv
self += self.driver.out >> self.coupler.A
self += self.coupler.B >> self.recv.inp
# Constrain the full end-to-end path
topo = Topology(self.driver.out, self.recv.inp)
with ReferencePlanes(self.GND):
self.cst = Constrain(topo).insertion_loss(3.0)
When a topology passes through a component that does not have an embedded BridgingPinModel, define one at the circuit level:
# Signal path: driver → cap → receiver
self += self.driver.out >> self.cap.p1
self += self.cap.p2 >> self.receiver.inp
# Add BridgingPinModel so constraint engine tracks delay/loss through the cap
self.bridge = BridgingPinModel(self.cap.p1, self.cap.p2, delay=6e-12, loss=0.5)
# Constraint sees the full path including cap delay/loss
topo = Topology(self.driver.out, self.receiver.inp)
self.cst = Constrain(topo).insertion_loss(3.0)
Reference planes specify which net serves as the return path for each routing layer.
self.GND = Net(name="GND")
# All reference layers use GND
with ReferencePlanes(self.GND):
self.cst = Constrain(topo).structure(rs50)
# Different reference nets per layer
with ReferencePlanes({0: self.GND, 1: self.GND, 2: self.PWR}):
self.cst = Constrain(topo).structure(rs50)
self.cst = Constrain(topo).structure(rs50, ref_layers={0: self.GND})
Important: If a routing structure has .reference() definitions (from substrate-modeler), you MUST provide ReferencePlanes. Without them, the constraint will error at build time.
For reusable protocol-specific constraint bundles, subclass SignalConstraint[T]:
from dataclasses import dataclass
from jitx.si import SignalConstraint, DiffPairConstraint
from jitx.net import DiffPair, Port
from jitx.toleranced import Toleranced
from jitx import current
# 1. Define port bundle
class MySerialLink(Port):
tx = DiffPair()
rx = DiffPair()
# 2. Define standard parameters
@dataclass(frozen=True)
class MyStandard:
skew: Toleranced = Toleranced(0, 1e-12)
loss: float = 12.0
impedance: Toleranced = Toleranced(100, 10)
# 3. Define constraint class
class MyConstraint(SignalConstraint["MySerialLink"]):
def __init__(self, standard: MyStandard,
structure: DifferentialRoutingStructure | None = None):
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: MySerialLink, dst: MySerialLink):
# Crossover: TX→RX, RX→TX
self._dp_cst.constrain(src.tx, dst.rx)
self._dp_cst.constrain(dst.tx, src.rx)
The constrain_topology method handles topology creation and constraint application together. Use it when inserting components (like AC coupling caps) into the topology:
class MyDesignCircuit(Circuit):
def __init__(self):
self.fpga = FPGA()
self.phy = PHY()
std = MyStandard()
cst = MyConstraint(std)
# constrain_topology creates proxies and applies constraints
with cst.constrain_topology(
self.fpga.serial, self.phy.serial
) as (src, dst):
# Insert AC coupling caps in the TX path
self.coupler = DiffPairCoupler()
self.topos = [
src.tx >> self.coupler.A,
self.coupler.B >> dst.rx,
dst.tx >> src.rx, # RX path direct
]
For complex protocols with multiple signal groups, compose constraints:
class DDR4Constraint(SignalConstraint["DDR4"]):
def __init__(self, standard):
self._data_cst = DDR4DataConstraint(standard)
self._acc_cst = DDR4AccConstraint(standard)
def constrain(self, src: DDR4, dst: DDR4):
self._data_cst.constrain(src.data, dst.data)
self._acc_cst.constrain(src.acc, dst.acc)
# Cross-group: CK-to-DQS timing
...
See jitx_protocols_ext for complete protocol implementations including PCIe, SATA, SFP, DDR4, LPDDR4, LPDDR5, and GDDR7.
JITX provides built-in protocol constraints in jitxlib. Check your installed version for available protocols:
grep -r "class.*Constraint.*SignalConstraint" .venv/lib/python*/site-packages/jitxlib/protocols/
Common built-in protocols:
| Protocol | Import Path | Port Bundle |
|----------|-------------|-------------|
| USB | jitxlib.protocols.usb | USB2, USB3 |
| DisplayPort | jitxlib.protocols.displayport | DisplayPort |
| RGMII | jitxlib.protocols.ethernet.mii.rgmii | RGMII |
| 100Base-TX | jitxlib.protocols.ethernet.mdi.mdi100base_tx | MDI100BaseTX |
| 1000Base-T | jitxlib.protocols.ethernet.mdi.mdi1000base_t | MDI1000BaseT |
For protocol-specific timing parameters (skew, loss, impedance per standard version), see references/protocol-standards.md.
Via structures let the constraint engine track signal transitions between layers. They are Circuit subclasses with sig_in, sig_out, and COMMON ports.
from jitxlib.via_structures import SingleViaStructure, PolarViaGroundCage, SimpleAntiPad
These DO NOT EXIST — never import:
jitx.via_structures, jitx.si.ViaStructure, jitxlib.vias
from jitxlib.via_structures import SingleViaStructure
# Bare via — no ground cage or antipads
self.via = SingleViaStructure(
MySubstrate.MicroVia,
ground_cages=[],
antipads=[],
insertion_points=[],
)
self.GND += self.via.COMMON
# Chain topology through via
self += self.driver.out >> self.via.sig_in
self += self.via.sig_out >> self.receiver.inp
# Constrain full path (via is transparent to the constraint)
topo = Topology(self.driver.out, self.receiver.inp)
rs50 = current.substrate.routing_structure(50.0)
with ReferencePlanes(self.GND):
self.cst = Constrain(topo).structure(rs50).insertion_loss(3.0)
For controlled-impedance via transitions, add a PolarViaGroundCage and optional SimpleAntiPad:
from jitxlib.via_structures import SingleViaStructure, PolarViaGroundCage, SimpleAntiPad
self.rf_via = SingleViaStructure(
MySubstrate.MicroviaL1L2,
ground_cages=[
PolarViaGroundCage(
MySubstrate.BlindViaL1L4, # Via type for cage
count=12, # Number of ground vias
radius=0.6, # Distance from signal via (mm)
)
],
antipads=[SimpleAntiPad(shape, layers)],
insertion_points=[],
)
self.GND += self.rf_via.COMMON
# Same topology pattern as bare via
self += self.driver.out >> self.rf_via.sig_in
self += self.rf_via.sig_out >> self.receiver.inp
topo = Topology(self.driver.out, self.receiver.inp)
rs50 = current.substrate.routing_structure(50.0)
with ReferencePlanes(self.GND):
self.cst = Constrain(topo).insertion_loss(1.0).structure(rs50)
sig_in / sig_out — Signal enters and exits the via structure. Chain these with >> into your topology.COMMON — Ground connection for the cage vias. Always connect to your GND net with +=.insertion_points=[] — Required parameter. Pass empty list unless using custom insertion point geometry..at(x, y, rotate=angle) for placement.DifferentialViaStructure — Same pattern but with DiffPair ports (sig_in.p, sig_in.n, etc.) and a pitch parameter for P/N spacing.For RoutingStructure, DifferentialRoutingStructure, and substrate via definitions, see the jitx-substrate-modeler skill.
# WRONG: Using + for SI-constrained signals (no topology created)
self.data_net = self.src.data + self.dst.data
# CORRECT: Use >> for topology
self += self.src.data >> self.dst.data
# WRONG: Topology not stored (silently dropped)
self.src.data >> self.dst.data
# CORRECT: Store with += or named attribute
self += self.src.data >> self.dst.data
# WRONG: Using = for connections (Python assignment, no electrical connection)
self.src.data = self.dst.data
# WRONG: Constrain without ReferencePlanes when routing structure needs it
Constrain(topo).structure(rs50_with_reference)
# CORRECT: Wrap in ReferencePlanes
with ReferencePlanes(self.GND):
Constrain(topo).structure(rs50_with_reference)
# WRONG: Creating Topology before creating the >> connection
topo = Topology(self.src, self.dst) # Path doesn't exist yet!
self += self.src >> self.dst
# CORRECT: Create >> first, then identify with Topology
self += self.src >> self.dst
topo = Topology(self.src, self.dst)
pyright path/to/circuit.py
python -m jitx build <module.path.DesignClass>
Don't run parallel JITX builds against the same project — sequence them. See jitx/SKILL.md "Build Safety".
SI constraint violations appear in the Issues List under "Unsatisfied Signal Constraints" in the JITX UI.
For complete class definitions, all parameters, and method signatures:
ruff format path/to/circuit.py
testing
This skill should be used when the user asks to "create a substrate", "define a stackup", "add via definitions", "set up routing structures", "configure impedance control", "define differential pairs", "set fabrication rules", "ring a shape with fence vias", "fence a pour outline", "fence an antipad", or "model a PCB layer structure". Ask the user which fabrication house they are targeting — if they confirm JLCPCB, predefined substrates from jitxlib.jlcpcb (JLC04161H_1080, JLC04161H_7628, JLC06161H_7628) are available with 4/6-layer FR-4, 50/90/100 ohm routing structures, vias, and fab rules. Otherwise, create a custom substrate. Covers Stackup, Symmetric, Conductor, Dielectric, Via (laser, mechanical, backdrilled, blind, buried, stacked), RoutingStructure, DifferentialRoutingStructure, NeckDown, via fencing along traces, fenced pour outlines (Tag + fence_via rule paired with a Pour + optional same-shape KeepOut — covers antipads, RF cavities, BGA breakouts), geometry, reference planes, and FabricationConstraints.
development
This skill should be used when the user asks to "wire up", "connect", "build a circuit", create an "application circuit", work with passives (resistors, capacitors), set up power connections, "add pours", or "place components". Covers the Circuit class, net operators, passive queries, voltage dividers, and copper geometry. For provide/require pin assignment patterns, use jitx-pin-assignment instead.
development
Base skill for JITX hardware design workflow. Use for JITX Python projects, PCB design, circuit creation, and build commands. Use when the user asks to "build my JITX design", "set up JITX environment", "create a circuit", "build a complete board", "design a PCB from requirements", or "create a full JITX project". For multi-component designs (3+ components, substrate, circuits), invoke the Project Builder workflow for orchestrated parallel agent execution with quality gates. CRITICAL - If user asks to create/model/generate a component or mentions a part number (NE555, LM1117, RP2040, etc.), immediately invoke jitx-component-modeler subskill. If user asks to create a substrate, stackup, via definitions, or routing structures, invoke jitx-substrate-modeler subskill.
testing
This skill should be used when the user asks about "provide/require patterns", "@provide.one_of" or "@provide.subset_of" decorators, "programmatic Provide", "pin muxing" (MCU peripherals on shared pins), "DiffPair P/N polarity swapping", "PCIe lane swapping" or width variants, "DDR4 byte/bit swapping", "LPDDR5 channel swapping", "hierarchical provider composition", topology (>>) on pin-assigned ports, ConstrainDiffPair or ConstrainReferenceDifference with provide/require, or "flexible pin mapping" for FPGAs and MCUs. Covers provide, Provide, require, all_of, one_of, subset_of, and protocol-specific pin flexibility with SI constraints.