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")) ) }