十二 · 2023年11月30日

位域一键提取/封装

✎ 编 者 按 

聊一聊SpinalHDL 1.9.4版本中的PackedBundle、PackedWordBundle的使用

位域的提取与封装

在逻辑设计里,但凡牵涉到协议,一般都避免不了协议字段的提取。以下面的一个简单协议为例:

image.png

这里256bit输入数据,包含了五个协议字段:

  • host_addr:64 bits
  • card_addr:64 bits
  • length:14 bits
  • sop:1 bits
  • eop:1 bits 

在进行协议解析时,我们可能会定义如下数据类型:

case class Descriptor() extends Bundle{
    val host_addr=UInt(64 bits)
    val card_addr=UInt(64 bits)
    val length=UInt(14 bits)
    val sop=Bool()
    val eop=Bool()

  override def assignFromBits(data:Bits)={
    host_addr.assignFromBits(data(0,64 bits))
    card_addr.assignFromBits(data(64,64 bits))
    length.assignFromBits(data(128,14 bits))
    sop:=data(144)
    eop:=data(145)
  }

  override def asBits():Bits={
    eop##sop##B(0,2 bits)##length##card_addr##host_addr
  }
}

在Descriptor中,我们重写了assignFromBits()和asBits用于协议字段的提取与数据流的的封装.如此,我们在使用时即可在代码使用时使代码里尽可能的简洁明了:

image.png

在一个大型工程里,往往可能存在许多的协议定义,那么协议的提取与数据流封装就往往需要许多这种assignFromBits()和asBits的重写了,“繁重的”体力劳动。

在SpinalHDL 1.9.4版本中,引入了PackedBundle、PackedWordBundle两个组件(之前的版本略有bug)。从而能够避免这种重复的体力活。像上面的结构中,可以直接这么定义Descriptor数据类型:

case class Descriptor() extends PackedBundle {
  val host_addr=UInt(64 bits).packFrom(0)
  val card_addr=UInt(64 bits).packFrom(64)
  val length=UInt(14 bits) //根据当前已使用的位域推断其对应的位域
  val sop=Bool().packFrom(128+16)
  val eop=Bool().packFrom(128+16+1)
}

我们无需再override任何函数,仅需定义数据类型即可。在使用时:

image.png

通过unpack,可以从data_in中提取协议字段,通过packed方法,可以将协议字段按照位域封装成数据流。

PackedBundle

在PackedBundle中,为SpinalHDL中的基础数据类型隐式扩展了DataPositionEnrich类。为其定义了用于位域绑定的函数:

  • def pack(range: Range)
  • def pack(range: Range, endianness: Endianness = LITTLE)
  • def packFrom(pos: Int)
  • def packTo(pos: Int)

通过这几个函数,我们可以在使用时对定义的字段绑定位域。这里面在使用时更倾向于后面两种方式。对于packTo与packFrom,下面的Descriptor描述方式和上面的Descriptor是等效的:

case class Descriptor() extends PackedBundle {
  val host_addr=UInt(64 bits).packTo(63)
  val card_addr=UInt(64 bits).packTo(127)
  val length=UInt(14 bits)
  val sop=Bool().packFrom(128+16)
  val eop=Bool().packFrom(128+16+1)
}

而通过PackedBundle中所提供的pack方法,可以用于将我们定义的数据类型封装成数据流:

  • def packed: Bits

而对于从数据流中提取协议字段,则可以通过unpack方法:

  • def unpack(bits: Bits)
  • def unpack(bits: Bits, hi: Int, lo: Int)

第二个方法使用场景可能相对较少,感兴趣的可以去看源代码。

PackedWordBundle

PackedWordBundle是在PackedBundle的基础上扩展而来,从而能够按照Word进行位域绑定,使用相对简单,不再做额外赘述,参考例子:

image.png

使用注意

对于PackedBundle、PackedWordBundle,其有种C语言位域结构体的味道,可以方便的定义位域,减少重复性的开发工作。不过其中有一点是其允许位域重复,如下所示:

case class Descriptor() extends PackedBundle {
  val host_addr=UInt(64 bits).packTo(63)
  val card_addr=UInt(64 bits).packTo(63)
  val length=UInt(14 bits)
  val sop=Bool().packFrom(128+16)
  val eop=Bool().packFrom(128+16+1)
}

将card_addr与host_addr绑定到相同的位置是允许的,在进行pack时由于Last Valid Assignment Win,host_addr将不会被使用。故在使用时需注意别重复绑定。

☆ END ☆

作者:玉骐
原文链接:Spinal FPGA
微信公众号:
 title=

推荐阅读

更多SpinalHDL技术干货请关注[Spinal FPGA]欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。
推荐阅读
关注数
1578
内容数
131
用SpinalHDL提升生产力
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息