终于来到了敏捷型语言开发。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
2
3
sdk list java
sdk use java 17.0.1-open
sdk current java

如果要查看所有通过sdk已安装的java版本,可以查看~/.sdkman/candidates/java

chisel的运行需要scala,这两者的安装方式如下:

1
2
sudo pacman -S sbt
sdk install scalacli

我们可以通过以下命令确认chisel环境准备就绪:

1
2
curl -O -L https://github.com/chipsalliance/chisel/releases/latest/download/chisel-example.scala
scala-cli 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
2
yay -S mill
# sudo pacman -S sbt

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
2
3
4
5
6
7
8
//> using scala 2.13.18
//> using dep org.chipsalliance::chisel:7.11.0

import chisel3._

class MyModule extends Module{

}

在这里和UVM一样,用extends <base_class>完成类的继承。

最开头的//>用来引入依赖,分别指定了scala和chisel版本,比如此处使用了scala 2.13.18, chisel 7.11.0版本。

class

同样是类似UVM,chisel也内置了很多class和method用来简化设计。有以下几个常用的class:

  1. Module

这是chisel中最基本的构建块,每个硬件设计模块都应该继承Module.

1
2
3
4
5
6
7
8
9
10
11
12

//> using scala 2.13.18
//> using dep org.chipsalliance::chisel:7.11.0

import chisel3._

class MyModule extends Module{
val io = IO(new Bundle{
val in = Input(UInt(8.W)
val out = output(UInt(8.W)))
})
}

在上面的代码中,创建了一个MyModule的新模块,在这个模块中,创建了一个IO模块(名为io),传入的参数是一个Bundle类型,包括一个输入和输出,输入和输出都是8.W.

val在设计中常用于定义硬件信号,寄存器,模块等,他本质是scala的关键字,定义一个不可变的变量(const),此处是不可变绑定,也就是说val x,x的值可以改变,但是不能把他变成另一种类型或者指向其他对象。

UInt(8.W)代表一种数据类型,也就是无符号整数,传入的参数用来定义无符号整数的格式,此处定义了8.W.W代表参数用于控制width,意味着8用于指定位宽。

  1. Bundle

用于组合多个信号端口,作为模块的输入输出接口。

  1. IO

用于声明模块的输入输出信号。

  1. UInt, SInt

代表无符号和有符号整数类型。

  1. Wire

用于定义组合逻辑中的信号,通常表示连线。

1
2
val sum = Wire(UInt(8.W))
sum := io.in1 + io.in2

:=用于重新赋值(也就是第二次及以后的赋值)。

  1. Reg

用于定义时序逻辑的存储器,用来存储数据。

1
2
val reg = Reg(UInt(8.W))
reg := io.in1 + io.in2
  1. RegInit

表示带有初始值的寄存器。

1
2
val reg = RegInit(0.U(8.W))
reg := io.in1 + io.in2

初始值被设定为0.U(8.W)0被指定为.U,就是代表数值为无符号数0,8.W就是8位宽。

  1. Clock

定义时钟信号。

1
val clk = Clock()
  1. Reset

定义复位信号。

1
val rst = Reset()
  1. Cat

可以拼接多个信号。

1
2
3
val a = UInt(4.W)
val b = UInt(4.W)
val c = Cat(a, b)
  1. Mux

实现多路选择器(其实只能二选一,多路需要嵌套或者用其他方式比如Vec+options实现)。

1
2
3
val sel = UInt(2.W)
val out = Mux(sel === 1.U, io.in1, io.in2)
// val out = Mux(sel === 1.U, io.in1, Mux(sel === 2.U, io.in2, io.in3))
  1. Decoupled

用于流水线设计的数据通道,是可以设计数据流和控制信号分离的类型。
定义带有valid和ready信号的数据流通道。

1
val Decoupled = Decoupled(UInt(8.W))
  1. Vec

一个向量类型。

1
val v = Vec(4, UInt(8.W))

此外还有控制语句when/otherwise:

1
2
3
4
5
when(io.in1 > 0.U){
reg := io.in1
}.otherwise{
reg := 0.U
}