Bundle Statements¶
A pcb-bundle
statement defines a hierarchical group of ports, used to
organize and structure a JITX design.
Bundles can include ports or even other bundles.
Using bundles allows us to structure our designs:
we can connect bundles with net
statements, or use
supports
and require
statements with bundles for automated pin assignment.
Signature¶
pcb-bundle bundle-name (arg1:<Type1>, ...) :
name = <String|False>
description = <String|False>
; Any number of port statements:
port <REF>
port <REF> : <PortType>
The expression bundle-name
identifies this bundle definition.
The argument list (arg1:<Type1>, ...)
is optional, providing a way
to parameterize bundles by arbitrary JITX values.
We will refer to a parameterized bundle as a bundle generator.
Statements:
* name
- This is an optional statement setting the name of the bundle
* description
- This is an optional statement providing a description of the bundle
* port
- A bundle can contain an arbitrary number of port
statements.
There are two ways to construct a port
:
- port <REF>
is a shorthand for constructing a single-pin port.
- port <REF> : <PortType>
allows the construction of arbitrary ports -- either
single-pins, bundle instances, or arrays of ports. For more information, see
the port
statement.
Usage¶
Bundles are the key to providing structured interfaces between different components and modules. The most straightforward use of bundles is to provide a named and reusable set of ports:
pcb-bundle i2c:
port sda
port scl
This allows us to easily instantiate an i2c
bundle with a port
statement:
pcb-component my-component :
; An instance of the i2c bundle
port my-i2c-port : i2c
Importantly, bundles can be parametric, and can reference other bundles hierarchically. Here is a definition for a variable-size bank of lvds ports:
; A bundle representing a differential pair
pcb-bundle diff-pair :
port N
port P
; lvds-bank is a hierarchical bundle definition
; It includes a clock differential pair `clk`
; and a port array `data` of multiple differential pairs
pcb-bundle lvds-bank (width:Int) :
port clk : diff-pair
port data : diff-pair[width]
Our lvds-bank
bundle is parameterized by the size of the data bus.
This concept is reminiscent of record
types in VHDL, or
struct
in SystemVerilog.
Another use of parametric bundles is to represent optional ports. The definition of uart in the JITX standard library does just this in order to provide a flexible definition of a UART bundle.
; An enumeration of possible UART ports
public pcb-enum jsl/bundles/comms/UARTPins :
UART-DTR
UART-CTS
UART-DCD
UART-RI
UART-DST
UART-RTS
UART-CK
UART-DE
UART-RX
UART-TX
; A parameterized bundle definition
pcb-bundle uart (pins:Collection<UARTPins>):
name = "UART"
for p in pins do :
switch(p) :
UART-DTR : make-pin(`dtr)
UART-CTS : make-pin(`cts)
UART-DCD : make-pin(`dcd)
UART-RI : make-pin(`ri)
UART-DST : make-pin(`dst)
UART-RTS : make-pin(`rts)
UART-CK : make-pin(`ck)
UART-DE : make-pin(`de)
UART-RX : make-pin(`rx)
UART-TX : make-pin(`tx)
We can then use this parameterized bundle to define a minimal UART interface:
public defn minimal-uart () :
uart(UART-RX, UART-TX)
However, the parent bundle is flexible, and can be used to define more sophisticated variants of UART as well.
Pin Assignment¶
Bundles are the key to automatic pin assignment. Supports and require statements use bundles exclusively.
In this simple example, we request one of the available i2c
ports
from a microcontroller.
pcb-module my-module :
inst mcu : stm32f405G7
require bus:i2c from mcu
Parameterized bundles allow a notion of inheritance during pin assignment. Consider two versions of a UART bundle, and a component that supports both:
public defn minimal-uart () :
uart(UART-RX, UART-TX)
; "flow control" UART
public defn fc-uart () :
uart(UART-TX, UART-RX, UART-CTS, UART-RTS)
pcb-component mcu :
port p : pin[100]
supports minimal-uart() :
minimal-uart().UART-RX => self.p[1]
minimal-uart().UART-TX => self.p[2]
supports fc-uart() :
fc-uart().UART-RX => self.p[3]
fc-uart().UART-TX => self.p[4]
fc-uart().UART-CTS => self.p[5]
fc-uart().UART-RTS => self.p[6]
Now, if we instantiate mcu
, and require
a minimal-uart
port,
JITX can solve the pin assignment problem by using either our
microcontroller's support for minimal-uart
or fc-uart
.
pcb-module main-module :
inst mcu : mcu
require uart-port : minimal-uart() from self.mcu
JITX is free to use either ports mcu.p[1], mcu.p[2]
or ports mcu.p[3], mcu.p[4]
to satisfy the require
statement above.
Even though ports mcu.p[3]
and mcu.p[4]
support the fc-uart()
bundle
instead of the required minimal-uart()
bundle, the following
conditions are met:
1. fc-uart()
is a strict superset of minimal-uart()
; it contains
every port found in minimal-uart()
.
2. Both fc-uart()
and minimal-uart()
come from the same generator.
Both are instances of the same parameterized bundle, in this case, uart()
.
If we had instead defined two different bundles
(pcb-bundle minimal-uart
and pcb-bundle fc-uart
), the pin solver
would not know that fc-uart
"inherits" from minimal-uart
, even if it
still had a superset of its ports.
For example, consider hypothetical bundles for i2c
and i3c
:
pcb-bundle i2c :
port sda
port scl
pcb-bundle i3c :
port sda
port scl
Even though i3c
has the same port
statements as i2c
, and
in practice i3c
is compatible with i2c
, JITX will not be able to
satisfy i2c
constraints using i3c
ports, since they
do not share a parent.
Introspection¶
Consider once again the following simple bundles:
pcb-bundle diff-pair :
port N
port P
pcb-bundle lvds-bank (width:Int) :
port clk : diff-pair
port data : diff-pair[width]
In a JITX program, the values diff-pair
and lvds-bank(4)
both have type Bundle
, where Bundle
is a subtype of JITXDef
.
This gives us access to powerful introspection functionality.
For example, we can write ports(diff-pair)
to iterate over
all ports in the bundle definition.
In fact, all of the normal JITX introspection idioms will work
on bundle definitions; however, it is important to remember that
we are reasoning about the definition, not an instance.
For example, consider the following simple design:
pcb-component my-component :
port dp : diff-pair
pcb-module my-module :
inst c : my-component
; Will print "c" for both ports, since they
; belong to the component instance
for p in ports(c.dp) :
println(ref(containing-instance(p)))
; Will print "false" for both ports.
; These come from the bundle definition.
for p in ports(diff-pair) :
println(containing-instance(p))
Besides the usual introspection functions in jitx/commands
, it
is often useful to reason directly about the Bundle
.
For example, we can write code like this:
val pt = port-type(bus)
match(pt):
(b:Bundle): if b == diff-pair : ...