Bundles

A pcb-bundle is a collection of pins. Usually we create bundles to group pins that we want to keep together. For example we can create a bundle for an i2c bus with the following statement:

pcb-bundle i2c:
  port sda
  port scl

When to Use Bundles

Bundles are useful when you have any system which has multiple connections that you want to connect at once, such as:

  • connectors
  • busses
  • GPIO
  • protocols (SPI, I2C, etc.)

Tutorial

Now that our photodiodes are in order, we want to quickly bring up the rest of our design, which will be connected with interfaces like i2c and spi. We manage those connections in JITX using bundles.

We can use our new bundle to define i2c interfaces on components and wire them up. For example, let's say we want to connect a thermocouple amplifier to a microcontroller using an i2c bus.

pcb-component thermocouple-amplifier :
  manufacturer = "Microchip"
  mpn = "MCP9600"
  description = "Thermocouple EMF to Temperature Converter, ±1.5°C Maximum Accuracy"
  port i2c-node : i2c
  pin-properties :
    [pin:Ref      | pads:Int ...         | side:Dir     ]
    [GND      | 1, 3, 5, 6, 7, 9, 10, 13, 17, 18, 21 | Down  ]
    [Vin+     | 2               | Right   ]
    [Vin-     | 4               | Right   ]
    [Vdd      | 8               | Up      ]
    [Alert[1] | 11              | Left    ]
    [Alert[2] | 12              | Left    ]
    [Alert[3] | 14              | Left    ]
    [Alert[4] | 15              | Left    ]
    [ADDR     | 16              | Left    ]
    [i2c-node.scl  | 19         | Left    ]
    [i2c-node.sda  | 20         | Left    ]

  make-box-symbol()
  assign-landpattern(qfn-landpattern(0.65, 5.0, 20, 0.3, 0.4, [3.25 3.25]))

pcb-component microcontroller :
  port i2c-controller : i2c 
  pin-properties :
    [pin:Ref | pads:Int ... | side:Dir]
    [i2c-controller.scl   | 1 | Right]
    [i2c-controller.sda   | 2 | Right]
    for i in 3 to 14 do :
      [PA[i]   | i + 1 | Right]
    [vdd       | 15 | Up]
    [gnd       | 16 | Down]
  make-box-symbol()
  assign-landpattern(soic127p-landpattern(16))

pcb-module my-design :
  inst K : thermocouple-amplifier                                       
  inst mcu : microcontroller
  net (K.i2c-node, mcu.i2c-controller)      

  inst high-sensitivity-photodiode : amplified-photodiode(photodiode, 3.3, 500.0,  1.0e6)
  net (high-sensitivity-photodiode.output mcu.PA[4])

In our thermocouple-amplifier component, port i2c-node : i2c defines a port named i2c-node and sets the type to be i2c. i2c-node now has the two pins from the i2c bundle, sda and scl. We can then assign the pins of the i2c-node port to concrete land pattern pads.

We then add a similar port to the MCU named i2c-controller and assign it to concrete pads on the land pattern.

After we instantiate these components in our design, we can directly connect the i2c ports together using the net statement. JITX does the work to go into the bundle and connect scl to scl and sda to sda, so at the end of the day pin 19 on the light sensor gets connected to pin 1 on the micro controller.

This direct connection works with bundles of identical type, no matter how complex. It's an easy way to reduce errors in our design - when we connect i2c ports there is no way to accidentally connect sda to scl.

Here's a link to the complete tutorial code you can run to produce this design. Here is a portion of the schematic showing our i2c connection:

mcu

Complex bundles

pcb-bundles can include other bundles hierarchically, with as many layers of hierarchy as we need. Let's create a bundle for a DDR3 interface.

pcb-bundle diff-pair:
  port P
  port N

pcb-bundle ddr3-data :
  port ck : diff-pair
  port ldqs : diff-pair
  port udqs : diff-pair
  port ba : pin[3]
  port a : pin[15]
  port dq : pin[16]
  port cke
  port odt
  port nCS
  port nRAS
  port nCAS
  port nWE
  port dmu
  port dml
  port nRESET

pcb-bundle ddr3-power :
  port vddq
  port vssq
  port vdd
  port vss
  port vrefdq
  port vrefca
  port zq

pcb-bundle ddr3 :
  port data  : ddr3-data
  port power : ddr3-power

We first define a bundle for a differential pair, with pins N and P. We then use that bundle inside a new ddr3-data bundle for the DDR3 clock, and data strobes. The ddr3 data bundle also includes banks of address and data pins and the control pins. We then create a ddr3-power bundle with the supply and reference voltages. Finally we wrap ddr3-data and ddr3-power up into a single ddr3 bundle.

We can still access the low-level pins using the . operator: ddr3.data.ck.P gets us the positive pin of the DDR3 clock. (ddr3.data.clk would get us the differential pair). More importantly, we don't have to fuss with the low-level pins because we can net the top level bundles together:

net (FPGA.ddr3, SDRAM.ddr3)

This net statement connects a total of 56 pins from an FPGA to your SDRAM, making sure everything is connected correctly by recursively connecting all of the sub-bundles.

(You can create parametric bundles if you wanted to capture the full scope of all possible DDR3 connections (x4 x8, etc...).)