Ports¶
port
is a JITX statement that defines the electrical connection points for a component or module.
The port statement can be used in the following contexts:
Each port
statement in any of these contexts is public
by default. This means that each port can be accessed externally by using dot
notation.
Signature¶
; Shorthand for single-pin port
port <NAME>
; Port with concrete type
port <NAME> : <TYPE>
<NAME>
- Symbol name for the port in this context. This name must be unique in the current context.<TYPE>
- The type of port to construct. This can be the keywordpin
for a single-pin port, apcb-bundle
, or an array:pin
: A single-pin port. Pins are the fundamental building-blocks of ports.<BUNDLE>
: The name of apcb-bundle
representing a set of ports<TYPE>[<ARRAY:Int|Tuple]
: An array initializer. Either an integern
, initializing an array ofn
sequential elements indexed from0
, or a tuple[i_0, i_1, i_2, ..., i_n]
of indices. In the latter case, the indices do not need to be sequential.
Port types are recursive: we can initialize arrays of pins, bundles, or even arrays.
Watch Out! - There is no space between
<TYPE>
and the[
opening bracket of the array initializer.
Syntax¶
There are multiple forms of the port
statement to allow for flexible construction of the interface to a particular component. The following sections outline these structures.
Basic Port Declaration¶
The user has two options when declaring a single pin:
; Single Pin 'a'
port a : pin
; Equivalent to
port a
The port a
structure is a shorthand version for declaring single pins.
Pin Arrays¶
To construct a bus of single pins, we use the array constructor:
port contiguous-bus : pin[6]
port non-contiguous-bus : pin[ [2, 5, 6] ]
println(indices(non-contiguous-bus))
; Prints:
; [2 5 6]
The contiguous-bus
port is defined as a contiguous array of pins with indices: 0, 1, 2, 3, 4, & 5.
The non-contiguous-bus
port is defined an array with explicit indices: 2, 5, & 6. The indices
function is a helper function for determining what array indices are available on this port.
Attempting to access a port index that does not exist or is negative will result in an error.
We can also define higher-dimensional arrays, and access them in the same way:
pcb-module multi-dim-ports :
; A two-dimensional array of pins
port two-d-bus : pin[2][4]
println(indices(two-d-bus))
; Prints:
; [0 1 2 3]
println(indices(two-d-bus[0]))
; Prints:
; [0 1]
Bundle Port¶
A bundle port is a connection point that consolidates multiple individual signals into one entity. The structure of this port is defined by the pcb-bundle
type used in its declaration:
pcb-bundle i2c:
port sda
port scl
pcb-module mcu:
port data : i2c
In this example, we define the i2c
bundle. Notice that this bundle is constructed from same port
statements defined above.
The data
port is then constructed with the bundle name i2c
as the type.
The individual signals of the data
port are accessible using dot
notation:
pcb-module top-level :
inst U1 : mcu
println(port-type(mcu.data.sda))
; Prints:
; [SinglePin object]
Bundle Array Port¶
As with the single pin, we can construct port arrays of bundle types:
pcb-bundle i2c:
port sda
port scl
pcb-module mcu:
port busses : i2c[3]
In this example, we construct a contiguous array of 3 I2C bus ports. We can access the individual signals of these busses as well:
pcb-module top-level :
inst U1 : mcu
println(port-type(mcu.busses[0].scl))
; Prints:
; [SinglePin object]
Port Types¶
Each port has a type associated with it. That type can be accessed with the function port-type . This function returns a PortType
instance that can be one of the following types:
Each port has a type associated with it. That type can be accessed with the function port-type . This function returns a PortType
instance that can be one of the following types:
SinglePin
- A port instance of typepin
Bundle
- A port instance of type BundlePortArray
- An array of ports
We would typically use the result of this function with a match statement:
pcb-module amp:
port vout : pin[4]
match(port-type(vout)):
(s:SinglePin): println("Single")
(b:Bundle): println("Bundle")
(a:PortArray): println("Array")
This structure allows a different response depending on the type of port.
The Bundle
PortType¶
The Bundle
port type provides the user with access to the type of bundle that was used to construct this port:
pcb-bundle i2c:
port sda
port scl
pcb-module mcu:
port bus : i2c
pcb-module top-level:
inst U1 : mcu
match(port-type(U1.bus)):
(b:Bundle):
println("Bundle Type: %_" % [name(b)])
(x): println("Other")
; Prints:
; Bundle Type: i2c
Notice that the b
object is a reference to the pcb-bundle i2c
definition. This provides a convenient way to check if a given port matches a particular bundle type.
Walking a PortArray
¶
It is often useful to walk a PortArray
instance and perform some operation on each of the SinglePin
instances of that PortArray
. Because PortArray
instances can be constructed from either single pins or bundles, they can form arbitrarily complex trees of signals. To work with trees of this nature, recursion is the tool of choice.
Note: This is a more advanced example with recursion. Fear not brave electro-adventurer - These examples will serve you well as you become more comfortable with the JITX Environment.
defn walk-port (f:(JITXObject -> False), obj:JITXObject) -> False :
match(port-type(obj)):
(s:SinglePin): f(obj)
(b:Bundle|PortArray):
for p in pins(obj) do:
walk-port(f, p)
pcb-module top-level:
port bus : i2c[3]
var cnt = 0
for single in bus walk-port :
cnt = cnt + 1
println("Total Pins: %_" % [cnt])
; Prints
; Count: 1
; Count: 2
; Count: 3
; Count: 4
; Count: 5
; Count: 6
The i2c
bundle has 2 pins and there are 3 i2c
ports in the array which results in 6 total pins.
In this example, we construct a Sequence Operator
that will allow us to walk the pins of a port. The structure of a Sequence Operator
is:
defn seq-op (func, seq-obj) :
...
Where the seq-obj
is a Sequence
of objects. The for
statement will iterate over the objects in the seq-obj
sequence and then call func
on each of the objects in the sequence. The value returned by this function can optionally be captured and returned.
In our example, walk-port
is a Sequence Operator
that doesn't return any result. Notice how walk-port
has replaced the do
operator that we normally see in a for
loop statement.
So where does the func
function come from then? The for
statement constructs a function from the body
of the for
statement. In this example, the function effectively becomes:
defn func (x:JITXObject) -> False :
cnt = cnt + 1
Notice that this function is a closure. It is leveraging the top-level
context to access the cnt
variable defined before the for
statement.
A similar construction in Python might look like:
cnt = 0
def func(signal):
global cnt
cnt = cnt + 1
for x in walk-port(bus):
func(x)
Where walk-port
would need to be implemented as a generator
in python that constructs a sequence.