Chisel是一种用于硬件设计的语言. 它是一种HDL(Hardware Description Language), 而非HLS(High-Level Synthesis)语言. Chisel在纯Verilog设计的基础上引入了更多面向对象的特性, 以及更强大的类型系统. 虽然Chisel基于Scala这种高级语言, 如果此前只接触过高级语言而没有接触过Verilog的话, 可能看起来觉得Chisel比Verilog更好上手. 但实际上并非如此, 仍然建议先有一定的Verilog基础再学习Chisel.
到目前为止, Chisel的工业上的应用范围仍然极窄, 主要集中在FPGA设计和RISC-V指令集芯片上. 大多数开发工具链都不原生支持Chisel(基本就没有), 需要将Chisel代码编译成Verilog/SystemVerilog代码后才能进行综合和实现. 所以直到现在都不能说学了Chisel就有很直接的作用, 现阶段它更像是一个玩具, 或者给你提供一些硬件的代码架构设计思路.
安装Scala
Chisel是一个Scala的编译器插件, 它依赖于Scala实现. 目前Chisel仍然只支持到Scala 2.12.x版本, Scala 3.x版本仍然不在支持范围内.
安装步骤参考Scala Install. 推荐在Linux上或者WSL上使用Scala(对于大多数非特定于Windows平台的开发来讲, Linux的开发体验都更好)
Scala要求Java开发环境, 需要先安装JDK. 推荐版本17或者21或更高.
sudo apt install openjdk-21-jdk
sudo pacman -S jdk21-openjdk # On Arch-based Distros
接着下载Scala官方的版本管理器(类似rustup
, nvm
).
curl -fL https://github.com/coursier/coursier/releases/latest/download/cs-x86_64-pc-linux.gz | gzip -d > cs && chmod +x cs && ./cs setup
以上命令会在当前目录中下载一个名为cs
的可执行文件, 然后自动开始安装过程. 期间建议保持梯子连接(需要从Maven仓库下载依赖包). 安装完成后程序会提示将~/.local/share/coursier/bin
添加到环境变量中, 记得确认添加.
cs
会自动安装scala
和sbt
包管理器, 安装完成后通过查看版本号确认安装成功.
scala --version
# Scala code runner version: 1.5.4
# Scala version (default): 3.6.4
sbt --version
# sbt runner version: 1.10.11
配置开发环境
VSCode上通过Metals插件提供Scala的语言服务器支持. 另外推荐安装Scala Syntax插件, 提供更好的语法高亮和代码补全.
安装好这两个插件后能自动识别当前工作区内的build.sbt
文件, 并进行项目配置. 它会在检测到build.sbt
文件时右下角弹窗提示是否导入工程("Import Build"), 选择导入工程即可. 然后它会自动下载build.sbt
中指定的编译器插件, 依赖包等内容, 并完成项目配置.
但根据我的经验, Metals的开发体验并不是很好, 主要是它的语言服务器太容易崩了. 崩溃的情景包括不限于: 使用scalafmt
格式化代码时整个语言服务器挂掉, 单个文件中语法错误过多时直接挂掉, 使用中毫无预兆突然挂掉. 而且对于低版本的Scala支持不好, 偏偏很多Chisel版本依赖相对较旧的Scala版本.
所以个人觉得最好的开发环境是Intellij IDEA, 然后在上面安装JetBrains的Scala插件. 这个插件同样能自动检测工作区的sbt
配置并加载项目, 而且提供了稳定得多的语言服务器支持.
至于IDEA怎么配置就不多说了, 网上教程很多, 和CLion, GoLand这些JB家其它产品类似的. 学生认证可以免费用. 当然如果你说电脑内存不够, IDEA上来先吃2G内存顶不住, 那用VSCode也无妨, Metals挂掉的时候手动重启VSCode工作区就行了.
创建Chisel项目
截止本文撰写时, Chisel的最新版本已经到了6.7.0, 并且Chisel7已经有两个RC版本了. 但网上的大部分文档, 包括Chisel Bootcamp都没有更新, 还是基于Chisel 3.x版本的. 建议使用较新的Chisel版本, 因此本系列文章都会基于Chisel 6.7.0版本进行编写.
一个典型的Chisel项目结构如下:
chisel-project .git src main scala adder Adder.scala test scala adder AadderSpec.scala .gitignore build.sbt
略显麻烦, 但Java系的语言都是按照这个文件结构组织项目的, 也没什么办法. Adder.scala
是Chisel的模块设计文件, 这个文件中的顶层模块名称应该和文件名一致. AdderSpec.scala
是Chisel的测试文件, 对应的是Adder
模块的测试. 不过仿真和波形一般不直接用Chisel提供的ChiselSim框架, 而采用Verilator进行仿真(至少我的习惯是这样的). Verilator在后面会提及, 它的仿真速度和功能要远胜ChiselSim.
build.sbt
是Scala的构建工具sbt
的配置文件, 类似cargo.toml
. 如果要使用Git进行版本控制, .gitignore
中至少应该包含以下目录
.idea/
.vscode/
.metals/
.bloop/
out/
target/
generated # Chisel编译出的Verilog文件位置
接下来创建一个新的Chisel项目, 首先配置sbt
工具.
ThisBuild / scalaVersion := "2.13.16"
ThisBuild / version := "1.0.0"
ThisBuild / organization := "%ORGNIZATION%" // 组织名称
val chiselVersion = "6.7.0"
val scalatestVersion = "3.2.19"
lazy val root = (project in file("."))
.settings(
name := "chisel-project", // 项目名称
libraryDependencies ++= Seq(
"org.chipsalliance" %% "chisel" % chiselVersion,
"org.scalatest" %% "scalatest" % scalatestVersion % "test",
),
scalacOptions ++= Seq(
"-language:reflectiveCalls",
"-deprecation",
"-feature",
"-Xcheckinit",
"-Ymacro-annotations",
),
addCompilerPlugin("org.chipsalliance" %% "chisel-plugin" % chiselVersion cross CrossVersion.full),
)
你也可以自己指定Chisel版本和Scala版本, 但要确保这两者版本匹配, 否则sbt
没法从Maven上下载到正确的包. 要确定某个版本组合是否合理, 可以直接去Maven仓库看. 其中chisel-plugin
是Chisel的编译器插件. 举个例子, 在chisel-plugin 2.13.16
目录下只有6.7.0这一个版本, 表明Scala 2.13.16只能搭配Chisel 6.7.0使用. 如果没有特殊需求, 建议用最新的Chisel版本.
接下来在src/main/scala/adder
目录下创建一个新的Scala文件, 例如Adder.scala
, 内容如下:
package adder // 这里的包名和文件夹名要一致
import chisel3._
import circt.stage.ChiselStage._
class Adder extends RawModule {
val io = IO(new Bundle {
val a = Input(UInt(8.W))
val b = Input(UInt(8.W))
val sum = Output(UInt(8.W))
})
io.sum := io.a + io.b
}
object Main extends App {
emitSystemVerilogFile(new Adder, args = Array("--target-dir", "generated"))
}
然后在src/test/scala/adder
目录下创建一个新的Scala文件, 例如AdderSpec.scala
, 内容如下:
package adder
import chisel3._
import chisel3.simulator.EphemeralSimulator._
import org.scalatest.flatspec.AnyFlatSpec
class AdderSpec extends AnyFlatSpec {
behavior of "Adder"
it should "add two numbers" in {
val testCases = Seq(
(1.U, 2.U, 3.U),
(0.U, 0.U, 0.U),
(255.U, 1.U, 0.U), // overflow test
)
simulate(new Adder) { dut =>
testCases.foreach { case (a, b, sum) =>
dut.io.a.poke(a)
dut.io.b.poke(b)
dut.io.sum.expect(sum)
println(s"Test case: a = $a, b = $b, sum = ${dut.io.sum.peek().litValue}")
}
println("All test cases passed.")
}
}
}
关于这些代码的具体含义, 我们留到后面再讲. 现在只要知道为了将Chisel代码编译成Verilog代码, 需要在Main
对象中调用emitSystemVerilogFile
方法. 这个方法的第一个参数是要编译的Chisel对象, 第二个参数是编译选项, 这里我们指定了输出目录为generated
.
接下来在终端输入sbt runMain
命令, 会运行Main
对象中的代码. 如果一切顺利, 会在generated
目录下生成一个Adder.sv
文件, 这个文件就是Chisel编译后的Verilog代码.
sbt runMain adder.Main # adder是包名, Main是对象名
# 或者
sbt run
为了运行测试, 可以输入sbt test
命令, 这会运行src/test/scala/adder/AdderSpec.scala
中的测试代码. 如果测试通过, 会输出代码中的通过提示.
sbt testOnly adder.AdderSpec
# 或者
sbt test
如果一切顺利, 现在你应该能理解Chisel的基本项目文件结构了. 接下来将正式开始介绍Chisel的基本语法和用法.
sbt
需要依赖JVM, 如果每次编译完Chisel/测试完都关闭sbt
, 下一次再重新启动可能会浪费一些性能和时间在JVM上. 所以建议在终端中直接输入sbt
进入sbt
命令行界面, 每次要编译Chisel的时候输入run
即可, 要测试的时候输入test
即可. 如果你使用的是VSCode/IDEA提供的"构建"按钮, 它们就是每次构建/测试都要重启sbt
的. 如果在意每次都重启JVM多花的时间, 手动在sbt
命令行中输入run
/test
即可.
可选: 配置Verilator
Verilator是一个用来进行仿真, 功能验证和查看波形的开源工具. 最大的特点之一是它将Verilog代码转换为C++代码, 并且允许用C++写Testbench. 这样就让Testbench的扩展性非常强, 你可以轻松将硬件设计融入一整个Workflow中. 而且由于仿真程序是用C++编译出来的, 它的仿真速度非常快(对于一些复杂设计而言很有优势). 但缺点是对于各家私有IP核支持一般, 如果厂商提供了IP的Verilator C++库, 那仿真功能不会受到影响, 否则完全无法对设计中的IP核部分进行仿真. 不过Verilator还是一个很通用的工具, 不少公司内部也有在使用.
要使用Verilator, 需要先安装CMake和GCC编译器(也可以选择手写Makefile, 不推荐使用Clang系编译器)
sudo apt install build-essential cmake
sudo pacman -S --needed base-devel cmake # On Arch-based Distros
接着安装Verilator
sudo apt install verilator
sudo pacman -S verilator # On Arch-based Distros
Verilator可以对手写的Verilog代码进行静态检查(Lint), 有助于发现硬件设计中的潜在问题. 不过这里主要讲Chisel, 不会直接手写Verilog代码, 所以这里可能用不到这个功能, 只是顺带一提.
verilator --lint-only /path/to/verilog_file.v
Verilator仿真程序的编译流程通过CMake进行管理, 大概可以有这样的文件目录结构:
test src rtl Adder.sv tb Adder_tb.cpp CMakeLists.txt
其中Adder.sv
是RTL设计文件, 然后在CMakeLists.txt
中配置Verilator. 这是一个最简示例, 根据需要可以添加更多CMake选项.
cmake_minimum_required(VERSION 3.10)
project(AdderTestbench)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # for clangd
find_package(verilator HINTS $ENV{VERILATOR_ROOT} REQUIRED)
set(VERILOG_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/rtl/Adder.sv)
set(TB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/tb/Adder_tb.cpp)
add_executable(tb ${TB_SRC})
verilate(tb
TRACE # 打印波形
TOP_MODULE Adder # 指定顶层模块名称
SOURCES ${VERILOG_SRC}
VERILATER_ARGS -O2
)
为了让CMake正确配置项目, 你需要先有一个Adder.sv
文件, 否则CMake会报错找不到文件. 配置项目的过程中Verilator会为Verilog设计文件生成对应的头文件, 在Testbench中会用到.
一个Testbench文件通常可以有以下的结构:
#include "verilated.h"
#include "verilated_vcd_c.h"
#include "VAdder.h" // Verilator生成的头文件
#include <iostream>
#define MAX_SIM_TIME 1000
#define VTOP VAdder // 顶层模块名称
uint64_t sim_time = 0;
int main(int argc, char **argv, char **env) {
Verilated::commandArgs(argc, argv);
Verilated::traceEverOn(true); // 打开波形输出
VTOP *top = new VTOP;
VerilatedVcdC *tfp = new VerilatedVcdC;
top->trace(tfp, 99); // 设置波形追踪层数
tfp->open("waveform.vcd"); // 打开波形文件
bool failed = false;
while (sim_time < MAX_SIM_TIME) {
// 向顶层模块输入信号, 例如
// top->a = 1;
// top->b = 1;
top->eval(); // 评估顶层模块
tfp->dump(sim_time); // 记录波形
if (!/* 检查功能是否正确的条件, 比如 top->sum = 2 */) {
std::cout << "Test failed at time " << sim_time << '\n';
std::cout << "Expected: " << /* 预期值 */ << '\n';
std::cout << "Got: " << /* 实际值 */ << '\n';
failed = true;
}
}
top->final();
tfp->close(); // 关闭波形文件
// 清理资源
delete top;
delete tfp;
// 通过返回值表明测试是否通过, 这在工具链中很有用
return failed;
}
如果已经准备好了Adder.sv
文件和Adder_tb.cpp
文件, 那可以编译仿真程序了.
mkdir build && cd build
cmake ..
cmake --build . -j 0 # 0表示使用所有CPU核心进行编译
./tb # 运行仿真程序
然后应该能看到仿真程序的输出, 测试通过还是没通过. 然后在tb
所在的目录下会生成一个waveform.vcd
文件, 这个文件就是Verilator生成的波形文件. 可以用GTKWave等工具打开查看波形.
sudo apt install gtkwave
sudo pacman -S gtkwave # On Arch-based Distros
gtkwave waveform.vcd
到此应该大致清楚Verilator进行硬件设计仿真的流程了. 可以把Chisel和Verilator配合起来使用, 用Chisel生成Verilog文件, 再将Verilog文件添加到Verilator项目中进行仿真.
在GitHub上我提供了这一章用到的源代码文件Chisel-Bootcamp, 里面还添加了一些CMake自定义目标和一个顶层Makefile方便代码编译和运行. 你可以在Makefile所在目录中执行make compile
编译Chisel代码, make sim
对编译出的代码进行仿真, make show
查看波形. 或者可以直接执行make
编译Chisel代码并进行仿真. 希望这有助于你对这一节的整个工作流程(Workflow)的理解.