十二 · 2022年04月01日

复合类型也有“骚操作”

✎ 编 者 按 
   通过继承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
微信公众号:
 title=

推荐阅读

更多SpinalHDL技术干货请关注Spinal FPGA专栏。
推荐阅读
关注数
1581
内容数
133
用SpinalHDL提升生产力
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息