si_signal_constraint module#

A reusable protocol constraint: a SignalConstraint subclass, built and applied on ordinary nets.

ExampleLinkConstraint constrains an ExampleSerialLink bundle (a tx and an rx pair) between two ends. Its parameters live in a frozen dataclass, ExampleLinkStandard, and its __init__ calls super().__init__() first: the base initializer creates the collection that self.add(...) extends. It looks the differential structure up on the design’s substrate by impedance unless one is passed, and holds one DiffPairConstraint stored on self. constrain(src, dst) applies it across the crossover, each end’s tx to the other end’s rx.

The circuit calls self.link_cst.constrain(self.host.link, self.phy.link) on the chips’ own bundles, the true ends, and wires the parts between them as usual. The host’s transmit pair passes through ExampleDualCoupler, which carries a BridgingPinModel on each leg. The constraint does not change for the coupler; the pin models carry the path across it.

SignalConstraint.constrain_topology(src, dst) calls constrain(src, dst) at once and returns a context manager that yields (src, dst); it only moves an end along a >> chain already in the circuit, so on ordinary nets it adds nothing over constrain.

The standard’s values are illustrative, not from any specification. For protocols with several signal groups, compose: hold one constraint per group and call each from constrain, as jitxexamples.protocols.memory does for DDR4 and LPDDR4.

Checked with the design-issues query, run as a script from a project root with that project’s runtime running (jitx 4.6.0rc10, runtime 4.6.0-rc.16):

import jitx

from jitxexamples.patterns.si_signal_constraint import SerialLinkDesign


def main() -> None:
    with jitx.runtime as rt:
        report = rt.submit(SerialLinkDesign).issues(satisfied=True, wait=30)
        if not report.complete:
            print("report incomplete: raise wait and run again")
        for group in report.groups():
            print("met" if group.satisfied else "NOT MET", group.text)
            if len(group.issues) > 1:
                for issue in group:
                    print("   ", issue.text)


if __name__ == "__main__":
    main()

It printed:

met Loss: 0.389db - Target: 1.5db ± 1.5db from host.link.tx.p to phy.link.rx.p
met Loss: 0.389db - Target: 1.5db ± 1.5db from host.link.tx.n to phy.link.rx.n
met Loss: 0.356db ± 0db - Target: 1.5db ± 1.5db from phy.link.tx.p to host.link.rx.p
met Loss: 0.356db ± 0db - Target: 1.5db ± 1.5db from phy.link.tx.n to host.link.rx.n
met Skew: 0s - Target: ± 2ps (longer from host.link.tx.p to phy.link.rx.p, shorter from host.link.tx.n to phy.link.rx.n)
met Skew: 0s - Target: ± 2ps (longer from phy.link.tx.p to host.link.rx.p, shorter from phy.link.tx.n to host.link.rx.n)
met Satisfied

The transmit legs measure more loss than the receive legs, 0.389 dB against 0.356 dB. Their path adds the coupler’s 0.06 dB per leg but carries 1.5 mm less trace, the coupler’s pad span, so the difference is 0.033 dB, not the model’s 0.06 dB.

The last line is the Routing category’s group: every code route was made. It is not a constraint verdict.

class ExampleLinkStandard(skew=Toleranced(0, 2e-12, 2e-12), loss=3.0, impedance=100.0)[source]#

Bases: object

Limits for the fictional serial link. The values are illustrative, not taken from any standard.

Parameters:
skew: Toleranced = Toleranced(0, 2e-12, 2e-12)#

Intra-pair skew window, in seconds.

loss: float = 3.0#

Maximum insertion loss per leg, in dB.

impedance: float = 100.0#

Differential impedance target, in ohms, used to look up the routing structure on the design’s substrate.

Bases: Port

A full-duplex serial link: one transmit pair and one receive pair.

tx = DiffPair()#
rx = DiffPair()#
class ExampleLinkConstraint(standard, structure=None)[source]#

Bases: SignalConstraint[ExampleSerialLink]

Constrain a serial link between two ends: each end’s tx to the other end’s rx, with the standard’s skew, loss and structure.

Parameters:
constrain(src, dst)[source]#

Called to apply implementation specific constraints. Note that all constraints should be added to the container by calling add() on self. It is valid to just set member attributes as well, as with everything in JITX, but be aware that your constraint may be used multiple times with different topologies, and thus be careful with overwriting values (e.g. some constraints may use a single DiffPairConstraint to constrain all its diff pairs).

Return type:

None

Parameters:
class ExampleLinkChip(rx_first=False)[source]#

Bases: Component

Fictional chip with one serial link. Not a real product, and it has no pin models.

rx_first puts the receive pair on the lower two pads, so two chips facing each other line their crossing pairs up.

Parameters:

rx_first (bool)

manufacturer: str | None = 'Fictional'#

Manufacturer for this component.

mpn: str | None = 'EXAMPLE-LINK'#

Manufacturer part number for this component.

reference_designator_prefix: str | None = 'U'#

Reference designator prefix for this component. This will be used to generate a unique reference designator for each component of the same type.

Note

This field is a required data point for matching components between builds. If this value changes (including changing from unset to set), it will be treated as a new component, even if nothing else changes.

class CouplerPads[source]#

Bases: Landpattern

Two columns of two square pads, 1.5 mm apart: a left, b right.

class ExampleDualCoupler[source]#

Bases: Component

Fictional two-channel AC coupling part for one differential pair. Not a real product.

The signal passes through it from a to b on each leg, so each leg carries a BridgingPinModel: 3 ps and 0.06 dB, values that are illustrative.

manufacturer: str | None = 'Fictional'#

Manufacturer for this component.

mpn: str | None = 'EXAMPLE-COUPLER'#

Manufacturer part number for this component.

reference_designator_prefix: str | None = 'C'#

Reference designator prefix for this component. This will be used to generate a unique reference designator for each component of the same type.

Note

This field is a required data point for matching components between builds. If this value changes (including changing from unset to set), it will be treated as a new component, even if nothing else changes.

a = DiffPair()#
b = DiffPair()#
through = [BridgingPinModel(DiffPair().p, DiffPair().p, delay=3e-12, loss=0.06), BridgingPinModel(DiffPair().n, DiffPair().n, delay=3e-12, loss=0.06)]#

Bases: Circuit

Two chips joined by a serial link, constrained by one ExampleLinkConstraint; the transmit pair from host passes through a coupler.

class SerialLinkDesign(*args, **kwargs)[source]#

Bases: SampleDesign

The serial link on the sample board.

circuit: Circuit = SerialLink()#