✎ 编 者 按
通过继承Bundle,在SpinalHDL中我们可以定义各种各样的复合数据类型。今天,关于Bundle的几个容易被忽略的点,一同来看下。
》Bundle
个人在使用SpinalHDL来描述电路时,凡是牵涉到需要定义多种类型为一组信号(比如Axi4总线)时,一般均会通过扩展Bundle来进行总线的定义。这里为简单起见,定义如下的port:
case class port() extends Bundle with IMasterSlave {
val data0 = UInt(8 bits)
val data1 = UInt(8 bits)
def sum = data0 + data1
override def asMaster(): Unit = out(data0,data1)
}
这里在port中,定义了两个8bit位宽的data0,data1两个UInt信号以及为该方法定义了一个sum求和函数,文中针对该总线进行功能描述。而就SpinalHDL中关于代码描述的一些建议,在之前的文章中也有提及:《SpinalHDL代码组织结构之Component》
》assignFromBits
当你的接口定义即成了Bundle时,你所定义的所有信号都存储在一个elements的ArrayBuffer中。而在Bundle里,其定义了assignFromBits函数,可以用Bits信号给这种复合类型接口进行赋值:
override def assignFromBits(bits: Bits): Unit = {
var offset = 0
for ((_, e) <- elements) {
val width = e.getBitsWidth
e.assignFromBits(bits(offset, width bit))
offset = offset + width
}
}
override def assignFromBits(bits: Bits, hi: Int, lo: Int): Unit = {
var offset = 0
var bitsOffset = 0
for ((_, e) <- elements) {
val width = e.getBitsWidth
if (hi >= offset && lo < offset + width) {
val high = Math.min(hi-offset,width-1)
val low = Math.max(lo-offset,0)
val bitUsage = high - low + 1
e.assignFromBits(bits(bitsOffset,bitUsage bit), high,low)
bitsOffset += bitUsage
}
offset = offset + width
}
}
去看他的逻辑实现其实很简单,其实现就是将你在Bundle中定义的信号按照你信号定义的顺序依次从Bits信号的低比特位置依次获取值。这使得在某些场景下信号的赋值变得十分便捷:
case class bundleTest() extends Component {
val io = new Bundle {
val data = in UInt (16 bits)
val sum = out UInt (8 bits)
}
val dataBus = port()
dataBus.assignFromBits(io.data.asBits)
io.sum := dataBus.sum
}
这里只是一个简单的求和电路,通过assignFromBits来对dataBus中的data0,data1进行赋值,其顺序为:
assign _zz_dataBus_data0 = io_data;
assign dataBus_data0 = _zz_dataBus_data0[7 : 0];
assign dataBus_data1 = _zz_dataBus_data0[15 : 8];
》asBits
asBits方法与上面的assignFromBits功能相反,能够让我们快速的将复合类型快速填充到信号Bits信号中去:
case class bundleTest() extends Component {
val io = new Bundle {
val dataBus=slave(port())
val data=out UInt(16 bits)
}
io.data.assignFromBits(io.dataBus.asBits)
}
其实现也是一样,先定义的信号会放在低位:
assign io_data = {io_dataBus_data1,io_dataBus_data0};
》比较判断
对于复合数据类型场景,SpinalHDL也定义了比较操作符===和=/=,用于两个总线端口是否完全匹配:
case class bundleTest() extends Component {
val io = new Bundle {
val dataBus0, dataBus1 = slave(port())
val isSame = out Bool ()
}
io.isSame := io.dataBus0 === io.dataBus1
}
这避免了我们手动去一一对比判定,这里的实现等效于:
assign io_isSame = ((io_dataBus0_data0 == io_dataBus1_data0) && (io_dataBus0_data1 == io_dataBus1_data1));
》getZero
对于复合数据类型,当我们需要将所有的信号统一赋值为0时可以调用getZero方法:
case class bundleTest() extends Component {
val io = new Bundle {
val dataBusIn = slave(port())
val sel = in Bool ()
val dataBusOut = master(port())
}
when(io.sel) {
io.dataBusOut := io.dataBusIn
} otherwise {
io.dataBusOut := port().getZero
}
}
这里电路描述当sel为高电平时,输入输出直连,否则所有的输出端口均赋值为0。通过getZero,避免了在复合数据类型中手动一一赋值的麻烦。当然,你也可以这么来写达到相同的效果:
io.dataBusOut.assignFromBits(B(0, io.dataBusOut.getBitsWidth bits))
》写在最后
当你觉得有一个功能在SpinalHDL中描述起来很费劲时,大概率是你没找对方法~
☆ END ☆
作者:玉骐
原文链接:Spinal FPGA
微信公众号:
推荐阅读
更多SpinalHDL技术干货请关注Spinal FPGA专栏。