『chisel』通过最小项目理解 Chisel 项目结构

本文写于 2024年10月4日,此时 chisel 最新版本为 6.5.0 。

Overview

Chisel (Constructing Hardware In a Scala Embedded Language) 是新兴的硬件描述语言,是采用Scala作为基础、利用chisel第三方库的Domain Specific Language。此前第一次学习 Chisel 的时候教程推荐的是用 Scala-Cli 进行构建;但是日后肯定要用多文件进行构建,因此需要寻找能够进行工程级构建的方法。在网上找了许久总感觉不得其法, Chisel bootcampJupyter 用的 setup 代码也不能直接套过来用,很难受。在锐神视频的建议下决定自己找些 example 来学习,故有此文。锐神视频链接为: Chisel 入门 - 王锐 - 一生一芯双周分享会

获取 Github repo

首先通过这个链接获取 Github 项目模板并跟随指引部署到自己的 Github 仓库:Chisel Project Template
我把基于这个模板新建的repo命名为: chisel_helloworld 。这个仓库包含很多东西,其中我们需要关注的内容有:

chisel_helloworld
├── ...
├── README.md
├── src
│   ├── main
│   │   └── scala
│   │       ├── gcd
│   │       │   ├── DecoupledGCD.scala
│   │       │   └── GCD.scala
│   │       └── misc
│   │           └── Hello.scala
│   └── test
│       └── scala
│           └── gcd
│               └── GCDSpec.scala
├── build.sbt
└── build.sc

其中 src/main/scala/misc/Hello.scala 是我添加的文件,其内容为:

package misc

/*
 * This code is a minimal hardware described in Chisel.
 * 
 * Blinking LED: the FPGA version of Hello World
 */
import chisel3._

/**
 * The blinking LED component.
 */
class Hello extends Module {
  val io = IO(new Bundle {
    val led = Output(UInt(1.W))
  })
  val CNT_MAX = (50000000 / 2 - 1).U

  val cntReg = RegInit(0.U(32.W))
  val blkReg = RegInit(0.U(1.W))

  cntReg := cntReg + 1.U
  when(cntReg === CNT_MAX) {
    cntReg := 0.U
    blkReg := ~blkReg
  }
  io.led := blkReg
}

/**
 * An object extending App to generate the Verilog code.
 */
object Hello extends App {
  emitVerilog(new Hello())
}

这份代码来自 chisel-example 。我修改了两处:

  • 第一行的 package misc :在同一个 package 里面可以有很多份代码,会在后面再讨论这件事情
  • 倒数第二行的 emitVerilog(new Hello()) :顾名思义,这里是构建产生 .vverilog 文件的语句,其完整API路径是 chisel3.emitVerilog ,由于我们有一个 import chisel3._ 因此这里可以直接使用

构建项目得到 verilog 文件

Scala的构建工具有两个,一个是 SBT ,另一个是 mill 。从上面的文件目录树里可以看到有 build.sbtbuild.sc 两个文件,前者是 SBT 的配置文件,后者是 mill 的配置文件。只要有 build.sbt 就可以使用 SBT 进行构建;使用 mill 的项目则常常同时具有 build.sbtbuild.sc 。这两个文件是项目配置文件,打开 build.sbt 就能看到所用 Scala 以及 Chisel 的版本信息。
上面这个项目中的 build.sbt 文件长这样:

// See README.md for license details.

ThisBuild / scalaVersion     := "2.13.12"
ThisBuild / version          := "0.1.0"
ThisBuild / organization     := "com.github.playasmegumin"

val chiselVersion = "6.2.0"

lazy val root = (project in file("."))
  .settings(
    name := "chisel_helloworld",
    libraryDependencies ++= Seq(
      "org.chipsalliance" %% "chisel" % chiselVersion,
      "org.scalatest" %% "scalatest" % "3.2.16" % "test",
    ),
    scalacOptions ++= Seq(
      "-language:reflectiveCalls",
      "-deprecation",
      "-feature",
      "-Xcheckinit",
      "-Ymacro-annotations",
    ),
    addCompilerPlugin("org.chipsalliance" % "chisel-plugin" % chiselVersion cross CrossVersion.full),
  )

可以看到这里的 Chisel 版本是 6.2.0 。Chisel 的版本迭代非常快,在跑网络上的 demo 时如果出现编译问题请查看对应版本的 Chisel API ,链接在这: Docs - chisel-lang
因为没搞明白 mill 怎么使,这里我用的是 SBT ,直接在项目根目录运行 sbt run 即可。

$ sbt run
[info] welcome to sbt 1.9.7 (Eclipse Adoptium Java 17.0.12)
[info] loading settings for project chisel_helloworld-build from plugins.sbt ...
[info] loading project definition from /home/duzelong/ysyx/chisel/chisel_helloworld/project
[info] loading settings for project root from build.sbt ...
[info] set current project to chisel_helloworld (in build file:/home/duzelong/ysyx/chisel/chisel_helloworld/)
[info] compiling 1 Scala source to /home/duzelong/ysyx/chisel/chisel_helloworld/target/scala-2.13/classes ...

Multiple main classes detected. Select one to run:
 [1] gcd.GCD
 [2] misc.Hello

Enter number:

因为在 src/main/scala 下面有两个 “包” :

└── src
    └── main
        └── scala
            ├── gcd
            │   ├── DecoupledGCD.scala
            │   └── GCD.scala
            └── misc
                └── Hello.scala

所以这里 SBT 给了我们两个选项,一个是 gcd 包的 GCD ,另一个是 misc 包的 Hello 。这里需要辨析一下,包的归属和目录结构没有什么关系,主要是在 GCD.scalaHello.scala 分别用 package gcdpackage misc 定义了各自所属的包名。文件夹结构可以随你喜欢的设置,不会有很大的影响。一开始我还尝试了这样的放置方法:

└── src
    └── main
        └── scala
            ├── gcd
            │   ├── DecoupledGCD.scala
            │   └── GCD.scala
            └── Hello.scala

也并不影响文件和 package 的从属关系,这个 package 事实上很像命名空间。如果采用下面的目录结构,同时删除 src/main/scala/Hello.scalapackage misc

└── src
    └── main
        └── scala
            ├── gcd
            │   ├── DecoupledGCD.scala
            │   └── GCD.scala
            ├── misc
            │   └── Hello.scala
            └── Hello.scala         

则运行 sbt run 后会显示:

$ sbt run
[info] welcome to sbt 1.9.7 (Eclipse Adoptium Java 17.0.12)
[info] loading settings for project chisel_helloworld-build from plugins.sbt ...
[info] loading project definition from /home/duzelong/ysyx/chisel/chisel_helloworld/project
[info] loading settings for project root from build.sbt ...
[info] set current project to chisel_helloworld (in build file:/home/duzelong/ysyx/chisel/chisel_helloworld/)
[info] compiling 1 Scala source to /home/duzelong/ysyx/chisel/chisel_helloworld/target/scala-2.13/classes ...

Multiple main classes detected. Select one to run:
 [1] Hello
 [2] gcd.GCD
 [3] misc.Hello

Enter number:

可见,在不同包里的 Hello 模块不会互相冲突,但是如果两个 Hello.scala 中都有 package misc ,则会下面的结果:

$ sbt run
[info] welcome to sbt 1.9.7 (Eclipse Adoptium Java 17.0.12)
[info] loading settings for project chisel_helloworld-build from plugins.sbt ...
[info] loading project definition from /home/duzelong/ysyx/chisel/chisel_helloworld/project
[info] loading settings for project root from build.sbt ...
[info] set current project to chisel_helloworld (in build file:/home/duzelong/ysyx/chisel/chisel_helloworld/)
[info] compiling 1 Scala source to /home/duzelong/ysyx/chisel/chisel_helloworld/target/scala-2.13/classes ...
[info] compiling 2 Scala sources to /home/duzelong/ysyx/chisel/chisel_helloworld/target/scala-2.13/classes ...
[error] /home/duzelong/ysyx/chisel/chisel_helloworld/src/main/scala/misc/Hello.scala:14:7: Hello is already defined as class Hello
[error] class Hello extends Module {
[error]       ^
[error] /home/duzelong/ysyx/chisel/chisel_helloworld/src/main/scala/misc/Hello.scala:34:8: Hello is already defined as object Hello
[error] object Hello extends App {
[error]        ^
[error] two errors found
[error] (Compile / compileIncremental) Compilation failed
[error] Total time: 2 s, completed 2024年10月4日 下午7:29:02

就是很经典的符号冲突错误。
接上面的步骤,我们运行 sbt run 后选择 misc.Hello ,则有:

$ sbt run
[info] welcome to sbt 1.9.7 (Eclipse Adoptium Java 17.0.12)
[info] loading settings for project chisel_helloworld-build from plugins.sbt ...
[info] loading project definition from /home/duzelong/ysyx/chisel/chisel_helloworld/project
[info] loading settings for project root from build.sbt ...
[info] set current project to chisel_helloworld (in build file:/home/duzelong/ysyx/chisel/chisel_helloworld/)
[info] compiling 2 Scala sources to /home/duzelong/ysyx/chisel/chisel_helloworld/target/scala-2.13/classes ...

Multiple main classes detected. Select one to run:
 [1] Hello
 [2] gcd.GCD
 [3] misc.Hello

Enter number: 3
[info] running misc.Hello 
[success] Total time: 10 s, completed 2024年10月4日 下午7:31:03

然后会在根目录里发现多了一个 Hello.sv ,里面就是我们需要的 Verilog 代码,至此本文的目的就达成了。

Others

当然这里还有其他几个值得细究的点。

在哪里都能运行 sbt run 吗?

目前看来是不行,如果在 src/ 目录下运行 sbt run 就会有这样的后果:

src$ sbt run
[warn] No sbt.version set in project/build.properties, base directory: /home/duzelong/ysyx/chisel/chisel_helloworld/src
[info] welcome to sbt 1.10.2 (Eclipse Adoptium Java 17.0.12)
[info] set current project to src (in build file:/home/duzelong/ysyx/chisel/chisel_helloworld/src/)
[error] java.lang.RuntimeException: No main class detected.
[error] 	at scala.sys.package$.error(package.scala:30)
[error] stack trace is suppressed; run last Compile / bgRun for the full output
[error] (Compile / bgRun) No main class detected.
[error] Total time: 1 s, completed 2024年10月4日 下午7:32:24

看来在哪里运行 sbt runSBT 就会把哪里当作项目的根目录。

生成 Hello.sv 的位置可以改吗?

在项目根目录运行 sbt run 的时候就会在根目录生成 Hello.sv 。我猜测是直接把输出文件生成在了运行 sbt run 的目录。但是实际上只能在项目根目录运行 sbt run ,因此我的猜测没什么意义。
如果要修改生成 verilog 文件的路径,可能需要深入 SBT 的构建脚本,那就之后再看。

生成 verilog 文件的项目组件是什么?

似乎是用 FIRRTL 生成的,可以再看看。这块应该是 Chisel 自身的特性。