RTL设计:寻找一的个数

文摘   2024-10-26 19:24   上海  
✎ 编 者 按 

最近一同事问一个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是不是没啥用,那就自己慢慢思考吧,学门语言不至于成为一项罪过~

☆ END ☆

 


IC技术圈
致力于建立IC技术知识、IC技术圈内人的联系
 最新文章