Kika's
Blog
图片简介 | CC BY 4.0 | 换一张

Chisel Memo

2023-12-19

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 自动为每个枚举值(greenorangered)分配了一个唯一的整数标识符(注意在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 中,===== 是两种不同的相等性比较运算符,它们在进行比较时的语义不同:

  1. == 运算符:在 Chisel 中,== 是 Scala 本身的相等性比较运算符。它通常用于比较两个对象是否相等,而不是用于比较硬件节点(比如寄存器、线网等)。在使用 Chisel 编写硬件时,通常不会使用 == 来比较两个硬件信号,因为它比较的是对象的引用或值,而不是它们在硬件逻辑上的等价性。

  2. === 运算符:这是 Chisel 特有的相等性比较运算符,用于比较两个硬件信号。当你想要在硬件描述中比较两个信号是否相等时,应该使用 ===。这个运算符会在生成的硬件电路中创建相应的比较逻辑。

例如,考虑两个 UInt(无符号整数)硬件信号 ab

  • 使用 a === b 会生成一个硬件电路,该电路在运行时比较 ab 的值。
  • 使用 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.

+&+

  1. + 运算符:这是一个标准的加法运算符,用于将两个数相加。在 Chisel 中,使用 + 运算符时,如果加法结果超出了操作数的位宽,将会产生溢出,但这个溢出并不会被自动处理。也就是说,+ 运算符执行的是模运算,其结果被限制在操作数的原始位宽内。

  2. +& 运算符:这是一种带进位的加法运算符。使用 +& 时,如果加法操作导致溢出(即结果需要更多的位来表示),则会自动扩展结果的位宽以适应完整的加法结果。这种运算符在需要保留所有进位信息,防止溢出时非常有用。

简单来说,如果你使用 + 运算符进行加法,你需要自己处理可能的溢出问题。而如果使用 +&,Chisel 会自动处理溢出,确保结果包含所有的进位信息。