Chisel,说爱你不容易
很早就听说Chisel,伯克利的又一大作,也听说其学习门槛高,基于scala语言,语法非常灵活。近期的国科大搞的“一生一芯”计划,“香山”处理器等,更是将Chisel推向了又一个高潮,据包老师说,Chisel的开发效率远高于verilog香山高性能riscv处理器,这种提高生产力的语言,怎么能不学?
一、Chisel的难点
Chisel 早期的门槛有两个,一个是开发环境,另一个是从verilog转变。开发环境说来简单, 真搭起来还真不容易,我花了两三天时间才实现想要的效果:
- 产生电路的.v文件
- 产生.vcd文件查看波形
- 不产生波形,基于scala仿真
虽然网上的资料很多,但Chisel的更新很快,按照一些入门教程做,还不一定能跑通,报错也难搜到解决方案,毕竟用scala的人没有python多。
从verilog的转变倒是不难,chisel tutorial 提供了很多样例。推荐阅读顺序:
- 官网文档
- chisel book web
- chisel-getting-started,配套chisel tutorial但其仿真用的是iotests,不是最新的chiseltest
- chisel-bootcamp可以交叉着看
- chisel_cheatsheet.pdf
- chisel-book.pdf
二、环境配置
类似的教程网上有很多,一抓一大把,基本上是基于IntelliJIDEA,但我的虚拟机跑起来经常卡,于是转投vscode。IntelliJIDEA牛逼的地方在于,它会实时检查语法错误,如果一直没跑通,可以用它打开再debug。
我的环境是Ubuntu20.04 ,先装好java,sbt,verilator(如果中途报错,搜索就有答案)
sudo apt-get install default-jdk
sudo apt-get install verilator
sudo apt-get install sbt
如果装不上sbt,可以参考官网sbt download
echo "deb https://repo.scala-sbt.org/scalasbt/debian all main" | sudo tee /etc/apt/sources.list.d/sbt.list
echo "deb https://repo.scala-sbt.org/scalasbt/debian /" | sudo tee /etc/apt/sources.list.d/sbt_old.list
curl -sL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x2EE0EA64E40A89B84B2DF73499E82A75642AC823" | sudo apt-key add
sudo apt-get update
sudo apt-get install sbt
抱着极简的想法,我决定从零开始,建了这几个文件夹还有一个.sbt文件。
sbt
一直没懂sbt文件是做什么,直到现在才知道,它会下载之后工程需要用到的库,用于版本管理。为什么要学习SBT?这篇文章写得很详细,sbt解决了这些问题:
- 都是同样的代码,为什么在我的机器上可以编译执行,而在他的机器上就不行?
- 为什么在我的机器上可以正常打包,而配置管理员却打不出来?
- 项目组加入了新的人员,我要给他说明编译环境如何设置,但是让我挠头的是,有些细节我也记不清楚了。
- 我的项目依赖一些jar包,我应该把他们放哪里?放源码库里?
- 这是我开发的第二个项目,还是需要上面的那些jar包,再把它们复制到我当前项目的svn库里吧
- 现在是第三次,再复制一次吧 ----- 这样真的好吗?
- 我写了一个数据库相关的通用类,并且推荐给了其他项目组,现在已经有五个项目组在使用它了,今天我发现了一个bug,并修正了它,我会把jar包通过邮件发给其他项目组。
- 项目进入测试阶段,每天都要向测试服务器部署一版。每次都手动部署,太麻烦了。
build.sbt
该文件参考chisel book仓库的build.sbt,找了好久才找到这个既简洁,又支持新旧两种测试环境的sbt文件:
scalaVersion := "2.12.13"
scalacOptions ++= Seq(
"-deprecation",
"-feature",
"-unchecked",
"-Xfatal-warnings",
"-Xsource:2.11",
"-language:reflectiveCalls",
// Enables autoclonetype2
"-P:chiselplugin:useBundlePlugin"
)
resolvers ++= Seq(
Resolver.sonatypeRepo("snapshots"),
Resolver.sonatypeRepo("releases")
)
// Chisel 3.4
libraryDependencies += "edu.berkeley.cs" %% "chisel3" % "3.4.3"
libraryDependencies += "edu.berkeley.cs" %% "chisel-iotesters" % "1.5.3"
libraryDependencies += "edu.berkeley.cs" %% "chiseltest" % "0.3.3"
三、开发
生成verilog
接下来可以在/src/main/scala/examples的目录下创建文件FullAdder.scala,这是chisel-tutorial里的样例:
package examples
import chisel3._
class FullAdder extends Module {
val io = IO(new Bundle {
val a = Input(UInt(1.W))
val b = Input(UInt(1.W))
val cin = Input(UInt(1.W))
val sum = Output(UInt(1.W))
val cout = Output(UInt(1.W))
})
// Generate the sum
val a_xor_b = io.a ^ io.b
io.sum := a_xor_b ^ io.cin
// Generate the carry
val a_and_b = io.a & io.b
val b_and_cin = io.b & io.cin
val a_and_cin = io.a & io.cin
io.cout := a_and_b | b_and_cin | a_and_cin
}
// Generate Verilog file
import chisel3.stage.ChiselStage
object u_FullAdder extends App{
(new ChiselStage).emitVerilog(new FullAdder)
}
然后在mytest_a目录下运行sbt。runMain会自动在main目录的scala文件夹搜索,examples表示‘scala’下的目录,u_FullAdder是生成对象。
sbt 'runMain examples.u_FullAdder'
这样会在mytest_a下产生.v,.fir,.json 文件。.v文件长这样
module FullAdder(
input clock,
input reset,
input io_a,
input io_b,
input io_cin,
output io_sum,
output io_cout
);
wire a_xor_b = io_a ^ io_b; // @[FullAdder.scala 15:22]
wire a_and_b = io_a & io_b; // @[FullAdder.scala 18:22]
wire b_and_cin = io_b & io_cin; // @[FullAdder.scala 19:24]
wire a_and_cin = io_a & io_cin; // @[FullAdder.scala 20:24]
assign io_sum = a_xor_b ^ io_cin; // @[FullAdder.scala 16:21]
assign io_cout = a_and_b | b_and_cin | a_and_cin; // @[FullAdder.scala 21:34]
endmodule
仿真
仿真有两个版本,一种是旧版,基于chisel3.iotesters,chisel-tutorial的样例仿真是基于它的,所以也得学。另一种是新版,基于chiseltest,两种相差不大,这篇知乎讲得很好chisel生成Verilog与基本测试,本文很多也是参考这篇的。
chisel3.iotesters
在/src/test/scala/examples的目录下创建文件FullAdderTest.scala,如下:
package examples
import chisel3.iotesters.{PeekPokeTester, Driver, ChiselFlatSpec}
class FullAdderTests(c: FullAdder) extends PeekPokeTester(c) {
for (t <- 0 until 4) {
val a = rnd.nextInt(2)
val b = rnd.nextInt(2)
val cin = rnd.nextInt(2)
val res = a + b + cin
val sum = res & 1
val cout = (res >> 1) & 1
poke(c.io.a, a)
poke(c.io.b, b)
poke(c.io.cin, cin)
step(1)
expect(c.io.sum, sum)
expect(c.io.cout, cout)
}
}
object FullAdderTestGen extends App{
chisel3.iotesters.Driver.execute(args,()=>new FullAdder())(c => new FullAdderTests(c))
}
然后在mytest_a目录下运行sbt。test表示在src/test/scala目录搜索。
// ./generated目录生成vcd文件
sbt 'test:runMain examples.FullAdderTestGen -td ./generated/FullAdder --backend-name verilator'
// 只输出仿真结果
sbt 'test:runMain examples.FullAdderTestGen -td ./generated/FullAdder --is-verbose'
chiesel.test
将FullAdderTest.scala修改如下:(不需要再写object)
package examples
import org.scalatest._
import chisel3._
import chisel3.experimental.BundleLiterals._
import chiseltest._
import java.util.Random
class FullAdderTest extends FlatSpec with ChiselScalatestTester with Matchers{
behavior of "mytest2"
it should "do something" in{
test(new FullAdder() ){ c =>
val randNum = new Random
val a = randNum.nextInt(2)
val b = randNum.nextInt(2)
val cin = randNum.nextInt(2)
val res = a + b + cin
val sum = res & 1
val cout = (res >> 1) & 1
c.io.a.poke(a.U)
c.io.b.poke(b.U)
c.io.cin.poke(cin.U)
c.clock.step(1)
c.io.sum.expect(sum.U)
c.io.cout.expect(cout.U)
}
}
}
然后在mytest_a目录下运行sbt。
// target_run_dir会生成vcd文件
sbt ‘testOnly examples.FullAdderTest -- -DwriteVcd=1’
// 只输出仿真结果
sbt ‘testOnly examples.FullAdderTest’
补一个另一种生成verilog的方法,生成的代码没有ifdef,RANDMIZE 这种“脏东西”,需要用到chisel 3.5模板@015e98f:
import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage}
import firrtl.options.TargetDirAnnotation
object u_FullAdder extends App {
(new chisel3.stage.ChiselStage).execute(
Array("-X", "verilog","--emission-options=disableMemRandomization,disableRegisterRandomization"),
Seq(ChiselGeneratorAnnotation(() => new FullAdder()),
TargetDirAnnotation("Verilog"))
)
}