『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 bootcamp 中 Jupyter 用的 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())
:顾名思义,这里是构建产生.v
的 verilog 文件的语句,其完整API路径是chisel3.emitVerilog
,由于我们有一个import chisel3._
因此这里可以直接使用
构建项目得到 verilog 文件
Scala的构建工具有两个,一个是 SBT ,另一个是 mill 。从上面的文件目录树里可以看到有 build.sbt
和 build.sc
两个文件,前者是 SBT 的配置文件,后者是 mill 的配置文件。只要有 build.sbt
就可以使用 SBT 进行构建;使用 mill 的项目则常常同时具有 build.sbt
和 build.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.scala
和 Hello.scala
分别用 package gcd
和 package misc
定义了各自所属的包名。文件夹结构可以随你喜欢的设置,不会有很大的影响。一开始我还尝试了这样的放置方法:
└── src
└── main
└── scala
├── gcd
│ ├── DecoupledGCD.scala
│ └── GCD.scala
└── Hello.scala
也并不影响文件和 package
的从属关系,这个 package
事实上很像命名空间。如果采用下面的目录结构,同时删除 src/main/scala/Hello.scala
的 package 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 run
, SBT 就会把哪里当作项目的根目录。
生成 Hello.sv
的位置可以改吗?
在项目根目录运行 sbt run
的时候就会在根目录生成 Hello.sv
。我猜测是直接把输出文件生成在了运行 sbt run
的目录。但是实际上只能在项目根目录运行 sbt run
,因此我的猜测没什么意义。
如果要修改生成 verilog 文件的路径,可能需要深入 SBT 的构建脚本,那就之后再看。
生成 verilog 文件的项目组件是什么?
似乎是用 FIRRTL 生成的,可以再看看。这块应该是 Chisel 自身的特性。