Chisel简明教程
1. 简介
Chisel(Scala嵌入式硬件构造语言)是一种嵌入在高级编程语言Scala中的硬件构造语言。Chisel是一个特殊类定义、预定义对象和Scala内部使用约定的库,因此当你编写Chisel代码时,实际上是在编写一个构建硬件图的Scala程序。随着经验的积累并希望使代码更简洁或更可重用,利用Scala语言的潜在强大功能变得很重要。它是由加州大学伯克利分校的 ADEPT 实验室开发的。
Chisel 的主要特点如下:
- Scala 嵌入式语言: Chisel 是 Scala 编程语言的一个嵌入式领域特定语言(EDSL)。这意味着 Chisel 程序可以利用 Scala 的全部功能,如面向对象编程、函数式编程、类型系统等。
- 硬件抽象: Chisel 提供了一套抽象的硬件构建块,如寄存器、存储器、多路复用器等,使开发人员可以更高效地描述硬件电路。
- 硬件生成: Chisel 编译器可以将 Chisel 代码转换为 Verilog 或 FIRRTL(Flexible Intermediate Representation for RTL)格式,从而可以与其他硬件设计工具集成。
- 可扩展性: Chisel 的模块化设计允许开发人员定义和扩展自己的硬件构建块,并将它们集成到更大的系统中。
- 性能: Chisel 的设计目标之一是生成高效的硬件电路。通过利用 Scala 的优化和特性,Chisel 可以产生与手写 Verilog 代码性能相当的电路。
- 可测试性: Chisel 提供了良好的测试支持,开发人员可以编写单元测试和属性测试来验证电路的正确性。
Chisel 的主要应用场景包括:
- 处理器设计: Chisel 被用于设计 RISC-V 等处理器架构。
- 加速器设计: Chisel 可以用于设计高性能的专用加速器,如机器学习加速器。
- 自定义硬件加速: Chisel 可用于在 FPGA 或 ASIC 上实现定制的硬件加速器。
- 教育和研究: Chisel 被广泛用于大学和研究机构的硬件课程和项目中。
2. 安装
Chisel是基于Scala实现的,Scala又是基于Java实现的,所以需要安装Java。需要严格按照官方要求安装temurin-17-jdk,如果安装其他版本的java,部分情况下可能无法编译。Linux使用22.04版本。
2.1. 安装Scala
sudo apt update
sudo apt install -y curl
curl -fL https://github.com/Virtuslab/scala-cli/releases/latest/download/scala-cli-x86_64-pc-linux.gz | gzip -d > scala-cli
chmod +x scala-cli
sudo mv scala-cli /usr/local/bin/scala-cli
scala-cli version
scala-cli install completions --shell bash
2.2. 安装Chisel
Chisel在执行的过程中,会下载相关库,所以初次编译Chisel工程时,需要联网。
curl -O -L https://github.com/chipsalliance/chisel/releases/latest/download/chisel-example.scala
scala-cli chisel-example.scala
如果输出如下信息,表明Chisel安装成功。
2.3. 安装Java
用sudo权限执行下面命令,因为网络服务的问题,temurin-17-jdk的安装可能需要几小时。
# Ensure the necessary packages are present:
apt install -y wget gpg apt-transport-https
# Download the Eclipse Adoptium GPG key:
wget -qO - https://packages.adoptium.net/artifactory/api/gpg/key/public | gpg --dearmor | tee /etc/apt/trusted.gpg.d/adoptium.gpg > /dev/null
# Configure the Eclipse Adoptium apt repository
echo "deb https://packages.adoptium.net/artifactory/deb $(awk -F= '/^VERSION_CODENAME/{print$2}' /etc/os-release) main" | tee /etc/apt/sources.list.d/adoptium.list
# Update the apt packages
apt update
# Install
apt install -y temurin-17-jdk
2.4. 安装sbt
sbt( Scala build tool)是Scala的编译工具,可以简化编译与测试。
-s -L https://github.com/sbt/sbt/releases/download/v1.9.7/sbt-1.9.7.tgz | tar xvz
sudo mv sbt/bin/sbt /usr/local/bin/
3. 示例
一个标准的Chisel工程,由Chisel电路源码,测试代码,sbt配置文件,make文件构成。推荐使用官方的工程模板,在此基础上进行修改添加。
官方工程模板路径:https://github.com/chipsalliance/chisel-template.git
3.1. 文件结构
- main目录下存放电路Chisel代码。
- test目录下存放电路试Chisel代码。
- build.sc是存放mill编译信息,暂时不用。
- build.sbt是存放sbt编译信息,包括各种库的版本号等。
3.2. 运行
sbt run
此命令是用来将Chisel电路代码转换为Verilog/SystemVerilog电路代码的。初始编译时,会下载相关库。再次编译则不再下载,直接编译。
编译成功,并且默认在当前目录生成GCD.v的Verilog代码,其内容为:
3.3. 测试
Chisel框架可以直接对Chisel电路代码进行测试,这种测试方式效率更高,并且测试的过程中同样可以生成vcd波形。因为Chisel生成的Verilog/SystemVerilog代码可读性不好,不好调试。所以尽量在Chisel代码阶段就进行全面的验证,尽量让Chisel生成的Verilog之后进行的验证不出现Bug。
4. Chisel语法
4.1. 基本信号类型
Chisel有三种数据类型,分别为Bits、Uint和Sint。其中Uint和Sint是从Bits扩展而来,目前主要使用Uint和Sint。
Bits(8.W) // 位宽8Bit类型
UInt(8.W) // 位宽8Bit无符号类型
SInt(10.W) // 位宽8Bit有符号类型
4.2. 常量
常量就是字面量,常量的位宽描述可以省略,编译器会自动根据常量大小推断位宽大小。
4.2.1. 数字常量
Chisel的数据进制有16进制h, 8进制o, 2进制b。如下常量255,位宽默认推断为8。
"hff".U
"o377".U
"b1111_1111".U
数字字面量可以用下划线分隔,提升可读性。
4.2.2. 字符常量
val c = 'A'.U
4.2.3. 数据类型
U表示UInt,S表示Sing。
0.U // 定义了一个UInt类型的常量0
-3.S // 定义了一个SInt类型的常量-3
3.U(4.w) // 定义了一个位宽为4的UInt类型常量3
4.3. 逻辑类型
Chisel引入了Bool类型,其有true和false两个值。Bool常量:
true.B
false.B
4.4. 组合电路
组合电路就运用各种操作符的组合来描述的电路,组合电路没有时钟和寄存器。下面代码描述一个与门连接信号a和b,然后把输出信号与信号c通过或门连接在一起:
val logic = (a & b) | c
对对应的电路示意图如下:
4.5. 操作符
下表为Chisel内置的操作符,并按优先级排列。
Operator | Description | Data types |
/ % | multiplication, division, modulus | UInt, SInt |
+ - | addition, subtraction | UInt, SInt |
=== =/= | equal, not equal | UInt, SInt, returns Bool |
> >= < <= | comparison | UInt, SInt, returns Bool |
<< >> | shift left, shift right (sign extend on SInt) | UInt, SInt |
˜ | NOT | UInt, SInt, Bool |
& | ˆ | AND, OR, XOR | UInt, SInt, Bool |
! | logical NOT | Bool |
&& || | logical AND, OR | Bool |
4.6. 函数操作
下表为Chisel内置函数操作符。
Function | Description | Data types |
v.andR v.orR v.xorR | AND, OR, XOR reduction | UInt, SInt, returns Bool |
v(n) | extraction of a single bit | UInt, SInt |
v(end, start) | bitfield extraction | UInt, SInt |
Fill(n, v) | bitstring replication, n times | UInt, SInt |
a ## b | bitfield concatenation | UInt, SInt |
Cat(a, b, ...) | bitfield concatenation | UInt, SInt |
// 对输入向量 v 进行逻辑与操作,并返回结果。
val andResult = v.asUInt().andR()
// 对输入向量 v 进行逻辑或操作,并返回结果。
val orResult = v.asUInt().orR()
// 对输入向量 v 进行逻辑异或操作,并返回结果
val xorResult = v.asUInt().xorR()
// 从向量 v 中提取索引为 n 的单个位。
val singleBit = v(n)
// 从向量 v 中提取从索引 start 到索引 end 的位字段
val bitfield = v(end, start)
// 将位串 v 复制 n 次以生成较长的位串。
val replicatedBits = Fill(n, v)
// 将位字段 a 和 b 进行连接形成更大的位字段。
val concatenated = a ## b
// 将多个位字段 a、b、... 进行连接形成更大的位字段。
// ##适用于连接两个字段,Cat适用于连接多个字段
val concatenatedFields = Cat(a, b, ...)
4.7. 寄存器
Chisel中提供Register组件,对应Verilog中的Register,其本质是一个D-触发器(D flip-flop)的集合。Chisel中的寄存器默认连接到全局时钟,并在每个时钟的上升沿更新寄存器的值。在声明寄存器并初始化时,寄存器会默认连接到全局同步复位信号。
val reg = RegInit(0.U(8.W)) // 声明寄存器并初始化
reg := d // 将信号d连接到寄存器reg
val q = reg // 将寄存器的输出赋值给变量q
Chisel中有一个隐式的全局时钟信号clock,一个隐式的全局同步复位信号reset。当使用RegInit创建一个寄存器时,就默认将clock和reset接入了创建的寄存器reg。上述代码的等效电路图如下:
上述代码也可简化为下面的形式:
val reg = RegNext(d, 0.U(8.W))
val q = reg
带使能的寄存器,使能时才赋值值inVal,否则使用默认值0.U(4.W):
val resetEnableReg2 = RegEnable(inVal, 0.U(4.W), enable)
利用寄存器检测上升沿,当din在上一个时钟为低,在当前时钟为高时,表示din是上升沿:
val risingEdge = din & !RegNext(din)
4.8. Bundle
如果有一组信号会用在多个模块中,为了复用这一组信号,就需要用到Bundle。
class Channel() extends Bundle {
val data = UInt(32.W)
val valid = Bool()
}
这样就可以在多个模块中实例化Channel模块来复用信号,提升代码编写效率。
val ch = Wire(new Channel())
ch.data := 123.U
ch.valid := true.B
val b = ch.valid
Chisel不允许给提取范围赋值,高低Byte示例:
val assignWord = Wire(UInt(16.W))
// assignWord(7, 0) := lowByte // Error
class Split extends Bundle {
val high = UInt(8.W)
val low = UInt(8.W)
}
val split = Wire(new Split())
split.low := lowByte
split.high := highByte
assignWord := split.asUInt // 类型转换
4.9. Vec
Chisel中使用Vec来表示一组相同类型信号的集合,也即一个向量(Vector,和C中的数组一个意思)。Vec可以通过索引来访问相应的元素。
val v = Wire(Vec(3, UInt(4.W))) // 构建3个UInt(4.W)类型的线网
v(0) := 1.U
v(1) := 3.U
v(2) := 5.U
val idx = 1.U(2.W)
val a = v(idx)
val registerFile = Reg(Vec(32, UInt(32.W))) // 构建32个UInt(32.W)的寄存器
registerFile(idx) := dIn
val dOur = registerFile(idx)
4.10. 线网
线网(Wire),在数据电路设计中,Wire相当连线,起辅助作用。Wire定义的对象,也可以理解为引用,是别名,并不会形成实际电路器件。
val number = Wire(UInt())
number := 10.U
val reg = Reg(SInt()) // =是构建对象
reg := number - 3.U // :=是连线,可以理解为将number-3.U接到寄存器的输入信号上
Wire声明初始化的简化形式:
val number = WireDefault(10.U(4.W))
4.11. IO
IO是Chisel中用于声明模块接口信号,即Verilog中的Port。Input和Output默认都是Wire类型。
class MyModule extends Module {
val io = IO(new Bundle {
val in = Input(UInt(8.W)) // 输入信号
val out = Output(UInt(8.W)) // 输出信号
})
io.out := io.in + 1.U
}
Chisel中没有inout类型,可以自定义一个类似的类型:
import chisel3._
class MyModule extends Module {
val io = IO(new Bundle {
val inout = new BidirectionalIO(UInt(8.W))
val out = Output(UInt(8.W))
})
io.out := io.inout.data + 1.U
}
class BidirectionalIO[T <: Data](gen: T) extends Bundle {
val data = Input(gen)
val enable = Input(Bool())
val output = Output(gen)
val direction = Output(Bool())
}
object Main extends App {
chisel3.Driver.execute(args, () => new MyModule)
}
4.12. 包
包(Package)是Chisel中管理代码组织结构的。
4.12.1. 创建包
直接在文件中首行指定包名即创建了一个包,多个文件可以共用一个包名。下例为一个mypack包下一个Abc类。
package mypack
import chisel3._
class Abc extends Module {
val io = IO(new Bundle{})
}
4.12.2. 导入包
导入整个包:
import mypack._ // 利用通配符—导入整个包的内容
class AbcUser extends Module {
val io = IO(new Bundle{})
val abc = new Abc()
}
导入指定包的指定模块:
import mypack.Abc
class AbcUser extends Module {
val io = IO(new Bundle{})
val abc = new Abc()
}
4.13. 模块
Chisel使用模块来描述硬件电路,等价于Verilog中的模块(Module)。示例:
class MyModule extends Module {
val io = IO(new Bundle {
val in = Input(UInt(8.W))
val out = Output(UInt(8.W))
})
// 硬件逻辑
io.out := io.in + 1.U
}
Verilog示例:
module MyModule(
input wire [7:0] in, // Port,输入信号
output wire [7:0] out // Port,输出信号
);
assign out = in + 1;
endmodule
4.14. switch/is
switch/is可以转换为一个复合的多路径选择器电路。示例:
class ALU extends Module {
val io = IO(new Bundle {
val a = Input(UInt(16.W))
val b = Input(UInt(16.W))
val fn = Input(UInt(2.W))
val y = Output(UInt(16.W))
})
io.y := 0.U
switch(io.fn) {
is(0.U) {io.y := io.a + io.b}
is(1.U) {io.y := io.a - io.b}
is(2.U) {io.y := io.a | io.b}
is(3.U) {io.y := io.a & io.b}
}
}
Verilog:
module ALU(
input clock,
input reset,
input [15:0] io_a,
input [15:0] io_b,
input [1:0] io_fn,
output [15:0] io_y
);
wire [15:0] _io_y_T_1 = io_a + io_b;
wire [15:0] _io_y_T_3 = io_a - io_b;
wire [15:0] _io_y_T_4 = io_a | io_b;
wire [15:0] _io_y_T_5 = io_a & io_b;
wire [15:0] _GEN_0 = 2'h3 == io_fn ? _io_y_T_5 : 16'h0;
wire [15:0] _GEN_1 = 2'h2 == io_fn ? _io_y_T_4 : _GEN_0;
wire [15:0] _GEN_2 = 2'h1 == io_fn ? _io_y_T_3 : _GEN_1;
assign io_y = 2'h0 == io_fn ? _io_y_T_1 : _GEN_2;
endmodule
4.15. Bulk Connection
Verilog中的Port经常遇到非常多的信号,两个模块间的信号连接需要编写大量类似的代码,而且容易出错。Chisel提供一种块连接(Buld Connection)操作符<>来快速连接模块中的端口信号,提升效率,减少出错。一个简单流水线示例,两个块连接的Bundle必须输入输出相匹配,这里可以用内置函数Flipped来将IO类型反转。示例代码:
class CommIO extends Bundle {
val instr = Output(UInt(32.W))
val pc = Output(UInt(32.W))
}
class Fetch extends Module {
val io = IO(new Bundle {
val a = Input(UInt(32.W))
val b = Input(UInt(32.W))
val commIO = new CommIO()
})
io.commIO.instr := io.a
io.commIO.pc := io.b
}
class Decode extends Module {
val io = IO(new Bundle {
val commIO = new CommIO()
val inIO = Flipped(new CommIO())
})
io.commIO.instr := io.inIO.instr + 1.U
io.commIO.pc := io.inIO.pc+ 1.U
}
class Top extends Module {
val io = IO(new Bundle {
val result = Input((UInt(32.W)))
val out = Output(Bool())
})
val fetch = Module(new Fetch())
val decode = Module(new Decode())
fetch.io.a := io.result + 2.U
fetch.io.b := io.result + 3.U
decode.io.inIO <> fetch.io.commIO
io.out := decode.io.commIO.pc.orR | decode.io.commIO.instr.orR
}
4.16. 函数
模块内的可以使用函数来复用代码,如:
class testModule extends Module {
def adder (x: UInt, y: UInt) = { // 定义一个加法函数
x + y // 最后一个值作为返回值
}
val io = IO(new Bundle {
val a = Input(UInt(32.W))
val b = Input(UInt(32.W))
val out = Output(UInt(32.W))
})
val out = adder(io.a, io.b)
io.out := out
}
4.17. Chisel中使用Verilog
有一些最佳实践的Verilog代码,Chisel中希望拿过来直接使用。有2种方法使用Verilog,分别为:
4.17.1. 内联Verilog代码
Chisel代码:
class BlockBoxAdderIO extends Bundle {
val a = Input(UInt(32.W))
val b = Input(UInt(32.W))
val cin = Input(Bool())
val c = Output(UInt(32.W))
val cout = Output(Bool())
}
class InlineBlackBoxAdder extends HasBlackBoxInline {
val io = IO(new BlockBoxAdderIO)
setInline("InlineBlackBoxAdder.v",
s"""
|module InlineBlackBoxAdder(a, b, cin, c, cout);
|input [31:0] a, b;
|input cin;
|output [31:0] c;
|output cout;
|wire [32:0] sum;
|
|assign sum = a + b + cin;
|assign c = sum[31:0];
|assign cout = sum[32];
|
|endmodule
""".stripMargin)
}
class Top extends Module {
val io = IO(new BlockBoxAdderIO)
val adder = Module(new InlineBlackBoxAdder)
adder.io.a := 0.U
adder.io.b := 0.U
adder.io.cin := false.B
io.c := adder.io.c
io.cout := adder.io.cout
}
Verilog代码,两个文件:
module InlineBlackBoxAdder(a, b, cin, c, cout);
input [31:0] a, b;
input cin;
output [31:0] c;
output cout;
wire [32:0] sum;
assign sum = a + b + cin;
assign c = sum[31:0];
assign cout = sum[32];
endmodule
module Top(
input clock,
input reset,
input [31:0] io_a,
input [31:0] io_b,
input io_cin,
output [31:0] io_c,
output io_cout
);
wire [31:0] adder_a;
wire [31:0] adder_b;
wire adder_cin;
wire [31:0] adder_c;
wire adder_cout;
InlineBlackBoxAdder adder (
.a(adder_a),
.b(adder_b),
.cin(adder_cin),
.c(adder_c),
.cout(adder_cout)
);
assign io_c = adder_c;
assign io_cout = adder_cout;
assign adder_a = 32'h0;
assign adder_b = 32'h0;
assign adder_cin = 1'h0;
endmodule
4.17.2. 加载Verilog文件
InlineBlackBoxAdder.v文件存放在src/main/resources/ResourceBlackBoxAdder.v。
class BlockBoxAdderIO extends Bundle {
val a = Input(UInt(32.W))
val b = Input(UInt(32.W))
val cin = Input(Bool())
val c = Output(UInt(32.W))
val cout = Output(Bool())
}
class ResourceBlackBoxAdder extends HasBlackBoxResource {
val io = IO(new BlockBoxAdderIO)
addResource("/ResourceBlackBoxAdder.v")
}
class Top extends Module {
val io = IO(new BlockBoxAdderIO)
val adder = Module(new ResourceBlackBoxAdder)
adder.io.a := 0.U
adder.io.b := 0.U
adder.io.cin := false.B
io.c := adder.io.c
io.cout := adder.io.cout
}
Chisel会将src/main/resources/ResourceBlackBoxAdder.v考虑到生成目录中。
Verilog:
module Top(
input clock,
input reset,
input [31:0] io_a,
input [31:0] io_b,
input io_cin,
output [31:0] io_c,
output io_cout
);
wire [31:0] adder_a;
wire [31:0] adder_b;
wire adder_cin;
wire [31:0] adder_c;
wire adder_cout;
ResourceBlackBoxAdder adder (
.a(adder_a),
.b(adder_b),
.cin(adder_cin),
.c(adder_c),
.cout(adder_cout)
);
assign io_c = adder_c;
assign io_cout = adder_cout;
assign adder_a = 32'h0;
assign adder_b = 32'h0;
assign adder_cin = 1'h0;
endmodule
4.18. when
除了swich这种条件选择方式来描述多路选择电路之处,还可以when来描述更灵活的多路选择逻辑电路。
val w = Wire(UInt())
when (cond) {
w := 1.U
} .otherwise {
w := 2.U
}
多层条件逻辑:
val w = Wire(UInt())
when (cond) {
w := 1.U
} .elsewhen (cond2) {
w := 2.U
} .otherwise {
w := 3.U
}
其对应的电路示意图:
4.19. 时序电路
时序电路需要时钟来驱动,并且一般需要使用上一个时钟的状态。时序电路一般用来设计计数器、状态机等电路。在Chisel中谈论的时序电路默认指同时时序电路。
如:
val regVal = RegInit(0.U(4.W))
regVal := inVal
电路图:
波形:
4.20. 参数化
Chisel的参数化是继承自Scala的,不仅支持值参数化,还是类型泛型参数化。
4.20.1. 值参数化
直接通过构造函数传递值参数,这种方式可以实现可变位宽。
class MyModule(width: Int) extends Module {
val io = IO(new Bundle {
val in = Input(UInt(width.W))
val out = Output(UInt(width.W))
})
val shifted = io.in << 2 // 将输入数据左移两位,偏移量为2
io.out := shifted
}
在实例化模块时,你可以直接传递参数值。
val myModule1 = Module(new MyModule(8)) // 创建一个8位宽的模块
val myModule2 = Module(new MyModule(16)) // 创建一个16位宽的模块
4.20.2. 类型参数化
上面的值参数化指定了类型,如果参数类型可变,其泛化能力更强,可以进一步提升代码的复用性。
class DecoupledIO[T <: Data](gen: T) extends Bundle {
val ready = Input(Bool())
val valid = Output(Bool())
val bits = Output(gen)
}
在实例化时:
val m1 = DecoupledIO(Uint(4.W))
val m2 = DecoupledIO(Sint(8.W))
4.20.3. 混合参数化
值参数化和类型参数化混合使用。
class DataBundle extends Bundle {
val a = UInt(32.W)
val b = UInt(32.W)
}
class Fifo[T <: Data](gen: T, n: Int) extends Module {
val io = IO(new Bundle {
val enqVal = Input(Bool())
val enqRdy = Output(Bool())
val deqVal = Output(Bool())
val deqRdy = Input(Bool())
val enqDat = Input(gen)
val deqDat = Output(gen)
})
val enqPtr = RegInit(0.U((log2Up(n)).W))
val deqPtr = RegInit(0.U((log2Up(n)).W))
val isFull = RegInit(false.B)
val doEnq = io.enqRdy && io.enqVal
val doDeq = io.deqRdy && io.deqVal
val isEmpty = !isFull && (enqPtr === deqPtr)
val deqPtrInc = deqPtr + 1.U
val enqPtrInc = enqPtr + 1.U
val isFullNext = Mux(doEnq && ~doDeq && (enqPtrInc === deqPtr),
true.B, Mux(doDeq && isFull, false.B,
isFull))
enqPtr := Mux(doEnq, enqPtrInc, enqPtr)
deqPtr := Mux(doDeq, deqPtrInc, deqPtr)
isFull := isFullNext
val ram = Mem(n, gen)
when (doEnq) {
ram(enqPtr) := io.enqDat
}
io.enqRdy := !isFull
io.deqVal := !isEmpty
ram(deqPtr) <> io.deqDat
}
实例化:
val fifo = Module(new Fifo(new DataBundle, 8))
4.21. 枚举类型
枚举类型可以提升代码的可读性,适用于match/case或switch/is。
object StoreFunct3 extends ChiselEnum {
val sb, sh, sw = Value
val ukn = Value(7.U)
}
4.22. BlackBox
像一些IP,无法直接引入Chisel。如果自行编写一个空的Module,将端口信号留空来模拟IP时,Chisel会报错。因为Chisel要求端口信号必须有连接。为了解决这个问题Chisel提供了BlackBox模块,可以实际模拟IP进行实例化。
4.22.1. 普通
class FakeMod extends BlackBox {
val io = IO(new Bundle {
val a = Input(UInt(32.W))
val clk = Input(Clock())
val reset = Input(Bool())
val b = Output(UInt(4.W))
})
}
class MyMod extends Module {
val io = IO(new Bundle {
val inA = Input(UInt(32.W))
val outB = Output(UInt(4.W))
})
val dut = Module(new FakeMod)
dut.io.a := io.inA
dut.io.clk := clock
dut.io.reset := reset
io.outB := dut.io.b
}
object Main extends App {
emitVerilog(
new MyMod,
Array(
"--emission-options=disableMemRandomization,disableRegisterRandomization",
"--info-mode=use",
"--target-dir=hdl",
"--full-stacktrace"
)
)
}
生成的Verilog:
module MyMod(
input clock,
input reset,
input [31:0] io_inA,
output [3:0] io_outB
);
wire [31:0] dut_a;
wire dut_clk;
wire dut_reset;
wire [3:0] dut_b;
FakeMod dut (
.a(dut_a),
.clk(dut_clk),
.reset(dut_reset),
.b(dut_b)
);
assign io_outB = dut_b;
assign dut_a = io_inA;
assign dut_clk = clock;
assign dut_reset = reset;
endmodule
4.22.2. 固定参数化
Verilog中的Module实例化时,很多是带有参数的。Chisel参数版本虚拟Module.
class IBUFDS extends BlackBox(Map("DIFF_TERM" -> "TRUE",
"DELAY" -> 5)) {
val io = IO(new Bundle {
val O = Output(Clock())
val I = Input(Clock())
})
}
class Top extends Module {
val io = IO(new Bundle {
val a = Output(UInt(4.W))
})
val ibufds = Module(new IBUFDS)
ibufds.io.I := clock
io.a := 4.U
}
生成的Verilog:
module Top(
input clock,
input reset,
output [3:0] io_a
);
wire ibufds_O;
wire ibufds_I;
IBUFDS #(.DELAY(5), .DIFF_TERM("TRUE")) ibufds (
.O(ibufds_O),
.I(ibufds_I)
);
assign io_a = 4'h4;
assign ibufds_I = clock;
endmodule
4.22.3. 动态参数化
在实例化的时候指定参数大小。
class IBUFDS(val delay: Int) extends BlackBox(Map("DIFF_TERM" -> "TRUE",
"DELAY" -> delay)) {
val io = IO(new Bundle {
val O = Output(Clock())
val I = Input(Clock())
})
}
class Top extends Module {
val io = IO(new Bundle {
val a = Output(UInt(4.W))
})
val ibufds = Module(new IBUFDS(10))
ibufds.io.I := clock
io.a := 4.U
}
生成的Verilog:
module Top(
input clock,
input reset,
output [3:0] io_a
);
wire ibufds_O;
wire ibufds_I;
IBUFDS #(.DELAY(10), .DIFF_TERM("TRUE")) ibufds (
.O(ibufds_O),
.I(ibufds_I)
);
assign io_a = 4'h4;
assign ibufds_I = clock;
endmodule
5. 内置方法
Chisel中的内置方法是以Scala 中的单例对象来实现的。
5.1. Reg
- RegEnable,创建可使能的寄存器。
- ShiftRegister,创建移位寄存器。
- ShiftRegister,创建Vec的移位寄存器。
5.2. OneHot
OHToUInt("b0100".U) // results in 2.U
UIntToOH(2.U) // results in "b0100".U
5.3. Mux
MuxCase(default, Array(c1 -> a, c2 -> b))
MuxLookup(idx, default)(Seq(0.U -> a, 1.U -> b))
MuxLookup(myEnum, default)(Seq(MyEnum.a -> 1.U, MyEnum.b -> 2.U, MyEnum.c -> 3.U))
val hotValue = PriorityMux(Seq(
io.selector(0) -> 2.U,
io.selector(1) -> 4.U,
io.selector(2) -> 8.U,
io.selector(4) -> 11.U,
))
val hotValue = Mux1H(Seq(
io.selector(0) -> 2.U,
io.selector(1) -> 4.U,
io.selector(2) -> 8.U,
io.selector(4) -> 11.U,
))
5.4. Lookup
ListLookup 和 Lookup 都是 Chisel 中用于根据地址选择不同值的工具。它们的功能类似于 Mux,但可以根据更复杂的方式进行选择。
ListLookup(2.U, // address for comparison
List(10.U, 11.U, 12.U), // default "row" if none of the following cases match
Array(BitPat(2.U) -> List(20.U, 21.U, 22.U), // this "row" hardware-selected based off address 2.U
BitPat(3.U) -> List(30.U, 31.U, 32.U))
) // hardware-evaluates to List(20.U, 21.U, 22.U)
Lookup(2.U, 0.U, Seq(
BitPat(2.U) -> 20.U,
BitPat(3.U) -> 30.U
)) // hardware-evaluates to 20.U
5.5. GradCode
2进制和格雷码互相转换,BinaryToGray 和 GrayToBinary 。
5.6. Counter
快速创建计数器。
class MyModule extends Module {
val io = IO(new Bundle {
val enable = Input(Bool())
val count = Output(UInt(8.W))
})
val counter = Counter(10) // 创建一个计数范围为 0 到 9 的计数器
when(io.enable) {
counter.inc() // 在每个时钟周期内增加计数器的值
}
io.count := counter.value
}
5.7. Bitwise
- FillInterleaved,逐步填充。
* FillInterleaved(2, "b1 0 0 0".U) // equivalent to "b11 00 00 00".U
* FillInterleaved(2, "b1 0 0 1".U) // equivalent to "b11 00 00 11".U
* FillInterleaved(2, myUIntWire) // dynamic interleaved fill
*
* FillInterleaved(2, Seq(false.B, false.B, false.B, true.B)) // equivalent to "b11 00 00 00".U
* FillInterleaved(2, Seq(true.B, false.B, false.B, true.B)) // equivalent to "b11 00 00 11".U
- PopCount,统计1或true个数
PopCount(Seq(true.B, false.B, true.B, true.B)) // evaluates to 3.U
PopCount(Seq(false.B, false.B, true.B, false.B)) // evaluates to 1.U
PopCount("b1011".U) // evaluates to 3.U
PopCount("b0010".U) // evaluates to 1.U
PopCount(myUIntWire) // dynamic count
- Fill,填充
Fill(2, "b1000".U) // equivalent to "b1000 1000".U
Fill(2, "b1001".U) // equivalent to "b1001 1001".U
Fill(2, myUIntWire) // dynamic fill
- Reverse,取反
Reverse("b1101".U) // equivalent to "b1011".U
Reverse("b1101".U(8.W)) // equivalent to "b10110000".U
Reverse(myUIntWire) // dynamic reverse
5.8. Math
Log2(8.U) // evaluates to 3.U
Log2(13.U) // evaluates to 3.U (truncation)
Log2(myUIntWire)
count := log2Up(4).asUint // 这里直接在编译时进行计算出结果
5.9. rand
// 创建一个 32 位的伪随机数发生器
val prng = random.LFSR(4)
io.count := prng
6. Chisel常见电路模块
6.1. 多路选择器
val y = Mux(sel, a, b)
当sel是个Chisel中的Bool类型值,为true的时候选择输出a,否则选择输出b。这里a和b可以是任意的Chisel基本类型或聚合类(比如bundle或vector),只要它俩的类型是一样的就行。
其电路示意图如下:
Chisel支持多种多路选择器,分别为:
MuxCase(default, Array(c1 -> a, c2 -> b, ...))
MuxLookup(idx, default)(Seq(0.U -> a, 1.U -> b, ...))
val hotValue = chisel3.util.Mux1H(Seq(
io.selector(0) -> 2.U,
io.selector(1) -> 4.U,
io.selector(2) -> 8.U,
io.selector(4) -> 11.U,
))
6.2. printf
Scala风格:
val myUInt = 33.U
printf(cf"myUInt = $myUInt") // myUInt = 33
// Hexadecimal
printf(cf"myUInt = 0x$myUInt%x") // myUInt = 0x21 // myUInt = 0x21
// Binary
printf(cf"myUInt = $myUInt%b") // myUInt = 100001 // myUInt = 100001
// Character
printf(cf"myUInt = $myUInt%c") // myUInt = !
C风格:
val myUInt = 32.U
printf("myUInt = %d", myUInt) // myUInt = 32
6.3. 计数器
- 可置位计数器:
实现简单,只支持正向计数,可能溢出。
class UpCounter(counterWidth: Int) extends Module {
val io = IO(new Bundle {
val count = Output(UInt(counterWidth.W))
})
val count = RegInit(0.U(counterWidth.W))
count := count + 1.U
io.count := count
}
- 加减计数器:
支持正反向计数,可能溢出。
class UpDownCounter(counterWidth: Int) extends Module {
val io = IO(new Bundle {
val count = Output(UInt(counterWidth.W))
val inc = Input(Bool())
val dec = Input(Bool())
})
val count = RegInit(0.U(counterWidth.W))
count := Mux(io.inc, count + 1.U, Mux(io.dec, count - 1.U, count))
io.count := count
}
- 环形计数器:
使用独热编码实现。
class RingCounter(counterWidth: Int) extends Module {
val io = IO(new Bundle {
val count = Output(UInt(counterWidth.W))
val reset = Input(Bool())
})
val count = RegInit(1.U(counterWidth.W))
when(io.reset) { count := 1.U }
.otherwise { count := Cat(count(counterWidth-2,0), count(counterWidth-1)) }
io.count := count
}
- 扭环形计数器:
每次也只变动1位,搞信号干扰好。
class TwistedRingCounter(counterWidth: Int) extends Module {
val io = IO(new Bundle {
val count = Output(UInt(counterWidth.W))
val reset = Input(Bool())
})
val count = RegInit(1.U(counterWidth.W))
when(io.reset) { count := 1.U }
.otherwise { count := Cat(count(0), count(counterWidth-1,1)) }
io.count := count
}
- 格雷码计数器:
一次只有一位变化,可以提升信号的准确性。但是消耗更多资源。
class GrayCounter(counterWidth: Int) extends Module {
val io = IO(new Bundle {
val count = Output(UInt(counterWidth.W))
})
val count = RegInit(0.U(5.W))
count := count + 1.U
io.count := (count >> 1.U) ^ count
}
6.4. 解码器
Decoder可以翻译为解码器或译码器,如2-4独热解码器可以将2位2进制编码转换为4位独热编码。
电路图:
真值表:
分析真值表,发现这种逻辑既可以用when表示,如:
result := 0.U
when (sel === 0.U) {
result := 1.U
}.elsewhen (sel === 1.U) {
result := 2.U
}.elsewhen (sel === 2.U) {
result := 4.U
}.otherwise {
result := 8.U
}
也可以用swich表示,如:
result := 0.U
switch (sel) {
is (0.U) { result := 1.U}
is (1.U) { result := 2.U}
is (2.U) { result := 4.U}
is (3.U) { result := 8.U}
}
6.5. 脉冲宽度调制
脉冲宽度调制(PWM,Pulse-Width Modulation)是一个信号处理的术语,用于将信号调制为常量周期且占空比在一定范围内的信号。
class Pwm extends Module {
val io = IO(new Bundle {
var inHigh = Input(UInt(4.W))
var inCycle = Input(UInt(4.W))
var dout = Output(Bool())
})
val cntReg = RegInit(0.U(io.inCycle.getWidth.W))
cntReg := Mux(cntReg === (io.inCycle-1.U), 0.U, cntReg + 1.U)
io.dout := io.inHigh > cntReg
}
class PwmTest extends AnyFlatSpec with ChiselScalatestTester {
"PWM" should "pass" in {
test(new Pwm).withAnnotations(Seq(WriteVcdAnnotation)) { dut =>
dut.io.inHigh.poke(4.U)
dut.io.inCycle.poke(10.U)
dut.clock.setTimeout(50001)
// Just let it run to generate the waveform
dut.clock.step(50000)
}
}
}
生成的波形如下,占空比40%。
6.6. 移位寄存器
利用寄存器加拼接功能,可以快速实现移位加拼接,适用于UART的读写操作。
class ShiftRegister extends Module {
val io = IO(new Bundle {
val din = Input(UInt(1.W))
val dout = Output(UInt(1.W))
})
val shiftReg = Reg(UInt(4.W))
shiftReg := shiftReg(2, 0) ## io.din
io.dout := shiftReg(3)
}
class ShiftRegisterTest extends AnyFlatSpec with ChiselScalatestTester {
"ShiftRegister" should "pass" in {
test(new ShiftRegister).withAnnotations(Seq(WriteVcdAnnotation)) { dut =>
dut.io.din.poke(1.U)
dut.clock.step()
dut.io.din.poke(0.U)
dut.clock.step(3)
dut.io.dout.expect(1.U)
dut.clock.step()
dut.io.dout.expect(0.U)
dut.clock.step()
dut.io.dout.expect(0.U)
}
}
}
6.7. 内存模块
6.7.1. 同步SRAM
Chisel有提供SyncReadMem构造SRAM。下面是一个8位通道,1024Bit的SRAM。SyncReadMem的实现是以Register来模拟的。实际使用FPGA可以通过Vivado中的IP核生成,芯片生产应用中常通过Memory Compiler生成来替换此模块。
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)
}
}
class MemoryTest extends AnyFlatSpec with ChiselScalatestTester {
"Memory" should "pass" in {
test(new Memory).withAnnotations(Seq(WriteVcdAnnotation)) { dut =>
dut.io.wrEna.poke(true.B)
for (i <- 0 to 10) {
dut.io.wrAddr.poke(i.U)
dut.io.wrData.poke((i*10).U)
dut.clock.step()
}
dut.io.wrEna.poke(false.B)
dut.io.rdAddr.poke(10.U)
dut.clock.step()
dut.io.rdData.expect(100.U)
dut.io.rdAddr.poke(5.U)
dut.io.rdData.expect(100.U)
dut.clock.step()
dut.io.rdData.expect(50.U)
// Same address read and write
dut.io.wrAddr.poke(20.U)
dut.io.wrData.poke(123.U)
dut.io.wrEna.poke(true.B)
dut.io.rdAddr.poke(20.U)
dut.clock.step()
println(s"Memory data: ${dut.io.rdData.peekInt()}")
}
}
}
6.7.2. 大容量
下面的示例是一个32位带宽,参数化容量的SRAM。
class DataMem(memAddrWidth: Int) extends Module {
val io = IO(new Bundle {
val rdAddr = Input(UInt(memAddrWidth.W))
val rdData = Output(UInt(32.W))
val wrAddr = Input(UInt(memAddrWidth.W))
val wrData = Input(UInt(32.W))
val wr = Input(Bool())
val wrMask = Input(UInt(4.W))
})
val mem = SyncReadMem(1 << memAddrWidth, Vec(4, UInt(8.W)))
val rdVec = mem.read(io.rdAddr)
io.rdData := rdVec(3) ## rdVec(2) ## rdVec(1) ## rdVec(0)
val wrVec = Wire(Vec(4, UInt(8.W)))
val wrMask = Wire(Vec(4, Bool()))
for (i <- 0 until 4) {
wrVec(i) := io.wrData(i * 8 + 7, i * 8)
wrMask(i) := io.wrMask(i)
}
when (io.wr) {
mem.write(io.wrAddr, wrVec, wrMask)
}
}
6.8. 去抖动电路
在电路启动时,信号可能不稳定,有一些抖动信号,需要去抖动(Debouncing)电路来过滤抖动信号。去抖动,主要通过多次采样,如果采样结构符合预期,即认为是消除抖动了。如下示例,指定采样时间,采样3个信号,有2个符合要求,即认为是成功,并且连续两次操作。
class Debounce(fac: Int = 100000000/100) extends Module {
val io = IO(new Bundle {
val btnU = Input(Bool())
val led = Output(UInt(8.W))
})
val btn = io.btnU
//- start input_sync
val btnSync = RegNext(RegNext(btn))
//- start input_debounce
val btnDebReg = Reg(Bool())
val cntReg = RegInit(0.U(32.W))
val tick = cntReg === (fac-1).U
cntReg := cntReg + 1.U
when (tick) {
cntReg := 0.U
btnDebReg := btnSync
}
//- end
//- start input_majority
val shiftReg = RegInit(0.U(3.W))
when (tick) {
// shift left and input in LSB
shiftReg := shiftReg(1, 0) ## btnDebReg
}
// Majority voting
val btnClean = (shiftReg(2) & shiftReg(1)) | (shiftReg(2) & shiftReg(0)) | (shiftReg(1) & shiftReg(0))
//- end
//- start input_usage
val risingEdge = btnClean & !RegNext(btnClean)
// Use the rising edge of the debounced and
// filtered button to count up
val reg = RegInit(0.U(8.W))
when (risingEdge) {
reg := reg + 1.U
}
//- end
io.led := reg
}
6.9. Finite-State Machine
有限状态机(FSM,Finite-State Machine),描述一组状态(states)和状态之间的条件状态转移(state transitions)。有限,表示状态的数量确定的,不是无限的。状态机的代码结构更清晰,更容易与时序结合,适合表示数据电路设计中的逻辑。照输出我们可以将将FSM分为moore型和mealy型两类。
状态机的状态码编码方式有,二进制码、独热码和格雷码,对信号要求高的采用独热码或格雷码。
6.9.1. Moore
Moore状态机的输出只依赖于当前状态,与输入无关。状态机会根据当前状态和输入信号来确定下一个状态,并且状态转移和输出信号变化都是在时钟上升沿同步发生。块图如下:
示例状态转换图如下:
对应的状态转换表:
输出Ring Bell只与当前状态是否为Red有关,所以这是Moore状态机。
Chisel代码:
class SimpleFsm extends Module {
val io = IO(new Bundle{
val badEvent = Input(Bool())
val clear = Input(Bool())
val ringBell = Output(Bool())
})
// The three states
object State extends ChiselEnum {
val green, orange, red = Value
}
// The state register
import State._
val stateReg = RegInit(green)
// Next state logic
switch (stateReg) {
is (green) {
when(io.badEvent) {
stateReg := orange
}
}
is (orange) {
when(io.badEvent) {
stateReg := red
} .elsewhen(io.clear) {
stateReg := green
}
}
is (red) {
when (io.clear) {
stateReg := green
}
}
}
// Output logic
io.ringBell := stateReg === red
}
波形,当状态变为01(orange)时,且外部输出事件badEvent使能,此时状态转换为red,输出结果为响铃。
6.9.2. Meely
Meely型状态机的输出不仅与当前状态有关,同时与输入有关。结构图如下:
下图是一个上升沿检测的状态转换图,只有从状态zero到one,并且外部输入为1的时候,输出结果才算检测到。
对应的Chisel代码:
class RisingFsm extends Module {
val io = IO(new Bundle{
val din = Input(Bool())
val risingEdge = Output(Bool())
})
// The two states
object State extends ChiselEnum {
val zero, one = Value
}
// The state register
import State._
val stateReg = RegInit(zero)
// default value for output
io.risingEdge := false.B
// Next state and output logic
switch (stateReg) {
is(zero) {
when(io.din) {
stateReg := one
io.risingEdge := true.B
}
}
is(one) {
when(!io.din) {
stateReg := zero
}
}
}
}
6.10. Arbiter
当同时有多个输入时,如何决定输出呢?此时就需要用仲裁器(Arbiter)。Chisel内置Arbiter模块,可以简化操作。
class ArbBundle extends Bundle{
val number = UInt(3.W)
}
class ArbExample extends Module{
val io = IO(new Bundle{
val requests = Flipped(Vec(4, Decoupled(new ArbBundle)))
val out = Decoupled(new ArbBundle)
})
val arbiter = Module(new Arbiter(new ArbBundle, 4))
arbiter.io.in <> io.requests
io.out <> arbiter.io.out
}
上面使用的是Arbiter,是一个组合电路,当有多个输入时,每次都是从最小开始判断仲裁结果。
RRArbiter是轮询的,是一个时序电路,从前往后,每次判断一个,保证每个得到相同的仲裁概率。
class ArbBundle extends Bundle{
val number = UInt(3.W)
}
class ArbExample extends Module{
val io = IO(new Bundle{
val requests = Flipped(Vec(4, Decoupled(new ArbBundle)))
val out = Decoupled(new ArbBundle)
})
val arbiter = Module(new RRArbiter(new ArbBundle, 4))
arbiter.io.in <> io.requests
io.out <> arbiter.io.out
}
6.11. FIFO
FIFO就是队列(Queue)数据结构,在数据电路设计中,多称为FIFO。FIFO及其变种形式是数字电路设计中用得非常多的一种数据结构。示例为一个标准的FIFO,其有两个参数,分别为Size表示数据的宽度,depth表示数据的深度(其实现是以多个FifoRegister来存储数据)。
class WriterIO(size: Int) extends Bundle {
val write = Input(Bool())
val full = Output(Bool())
val din = Input(UInt(size.W))
}
class ReaderIO(size: Int) extends Bundle {
val read = Input(Bool())
val empty = Output(Bool())
val dout = Output(UInt(size.W))
}
class FifoRegister(size: Int) extends Module {
val io = IO(new Bundle {
val enq = new WriterIO(size)
val deq = new ReaderIO(size)
})
object State extends ChiselEnum {
val empty, full = Value
}
import State._
val stateReg = RegInit(empty)
val dataReg = RegInit(0.U(size.W))
when(stateReg === empty) {
when(io.enq.write) {
stateReg := full
dataReg := io.enq.din
}
}.elsewhen(stateReg === full) {
when(io.deq.read) {
stateReg := empty
dataReg := 0.U // just to better see empty slots in the waveform
}
}.otherwise {
// There should not be an otherwise state
}
io.enq.full := (stateReg === full)
io.deq.empty := (stateReg === empty)
io.deq.dout := dataReg
}
class BubbleFifo(size: Int, depth: Int) extends Module {
val io = IO(new Bundle {
val enq = new WriterIO(size)
val deq = new ReaderIO(size)
})
val buffers = Array.fill(depth) { Module(new FifoRegister(size)) }
for (i <- 0 until depth - 1) {
buffers(i + 1).io.enq.din := buffers(i).io.deq.dout
buffers(i + 1).io.enq.write := ~buffers(i).io.deq.empty
buffers(i).io.deq.read := ~buffers(i + 1).io.enq.full
}
io.enq <> buffers(0).io.enq
io.deq <> buffers(depth - 1).io.deq
}
object Main extends App {
ChiselStage.emitSystemVerilogFile(
new BubbleFifo(8, 16),
firtoolOpts = Array("-disable-all-randomization", "-strip-debug-info", "--split-verilog", "-o", "hdl/")
)
}
Testbench代码:
class BubbleFifoTest extends AnyFlatSpec with ChiselScalatestTester {
behavior of "Bubble FIFO"
it should "pass" in {
test(new BubbleFifo(8, 4))
.withAnnotations(Seq(WriteVcdAnnotation)) { dut =>
// Some default signal values.
dut.io.enq.din.poke("hab".U)
dut.io.enq.write.poke(false.B)
dut.io.deq.read.poke(false.B)
dut.clock.step()
var full = dut.io.enq.full.peekBoolean()
var empty = dut.io.deq.empty.peekBoolean()
// Write into the buffer
dut.io.enq.din.poke("h12".U)
dut.io.enq.write.poke(true.B)
dut.clock.step()
full = dut.io.enq.full.peekBoolean()
dut.io.enq.din.poke("hff".U)
dut.io.enq.write.poke(false.B)
dut.clock.step()
full = dut.io.enq.full.peekBoolean()
dut.clock.step() // see the bubbling of the first element
// Fill the whole buffer with a check for full condition
// Only every second cycle a write can happen
for (i <- 0 until 7) {
full = dut.io.enq.full.peekBoolean()
dut.io.enq.din.poke((0x80 + i).U)
dut.io.enq.write.poke((!full).B)
dut.clock.step()
}
// Now we know it is full, so do a single read and watch
// how this empty slot bubbles up to the FIFO input.
dut.io.deq.read.poke(true.B)
dut.io.deq.dout.expect("h12".U)
dut.clock.step()
dut.io.deq.read.poke(false.B)
dut.clock.step(6)
// Now read out the whole buffer.
// Also watch that maximum read out is every second clock cycle.
for (i <- 0 until 7) {
empty = dut.io.deq.empty.peekBoolean()
dut.io.deq.read.poke((!empty).B)
dut.clock.step()
}
// Now write and read at maximum speed for some time.
for (i <- 1 until 16) {
full = dut.io.enq.full.peekBoolean()
dut.io.enq.din.poke(i.U)
dut.io.enq.write.poke((!full).B)
empty = dut.io.deq.empty.peekBoolean()
dut.io.deq.read.poke((!empty).B)
dut.clock.step()
}
}
}
}
波形中可以看出随着时钟的推进,每个存储的数据往后移动。
7. 生成Verilog/SystemVerilog
Chisel的版本升级比较快,版本之间的兼容性不太好。Chisel架构有两个,一个是Berkeley大学开的,主要用来生成Verilog,一个是chipsalliance开发的,主要用来生成SystemVerilog。
7.1. 生成Verilog
scalaVersion := "2.13.10"
scalacOptions ++= Seq(
"-deprecation",
"-feature",
"-unchecked",
"-Xfatal-warnings",
"-language:reflectiveCalls",
)
val chiselVersion = "3.6.0"
addCompilerPlugin("edu.berkeley.cs" %% "chisel3-plugin" % chiselVersion cross CrossVersion.full)
libraryDependencies += "edu.berkeley.cs" %% "chisel3" % chiselVersion
libraryDependencies += "edu.berkeley.cs" %% "chiseltest" % "0.6.2"
上面的配置只能兼容Verilator v5.002及之前的版本。后续Verilator版本与Chisel3不兼容。
Chisel代码:
class GrayCounter(counterWidth: Int) extends Module {
val io = IO(new Bundle {
val count = Output(UInt(counterWidth.W))
})
val count = RegInit(0.U(5.W))
count := count + 1.U
io.count := (count >> 1.U) ^ count
}
object Main extends App {
emitVerilog(
new GrayCounter(4),
Array(
"--emission-options=disableMemRandomization,disableRegisterRandomization",
"--info-mode=use",
"--target-dir=hdl",
"--full-stacktrace"
)
)
}
7.2. 生成SystemVerilog
chipsalliance架构,生成的SystemVerilog代码更简洁。
ThisBuild / scalaVersion := "2.13.12"
ThisBuild / version := "2.5.0"
ThisBuild / organization := "edu.berkeley.cs"
val chiselVersion = "6.2.0"
val chiseltestVersion = "6.0.0"
lazy val root = (project in file("."))
.settings(
name := "%NAME%",
libraryDependencies ++= Seq(
"org.chipsalliance" %% "chisel" % chiselVersion,
"org.scalatest" %% "scalatest" % "3.2.16" % "test",
"edu.berkeley.cs" %% "chiseltest" % chiseltestVersion % "test",
),
scalacOptions ++= Seq(
"-language:reflectiveCalls",
"-deprecation",
"-feature",
"-Xcheckinit",
"-Ymacro-annotations",
),
addCompilerPlugin("org.chipsalliance" % "chisel-plugin" % chiselVersion cross CrossVersion.full),
)
Chisel代码:
class FakeMod extends BlackBox {
val io = IO(new Bundle {
val a = Input(UInt(32.W))
val clk = Input(Clock())
val reset = Input(Bool())
val b = Output(UInt(4.W))
})
}
class MyMod extends Module {
val io = IO(new Bundle {
val inA = Input(UInt(32.W))
val outB = Output(UInt(4.W))
})
val dut = Module(new FakeMod)
dut.io.a := io.inA
dut.io.clk := clock
dut.io.reset := reset
io.outB := dut.io.b
}
object Main extends App {
ChiselStage.emitSystemVerilogFile(
new MyMod,
firtoolOpts = Array("-disable-all-randomization", "-strip-debug-info", "--split-verilog", "-o", "hdl/")
)
}