【数字集成电路与系统设计】一些Chisel语法的介绍
目录
一、switch语句
二、MuxLookup
三、when
四、Vec/VecInit
Seq
Vec
VecInit
Vec 和 VecInit 的区别
五、PriorityMux
六、Reg/RegInit
七、RegEnable / RegNext
八、log2Ceil
一、switch语句
在Chisel中,switch
语句是一种强大的结构,用于根据某个信号的值来选择性地执行不同的代码块。这与许多传统编程语言中的switch
或case
语句非常相似,但Chisel的switch
语句被设计用于硬件描述领域,因此它处理的是硬件信号而不是简单的变量值。
下面是对switch
语句在Chisel中的用法进行整理后的内容:
// 假设state是一个Chisel的信号,它的类型可以是UInt、Bool等
// s0, s1, s2是state信号可能取的值,它们应该与state的类型相匹配
// 例如,如果state是UInt(2.W),那么s0, s1, s2可能是UInt(0.U), UInt(1.U), UInt(2.U)
switch(state) {
is(s0) {
// 当state的值等于s0时,执行这里的代码
// 这里可以放置对硬件行为的描述,如寄存器赋值、逻辑运算等
}
is(s1) {
// 当state的值等于s1时,执行这里的代码
// 类似于上面的描述,可以包含各种硬件设计元素
}
is(s2) {
// 当state的值等于s2时,执行这里的代码
// 继续描述在特定状态下的硬件行为
}
// 如果需要,还可以添加一个默认情况,类似于其他编程语言中的default:
// otherwise {
// // 当state的值不是s0、s1、s2中的任何一个时,执行这里的代码
// }
}
需要注意的是,switch
语句中的is
关键字用于指定每个分支的条件,即state
信号应该匹配的值。
二、MuxLookup
在Chisel中,MuxLookup
函数提供了一种便捷的方式来实现基于选择信号(sel
)的多路选择器(Mux),它根据sel
的值从一组预定义的映射中选择一个输入信号,并将其传递到输出端。如果sel
的值不匹配任何预定义的映射项,则输出一个指定的默认值。
// 假设sel是一个选择信号,其类型通常为UInt或Bool,但在这里我们假设它是UInt
// in0, in1, in2, in3是输入信号,它们可以是任何Chisel支持的信号类型,但类型必须一致
// default是当sel不匹配任何预定义值时的默认输出
// 使用MuxLookup函数来根据sel的值选择输入信号
val result = MuxLookup(sel, default, Array(
(0.U) -> in0, // 当sel为0时,result = in0
(1.U) -> in1, // 当sel为1时,result = in1
(2.U) -> in2, // 当sel为2时,result = in2
(3.U) -> in3 // 当sel为3时,result = in3
))
// 如果sel的值不是0, 1, 2, 或3中的任何一个,则result = default
在这个例子中,sel
是一个选择信号,它决定了从哪个输入信号(in0
, in1
, in2
, in3
)中选择输出到result
。每个输入信号都通过一个元组(UInt -> Signal)
与sel
的一个可能值相关联,其中UInt
是sel
的值,Signal
是对应的输入信号。default
是当sel
的值不匹配任何预定义项时的输出值。
三、when
在Chisel中,when
语句确实用于条件判断和执行,其工作方式类似于许多其他编程语言中的if-else
结构。when
语句允许你根据条件表达式的值来选择性地执行代码块。
// cond 和 otherCond 是条件表达式,它们可以是任何返回Bool类型的Chisel表达式
// 当 cond 为 true 时,执行第一个代码块
when (cond) {
// 当 cond 为 true 时执行的代码
} .elsewhen (otherCond) {
// 当 cond 为 false 且 otherCond 为 true 时执行的代码
} .otherwise {
// 当 cond 和 otherCond 都为 false 时执行的代码
}
在这个例子中,when
语句首先检查cond
是否为真。如果cond
为真,则执行紧随其后的代码块。如果cond
为假,则继续检查.elsewhen
后面的otherCond
。如果otherCond
为真,则执行.elsewhen
代码块中的代码。如果cond
和otherCond
都为假,则执行.otherwise
代码块中的代码。
四、Vec/VecInit
Seq
在Scala中,Seq
是一个集合类,用于存储多个元素。Seq
是不可变的,即一旦创建,其内容就不能被改变。在Chisel中,Seq
常用于初始化Vec
或进行其他类型的集合操作,因为它提供了一种灵活的方式来表示和操作一系列的元素。例如:
val mySeq = Seq(1.U, 2.U, 3.U, 4.U)
这里,mySeq
是一个包含四个1位宽无符号整数元素的不可变序列。
Vec
Vec
是Chisel特有的数据结构,用于定义向量(或数组)类型,可以存储多个相同类型的元素。Vec
常用于需要处理多组数据的场景,如多输入多输出信号的处理。Vec
是动态的,可以在运行时被修改和访问。例如:
val myVec = Wire(Vec(4, UInt(8.W)))
myVec(0) := 10.U
val firstElement = myVec(0)
在这个例子中,myVec
是一个包含四个8位宽无符号整数的向量,可以通过索引访问和修改其元素。
VecInit
VecInit
是一个方法,用于将Seq
中的元素转换为Vec
。这在静态初始化Vec
时非常有用,因为Seq
提供了一种方便的方式来定义一系列的元素。例如:
val myVec = VecInit(Seq(1.U, 2.U, 3.U, 4.U))
这里,myVec
是一个包含四个1位宽无符号整数元素的向量,这些元素是通过VecInit
从Seq
转换而来的。
Vec 和 VecInit 的区别
- 定义方式的区别:
Vec
在定义时需要指定其长度和元素的类型,通常用于动态创建和定义一个向量。VecInit
则用于静态初始化一个向量,其元素在初始化时已经确定,并且是从一个Seq
中转换而来的。
- 使用场景:
- 当需要一个可以动态操作和修改的向量时,使用
Vec
更为灵活。 - 当需要一个预定义的、固定的向量时,使用
VecInit
更为简洁和方便。
- 当需要一个可以动态操作和修改的向量时,使用
五、PriorityMux
PriorityMux 在 Chisel 中用于实现优先级选择器,它根据输入信号的优先级顺序选择第一个有效的输入信号并将其传递到输出端。下面是一个 PriorityMux 的示例:
val selSignals = Seq(cond0, cond1, cond2, cond3)
val inputs = Seq(in0, in1, in2, in3)
val result = PriorityMux(selSignals zip inputs)
在这个例子中,selSignals 是一组条件信号,当 cond0 为真时,result 输出 in0;如果cond0 为假且 cond1 为真,result 输出 in1,依此类推。如果所有条件信号都为假,PriorityMux 将输出默认值 default。
六、Reg/RegInit
在 Chisel 中,Reg 和 RegInit 用于定义和初始化寄存器。寄存器是一个存储单元,可以在时钟边沿存储和输出数据。
Reg 用于定义一个寄存器,可以存储一个指定类型的数据,但在定义时不会初始化寄存器的值,需要在电路运行过程中进行赋值。例如:
val myReg = Reg(UInt(8.W))
在这个例子中,myReg 是一个 8 位宽的无符号整数寄存器。
RegInit 用于定义并初始化寄存器,它在定义寄存器的同时为寄存器赋予一个初始值。例如:
val myRegInit = RegInit(0.U(8.W))
在这个例子中,myRegInit 是一个 8 位宽的无符号整数寄存器,并且初始值为 0。
Reg 和 RegInit 的区别主要体现在两个地方:
1)初始化:Reg 定义的寄存器在初始化时没有指定值,而 RegInit 定义的寄存器在初始化时有一个指定的初始值。
2)使用场景:Reg 适用于需要在电路运行过程中动态赋值的场景,而 RegInit 适用于需要在电路初始化时指定初始值的场景。
七、RegEnable / RegNext
RegEnable 用于定义一个带使能信号的寄存器。使能信号用于控制寄存器的更新,只有当使能信号为真时,寄存器才会在时钟边沿更新其值(注意:Chisel 中默认的有效时钟边沿是上升沿)。
val enable = true.B
val myRegEnable = RegEnable(next = 3.U, init = 0.U, enable = enable)
在这个例子中,myRegEnable 是一个带使能信号的寄存器,初始值为 0,当 enable 为真时,寄存器更新值为 3。
RegNext 用于创建一个时钟周期延迟的寄存器,它将当前周期的输入值在下一个时钟周期输出。
val myRegNext = RegNext(next = 3.U, init = 0.U)
在这个例子中,myRegNext 是一个寄存器,初始值为 0,每个时钟周期将输入值 3 存储并在下一个时钟周期输出。
RegEnable 和 RegNext 的区别主要体现在:RegEnable 需要一个使能信号来控制寄存器是否更新,适用于需要在特定条件下更新寄存器的场景。RegNext 则是实现时钟周期延迟,不需要使能信号,每个时钟周期都会更新寄存器的值。
八、log2Ceil
log2Ceil 是 Chisel 中的一个实用函数,用于计算一个给定整数的以 2 为底的对数,并向上取整。这个函数在设计硬件时尤其有用,因为它可以帮助确定存储某个最大值所需的最小位宽。它位于 chisel3.util 这个 package 中,需要 import chisel3.util._ 来导入。
例如,如果我们需要计数到 15,那么计数器的位宽至少要是 4 位,因为 2^4 = 16 是大于或等于 15 的最小 2 的幂。log2Ceil 可以帮助我们自动计算出这个位宽,而不需要手动计算。对应到下面的代码中,如果 value=15,那么 width=4。
val width = log2Ceil(value)