最近一同事问一个RTL设计问题,一个关于寻找1的个数的题目,不妨来一块儿休闲下
寻找连续一的个数
问题背景是这样的:
一个64比特的数据掩码,其中为1的位置连续,求其中1的个数,即有效字节数。
解决思路
这个问题,最无脑的解决方式估计就是把每个比特加起来了。这么做功能上是OK的,但时序和面积开销无疑就略显臃肿了。
那么这里换个思路,由于1的位置是连续的,那么我们只需要找到为1的最高比特的位置和为1的最低比特的位置就可以了,通过两个位置即可得到1的个数!
那么如何找到最高比特为1的位置和最低比特为1的位置呢?
三种思路。
casez
这里既然是找1的位置,那么可能最不需要思考的路子就是采用casez进行实现。方式也很简单,写两个casez,分别寻找最高比特位置和最低比特位置。
这种方式思路简单,在SpinalHDL里面去实现也很好描述,但使用Verilog来进行描述的话就可能略费功夫了:
import spinal.core._
import spinal.lib._
case class CountByteDemo() extends Component{
val io=new Bundle{
val data_in=in Bits(64 bits)
val num=out UInt (log2Up(64)+1 bits)
}noIoPrefix()
//msb位置获取
val data_msb_offset=UInt (log2Up(64)+1 bits)
switch(io.data_in){
for (index<-0 until 64){
val prefixZeroNum=64-(index+1)
val paddingNum=index
is(MaskedLiteral("0"*prefixZeroNum+"1"+"-"*paddingNum)){
data_msb_offset:=index+1
}
}
default(data_msb_offset:=0)
}
//lsb位置获取
val data_lsb_offset=UInt (log2Up(64) bits)
switch(io.data_in) {
for (index <- 0 until 64) {
val paddingNum=64-(index+1)
val zeroNum=index
is(MaskedLiteral("-" * paddingNum + "1" + "0" * zeroNum)) {
data_lsb_offset := index
}
}
default(data_lsb_offset := 0)
}
io.num:=RegNext(data_msb_offset-data_lsb_offset)
}
这里对于data_msb_offset赋值加1是为了在最后计算num时避免再加1.(最高位1位置为2,最低位位置为0,1的个数为3个)
OneHot
对于寻找最高位为1的位置和最低位为1的位置,在之前的文章《如何实现一个求对数电路逻辑》中曾提到过,可以先转换为独热码来进行实现,随后通过将独热码转换为二进制:
import spinal.core._
import spinal.lib._
case class CountByteDemo() extends Component{
val io=new Bundle{
val data_in=in Bits(64 bits)
val num=out UInt (log2Up(64)+1 bits)
}
noIoPrefix()
val data_msb_oh=OHMasking.last(io.data_in)
val data_lsb_oh=OHMasking.first(io.data_in)
val data_msb_offset=OHToUInt(data_msb_oh##False)
val data_lsb_offset=OHToUInt(data_lsb_oh)
io.num:=data_msb_offset-data_lsb_offset
}
SpinalHDL代码描述起来很简洁。
但这里有个关键问题是在实现OHMasking.last、OHMasking.first时存在一个大位宽的减法器,对于资源与时序略不友好。
最优解
同样是寻找最高位为1的位置和最低位为1的位置,也同样是采用独热码,可以采用如下图所示的思路:
这里之所以在刚开始时先在高位扩展1位,主要是针对数据中出现全为1的场景。
对应SpinalHDL代码描述也很简单:
case class CountByteDemo() extends Component{
val io=new Bundle{
val data_in=in Bits(64 bits)
val num=out UInt (log2Up(64)+1 bits)
}
noIoPrefix()
val data_in_expand=False##io.data_in
val data_in_reversed= ~data_in_expand
val data_in_reversed_right=data_in_reversed.rotateRight(1)
val data_in_reversed_left=data_in_reversed.rotateLeft(1)
val data_in_msb_oh=data_in_expand&data_in_reversed_right
val data_in_lsb_oh=data_in_expand&data_in_reversed_left
val data_in_msb_bit=OHToUInt(data_in_msb_oh##False)
val data_in_lsb_bit=OHToUInt(data_in_lsb_oh)
io.num:=RegNext(data_in_msb_bit-data_in_lsb_bit)
}
相应的,其所消耗的资源及时序相较于前两者也更具优势。
写在最后
不管公司生产环境有什么要求,在SpinalHDL中对于各种想法电路的验证,无论是设计还是方针,这都是Verilog无法媲美的,至于还在思考SpianlHDL是不是没啥用,那就自己慢慢思考吧,学门语言不至于成为一项罪过~