Types
Data
is the root of the Chisel type system.
UInt
and SInt
extend Bits
虽然chisel能够自动推断宽度,但是最好还是指定一下宽度(如4.W
)
UInt ()
UInt (8.W)
3.U(4.W) // An 4-bit constant of 3
We will need Scala vars to write hardware generators, but never need vars to name a hardware component.
Wire&Reg
Wire(UInt (8.W))
Reg(UInt (8.W))
// 带初始化值
WireDefault (10.U(4.W))
RegInit (0.U(8.W))
Reg
变种:
/* RegNext */
val q = RegNext(d)
// the same as
val q = Reg(UInt(1.W))
q := d
// with init val
RegNext (d, 0.U)
/* RegEnable */
val enableReg2 = RegEnable (inVal , enable )
// the same as
val enableReg = Reg(UInt(4.W))
when ( enable ) {
enableReg := inVal
}
// with init val
val resetEnableReg2 = RegEnable (inVal, 0.U(4.W), enable)
Vec
Wire(Vec (3, UInt (8.W)))
Reg(Vec (32, UInt (32.W)))
// 带初始值
VecInit (1.U(3.W), 2.U, 3.U) // This is different from a plain Vec that needs to be wrapped into a Wire. We could wrap the VecInit into a WireDefault, but this uncommon coding style.
RegInit ( VecInit (0.U(3.W), 1.U, 2.U))
// a register file containing 32 registers, each 32-bit wide and reset to 0
RegInit ( VecInit (Seq.fill (32) (0.U(32.W))))
Bundle
class Channel () extends Bundle {
val data = UInt (32.W)
val valid = Bool ()
}
when
val w = Wire(UInt ())
when (cond){
w := 1.U
}.elsewhen(cond2){
w := 2.U
}.otherwise{
w := 3.U
}
switch
import chisel3.util._
result := 0.U
switch (sel){
is ("b00".U) { result := "b0001".U }
is ("b01".U) { result := "b0010".U }
is ("b10".U) { result := "b0100".U }
is ("b11".U) { result := "b1000".U }
}
// or just:
result := 1.U << sel
工具函数
unsignedBitLength(n) // specify the number of bits for the counter cntReg needed to represent unsigned numbers up to (and including) n. = ⌊log (n)⌋ + 1
signedBitLength(n) // similar
A # B // cat A and B
Cat("b101".U, "b11".U) // equivalent to "b101 11".U
Fill(2, "b1000".U) // equivalent to "b1000 1000".U
cloneType // get the Chisel type, e.g.:
def myMuxAlt [T <: Data ]( sel: Bool , tPath: T, fPath : T): T
= {
val ret = Wire(fPath.cloneType)
ret := fPath
when (sel) {
ret := tPath
}
ret
}
ChiselEnum
枚举,用来定义状态机中的状态很好用
// The three states
object State extends ChiselEnum {
val green , orange , red = Value
}
Chisel 自动为每个枚举值(green
、orange
、red
)分配了一个唯一的整数标识符(注意在3.5里面默认是连续二进制编码,不是独热编码),也可以自定义:
object Opcode extends ChiselEnum {
val load = Value(0x03.U) // i "load" -> 000_0011
val imm = Value(0x13.U) // i "imm" -> 001_0011
// ...
}
Memory
SyncReadMem
FPGAs (and also ASICs) usually support synchronous memories. That means the read data is available one clock cycle after setting the address. To support on-chip memory, Chisel provides the memory constructor SyncReadMem.
下面就是一个1KiB SRAM读写的例子:
class Memory () extends Module {
val io = IO(new Bundle {
val rdAddr = Input(UInt (10.W))
val rdData = Output (UInt (8.W))
val wrAddr = Input(UInt (10.W))
val wrData = Input(UInt (8.W))
val wrEna = Input(Bool ())
})
val mem = SyncReadMem (1024 , UInt (8.W))
io. rdData := mem.read(io. rdAddr )
when(io. wrEna) {
mem. write (io.wrAddr , io. wrData )
}
}
Mem
Chisel also provides Mem, which represents a memory with synchronous write and an asynchronous read.
但是不推荐使用
DecoupledIO
定义类似如下:
class DecoupledIO[T <: Data](gen: T) extends Bundle {
val ready = Input(Bool())
val valid = Output(Bool())
val bits = Output(gen)
}
IrrevocableIO
类似,但是还遵循规则:当valid=1,ready=0
时,保持数据不变;一旦valid=1
之后就保持高电平直到ready=1
.
其实这差不多也就是AXI握手的规则,不过值得注意的是: 接受端的ready
可以在发送端的valid
拉高之前拉高,也可以等待valid
拉高一段时间之后再拉高.
class ReadyValidBuffer extends Module {
val io = IO(new Bundle {
val in = Flipped (new DecoupledIO (UInt (8.W))) // change the direction with Flipped
val out = new DecoupledIO (UInt (8.W))
})
val dataReg = Reg(UInt (8.W))
val emptyReg = RegInit (true.B)
io.in. ready := emptyReg
io.out. valid := ! emptyReg
io.out.bits := dataReg
when ( emptyReg & io.in.valid) {
dataReg := io.in.bits
emptyReg := false.B
}
when (! emptyReg & io.out.ready) {
emptyReg := true.B
}
}
结合Scala语法
参数化Bundles
class Payload extends Bundle {
val data = UInt (16.W)
val flag = Bool ()
}
class Port[T <: Data ](private val dt: T) extends Bundle{
val address = UInt (8.W)
val data = dt. cloneType
}
class NocRouter2 [T <: Data ](private dt: T, n: Int) extends Module {
val io =IO(new Bundle {
val inPort = Input(Vec(n, dt))
val outPort = Output (Vec(n, dt))
})
}
val router = Module (new NocRouter2(new Port(new Payload), 2))
打表
val table = VecInit (array.map(_.U(8.W)))
//
val msg = " Hello World!"
val text = VecInit (msg.map(_.U))
val len = msg.length.U
函数式编程
// to generated the chain of adders:
val sum = vec.reduce (x => x+x)
// or just
val sum = vec.reduce (_ + _)
// to generated a tree of adders:
val sum = vec.reduceTree (_ + _)
找出最小的值及其下标
class Two extends Bundle {
val v = UInt(w.W)
val idx = UInt (8.W)
}
val vecTwo = Wire(Vec(n, new Two ()))
for (i <- 0 until n) {
vecTwo(i).v := vec(i)
vecTwo(i).idx := i.U
}
val res = vecTwo.reduceTree((x, y) => Mux(x.v < y.v, x, y))
// or
val scalaVector = vec.zipWithIndex
.map((x) => MixedVecInit (x._1, x._2.U(8.W)))
val (minVal, minIdx) = VecInit (scalaVector)
.reduceTree ((x, y) => Mux(x(0) < y(0), x, y))
===
和=/=
在 Chisel 中,==
和 ===
是两种不同的相等性比较运算符,它们在进行比较时的语义不同:
-
==
运算符:在 Chisel 中,==
是 Scala 本身的相等性比较运算符。它通常用于比较两个对象是否相等,而不是用于比较硬件节点(比如寄存器、线网等)。在使用 Chisel 编写硬件时,通常不会使用==
来比较两个硬件信号,因为它比较的是对象的引用或值,而不是它们在硬件逻辑上的等价性。 -
===
运算符:这是 Chisel 特有的相等性比较运算符,用于比较两个硬件信号。当你想要在硬件描述中比较两个信号是否相等时,应该使用===
。这个运算符会在生成的硬件电路中创建相应的比较逻辑。
例如,考虑两个 UInt
(无符号整数)硬件信号 a
和 b
:
- 使用
a === b
会生成一个硬件电路,该电路在运行时比较a
和b
的值。 - 使用
a == b
是不正确的,因为它会尝试比较两个硬件节点的引用或对象等价性,而不是它们的值。
因此,在编写 Chisel 代码时,应该使用 ===
来比较硬件信号的等价性。=/=
同理.
:=
和 =
You use Scala’s “=” operator when creating a hardware object (and giving it a name) but you use Chisel’s “:=” operator when assigning or reassigning a value to an existing hardware object.
+&
和 +
-
+
运算符:这是一个标准的加法运算符,用于将两个数相加。在 Chisel 中,使用+
运算符时,如果加法结果超出了操作数的位宽,将会产生溢出,但这个溢出并不会被自动处理。也就是说,+
运算符执行的是模运算,其结果被限制在操作数的原始位宽内。 -
+&
运算符:这是一种带进位的加法运算符。使用+&
时,如果加法操作导致溢出(即结果需要更多的位来表示),则会自动扩展结果的位宽以适应完整的加法结果。这种运算符在需要保留所有进位信息,防止溢出时非常有用。
简单来说,如果你使用 +
运算符进行加法,你需要自己处理可能的溢出问题。而如果使用 +&
,Chisel 会自动处理溢出,确保结果包含所有的进位信息。