棋子 · 9月30日

时序优化——FPGA上加法器映射

✎ 编者按 
最近用一个实际体验不怎么样的片子,就跑版本时时序优化颇费功夫,在优化过程中一个关于加法器的优化,颇具特点,记录一下。

加法器引入的reset的复位扇出问题

对于复位信号,对于FPGA设计而言基本仅用在寄存器上,而在一个工程里发现reset接到了大量的LUT上,导致reset扇出过大,其中有一大类就是关于加法器的设计上。    

加法器设计很简单,正如下面的电路:

always @(posedge clk) begin
    if(rst) begin
        cnt <= 8'd0;
    end else if(clear) begin
        cnt <= 8'd0;
    end else begin
        cnt <= cnt + 8'(inc);
    end
end

一个很简单的8比特加法器。cnt在复位时为0,clear信号时cnt清零,否则当inc为1时进行加1。

这个电路在实际综合的时候,会将rst与clear接入到一个LUT2上面,然后送往寄存器的复位引脚上。

这个看起来是编译工具傻掉了,我们的理想情况是将复位直接送到寄存器的复位引脚上,然而工具对于代码结构的识别似乎没有那么智能。

从代码层面如何解

既然工具太笨,那么就要给他指导。可以对rst信号添加约束来解决这个问题。针对加法器这种情况,那么此处最佳做法就是从代码层面优化,使其看起来更符合在FPGA里面的结构样式。

对于一个寄存器,其最理想的模型大体是:

always_ff @(posedge clk ) begin
    if(rst) begin
        Q <= 1'b0;
    end else begin
        Q <= D;
    end
end

这里同样对加法器电路进行相似的改造:

always_ff @(posedge clk ) begin
    if(rst) begin
        cnt <= 8'd0;
    end else begin
        cnt <= (cnt&{8{~clear}}) + 8'((~clear) & inc ) ;
    end
end

在这里,在clear为1时 cnt将会被清除为0,否则将执行加法。如此,通过这种方式可以避免将复位与clear信号先经过一个LUT2后再送往寄存器复位引脚引发复位信号扇出过大。

扩展一下

基于上面的思路,在SpinalHDL中不妨做一个更通用的加法器FPGACounter:

/**
 * FPGA加法器
 * @param width 加法器位宽
 * @param initValue 加法器初始值
 * @param incEn 标识是否使能加操作
 * @param decEn 标识是否使能减操作
 * @param withClearValue 是否有清除信号
 * @param withSetValue 是否有置位信号
 * @param clearValue 清除时的清除值
 * @param setValue 置位时的置位值
 */
class FpgaCounter(width: Int, initValue: BigInt = 0, incEn: Boolean, decEn: Boolean, withClearValue: Boolean, withSetValue: Boolean, clearValue: BigInt, setValue: BigInt) extends Component {
  val io = new Bundle {
    val clear = (withClearValue) generate in Bool()
    val set = (withSetValue) generate in Bool()
    val inc = (incEn) generate in UInt (width bits)
    val dec = (decEn) generate in UInt (width bits)
    val count = out UInt (width bits)
  }
  noIoPrefix()
  io.count.setAsReg() init (initValue)
  val clear_value = (withClearValue) generate (U(clearValue, width bits))
  val set_value = (withSetValue) generate (U(setValue, width bits))
  val inc_mask_value = (incEn) generate UInt(width bits)
  val dec_mask_value = (decEn) generate UInt(width bits)
  val count_mask = UInt(width bits)
  val clear_mask = (withClearValue) generate Repeat(io.clear, width).asUInt
  val set_mask = (withSetValue) generate Repeat(io.set, width).asUInt
  if (withClearValue && withSetValue) {
    count_mask := (io.count & (~(clear_mask | set_mask))) | (set_value & set_mask) | (clear_value & clear_mask)
    if (incEn) {
      inc_mask_value := io.inc & (~(clear_mask | set_mask))
    }
    if (decEn) {
      dec_mask_value := io.dec & (~(clear_mask | set_mask))
    }
  } else if (withClearValue) {
    count_mask := (io.count & (~clear_mask)) | (clear_value & clear_mask)
    if (incEn) {
      inc_mask_value := io.inc & (~clear_mask)
    }
    if (decEn) {
      dec_mask_value := io.dec & (~clear_mask)
    }
  } else if (withSetValue) {
    count_mask := (io.count & (~set_mask)) | (set_value & set_mask)
    if (incEn) {
      inc_mask_value := io.inc & (~set_mask)
    }
    if (decEn) {
      dec_mask_value := io.dec & (~set_mask)
    }
  } else {
    count_mask := io.count
    inc_mask_value := io.inc
    dec_mask_value := io.dec
  }

  if (decEn && incEn) {
    io.count := count_mask + inc_mask_value - dec_mask_value
  } else if (decEn) {
    io.count := count_mask - dec_mask_value
  } else if (incEn) {
    io.count := count_mask + inc_mask_value
  }
}

这个加法器支持加、减、置位、清除这四个动作,根据需要的功能可以填入不同的参数(如果清除和置位均使能情况下,默认不会在同一个时钟周期下即拉清除,又拉复位功能)。同时为了使用时的方便,定义FpgaCounter Object:

object FpgaCounter {

  /**
   *
   * @param bitCount :计数器位宽
   * @param inc : 累加条件
   * @param dec : 减1条件
   * @param clear :计数器清零
   * @param set :计数器置位
   * @param initValue :count初始值
   * @param clearValue :clear后值
   * @param setValue :set后值
   * @return Count计数器
   */
  def BoolCount(bitCount: BitCount, inc: Bool=null,dec:Bool=null,clear:Bool=null,set:Bool=null,initValue:BigInt=0,clearValue:BigInt = 0, setValue:BigInt= -1): UInt = {
    require(inc!=null || dec!=null)
    val defaultSetValue= ((BigInt(1)<<bitCount.value)-1)&setValue
    val count_inst= new FpgaCounter(width = bitCount.value, initValue = initValue, incEn = inc!=null, decEn = dec!=null, withClearValue = clear!=null, withSetValue = set!=null, clearValue = clearValue, setValue = defaultSetValue)
    if(clear!=null){
      count_inst.io.clear:=clear
    }else{
      count_inst.io.clear.clear()
    }
    if(set!=null){
      count_inst.io.set:=set
    }else{
      count_inst.io.set.clear()
    }
    if(inc!=null){
      count_inst.io.inc:=U(inc,bitCount)
    }else{
      count_inst.io.inc.clearAll()
    }
    if(dec!=null){
      count_inst.io.dec:=U(dec,bitCount)
    }else{
      count_inst.io.dec.clearAll()
    }
    count_inst.io.count
  }

    /**
     *
     * @param bitCount :计数器位宽
     * @param inc : 累加条件
     * @param dec : 减1条件
     * @param clear :计数器清零
     * @param set :计数器置位
     * @param initValue :count初始值
     * @param clearValue :clear后值
     * @param setValue :set后值
     * @return Count计数器
     */
    def UIntCount(bitCount: BitCount, inc: UInt=null,dec:UInt=null,clear:Bool=null,set:Bool=null,initValue:BigInt=0,clearValue:BigInt = 0, setValue:BigInt= -1): UInt = {
      require(inc!=null || dec!=null)
      val defaultSetValue= ((BigInt(1)<<bitCount.value)-1)&setValue
      val count_inst= new FpgaCounter(width = bitCount.value, initValue = initValue, incEn = inc!=null, decEn = dec!=null, withClearValue = clear!=null, withSetValue = set!=null, clearValue = clearValue, setValue = defaultSetValue)
      if(clear!=null){
        count_inst.io.clear:=clear
      }else{
        count_inst.io.clear.clear()
      }
      if(set!=null){
        count_inst.io.set:=set
      }else{
        count_inst.io.set.clear()
      }
      if(inc!=null){
        count_inst.io.inc:=inc
      }else{
        count_inst.io.inc.clearAll()
      }
      if(dec!=null){
        count_inst.io.dec:=dec
      }else{
        count_inst.io.dec.clearAll()
      }
      count_inst.io.count
    }

}

这里定义了BoolCount和UIntCount,分别对应于加1或者加n场景。在使用时可采用如下方式:

case class CounterTest() extends Component {
  val io=new Bundle{
    val inc=in UInt(8 bits)
    val dec=in UInt(8 bits)
    val set= in Bool()
    val clear=in Bool()
    val cnt=out UInt(8 bits)
  }
  noIoPrefix()
  io.cnt:=FpgaCounter.UIntCount(bitCount = 8 bits, inc = io.inc, dec = io.dec, clear = io.clear, set = io.set, initValue = 0, clearValue = 0, setValue = 0xff)
}

写在最后

用FPGA越来越觉得最终设计电路时要考虑结合FPGA期间特性这个金玉良言。太真实了。

☆ END ☆

作者:玉骐
文章来源:Spinal FPGA

推荐阅读

更多IC设计干货请关注IC设计专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。

推荐阅读
关注数
19592
内容数
1303
主要交流IC以及SoC设计流程相关的技术和知识
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息