『chisel』透過最小專案理解 Chisel 專案結構

μSsia發表於2024-10-04

本文寫於 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 自身的特性。

相關文章