SI Usage Patterns

The statements discussed in Topologies and 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 in the JITX Standard Library (JSL). This document will provide an overview of these constructs. Feel free to review the source code to understand more of the details.

topo-net

Readers of the Topologies treatment will immediately notice the duplicative nature of the net and topology-segment statements. To remedy this and improve the utility of these statements, JSL provides the topo-net function. This function provides a more flexible method defining combination net and topology-segment statements.


  inst MCU : MCU-with-USB
  inst conn : USB-Connector
  
  topo-net( MCU.usb.bus => conn.usb.bus )

In this simple example, we just connect two endpoints together. The endpoints can be any type of port, SinglePin, Bundle, or PortArray. The only requirement is that all listed endpoints are of the same type. These endpoints can be module ports, component ports, or require ports.

We can also string together multiple components in sequence:

  inst MCU : MCU-with-USB
  inst ESD : TPD4E05
  inst conn : USB-Connector

  topo-net( MCU.usb.bus => ESD.D[0].A => ESD.D[0].B => conn.usb.bus )

Here the TPD4E05 is a pass-through style ESD protector.

topo-pair

The topo-net statement works well for any type of port, but with differential pairs, we often want to support more complex operations. In particular it is common to use AC blocking caps in a topology sequence. If we were to naively attempt this, it would be quite verbose:


  inst MCU : MCU-with-USB
  inst conn : USB-Connector

  inst bcap : ceramic-cap(470.0e-9)[2]

  topo-net(MCU.usb.bus.P => bcap[0].p[1])
  topo-net(bcap[0].p[2], conn.usb.bus.P)

  topo-net(MCU.usb.bus.N => bcap[1].p[1])
  topo-net(bcap[1].p[2], conn.usb.bus.N)

We can do better if we leverage the topo-pair construct and some coupler modules like dp-coupler


  inst MCU : MCU-with-USB
  inst conn : USB-Connector

  val block-cap = ceramic-cap(470.0e-9)
  inst tx-bcap : dp-coupler(block-cap)

  topo-pair(MCU.bus.lane.TX => tx-bcap => conn.bus.lane.TX)

Notice that this method allows us to construct the topology as a natural sequence of elements. This topo-pair function can accept either endpoint ports like topo-net or it can accept components like dp-coupler that provide a dual-pair supports statement.

Signal Ends

Often times when constructing a module sub-circuit, the actual endpoint of a topology is buried inside that sub-circuit (or recursively within a sub-sub-circuit). This makes it difficult to apply the routing structure to the terminal endpoints. This manifests in a section of a trace or differential pair that lacks a routing structure assignment. For example, consider the following:


pcb-differential-routing-structure diff-100:
  ...

pcb-module MCU-with-Blocking-Caps:
  port data : diff-pair

  inst ctl : MCU-with-USB

  val block-cap = ceramic-cap(470.0e-9)
  inst tx-bcap : dp-coupler(block-cap)

  ; Connects to the output data pair for the module
  topo-pair(ctl.bus.lane.TX => tx-bcap => data)

pcb-module top-level: 

  inst MCU : MCU-with-Blocking-Caps
  inst conn : USB-Connector
  
  val rte = MCU.data => conn.usb.lane.TX
  topo-net(MCU.data => conn.usb.lane.TX)
  diff-structure(diff-100, rte)

In this case, the differential routing structure diff-100 is applied to the topology from one side of tx-bcap to the conn component. It does not get applies to the topology segment from ctl to the other side of tx-bcap. This is analogous to the problem described here.

One method of working around this is to use the signal-ends approach defined in JSL.


pcb-differential-routing-structure diff-100:
  ...

pcb-module MCU-with-Blocking-Caps:
  port data : diff-pair

  inst ctl : MCU-with-USB

  val block-cap = ceramic-cap(470.0e-9)
  inst tx-bcap : dp-coupler(block-cap)

  ; Connects to the output data pair for the module
  topo-pair(ctl.bus.lane.TX => tx-bcap => data)

  set-signal-end(data, ctl.bus.lane.TX)

pcb-module top-level: 

  inst MCU : MCU-with-Blocking-Caps
  inst conn : USB-Connector
  
  val rte = MCU.data => conn.usb.lane.TX
  topo-net(MCU.data => conn.usb.lane.TX)

  val ep-rte = find-signal-end(MCU.data) => conn.usb.lane.TX
  diff-structure(diff-100, ep-rte)

The idea is that the module author recognizes that this hidden topology endpoint problem exists and uses the set-signal-end function to expose the true endpoint. This set-signal-end approach may be recursive in the face of multiple layers of hierarchical sub-modules. The find-signal-end function traverses these recursive definitions to find the final endpoint for application of the routing structure in a complete way.

SI-Constraints

Find an expressive and modular way to create topologies and constraints is a difficult problem. The SI-Constraint framework is intended to provide a mechanism that provides both:

  1. Reusable constructs for common applications like differential pairs, lanes (TX/RX), etc.
  2. The flexibility to add or remove components from the topology easily.

Here is a taste - a more complete example will be coming soon:


  val usb3-constraint = USB-Constraint(proto = USB3, route-struct = diff-90)
  val lane-constraint = LaneConstraint(usb3-constraint)

  within [src, dst] = constrain-topology(
    usb3-mux.usb-c.lane[i] => usb-c.conn.bus.lane[i],
    lane-constraint
    ):
    ; Receive
    require prot-rx : dual-pair from ss-esd
    topo-pair(src.RX => prot-rx => dst.RX)

    ; Transmit
    inst tx-bcap : dp-coupler(block-cap)
    require prot-tx : dual-pair from ss-esd
    topo-pair(src.TX => tx-bcap => prot-tx => dst.TX)

The conceptual idea is that the constraint definition for many protocols is typically the same in every application. In PCIe, USB, HDMI, etc there is typically a sequence of structure, timing-difference, and insertion-loss statements that are the same in almost every application. This framework encapsulates that concept in an interface type SI-Constraint.

In the example above, both USB-Constraint and LaneConstraint are SI-Constraint implementations. The USB-Constraint is more concrete and encodes the expected constraints of a USB superspeed bus. The LaneConstraint implementation is more abstract. A lane is a bundle consisting of a Transmit signal and a Receive signal. Commonly, these signals are defined as diff-pair bundles. The lane constraint will apply the passed USB-Constraint to both the Tx and Rx signals. This demonstrates the composability of the SI-Constraints framework.

A within clause is used to provide the user the opportunity to modify the topology before the constraints are applied. In this example, we insert blocking capacitors and/or ESD protection between the mux and the connector. The RX part of the lane gets only an ESD protector while the TX part of the lane gets both ESD protection and a blocking capacitor configuration.