2 Chisel基本内容

在本节中,我们将介绍数字设计的基本组件:组合电路和触发器。这些基本元素可以结合起来,构建更大、更有趣的电路。数字系统通常使用二进制信号,这意味着一个比特或信号只能有两种可能的值之一。这些值通常称为0和1。但是,我们也使用以下术语:low/high、false/true和de-asserted/asserted。这些术语表示二进制信号相同的两个可能值。

2.1信号类型和常量

Chisel提供了三种数据类型来描述信号、组合逻辑和寄存器:Bits、UInt和SInt。UInt和SInt扩展了Bits,所有这三种类型都表示一个位向量。UInt表示无符号整数,SInt表示有符号整数,Chisel使用二进制补码作为有符号整数表示。这里是不同类型的定义,

一个8位Bits,一个8位无符号整数,和一个10位有符号整数:

Bits(8.W)
UInt(8.W)
SInt(10.W)

位向量的宽度由Chisel宽度类型(width)定义。下面的表达式将Scala整数n转换为Chisel宽度,该宽度用于Bits向量的定义:

n.W
Bits(n.W)

常量可以通过使用Scala整数并将其转换为Chisel类型来定义:

0.U // defines a UInt constant of 0
-3.S // defines a SInt constant of -3

常量也可以通过使用Chisel宽度类型来定义宽度:

8.U(4.W) // An 4-bit constant of 8

8.U和4.W 可以把它看作是一个带类型的整数常量的变体。这种表示法类似于8L,在C、Java和Scala中表示一个长整数常量。

可能的误区:在定义具有专用宽度的常量时,一个可能的错误是缺少宽度.w说明符。例如,1. u(32)不会定义一个32位宽的常数来表示1。相反,表达式(32)被解释为从位置32提取位,其结果是单个位常数0。这可能不是我们的初衷。

Chisel得益于Scala的类型推理,在很多地方,类型信息都可以省略。这一点对位宽也是有效的。在许多情况下,Chisel会自动推断出正确的宽度。因此,Chisel对硬件的描述要比比VHDL或Verilog更简明,更易读。对于以十进制以外的其他进制定义的常量,常量被定义在一个字符串中
前面的h代表十六进制(16进制),o代表八进制(8进制),b代表二进制(基数2)。下面的例子显示了常数255在不同基数下的定义。在这个例子中,我们省略了位宽,Chisel推断了适合常数的最小宽度。常数的最小宽度,在这个例子中是8位。

"hff".U // hexadecimal representation of 255
"o377".U // octal representation of 255
"b1111_1111".U // binary representation of 255

上面的代码显示了如何使用下划线对代表常数的字符串中的数字进行分组。一个常数中的下划线是被忽略的。
为了表示逻辑值,Chisel定义了Bool类型。Bool可以代表一个真或假值。下面的代码显示了Bool类型的定义和Bool常量的定义。Bool常量的定义,通过将Scala布尔常量true和false转换为Chisel
Bool常量。

Bool()
true.B
false.B

图2.1: 表达式(a & b) | c的逻辑, 线路可以是一个比特或多个比特。Chisel表达式,和原理图是一样的

2.2组合电路

Chisel使用布尔代数运算符,正如它们在C、Java、Scala和其他一些编程语言中所定义的那样,来描述组合电路:&是AND运算符,|是OR运算符。下面这行代码定义了一个电路,将信号a和b相与,并将其结果与信号c相或。

val logic = (a & b) | c

图2.1是这个组合表达的原理图。请注意,这个电路 可以是一个比特矢量,而不仅仅是与AND和OR电路相结合的单线。
在这个例子中,我们没有定义信号逻辑的类型和宽度。两者都是 从表达式的类型和宽度推断出来的。在 Chisel的标准逻辑运算是:

val and = a & b // bitwise and
val or = a | b // bitwise or
val xor = a ^ b // bitwise xor
val not = ~a // bitwise negation

算术运算使用标准运算符。

val add = a + b // addition
val sub = a - b // subtraction
val neg = -a // negate
val mul = a * b // multiplication
val div = a / b // division
val mod = a % b // modulo operation

操作结果的宽度是加法和减法的运算符的最大宽度,乘法的两个宽度之和,除法和取余操作通常是分子宽度。
一个信号也可以首先被定义为某种类型的线网类型。之后,我们可以用:=更新操作符为该线分配一个值:

val w = Wire(UInt())
w := a & b

单个比特可以被提取出来,如下所示:

val sign = x(31)

一个子字段可以从末端到起始位置构成:

val lowByte = largeWord(7, 0)

比特字段与Cat字段相连接:

val word = Cat(highByte , lowByte)

表2.2显示了全部运算符的列表(也可参见内置运算符)。Chisel操作符的优先级是由电路的评估顺序决定的,它遵循Scala的运算符优先级。如果有疑问,使用小括号总是一个好的做法。
表2.2显示了定义在Chisel数据类型上和为其定义的各种函数。

2.2.1多路复用器

多路复用器是一种在备选方案之间进行选择的电路。在最基本的形式中,它在两个备选方案之间进行选择。图2.2显示了这样一个2:1的多路复用器,或简称为mux。根据选择信号(sel)的值,信号y将代表信号a或信号b。
一个多路复用器可以由逻辑构建。然而,由于多路复用是一个标准的操作,Chisel提供了一个多路复用器。

val result = Mux(sel, a, b)

表 2.1: Chisel 定义的硬件操作符

 表 2.2: chisel定义的硬件函数,在v上调用。

图2.2:  一个基本的2:1多路复用器

其中,当sel为true.B时,a被选中,否则b被选中。sel的类型是一个Chisel的Bool;输入a和b可以是任何Chisel基本类型或集合(bundles or vectors),只要它们是同一类型。
通过逻辑和算术运算以及多路复用器,每个组合电路都可以被描述。然而,Chisel为更优雅地描述一个组合电路提供了进一步的组件和控制抽象,这些组件和控制抽象将在后面的章节中描述。
描述数字电路所需的第二个基本组件是一个状态元素。也叫寄存器,接下来将介绍。

2.3 Registers

Chisel提供了一个寄存器,它是一个D触发器的集合。寄存器被隐含地连接到一个全局时钟,并在上升沿更新。当一个初始化寄存器的声明中提供了一个初始化值,它使用了一个连接到全局复位信号的同步复位。寄存器可以是任何Chisel类型,可以被表示为bits的集合。下面的代码定义了一个8位寄存器,复位时初始化为0。

一个输入通过:=更新操作符连接到寄存器,寄存器的输出可以在表达式中与名称一起使用。
一个寄存器也可以在定义处与它的输入相连。

图2.3显示了我们的寄存器定义的电路,其中有一个时钟,一个同步复位到0.U,输入d,和输出q。
全局信号时钟和复位隐式连接到每个定义的寄存器。寄存器也可以连接到它的输入和一个常量作为初始值在定义:

val reg = RegInit(0.U(8.W))
//===========================
reg := d
val q = reg
//===========================
val nextReg = RegNext(d)
//===========================
val bothReg = RegNext(d, 0.U)

为了区分表示组合逻辑和寄存器的信号,一种常见的做法是在寄存器名称后面加上Reg。另一种来自Java和Scala的常见做法是使用camelCase来标识由几个单词组成的标识符,约定函数和变量用小写字母开头,类(类型)用大写字母开头。

基于触发器的寄存器,同步复位为0

 图2.3: 基于触发器的寄存器,同步复位为0

2.3.1 Counting

计数是数字系统中的基本操作。然而,更常见的是使用计数来定义时间间隔。计算时钟周期并在时间间隔到期时触发操作。 一个简单的方法是计数为一个值。但是,在计算机科学和数字设计中,计数从0开始。因此,如果我们想数到10,我们从0到9计数。下面的代码演示了这样一个计数器,它计数到 9,并在达到 9 时绕到 0。

val cntReg = RegInit(0.U(8.W))
cntReg := Mux(cntReg === 9.U, 0.U, cntReg + 1.U)

2.4 Structure with Bundle and Vec

Chisel提供了两种结构来对相关信号进行分组:(1)一个 Bundle 来对不同类型的信号进行分组,以及 (2) 一个 Vec 来表示相同类型信号的可索引集合。Bundles 和 Vecs 可以任意嵌套。

一个Chisel bundle将几个信号分组。整个包可以作为一个整体来引用,也可以通过其名称来访问单个字段。我们可以通过定义一个Bundle(信号的集合),方法是定义一个继承了Bundle的类,并在构造函数块中把字段定义为vals。

class Channel() extends Bundle {
val data = UInt(32.W)
val valid = Bool()
}

为了使用一个bundle,我们用new创建它,并把它包装成一个Wire。访问方式是
使用点符号:

val ch = Wire(new Channel())
ch.data := 123.U
ch.valid := true.B
val b = ch.valid

点符号在面向对象的语言中很常见,其中x是一个对象的引用,而y是该对象的一个字段。由于Chisel是面向对象的,我们使用点符号来访问bundle中的字段。bundle类似于C语言,VHDL或者SystemVerilog中的结构。一个包也可以作为一个整体被引用。

val channel = ch

Chisel Vec表示相同类型的信号的集合(一个向量)。每个元素都可以通过一个索引来访问。Chisel Vec类似于其他编程语言中的数组数据结构。
一个Vec是通过调用构造函数来创建的,它有两个参数:元素的数量和元素的类型。一个组合式的Vec需要被包装成一个Wire

val v = Wire(Vec(3, UInt(4.W)))

单个元素用(index)来访问。

v(0) := 1.U
v(1) := 3.U
v(2) := 5.U
val idx = 1.U(2.W)
val a = v(idx)

将一个向量包装成一个Wire就是一个复用器。我们也可以把一个向量包进一个寄存器来定义一个寄存器阵列。下面的例子为一个处理器定义了一个寄存器文件;32个寄存器,每个32位宽,如同经典的32位RISC处理器,如32位版本的RISC-V。

val registerFile = Reg(Vec(32, UInt(32.W)))

该寄存器文件中的一个元素用一个索引被访问,并作为一个正常的寄存器使用。

registerFile(idx) := dIn
val dOut = registerFile(idx)

我们可以自由地混合bundle和vector。 当创建带有bundle类型的vector时,  我们需要传递向量场的原型。 使用我们上面定义的Channel ,我们可以用以下方法创建channels向量: 

val vecBundle = Wire(Vec(8, new Channel()))

一个bundle也可能包含一个vector。

class BundleVec extends Bundle {
val field = UInt(8.W)
val vector = Vec(4,UInt(8.W))
}

当我们想要一个需要重置值的bundle类型的寄存器时,我们首先创建一个bundle的Wire,根据需要设置各个字段,然后将这个bundle传递给一个RegInit。

val initVal = Wire(new Channel())
initVal.data := 0.U
initVal.valid := false.B
val channelReg = RegInit(initVal)

通过Bundles和Vecs的组合,我们可以定义我们自己的数据结构。

2.5 Chisel Generates Hardware

在看到一些最初的Chisel代码后,它可能看起来与经典的编程语言相似,如Java或C。然而,Chisel(或任何其他硬件描述语言)确实定义了硬件组件。在软件程序中,一行又一行的代码被执行,而在硬件中,所有行的代码都是并行执行的。
必须牢记,Chisel代码确实产生了硬件。试着想象一下,或在纸上画出由Chisel描述所产生的各个模块电路。每个组件的创建都会增加硬件;每个赋值语句产生门电路和触发器。
从技术上讲,当Chisel执行你的代码时,它是作为Scala程序运行的,而通过通过执行Chisel语句,它收集了硬件组件并连接了这些节点。这个硬件节点的网络就是硬件,它可以进行合成ASIC或FPGA的Verilog代码,也可以用Chisel测试器进行测试。硬件节点网络是完全并行执行的。


 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝是天的蓝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值