主体内容摘自:https://blog.csdn.net/qq_34291505/article/details/87570908
一、Chisel的数据类型Chisel定义了自己的一套数据类型,读者应该跟Scala的九种基本值类区分开来。而且Chisel也能使用Scala的数据类型,但是Scala的数据类型都是用于参数和内建控制结构,构建硬件电路还是得用Chisel自己的数据类型,在使用时千万不要混淆。当前Chisel定义的数据类型如下图所示,其中绿色方块是class,红色是object,蓝色是trait,箭头指向的是超类和混入的特质: 所有数据类型都继承自抽象基类Data,它混入了两个特质HasId和NamedComponent。如果读者查看Chisel3的源代码,就会看到很多参数传递时都用下界表明了是Data的子类。在实际硬件构成里,并不会用到Data,读者也不用关心它的具体实现细节。更多的,应该关注Data类的两大子类:聚合类Aggregate和元素类Element。
聚合类Aggregate:
常用子类是向量类Vec[T]和包裹类Bundle。
-
Vec[T]类用于包含相同的元素,元素类型T可以是任意的Data子类。因为Vec[T]混入了特质IndexedSeq[T],所以向量的元素能从下标0开始索引访问。
-
Bundle类用于被自定义的类继承,这样自定义的类就能包含任意Data的子类对象,常用于协助构造模块的端口,故而衍生出了一些预定义的端口子类。
-
混合向量类MixedVec[T]是Chisel3.2以上版本添加的语法,它与Vec[T]的不同在于可以包含不同类型的元素。
Element类:
衍生出了Analog、Bits和Clock三个子类,单例对象DontCare和特质Reset。
- Analog用于在黑盒中模拟inout端口,目前在实际Chisel里并无其他用途。
- Bits类的两个子类SInt和UInt是最常用的两个数据类型,它们是用补码表示的有符号整数和无符号整数。不仅用来协助定义端口位宽,还用来进行赋值。
- FixedPoint类提供的API带有试验性质,而且将来可能会发生改变,所以不常用。
- Bool类是Chisel自己的布尔类型,区别于Scala的Boolean。Bool类是UInt类的子类,因为它可以看成是1bit的UInt,而且它被混入Reset特质,因为复位信号都是用Bool类型的线网或寄存器使能的。此外,Bits类混入了特质ToBoolable,也就是说FixedPoint、SInt和UInt都能转换成多bit的Bool类型。
- Clock类表示时钟,Chisel里的时钟是专门的一个类型,并不像Verilog里那样是1bit的线网。复位类型Reset也是如此。
- 单例对象DontCare用于赋值给未驱动的端口或线网,防止编译器报错。
能够表示具体值的数据类型为UInt、SInt和Bool。实际可综合的电路都是若干个bit,所以只能表示整数,这与Verilog是一致的。要表示浮点数,本质还是用多个bit来构建,而且要遵循IEEE的浮点标准。
- 对于UInt,可以构成任意位宽的线网或寄存器。
- 对于SInt,在Chisel里会按补码解读,转换成Verilog后会使用系统函数$signed,这是可综合的。
- 对于Bool,转换成Verilog后就是1bit的线网或寄存器。
要表示值,则必须有相应的字面量。Chisel定义了一系列隐式类:fromBigIntToLiteral、fromtIntToLiteral、fromtLongToLiteral、fromStringToLiteral、fromBooleanToLiteral
。
回顾前面讲述的隐式类的内容,也就是会有相应的隐式转换。以隐式类fromtIntToLiteral为例,存在一个同名的隐式转换,把相应的Scala的Int对象转换成一个fromtIntToLiteral的对象。而fromtIntToLiteral类有两个方法U和S
,分别构造一个等值的UInt对象和SInt对象。再加上Scala的基本值类都是用字面量构造对象,所以要表示一个UInt对象,可以写成“1.U
”的格式,这样编译器会插入隐式转换,变成“fromtIntToLiteral(1).U
”,进而构造出字面值为“1”的UInt对象。同理,也可以构造SInt。还有相同行为的方法asUInt和asSInt
。
从几个隐式类的名字就可以看出,可以通过BigInt、Int、Long和String
四种类型的Scala字面量来构造UInt和SInt
。
按Scala的语法,其中BigInt、Int、Long
三种类型默认是十进制的,但可以加前缀“0x”或“0X”
变成十六进制。
对于String
类型的字面量,Chisel编译器默认也是十进制的,但是可以加上首字母“h”、“o”、“b”
来分别表示十六进制、八进制和二进制。此外,String
字面量可以用下划线间隔。
可以通过Boolean
类型的字面量——true和false
——来构造fromBooleanToLiteral
类型的对象,然后调用名为B和asBool
的方法进一步构造Bool
类型的对象。
1.U // decimal 1-bit lit from Scala Int.
0x100.U // hexadecimal 9-bit lit
"ha".U // hexadecimal 4-bit lit from string.
"o12".U // octal 4-bit lit from string.
"b1010".U // binary 4-bit lit from string.
"h_dead_beef".U // 32-bit lit of type UInt
5.S // signed decimal 4-bit lit from Scala Int.
-8.S // negative decimal 4-bit lit from Scala Int.
5.U // unsigned decimal 3-bit lit from Scala Int.
8.U(4.W) // 4-bit unsigned decimal, value 8.
-152.S(32.W) // 32-bit signed decimal, value -152.
true.B // Bool lits from Scala lits.
false.B
三、数据宽度
默认情况下,数据的宽度按字面值取最小,例如字面值为“8”的UInt对象是4位宽,SInt就是5位宽。但是也可以指定宽度。在Chisel2里,宽度是由Int类型的参数表示的,而Chisel3专门设计了宽度类Width。还有一个隐式类fromIntToWidth,就是把Int对象转换成fromIntToWidth类型的对象,然后通过方法W返回一个Width对象。
方法U、asUInt、S和asSInt
都有一个重载的版本,接收一个Width类型的参数,构造指定宽度的SInt和UInt对象。注意,Bool类型固定是1位宽。例如:
1.U // 字面值为“1”、宽度为1bit的UInt对象
1.U(32.W) // 字面值为“1”、宽度为32bit的UInt对象
UInt、SInt和Bool都不是抽象类,除了可以通过字面量构造对象以外,也可以直接通过apply工厂方法构造没有字面量的对象。UInt和SInt的apply方法有两个版本:
- 一个版本接收Width类型的参数构造指定宽度的对象,
- 另一个则是无参版本构造位宽可自动推断的对象。
有字面量(如1.U(32.W)
)的数据类型用于赋值、初始化寄存器等操作,而无字面量(如UInt(32.W)
)的数据类型则用于声明端口、构造向量等。
UInt、SInt和Bool三个类都包含四个方法:asUInt、asSInt、asBool和asBools。
- 其中asUInt和asSInt分别把字面值按无符号数和有符号数解释,并且位宽不会变化,要注意转换过程中可能发生符号位和数值的变化。例如,3bit的UInt值“b111”,其字面量是“7”,转换成SInt后字面量就变成了“-1”。
- asBool会把1bit的“1”转换成Bool类型的true,“0”转换成false。
- 如果位宽超过1bit,则用asBools转换成Bool类型的序列Seq[Bool]。
另外,Bool类还有一个方法asClock,把true转换成电压常高的时钟,false转换成电压常低的时钟。
Clock类只有一个方法asUInt,转换成对应的0或1。
val bool: Bool = false.B // always-low wire
val clock = bool.asClock // always-low clock
clock.asUInt // convert clock to UInt (width 1)
clock.asUInt.asBool // convert clock to Bool (Chisel 3.2+)
五、向量与混合向量
①向量的定义
如果需要一个集合类型的数据,除了可以使用Scala内建的数组、列表、集等数据结构外,还可以使用Chisel专属的Vec[T]。T必须是Data的子类,而且每个元素的类型、位宽必须一样。
Vec[T]
的伴生对象里有一个apply工厂方法,接收两个参数,第一个是Int类型,表示元素的个数,第二个是元素。它属于可索引的序列,下标从0开始。例如:
val myVec = Wire(Vec(3, UInt(32.W)))
还有一个工厂方法VecInit[T]
,通过接收一个Seq[T]
(这里的Seq包括seq、array、list、tuple、queue
等集合)作为参数来构造向量,或者是多个重复参数。不过,这个工厂方法常把有字面值的数据作为参数,用于初始化寄存器组、ROM、RAM等,或者用来构造多个模块。
val Vec1 = VecInit(1.U, 2.U, 3.U, 4.U)//重复参数
val Vec2 = VecInit(Seq.fill(8)(0.U(8.W)))//序列
因为Vec[T]
也是一种序列,所以它也定义了诸如map、flatMap、zip、foreach、filter、exists、contains等
方法。尽管这些方法应该出现在软件里,但是它们也可以简化硬件逻辑的编写,减少手工代码量。
②混合向量的定义
混合向量MixedVec[T]
与普通的向量Vec[T]
类似,只不过包含的元素可以不全都一样,比如位宽不一样。它的工厂方法是通过重复参数或者序列作为参数来构造的:
val Vec1 = MixedVec(UInt(8.W), UInt(16.W), UInt(32.W))//重复参数
或者
val Vec2 = MixedVec(Array(UInt(8.W), UInt(16.W), UInt(32.W)))//序列
并且也有一个叫MixedVecInit[T]
的单例对象,也是通过重复参数或者序列作为参数来构造的:
val Vec1 = MixedVecInit(1.U, 2.U, 3.U, 4.U)//重复参数
或者
val Vec2 = MixedVecInit(Seq.fill(8)(0.U(8.W)))//序列
从上面也看出来了,对于可以传入序列的向量,它们的序列参数并不一定要逐个手写,可以通过Scala的函数,比如fill、map、flatMap、to、until
等来生成。如下所示:
val mixVec = MixedVec((1 to 10) map { i => UInt(i.W) })//序列
val mixVecinit = MixedVecInit(Seq.fill(8)(0.U(8.W)))//序列
val vecinit= VecInit(Seq.fill(4)(4.U(8.W)))//序列
注:关于向量定义需要注意的
Vec和MixedVec定义的时候,最好不要直接给确切的值,只需要给出Chisel type,之后再给元素单独赋值即可,如上面的UInt(32.W)即可,否则会报错。原因是:vec接收的是数据类型,而带字面量的数据如 1.U 会被认为是硬件类型,就会报错,你自己可以试一下。
同样,VecInit和MixedVecInit必须给出确切的初始化的值,不能只给Chisel type。原因是:vecinit接收的是硬件类型,而Chisel type如UInt(32.W)是数据类型,如果传入就会报错,你自己也可以试一下。
③Vec和UInt的互相转换
Vec和UInt的转换,需要借助Bool类型的数据。所以中间需要使用到asBools和asUInt
。
- 使用asBools将UInt转换成Vec
import chisel3._
class Foo extends RawModule {
val uint = 0xc.U
val vec = VecInit(uint.asBools)
printf(p"$vec") // Vec(0, 0, 1, 1)
// Test
assert(vec(0) === false.B)
assert(vec(1) === false.B)
assert(vec(2) === true.B)
assert(vec(3) === true.B)
}
- 使用asUInt将Vec转换成UInt
import chisel3._
class Foo extends RawModule {
val vec = VecInit(true.B, false.B, true.B, true.B)
val uint = vec.asUInt
printf(p"$uint") // 13
// Test
// (remember leftmost Bool in Vec is low order bit)
assert(0xd.U === uint)
}
④向量和混合向量的维度与索引
myVec 其实是一个二维数据,因为每个元素都是32位宽的数据,每个bit都可以被索引到,如下:
val myVec = Wire(Vec(3, UInt(32.W)))
myVec(0)(5)//索引vec第一个元素的第6个bit
myVec(0)(3,0)//索引vec第一个元素的低4位
索引到所需bit后可以将其赋值给其他变量,但是最后一维的子字
是只读的,也即你不能对其赋值,如:
myVec(0)(3,0) := 1.U(4.W)
上面的代码会报错!!!如果想要对最后一维进行赋值,可以参考以下⑤中的方法。需要注意的是,这里说的是最后一维,其实和下面说的Bits类型是一致的,因为向量的最后一维对应的就是Bits类型,比如SInt和UInt。
假如现在有一个vec1,只想对它的第一个元素的低4位赋值,其余不变。
- 可行的办法1
val Vec1 = Wire(Vec(3, UInt(32.W)))
Vec1(0) := 1.U(32.W)
Vec1(1) := 1.U(32.W)
Vec1(2) := 1.U(32.W)
val Vec2 = Wire(Vec(3, UInt(32.W)))
Vec2(0) := 1.U(32.W)
Vec2(1) := 1.U(32.W)
Vec2(2) := 1.U(32.W)
val bools = VecInit(Vec1(0).asBools)
val seq = 1.U(4.W).asBools
for (i
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?