本文记录了初次使用chisel编码时遇到的一些问题及解决办法,希望对以后的工作有所帮助。
单bit使能需要定义为Bool型,在io端口定义如下
val pipe_en = Input(Bool())
注意:"Bool"后需要加"()"
Chisel中支持的Reg类型有RegNext、RegInit、RegEnable、ShiftRegister,具体的差别和使用方法可参考Chisel中的几种常见的寄存器_CrazyUncle的博客-CSDN博客
我们在编码时常需要对信号打拍的操作,可以用到的语句有RegNext和ShiftRegister,两者的使用方法:
RegNext是跟随寄存器,每个时钟的上升沿,都会采样一次传入的参数。
示例:
无复位: val reg0 = RegNext(io.a)
有复位: val reg1 = RegNext(io.a,0.U)
ShiftRegister是移位寄存器,第一个参数是待移位数据;第二个参数是需要延迟的周期数;第三个参数是指定的复位值,可以省略;第四个参数是移位的使能信号,默认值是1。
示例:
val reg2 = ShiftRegister(io.a,3,0.U,io.en)
表示reg2为在“io.en”为高时,对“io.a”打三拍之后的信号,复位后其值为0。
ShiftRegister可以代替RegNext,只需要将ShiftRegister的第二个参数改为1
RegNext只能打1拍,ShiftRegister可以打n拍,并可以加入打拍的使能信号
笔者在使用的过程中感到ShiftRegister比RegNext好用很多,甚至可以完全代替RegNext。因为RegNext只能打1拍,ShiftRegister可以打n拍,并可以加入打拍的使能信号。只需要将ShiftRegister的第二个参数改为1,就可以替代RegNext的功能。
可以通过BigInt、Int、Long和String四种类型的Scala字面量来构造UInt和SInt。按Scala的语法,其中BigInt、Int、Long三种类型默认是十进制的,但可以加前缀“0x”或“0X”变成十六进制。对于字符串类型的字面量,Chisel编译器默认也是十进制的,但是可以加上首字母“h”、“o”、“b”来分别表示十六进制、八进制和二进制。此外,字符串字面量可以用下划线间隔。
可以通过Boolean类型的字面量——true和false——来构造fromBooleanToLiteral类型的对象,然后调用名为B和asBool的方法进一步构造Bool类型的对象。例如:
1.U // 字面值为“1”的UInt对象
-8.S // 字面值为“-8”的SInt对象
"b0101".U // 字面值为“5”的UInt对象
true.B // 字面值为“true”的Bool对象
import chisel3._
import chisel3.experimental.RawModule
class Hello extends RawModule {
val io = IO(new Bundle {
})
val v = Wire(Vec(3, UInt(4.W)))
val a = Wire(UInt(4.W))
val idx = 1.U(2.W)
v(0) := 1.U
v(1) := 3.U
v(2) := 5.U
a := v(idx)
}
object TesterApp extends App {
chisel3.Driver.execute(Array("-td", "chisel"), () => new Hello())
}
转verilog:
module Hello( //@[chiseltest.scala 4:21]
);
wire [3:0] v[2:0]; //@[chiseltest.scala 7:15]
wire [3:0] a; //@[chiseltest.scala 8:15]
assign v[0] = 4'h1; //@[chiseltest.scala 10:8]
assign v[1] = 4'h3; //@[chiseltest.scala 11:8]
assign v[2] = 4'h5; //@[chiseltest.scala 12:8]
assign a = v[1]; //@[chiseltest.scala 13:5]
endmodule
// mux4.scala
package test
import chisel3._
class Mux4 extends Module {
val io = IO(new Bundle {
val in0 = Input(UInt(1.W))
val in1 = Input(UInt(1.W))
val in2 = Input(UInt(1.W))
val in3 = Input(UInt(1.W))
val sel = Input(UInt(2.W))
val out = Output(UInt(1.W))
})
val m0 = Module(new Mux2)
m0.io.sel := io.sel(0)
m0.io.in0 := io.in0
m0.io.in1 := io.in1
val m1 = Module(new Mux2)
m1.io.sel := io.sel(0)
m1.io.in0 := io.in2
m1.io.in1 := io.in3
val m2 = Module(new Mux2)
m2.io.sel := io.sel(1)
m2.io.in0 := m0.io.out
m2.io.in1 := m1.io.out
io.out := m2.io.out
重复多次例化某模块
// mux4_2.scala
package test
import chisel3._
class Mux4_2 extends Module {
val io = IO(new Bundle {
val in0 = Input(UInt(1.W))
val in1 = Input(UInt(1.W))
val in2 = Input(UInt(1.W))
val in3 = Input(UInt(1.W))
val sel = Input(UInt(2.W))
val out = Output(UInt(1.W))
})
val m = VecInit(Seq.fill(3)(Module(new Mux2).io)) // 例化了三个Mux2,并且参数是端口字段io
m(0).sel := io.sel(0) // 模块的端口通过下标索引,并且路径里没有“io”
m(0).in0 := io.in0
m(0).in1 := io.in1
m(1).sel := io.sel(0)
m(1).in0 := io.in2
m(1).in1 := io.in3
m(2).sel := io.sel(1)
m(2).in0 := m(0).out
m(2).in1 := m(1).out
io.out := m(2).out
}
import chisel3._
import chisel3.experimental.RawModule
class Hello extends RawModule {
val io = IO(new Bundle {
})
val v = Wire(Vec(3, UInt(4.W)))
val a = Wire(UInt(4.W))
val idx = 1.U(2.W)
v(0) := 1.U
v(1) := 3.U
v(2) := 5.U
a := v(idx)
}
当我们用寄存器写一个fifo的时候,往往会用到寄存器组,我们可以先看一下用chisel来实现它的定义和使用。
val fifo = RegInit(VecInit(Seq.fill(3)(0.U(dw.W))))
val waddr = RegInit(0.U(2.W))
when(io.vld) {waddr := waddr + 1.U}
when(io.vld) {
fifo(3) := io.data
switch(waddr){
is(0.U) fifo(0) := io.data
is(1.U) fifo(1) := io.data
is(2.U) fifo(2) := io.data
}
}
1)、定义
Vec代表相同类型的信号的集合(矢量)。每个元素都可以由索引访问。Chisel Vec类似于其他编程语言中的数组数据结构。Vec是通过调用带有两个参数的构造函数来创建的:元素的数量和元素的类型(元素的类型也可以是Vec)。
wire型的使用见右侧,此处主要讨论reg型
15行为定义寄存器组
RegInit表示定义带复位的寄存器
Seq是将变量打包成数组
VecInit:接受一个Seq作为参数来构造向量。VecInit常把有字面值的数据作为参数,用于初始化寄存器组等操作。