在设计中一旦牵涉到复杂的数据结构,封装是不二选择,这不仅能够让我们的代码看起来更优雅,也能够减少代码中的那些体力活儿(连线)。当我们的数据结构封装后如果在某些场景下需要设置为寄存器并赋初值时,你会怎么做呢?
什么才是优雅的代码
为简单起见,这里定义一个简单的数据结构:
case class data() extends Bundle with IMasterSlave{
val data0=UInt(8 bits)
val data1=UInt(16 bits)
override def asMaster(): Unit = {
out(data1)
in(data0)
}
当data作为master端口时,data0作为输入端口,data1作为输出端口。以下面的example为例:
case class multiDataSetAsRegTest()extends Component{
val io=new Bundle{
val dataIn=slave(data())
val dataOut=master(data())
}
noIoPrefix()
io.dataOut<>io.dataIn
}
那么现在问题来了,如果我们想让dataOut端口中的输出端口作为寄存器输出时,该如何去做呢?
刚学SpinalHDL时,我是这么来写的:
case class data(setAsReg:Boolean) extends Bundle with IMasterSlave{
val data0=if(setAsReg) Reg(UInt(8 bits)) else UInt(8 bits)
val data1=if(setAsReg) Reg(UInt(16 bits)) else UInt(16 bits)
override def asMaster(): Unit = {
out(data1)
in(data0)
}
嗯,看起来没毛病,我只需要在例化的时候添加下就好了:
case class multiDataSetAsRegTest()extends Component{
val io=new Bundle{
val dataIn=slave(data(false))
val dataOut=master(data(true))
}
noIoPrefix()
io.dataOut<>io.dataIn
}
没毛病是没毛病,但就是看起来有点儿略显繁琐,写着有点儿费手。而真正优雅的代码应该像我们在将UInt那样的数据类型声明为Reg时那般优雅:
case class multiDataSetAsRegTest()extends Component{
val io=new Bundle{
val dataIn=slave(data(false))
val dataOut=master(data(true))
}
noIoPrefix()
io.dataOut.setAsReg()
io.dataOut<>io.dataIn
}
那么如何去实现这个目标?
只提出问题不解决那就是耍流氓
上面的代码你在写的时候其实是没有问题的,但运行的时候会出错。我们自定义的data类型确实也存在一个setAsReg
方法。其继承关系如下:
不妨去看下multiData中的实现:
override def setAsReg(): this.type = {
elements.foreach(_._2.setAsReg())
this
}
当我们继承了multiData,那么我们所有的电路元素均会记录在一个ArrayBuffer中。
def elements: ArrayBuffer[(String, Data)]
ArrayBuffer中的每个元素为一个tuple,分别存放信号名及信号类型。我们在multiDataSetAsRegTest中添加一行打印信息:
io.dataOut.elements.foreach(println)
执行时便可以看到
(data0,(toplevel/dataOut_data0 : in UInt[8 bits]))
(data1,(toplevel/dataOut_data1 : out UInt[8 bits]))
好了,搞清楚这一点,那么就可以看下出错的原因了。之所以出错,是因为multiData中setAsReg的实现并未考虑到当数据类型被例化时若带有方向其并做甄别处理。那么我们完全可以在data中重写该方法:
override def setAsReg(): data.this.type ={
this.flattenForeach(signal>{
if(signal.isInput || signal.isInOut)
signal.setAsComb()
else
signal.setAsReg()
})
如此,上面的example便可以正常使用了
赋初值问题
有时候对于设置为寄存器的变量,我们需要进行赋初值,而像上面的example,直接调用data的init函数并不好用,当然你也可以显式直接对变量设置初始值,但当数据结构中的个数较多时则是一件繁琐的事情。
大多数情况下信号赋初值常为0,如果对于数据结构中所有被设为寄存器的变量都赋初值0,那么我们可以定义一个init方法(可能有点儿low):
def init(): data.this.type={
this.flattenForeach(signal=>{
if(signal.isReg){
signal match {
case x:Bool=>x.init(False)
case x:Bits =>x.init(0)
case x:UInt =>x.init(0)
case x:SInt => x.init(0)
}
}
})
this
}
如果只是想读部分信号赋初值,那么可以自行定义新的方法替换即可。
☆ END ☆
作者:玉骐
原文链接:https://mp.weixin.qq.com/s/TmaH_vXbfiiizPT0UlTRxg
微信公众号:
推荐阅读
更多SpinalHDL技术干货请关注Spinal FPGA专栏。