chisel
终于来到了敏捷型语言开发。chisel(Constructing Hardware in a Scala Embedded
Language)提供了更高层次的抽象,可以实现更快速的构建硬件,使得硬件更容易被理解和修改。
chisel’s env
要使用chisel首先要有java环境,每到这个环节我们一定要引入一个环境管理工具(因为依旧有多版本java的使用需求)。
这里引入的工具是SDKMAN!,他类似于nodejs的nvm和python的pyenv,可以使用SDKMAN!管理java环境的不同版本。
1 | curl -s "https://get.sdkman.io" | bash |
运行以上命令就可以下载SDKMAN!.
我们可以通过SDKMAN!来安装指定版本的OpenJDK.
1 | sdk install java 17.0.1-open # install openjdk 17.0.1 |
安装完成之后的切换版本:
1 | sdk list java |
如果要查看所有通过sdk已安装的java版本,可以查看~/.sdkman/candidates/java
chisel的运行需要scala,这两者的安装方式如下:
1 | sudo pacman -S sbt |
我们可以通过以下命令确认chisel环境准备就绪:
1 | curl -O -L https://github.com/chipsalliance/chisel/releases/latest/download/chisel-example.scala |
如果成功构建了chisel项目,则说明scala环境没有问题。
关于firtool,chisel6.0以后会自动配置,除非使用的是很老版本的OS;如果需要自己配置firtool,可以在~/.zshrc中加入:
1 | export CHISEL_FIRTOOL_PATH=/path/to/firtool |
scala-cli一般只适用于少数文件的组织,对于一个较大的项目,准备一个构建工具可以方便很多。sbt就是一个构建工具,印象中的chisel最开始就是用sbt来组织项目的,但是今天打开官方文档的时候发现现在更推荐使用mill,那我们也来尝尝新:
1 | yay -S mill |
verilator
如果需要接入verilator,还需要至少支持C++14和make的C++编译器,不过一般PC随便下个gcc就可以解决问题。
verilator的下载:
1 | sudo pacman -S verilator |
neovim
chisel本质还是scala,所以可以通过:
1 | :MasonInstall metals |
为nvim添加智能补全,跳转等功能。
syntax
对于一个没有怎么认真学过java的人来说,chisel这个语法确实是抽象。不过好在我们已经写过了DFT系列3-4,这个java的语法还是和UVM有一点相像
首先是最重要的继承语法,和UVM很像:
1 | //> using scala 2.13.18 |
在这里和UVM一样,用extends <base_class>完成类的继承。
最开头的//>用来引入依赖,分别指定了scala和chisel版本,比如此处使用了scala 2.13.18, chisel 7.11.0版本。不过chisel7已经被归档了,其实还是用chisel6更好一点。
class
同样是类似UVM,chisel也内置了很多class和method用来简化设计。有以下几个常用的class:
- Module
这是chisel中最基本的构建块,每个硬件设计模块都应该继承Module.
1 |
|
在上面的代码中,创建了一个MyModule的新模块,在这个模块中,创建了一个IO模块(名为io),传入的参数是一个Bundle类型,包括一个输入和输出,输入和输出都是8.W.
val在设计中常用于定义硬件信号,寄存器,模块等,他本质是scala的关键字,定义一个不可变的变量(const),此处是不可变绑定,也就是说val x,x的值可以改变,但是不能把他变成另一种类型或者指向其他对象。
UInt(8.W)代表一种数据类型,也就是无符号整数,传入的参数用来定义无符号整数的格式,此处定义了8.W,.W代表参数用于控制width,意味着8用于指定位宽。
- Bundle
用于组合多个信号端口,作为模块的输入输出接口。
- IO
用于声明模块的输入输出信号。
- UInt, SInt
代表无符号和有符号整数类型。
- Wire
用于定义组合逻辑中的信号,通常表示连线。
1 | val sum = Wire(UInt(8.W)) |
:=用于重新赋值(也就是第二次及以后的赋值)。
- Reg
用于定义时序逻辑的存储器,用来存储数据。
1 | val reg = Reg(UInt(8.W)) |
- RegInit
表示带有初始值的寄存器。
1 | val reg = RegInit(0.U(8.W)) |
初始值被设定为0.U(8.W),0被指定为.U,就是代表数值为无符号数0,8.W就是8位宽。
- Clock
定义时钟信号。
1 | val clk = Clock() |
- Reset
定义复位信号。
1 | val rst = Reset() |
- Cat
可以拼接多个信号。
1 | val a = UInt(4.W) |
- Mux
实现多路选择器(其实只能二选一,多路需要嵌套或者用其他方式比如Vec+options实现)。
1 | val sel = UInt(2.W) |
- Decoupled
用于流水线设计的数据通道,是可以设计数据流和控制信号分离的类型。
定义带有valid和ready信号的数据流通道。
1 | val Decoupled = Decoupled(UInt(8.W)) |
- Vec
一个向量类型。
1 | val v = Vec(4, UInt(8.W)) |
此外还有控制语句when/otherwise:
1 | when(io.in1 > 0.U){ |
for:
1 | for(i <- 0 until 4){ |
test
可以尝试写一个chisel版本的adder + tb.
adder.scala:
1 | //> using scala "2.13.12" |
adder_tb.scala:
1 | import chisel3._ |
此处test代码确实有点过于抽象了。
不过其实新的东西没有很多。首先是这个poke,看过DFT系列第四期就知道这个是写入的意思,当时UVM中frontdoor/backdoor,mirror/desired value概念很多容易混,但是这里的poke就是直接驱动信号,不绕过任何东西。
也就是说这里的poke就是UVM RAL里的write…
其次是新加的两个import,第一个import chiseltest._提供了poke expect clock.step等调试用方法和类,第二个import org.scalatest.flatspec.AnyFlatSpec,来自scalaTest,比如说很奇怪的一句:"Adder" should "add two numbers correctly",他是来自scala而不是chisel.
"Adder" should "add two numbers correctly"是用来描述测试的,"Adder" should "add two numbers correctly" in { ... }含义就是之后in之内的这个测试我要称他为:adder shoud add two numbers correctly,之后的in{}才表示具体怎么执行测试。最核心的一句是test(new Adder(8)) { c =>,在这里做了三件事,首先是实例化Adder用于之后的测试行为,其次是提供测试handle,c =>就是说Adder测试handle是c,类似于Adder c(.a(a), .b(b)),最后是自动生成电路并且启动仿真。
这个scala语法确实抽象,本质上是允许把should(“Adder”, “add two numbers correctly”)写成接近英语表述的形式。不过可以把"Adder" should "add two numbers correctly" in整体替换为test("adder should add two numbers correctly."),如下:
1 | import chisel3._ |
scala可能会下一大堆没什么用的东西(尤其是依赖写错的时候),可以通过rm -rf ~/.cache/coursier/ ~/.cache/scalacli/ ~/.ivy2/cache/删除。(有时候有路径污染也需要这个命令来重新构建)
run proj
对于已经写好的chisel项目,一般考虑scala-cli, mill, sbt的方式来运行。
scala-cli
scala-cli的常用命令:
1 | scala-cli compile . # compile files |
当然用.指定文件是很简陋的,之后文件架构组织起来还是需要对文件依次指定。
如果要生成verilog文件,需要在adder.scala添加一个main入口:
1 | //> using dep "org.chipsalliance::chisel:6.5.0" |
再运行scala-cli run . --main-class AdderMain,就会在当前路径下生成generated/Adder.sv.
如果只是想输出到终端:
1 | object AdderMain extends App { |
如果想要生成.v文件,可以在firtool加入:
1 | "--lowering-options=disallowLocalVariables,disallowPackedArrays,noAlwaysFF", |
或者使用emitVerilog.
如果想要生成多个模块到多个文件,多个实例化模块分别调用函数即可:
1 | object GenAll extends App { |
ChiselStage.emitSystemVerilogFile接收的三个参数是实例化模块,输出路径(一般指定这个就够了,这里其实应该是ChiselStage自身的参数),输出参数(传给firtool的参数)。
sbt
sbt不会自动构建(全部)文件架构,得要自己写。
最简单的方法是:
1 | mkdir myproject |
然后手动写两个文件,一个是build.sbt,描述环境依赖:
1 | scalaVersion := "2.13.12" |
一个是project/build.properties:
1 | sbt.version 1.12.10 |
然后把adder.scala写到src/main/scala,adder_tb.scala写到src/test/scala.
sbt其实可以构建模板结构,sbt new chipsalliance/chisel-template.g8,但是很老了。
常用命令:
1 | sbt compile |
mill
mill也要手动创建,不过可以稍微简单一点。
1 | mkdir myproject |
设计文件放到adder/src,测试adder/test/src.
build.mill:
1 | import mill._, scalalib._ |
常用命令:
1 | mill adder.compile |
verilator
chisel6+chiseltest默认后端是纯JVM仿真treadle2,如果想要切换到verilator,需要在测试文件中加入:
1 | import chiseltest._ |
这样verilator可以把chisel编译成C++仿真,速度会比treadle2快很多。


