Bundle
Description
The Bundle
is a composite type that defines a group of named signals (of any SpinalHDL basic type) under a single name.
A Bundle
can be used to model data structures, buses, and interfaces.
Declaration
The syntax to declare a bundle is as follows:
case class myBundle extends Bundle {
val bundleItem0 = AnyType
val bundleItem1 = AnyType
val bundleItemN = AnyType
}
For example, a bundle holding a color could be defined as:
case class Color(channelWidth: Int) extends Bundle {
val r, g, b = UInt(channelWidth bits)
}
You can find an APB3 definition among the Spinal HDL examples.
Conditional signals
The signals in the Bundle
can be defined conditionally.
Unless dataWidth
is greater than 0, there will be no data
signal in elaborated myBundle
, as demonstrated in the example below.
case class myBundle(dataWidth: Int) extends Bundle {
val data = (dataWidth > 0) generate (UInt(dataWidth bits))
}
Operators
The following operators are available for the Bundle
type:
Comparison
Operator |
Description |
Return type |
---|---|---|
x === y |
Equality |
Bool |
x =/= y |
Inequality |
Bool |
val color1 = Color(8)
color1.r := 0
color1.g := 0
color1.b := 0
val color2 = Color(8)
color2.r := 0
color2.g := 0
color2.b := 0
myBool := color1 === color2
Type cast
Operator |
Description |
Return |
---|---|---|
x.asBits |
Binary cast to Bits |
Bits(w(x) bits) |
val color1 = Color(8)
val myBits := color1.asBits
The elements of the bundle will be mapped into place in the order in which they are defined.
Thus, r
in color1
will occupy bits 0 to 8 of myBits
(LSB), followed by g
and b
in that order.
Convert Bits back to Bundle
The .assignFromBits
operator can be viewed as the reverse of .asBits
.
Operator |
Description |
Return |
---|---|---|
x.assignFromBits(y) |
Convert Bits (y) to Bundle(x) |
Unit |
x.assignFromBits(y, hi, lo) |
Convert Bits (y) to Bundle(x) with high/low boundary |
Unit |
The following example saves a Bundle called CommonDataBus into a circular buffer (3rd party memory), reads the Bits out later and converts them back to CommonDataBus format.
case class TestBundle () extends Component {
val io = new Bundle {
val we = in Bool()
val addrWr = in UInt (7 bits)
val dataIn = slave (CommonDataBus())
val addrRd = in UInt (7 bits)
val dataOut = master (CommonDataBus())
}
val mm = Ram3rdParty_1w_1rs (G_DATA_WIDTH = io.dataIn.getBitsWidth,
G_ADDR_WIDTH = io.addrWr.getBitsWidth,
G_VENDOR = "Intel_Arria10_M20K")
mm.io.clk_in := clockDomain.readClockWire
mm.io.clk_out := clockDomain.readClockWire
mm.io.we := io.we
mm.io.addr_wr := io.addrWr.asBits
mm.io.d := io.dataIn.asBits
mm.io.addr_rd := io.addrRd.asBits
io.dataOut.assignFromBits(mm.io.q)
}
IO Element direction
When you define a Bundle
inside the IO definition of your component, you need to specify its direction.
in/out
If all elements of your bundle go in the same direction you can use in(MyBundle())
or out(MyBundle())
.
For example:
val io = new Bundle {
val input = in (Color(8))
val output = out(Color(8))
}
master/slave
If your interface obeys to a master/slave topology, you can use the IMasterSlave
trait. Then you have to implement the function def asMaster(): Unit
to set the direction of each element from the master’s perspective. Then you can use the master(MyBundle())
and slave(MyBundle())
syntax in the IO definition.
There are functions defined as toXXX, such as the toStream
method of the Flow
class.
These functions can usually be called by the master side.
In addition, the fromXXX functions are designed for the slave side.
It is common that there are more functions available for the master side than for the slave side.
For example:
case class HandShake(payloadWidth: Int) extends Bundle with IMasterSlave {
val valid = Bool()
val ready = Bool()
val payload = Bits(payloadWidth bits)
// You have to implement this asMaster function.
// This function should set the direction of each signals from an master point of view
override def asMaster(): Unit = {
out(valid, payload)
in(ready)
}
}
val io = new Bundle {
val input = slave(HandShake(8))
val output = master(HandShake(8))
}