十二 · 2022年02月08日

Vec?Array?

一文区分Array与Vec的使用场景。

开胃菜—>数据选择器

先来看一个非常简单的例子,假定我们有一个输入数组,长度为8,此时我们根据select信号选择对应的输入作为输出。

很简单是不是?SpinalHDL中定义了Vec的结构体,我们完全可以这么来写:

case class demo0() extends Component {
  val io = new Bundle {
    val dataIn = in Vec (UInt(8 bits), 8)
    val select = in UInt (3 bits)
    val dataOut = out UInt (8 bits)
  }
  noIoPrefix()
  io.dataOut := io.dataIn(io.select)
}

如果能写出来,那么Vec是怎么用的你没什么问题了。那么我们稍变换一下,假定我们有八个Mem,根据Select来输出支持地址对应Mem的数据,该如何去实现呢?

看到这里也许你会不假思索的写下了下面这段代码:

case class demo0() extends Component {
  val io = new Bundle {
    val addr = in UInt (3 bits)
    val select = in UInt (3 bits)
    val dataOut = out UInt (8 bits)
  }
  val mem = Vec(Mem(UInt(8 bits), 8) init (Seq(0, 1, 2, 3, 4, 5, 6, 7)), 8)
  io.dataOut := mem(io.select)(io.addr) //异步读操作
}

代码写的很溜,然而一运行满屏报错,而错误的原因,就是“滥用”了Vec。

Vec什么场景下能用?

那么什么场景下能够用Vec呢?我们可以先来看下Vec的定义:

def Vec[T <: Data](gen: HardType[T], size: Int): Vec[T]

在Vec中,我们所传入的电路类型gen要求集成自Data,而在SpinalHDL中的数据类型定义,其给出了详细的继承关系:
image.png
也就意味着我们能往Vec中放置的只能限定于上面的类型(当然我们可以通过Bundle定义更加复杂的数据类型,例如AXI4,也是可以放在Vec中的)。而在上面的电路描述中,其向Vec中放置的是Mem,Mem本质上是一个电路实现模块,而并非一个电路数据类型,故而不能使用Vec来实现我们的需求。

知道了不能这么做,那该如何做?

别急,有Scala

SpinalHDL是基于Scala的,而所有的电路对象不管是电路数据类型还是电路模块其本质上都是对象,其和普通的软件语言对象没有本质的不同,再次贴上当初令我茅塞顿开的一段话:
image.png
像上面的需求,我们想要做的,无非是一个盛放Mem的容器而已,软件怎么写我们就怎么写就可以了,Scala中的Array可不用区分所容纳的究竟是电路模块还是电路数据类型,因此我们完全可以这么来实现:

case class demo0() extends Component {
  val io = new Bundle {
    val addr = in UInt (3 bits)
    val select = in UInt (3 bits)
    val dataOut = out UInt (8 bits)
  }
  val mem =
    Array.fill(8)(Mem(UInt(8 bits), 8) init (Seq(0, 1, 2, 3, 4, 5, 6, 7)))
  io.dataOut := mem.map(_(io.addr)).toSeq.read(io.select)
}

这里要做解释的是最后一行,其实现意图是通过map从每个Mem中读出指定地址的数据,得到一个Array[UInt]数组,而随后之所以调用toSeq在于我们从Array[UInt]中选择所使用的索引类型是UInt而非Int。这两者有极大的不同,UInt是一种电路数据结构类型而非Scala软件中常规的数据类型。Array是不支持UInt作为索引的。而SpinalHDL对Seq隐式扩展提供了read函数能够接受UInt作为索引读取相应位置的数据类型(会映射成switch电路):

def read(idx: UInt): T = {
    Vec(pimped).read(idx)
  }

写在最后

相对而言,Array的使用场景是大于Vec的,Vec能容纳的只有SpinalHDL中的电路数据类型,而Array则能盛放一切,小到Bool大到Component均可,故而最开始的电路你甚至可以这么来写:

case class demo0() extends Component {
  val io = new Bundle {
    val dataIn = Array.fill(8)(in UInt(8 bits))
    val select = in UInt (3 bits)
    val dataOut = out UInt (8 bits)
  }
  noIoPrefix()
  io.dataOut := io.dataIn.toSeq(io.select)
}
作者:玉骐
原文链接:Spinal FPGA
微信公众号:
 title=

推荐阅读

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