很早就听说Chisel,伯克利的又一大作,也听说其学习门槛高,基于scala语言,语法非常灵活。近期的国科大搞的“一生一芯”计划,“香山”处理器等,更是将Chisel推向了又一个高潮,据包老师说,Chisel的开发效率远高于verilog香山高性能riscv处理器,这种提高生产力的语言,怎么能不学?

一、Chisel的难点

Chisel 早期的门槛有两个,一个是开发环境,另一个是从verilog转变。开发环境说来简单, 真搭起来还真不容易,我花了两三天时间才实现想要的效果:

  • 产生电路的.v文件
  • 产生.vcd文件查看波形
  • 不产生波形,基于scala仿真

虽然网上的资料很多,但Chisel的更新很快,按照一些入门教程做,还不一定能跑通,报错也难搜到解决方案,毕竟用scala的人没有python多。

从verilog的转变倒是不难,chisel tutorial 提供了很多样例。推荐阅读顺序:

二、环境配置

类似的教程网上有很多,一抓一大把,基本上是基于IntelliJIDEA,但我的虚拟机跑起来经常卡,于是转投vscode。IntelliJIDEA牛逼的地方在于,它会实时检查语法错误,如果一直没跑通,可以用它打开再debug。

我的环境是Ubuntu20.04 ,先装好java,sbt,verilator(如果中途报错,搜索就有答案)

sudo apt-get install default-jdk
sudo apt-get install sbt
sudo apt-get install verilator

抱着极简的想法,我决定从零开始,建了这几个文件夹还有一个.sbt文件。
image.png

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’

测试文件github

补一个另一种生成verilog的方法,生成的代码没有ifdef这种“脏东西”:

import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage}
import firrtl.options.TargetDirAnnotation

object u_FullAdder extends App {
  (new chisel3.stage.ChiselStage).execute(
    Array("-X", "verilog"),
    Seq(ChiselGeneratorAnnotation(() => new FullAdder()),
      TargetDirAnnotation("Verilog"))
  )
}

标签: chisel, 敏捷开发, sbt

添加新评论