Chisel的基本概念
-
Chisel硬件表达
Chisel只支持二进制逻辑,不支持三态信号。 -
Chisel数据类型和数据字面量
-
数据类型用于指定状态元素中保存的值或wire上传输的值。 Chisel 所有的数据类型都是 Data 类的子类,所有最终继承自 Data 类的对象都可以在实际硬件中表示成一个 bit 向量。
-
常用的数据类型有: Bits,表示一行 bit 的集合; UInt,表示无符号整数; SInt,用补码表示有符号整数,它和 UInt 都是 FixedPoint 的子类; Bool,表示一个布尔值;还有其他 Data 的子类是这些常用类的超类,但是不用于构建电路,而是为了定义这些类.
-
Bundle 和 Vec 用于表示上述类型的集合,其中 Bundle 常用于构建模块的 I/O,而 Vec 常用于构建重复单元如多根线网、多个例化的模块、寄存器组等。
-
Bundle和Vec是可以允许用户使用其他数据类型来扩展Chisel数据类型集合的类。绑裹类型把若干命名的可以是不同类型的的域集合在一起变成一个连贯清晰的单元,这非常像 C 语言中的 struct。用户通过从 Bundle 衍生一个子类就可以定义自己的绑裹类型Bundle用class来定义,用户可以通过将一个类定义为Bundle的子类来定义自己的bundle。
-
Chisel 内建基础类和聚合类不需要使用 new 关键字,但是用户自定义的数据类型就必须使用。使用 Scala apply 构造器,用户自定义的数据类型也可以省略 new 关键字.
-
绑裹类型和向量类型可以任意相互嵌套构造复杂的数据结构。
-
-
Chisel数据字面量
-
Chisel的数据字面量可以通过 Scala 的 Int 和 String 类型隐式转换得到, Scala 的 Int 默认是十进制,以 0x 开头是十六进制,这会相应地转化成 Chisel的十进制和十六进制的数值字面量。字符串以“b”开头会被转化成 Chisel 的二进制,相应的,以“o”开头对应八进制,以“h”开头对应十六进制。常量和字面数值表示成 Scala 整数或者附有对应类型构造器的字符串:
1.U // decimal 1-bit lit from Scala Int.
“ha”.U // hexadecimal 4-bit lit from string.
“o12”.U // octal 4-bit lit from string.
“b1010”.U // binary 4-bit lit from string.
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.
true.B // Bool lits from Scala lits.
false.B -
Chisel编译器默认使用最小的位宽来保存常量,有符号类型会包括一个符号比特。字面数字也可以显式地指明位宽,如下所示:
“ha”.U(8.W) // hexadecimal 8-bit lit of type UInt
“o12”.U(6.W) // octal 6-bit lit of type UInt
“b1010”.U(12.W) // binary 12-bit lit of type UInt
5.S(7.W) // signed decimal 7-bit lit of type SInt
5.U(8.W) // unsigned decimal 8-bit lit of type UInt -
UInt 类型的字面常量的数值用零扩展到目标位宽。SInt 类型的字面常量的数值用符号为扩展到目标位宽。如果给出的位宽太小不能保存参量数值,Chisel会报错
-
-
组合电路
- 在Chisel中,电路会被表示为一张节点图。每个节点是具有零个或多个输入并驱动一个输出的硬件运算符。
- Uint是一种退化类型的节点,它没有输入,并且在其输出上驱动一个恒定的值。创建和连接节点的一种方法是使用字面表达式。
eg. (a&b)|(c&d) - 任何简单的表达式都可以直接转换成电路树,在叶子处使用命名的导线和操作符形成内部节点。表达式的电路输出取自树根处的运算符。
- 在Chisel中给一个wire命名就是声明一个变量,使用Scala中的关键词val。如下:
val sel = a | b
val out = (sel & in1) | (~sel & in2)
-
内建操作符
针对不同数据类型,Chisel定义了不同的硬件操作符,如下表所示:
Chisel有一个很大的特点就是变量宽度推测,wire的宽度是可以推测出来的,除了右移操作外,宽度推测机制得到的输出wire的宽度始终大于或者等于输入wire的宽度。按照如下规则推测:
-
函数
我们可以定义函数来分解一个重复的逻辑,这样可以在后续设计中重复使用。
eg. def clb(a: UInt, b: UInt, c: UInt, d: UInt): UInt = (a & b) | (~c & d)
其中clb是表示以a,b,c,d为参数的函数,并返回一个布尔电路的输出。def关键字是Scala的一部分,表示引入了一个函数定义,每个语句后面跟一个冒号,然后是它的类型,函数返回类型在参数列表之后的冒号之后。(=)符号将函数参数列表与函数定义分隔开。
然后我们就可以在其他的电路中使用了:val out = clb(a,b,c,d) -
端口
端口用作硬件组件的接口。一个端口可以是任意的Data对象,但它是具有方向的。 Chisel提供端口构造函数,以允许在构建时给对象添加(输入或输出)。原始的端口构造函数需要将方向作为第一个参数(方向为INPUT或OUTPUT),将位数作为第二个参数(除了始终为1位的布尔值)。eg. 端口声明如下所示
class Decoupled extends Bundle {
val ready = Bool(OUTPUT)
val data = UInt(INPUT, 32)
val valid = Bool(INPUT)
} -
模块
-
Module用class来定义,继承Module。继承自 Module。 包含一个用函数 IO() 包裹的接口,并且保存在一个名为 io 的端口域内。在它的构造器内连接它所有的子电路。