上一大部分介绍了Chisel的基础语法,但除了教程开始的Demo以外,我们还没有开始写Chisel代码,这对于学习编程语言来说是大忌。不过好在Chisel基础语法部分内容并不算多,眼睛过一遍可能也掌握个大差不差了。但不能总这样,所以这一部分就来讲讲如何开始我们的Chisel项目。
那么构建Chisel项目我们需要了解哪些东西呢?
首先,我们得知道怎么编译Chisel程序吧?其次,我们得知道怎么用Chisel代码生成Verilog代码来在FPGA上执行吧?再者我们还得知道怎么写测试来调试和验证我们的代码是否正确吧?
Chisel是用Scala写的,所以支持构建Scala项目的过程应该在Chisel项目上也适用。而Scala上一个流行的构建工具就是sbt,它是Scala interactive Build Tool的缩写。这个sbt不仅能够驱动编译和测试过程,还能够给Scala和Chisel下载正确版本的库,也就是有包管理的功能。
目前Chisel流行的项目构建工具就是sbt和mill,我们这里用sbt构建我们的项目。
build.sbt
配置文件Scala库中与Chisel和Chisel tester相关的部分是在构建过程从一个Maven仓库中下载下来的,而这些库是通过build.sbt
文件引用的。Chisel项目中一个典型的build.sbt
文件内容如下:
// See README.md for license details.
ThisBuild / scalaVersion := "2.13.8"
ThisBuild / version := "0.1.0"
ThisBuild / organization := "com.github.github3rr0r"
val chiselVersion = "3.5.1"
lazy val root = (project in file("."))
.settings(
name := "OhMyChisel",
libraryDependencies ++= Seq(
"edu.berkeley.cs" %% "chisel3" % chiselVersion,
"edu.berkeley.cs" %% "chiseltest" % "0.5.1" % "test"
),
scalacOptions ++= Seq(
"-language:reflectiveCalls",
"-deprecation",
"-feature",
"-Xcheckinit",
"-P:chiselplugin:genBundleElements",
),
addCompilerPlugin("edu.berkeley.cs" % "chisel3-plugin" % chiselVersion cross CrossVersion.full),
)
我们可以在build.sbt
文件中配置latest.release
来指定使用最新的Chisel版本,但是这样做的缺点就是每次都会从在线的Maven仓库中再搜索,如果没有联网的话就会构建失败。所以最好的做法还是在build.sbt
中指定特定的Chisel版本,其他的Scala库也一样。
比如上面给出的典型build.sbt
文件中,Scala版本指定为"2.13.8"
,Chisel版本指定为"3.5.1"
,chiseltest
版本指定为"0.5.1"
。指定版本不仅仅避免了没网时的尴尬,同时也保持了较好项目稳定性,比如由于Chisel发展较快,所以API不兼容的情况就在Chisel上时有发生。
sbt继承了Maven构建自动化工具的源代码组织结构约定,同时Maven也是开源Java库的仓库的管理者,Chisel在Maven仓库中的仓库在这里:Maven Repository: edu.berkeley.cs » chisel3 (mvnrepository.com)。
下面这张图展现了典型的Chisel项目源代码组织结构:
project
文件夹就是项目根目录,你也可以起其它名字,这个根目录就包含了build.sbt
,也有可能会包含一个用于构建过程的Makefile
文件,还有可能会有README
文件和LICENSE
文件。
项目根目录中的src
文件夹包含了全部的源代码,其中划分了main
文件夹和test
文件夹,前者包含了所有的硬件源码,后者包含了所有的测试器代码。Chisel是从Scala继承的,Scala又是从Java继承了源码软件包的组织结构。包(Package)可以把我们的Chisel代码组织为命名空间,包也可以包含子包。
项目根目录下的target
文件夹包含了类字节码文件和其他生成的文件,通常我们不在这个文件夹放我们生成的Verilog文件,而是在根目录下的generated
文件夹下生成Verilog文件。
为了在Chisel中利用命名空间的便利性,我们需要声明我们在某个包下定义了一个类,比如下面的例子就是在mypack
包中定义了一个Abc
类:
package mypack
import chisel3._
class Abc extends Module {
val io = IO(new Bundle{})
}
需要注意的是,这里我们导入了chisel3
这个包来使用Chisel中的类。
那现在我们想在其他上下文中(即不同的包命名空间)使用模块Abc
,那么我们就需要导入mypack
包中的组件:
import mypack._
class AbcUser extends Module {
val io = IO(new Bundle{})
val abc = new Abc()
}
上面两段代码中的下划线_
表示通配符,意思是包里面所有的类都会导入。
也可以不从mypack
导入所有的类型,而是使用完整名称mypack.Abc
来指示mypack
包中的Abc
:
class AbcUser2 extends Module {
val io = IO(new Bundle{})
val abc = new mypack.Abc()
}
还可以只导入Abc
这一个类来创建实例:
import mypack.Abc
class AbcUser extends Module {
val io = IO(new Bundle{})
val abc = new Abc()
}
简简单单一条指令就可以用sbt编译并运行Chisel项目:
sbt run
这条指令会从源代码树编译所有的Chisel代码,并类中搜索包含main
方法的对象的类,或者更简单地直接搜索从App
拓展的对象。如果找到了多个对象,那就会列举出所有符合条件的对象,我们可以选择一个来执行。我们也可以直接指定执行某个对象,将它作为参数传递给sbt
即可:
sbt "runMain mypacket.MyObject"
sbt默认值搜索源码树中的main
部分,而不会搜索test
部分,这个约定也是从Java和Scala来的。如果要执行基于ChiselTest
和ScalaTest
的测试,直接运行这条代码就行:
sbt test
如果我们写一个测试,没有遵循ChiselTest
的约定且包含了一个main
函数,但是又把它放在了源代码树的test
文件夹下,我们可以这么执行:
sbt "test:runMain mypacket.MyMainTest"
这一篇介绍了sbt和怎么用sbt构建、运行Chisel项目,还介绍了Chisel项目的源代码组织结构,差不多就可以实践了。但在开始实践之前我们还有两个问题没有解决,一个是怎么生成Verilog代码,另一个是怎么写测试、运行测试。虽然这篇文章还提到了用sbt运行测试,但是怎么写完全不知道。接下来,我们还会有几篇文章,前一篇介绍Chisel怎么生成Verilog代码以及Chisel的工具流,后面几篇介绍怎么写Chisel测试和调试的四种姿势。