Daisy-Chain Topologies#
Most signal integrity constraints need nothing more than the two ends of a path, joined by ordinary nets, as Signal Integrity Constraints describes. A daisy chain is different. One driver feeds several loads in turn, and the order in which the signal visits them is part of the design. The usual example is a DDR flyby clock or command/address bus, where the controller’s signal passes each DRAM in sequence. For a chain like that, we write the order down with a TopologyNet, built with the >> operator.
What is a Topology?#
In JITX, a “Route Topology” (usually shortened to topology) is an ordered set of connections between component pads. You can think of the topology as a tree where the edges are the “Topology Segments” and the nodes are physical component pads. Each node can attach to 1 or more segments.
Note that we said ordered, but not directed. There is no connotation of input or output, transmitter or receiver, applied to the endpoints or nodes of the topology tree.
The “Route Topology” tree is acyclic by definition. If we have nodes A, B, C, and D, then we can construct a topology A >> B >> D or we can construct A >> C >> D. But we can’t construct a topology that routes A >> D by two parallel B and C routes in the same topology.
For example:

In other words, there can only be one path from A to D.
Each segment of a topology is a TopologyNet, and the >> operator is the easiest way to make one. We often call these “topo-nets”. Don’t confuse them with Topology, the object a constraint takes: Topology(begin, end) builds nothing and only names the two ends of a path, whether the path runs over topo-nets or plain nets. Each node of a route topology must resolve to a physical pad: a component port, or a circuit port that forwards to exactly one component port (see Circuit Ports in a Chain). A part the signal passes through, like a series resistor, needs a BridgingPinModel, just as it does on a plain net.
A Flyby Address Line#
Here is one address line from a memory controller, through two DRAMs in turn. The parts are fictional stand-ins with one address pin each:
class AddressChain(jitx.Circuit):
def __init__(self):
...
self.a0 = self.ctrl.a0 >> self.dram0.a0 >> self.dram1.a0
self.a0_timing = Constrain(
[Topology(self.ctrl.a0, self.dram0.a0), Topology(self.ctrl.a0, self.dram1.a0)]
).timing(1e-9)
self.a0_rs = Constrain(Topology(self.ctrl.a0, self.dram1.a0)).structure(RS_50)
The >> chain is the connection itself, so there is no separate + net. The constraints are the same Constrain and Topology objects used everywhere else: a timing limit from the controller to each DRAM, and one routing structure over the whole chain.
Before the chain is routed, the design issues have nothing to measure, and they name each unrouted segment:
Signal Constraint:
- Unconnected Topology on Path: ctrl.a0, dram0.a0
- 2 issues: Unconnected Topology on Path: ctrl.a0, dram0.a0
- Unconnected Topology on Path: ctrl.a0, dram0.a0
- Unconnected Topology on Path: dram0.a0, dram1.a0
Routing:
- Satisfied
Once the segments are routed, in the board view or with code Routes from ctrl.a0 to dram0.a0 and from dram0.a0 to dram1.a0, the design issues, read with satisfied=True as in Checking Constraints with Design Issues, measure both:
Signal Constraint:
- Time: 78.396ps - Target: 500ps ± 500ps from ctrl.a0 to dram0.a0
- Time: 156.793ps - Target: 500ps ± 500ps from ctrl.a0 to dram1.a0
Routing:
- Satisfied
Notice that the path to dram1.a0 runs through dram0.a0, so its delay is the sum of both segments.
Notice also where the routing structure went. If we put .structure(RS_50) on a0_timing as well, both of its topologies claim the shared ctrl.a0 to dram0.a0 segment, on top of a0_rs, and the report adds:
Topology:
- Overlapping routing structures along path from ctrl.a0 to dram1.a0 (2 overlapped)
Apply the structure once, over the longest path of the chain.
The same three pads on a plain net, Net([ctrl.a0, dram0.a0, dram1.a0]), also measure once they are routed in this order, because the routes fix the order. What >> adds is the order itself, written into the design before anything is routed. In the board view, that order decides which connections you can make: Order Matters shows a connection that would break the chain being refused. The recordings in that section were made with an earlier release and have not been checked again since. That order is why >> belongs to daisy chains, and why a point-to-point signal doesn’t need it.
Branches Off a Chain#
A chain can also have a branch that is joined with a plain Net connection instead of a TopologyNet.

In this example, notice that the segment from U1.TX to U2.RX is part of the chain, built with the TopologyNet operator >>. In short hand, we typically call this a topo-net.
The segment from U1.TX to U3.RX only has a Net connection, typically created with + operator. This “appendage” net is not part of the chain, so the chain’s order says nothing about it.
The design issues treat the appendage differently. Once it is routed, a constraint from U1.TX to U3.RX measures like any other. Before it is routed, the report says No path for signal constraint for that path, where an unrouted topo-net segment would say Unconnected Topology. So a constraint on the appendage still works; what the appendage gives up is the chain’s order.
Circuit Topology Realization#
Realization is the process by which the abstract information of the circuit is formed into physical copper geometry in the board. This process has to take into account various clearance, width, and other requirements. It is where the rubber meets the road. The routing engine can either construct a circuit at this point - or it can’t and fails.
In the context of the SI topology, realization is affected by the choice of nodes and segments. One way that it affects realization is by the means of “Stubs.”
Consider this circuit definition:
class TopLevel(jitx.Circuit):
A = Resistor(resistance=10.0)
B = Resistor(resistance=10.0)
C = Resistor(resistance=10.0)
topos = [
A.p1 >> B.p1 >> C.p1,
]
This is a simple topology consisting of two segments and three nodes.
Here is a reasonable expectation for how this might realize:

In this rendering:
A=>U1B=>U2C=>U3
I’ve placed B on the bottom layer and hence it requires a via. The choice of how to place the via is where things get tricky. The order of operations matters.
Order Matters#
When we drag out the via for the connection, we can do so by dragging the via from A.p1 or from B.p1 (we could also do C but in this case, it is symmetric with A). In the following video, I drag the via from B.p1
In this case, the “Stub” is the copper from the via to B.p1. Because the via was dragged out from a pad on B, the via is associated with B. If we were to move the B component, the via would move with it. The routing engine considers the via an extension of the B component.
That is what allows us to select A.p1, the via, and C.p1 and successfully route the signal topology. The via is effectively B.p1.
Now - what if we dragged the via out from A.p1 instead of B.p1:
On the surface, it would seem like these are exactly the same. But there is an important difference. Because the via was dragged out from A.p1, the via is now associated with the A component. If we moved A, the via would move with it. For the connection between A.p1 and B.p1, this association has no effect. We can still connect B.p1 to the via. But the “Stub” has moved - the stub is now A.p1 to the via.
This presents problems for connecting to C.p1. Because the via is a stub for A.p1, we can’t connect the via directly to C.p1. This would violate the constraints of the topology. The runtime prevents this connection because it is technically a circuit inconsistent with what is described in code. Hence the blue “There are no valid connections…” banner.
This phenomenon will apply equally to control points as well.
Circuit Ports in a Chain#
In JITX we can use Circuit-derived class definitions to create sub-circuits and reusable designs. The ports in a Component map to a physical pad in a land pattern. The ports of a Circuit don’t necessarily have a 1:1 mapping from port to pad. If a Circuit port maps to 3 physical pads in the circuit definition, then should the topology create a branching tree? Should the topology connect to them serially?
When a Circuit port connects to one and only one component port, the Circuit port “forwards” to the component port, and the chain passes straight through it to that pad. A circuit port that fans out to two pads is ambiguous:

To keep the Y shape, put a physical pad at the junction, such as a test point:
class TestPoint(jitx.Component):
tp = jitx.Port()
...
class D(jitx.Circuit):
p = jitx.Port()
TP = TestPoint()
R1 = Resistor(resistance=10.0)
R2 = Resistor(resistance=4.7)
# This creates a 'Y' shaped topology
# with the test point at the junction
# as a physical pad.
topos = [
p >> TP.tp,
TP.tp >> R1.p1,
TP.tp >> R2.p1
]

By inserting the test point component, we can control the 1:1 relationship and still achieve the Y structure for the topology. The circuit ports effectively dissolve away as the topology is realized in the PCB.