Chisel(二):你的第一个Chisel工程应该这么来

推荐阅读官方文档:https://www.chisel-lang.org/chisel3/docs/introduction.html

官方速记手册:chisel_cheatsheet.pdf

官方github:https://github.com/chipsalliance/chisel3

Chisel3.5 API:https://www.chisel-lang.org/api/latest/

官网:https://www.chisel-lang.org/

Blog:

前言

被好几个教程折磨的死去活来,最终总结一下如何开始你的第一个Chisel环境

正文

1 参考文章

2 环境搭建

2.2 必要依赖项

(1) 安装Java

1
2
sudo apt install openjdk-11-jre-headless //安装java
sudo apt install openjdk-11-jdk-headless //安装javac

(2) verilator (chisel运行测试时需要)

1
sudo apt-get install verilator 

2.2 chisel环境

方法一:使用sbt

(1)安装sbt

1
2
3
4
5
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

(2)build.sbt

  • 方法一:从github上clone一个例程,使用里面的build.sbt,下面提供了几个,你可以试试
1
git clone https://github.com/schoeberl/chisel-examples.git
1
git clone https://github.com/freechipsproject/chisel-template 
1
git clone https://github.com/schoeberl/chisel-empty.git
  • 方法二:自己建立一个工程
1
2
3
4
5
6
7
8
9
10
11
12
build.sbt                   <- sbt构建定义文件,后缀名必须是.sbt
project/ <- project目录下的所有.scala与.sbt文件都会被自动载入
build.properties <- 指定sbt的版本,如sbt.version=1.3.1,sbt启动器会自动安装本地没有的版本
Build.scala <- .scala 形式配置
plugins.sbt <- 插件定义
src/
main/ <- 设计
resources/ <- 设计资源文件(verilog用于黑盒/bin/mem)
scala/ <- 设计文件
test/ <- 测试
resources/ <- 测试资源文件(verilog/bin/mem)
scala/ <- 测试源文件

https://zhuanlan.zhihu.com/p/128889109

(3)sbt

同样的sbt运行可以两种方式

  • 方法一:sbt

直接sbt run或者sbt tets

  • 方法二:指定主程序

例1:运行FullAdderGen单例对象

1
sbt "runMain test.FullAdderGen"
  • runMain,sbt固定用法,用于运行某个Main函数
  • test.FullAdder
    • test是个包package
    • FullAdderGen是个obj单例对象

例2:运行subproject下test目录中的某个Main函数

1
sbt "sim/test:runMain  spinal.sim.Test "
  • sim/test:runMain
    • sim/是个subproject
    • test:是个目录

例3:运行sim子项目下的的所有testOnly

1
sbt "sim/testOnly "

(未测试)例4:运行所有test

1
sbt testOnly

例5:最朴实的生成方式

  • sbt run:让你自己选择

image-20220520162234868

  • sbt test

    1
    2
    sbt test //sbt 运行全测试
    sbt "testOnly FunctionsTest -- -t ST_Within" //sbt运行指定测试
方法二:全工具搭建

请参考:

步骤:

  • 安装sbt
  • 安装scala(其实安装sbt就不用安装scala了)
  • 安装Firrtl
  • 安装chisel3
1
2
3
4
5
git clone https://github.com/freechipsproject/chisel3.git && cd chisel3
sbt compile
sbt test
sbt publishLocal
cd ~/.ivy2/local/edu.berkeley.cs/ && ls
  • 修改build.sbt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// build.sbt
def scalacOptionsVersion(scalaVersion: String): Seq[String] = {
Seq() ++ {
// If we're building with Scala > 2.11, enable the compile option
// switch to support our anonymous Bundle definitions:
// https://github.com/scala/bug/issues/10047
CrossVersion.partialVersion(scalaVersion) match {
case Some((2, scalaMajor: Long)) if scalaMajor < 12 => Seq()
case _ => Seq("-Xsource:2.11")
}
}
}

def javacOptionsVersion(scalaVersion: String): Seq[String] = {
Seq() ++ {
// Scala 2.12 requires Java 8. We continue to generate
// Java 7 compatible code for Scala 2.11
// for compatibility with old clients.
CrossVersion.partialVersion(scalaVersion) match {
case Some((2, scalaMajor: Long)) if scalaMajor < 12 =>
Seq("-source", "1.7", "-target", "1.7")
case _ =>
Seq("-source", "1.8", "-target", "1.8")
}
}
}

name := "MyChisel"
version := "3.2-SNAPSHOT"
scalaVersion := "2.12.6"
crossScalaVersions := Seq("2.11.12", "2.12.4")

resolvers += "My Maven" at "https://raw.githubusercontent.com/sequencer/m2_repository/master"
// bug fix from https://github.com/freechipsproject/chisel3/wiki/release-notes-17-09-14
scalacOptions ++= Seq("-Xsource:2.11")

libraryDependencies += "edu.berkeley.cs" %% "chisel3" % "3.2-SNAPSHOT"
libraryDependencies += "edu.berkeley.cs" %% "chisel-iotesters" % "1.2.+"
libraryDependencies += "edu.berkeley.cs" %% "chisel-dot-visualizer" % "0.1-SNAPSHOT"
libraryDependencies += "edu.berkeley.cs" %% "rocketchip" % "1.2"

scalacOptions ++= scalacOptionsVersion(scalaVersion.value)
javacOptions ++= javacOptionsVersion(scalaVersion.value)

3 一些你会用到的

编写第一个chisel程序

(1)chisel工程

1
2
3
4
5
6
7
8
9
10
11
12
build.sbt                   <- sbt构建定义文件,后缀名必须是.sbt
project/ <- project目录下的所有.scala与.sbt文件都会被自动载入
build.properties <- 指定sbt的版本,如sbt.version=1.3.1,sbt启动器会自动安装本地没有的版本
Build.scala <- .scala 形式配置
plugins.sbt <- 插件定义
src/
main/ <- 设计
resources/ <- 设计资源文件(verilog用于黑盒/bin/mem)
scala/ <- 设计文件
test/ <- 测试
resources/ <- 测试资源文件(verilog/bin/mem)
scala/ <- 测试源文件
  • 写设计程序要在<work-place>/src/main/scala
    • sbt编译时不需要带前缀
  • 写测试程序要在<work-place>/src/test/scala
    • sbt编译时需要带test:前缀
  • sbt指令执行在<work-place>/目录,也就是build.sbt所在目录
  • HasBlackBoxResource黑盒特质,setResource($PATH),$PATH在<work-place>/src/main/resource

(2)如何生成verilog

第一种生成verilog代码:

  • 程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*
* Location: src/main/scala/Add.scala
*
* Author: ChangGuilin
*
*/

package empty

import chisel3._
import chisel3.util._

class Add extends Module {
val io = IO(new Bundle {
val a = Input(UInt(8.W))
val b = Input(UInt(8.W))
val c = Output(UInt(8.W))
})

val reg = RegInit(0.U(8.W))
reg := io.a + io.b

io.c := reg
}

object AddMain extends App {
println("Generating the adder hardware")
emitVerilog(new Add(), Array("--target-dir", "generated"))
}
  • sbt指令
1
sbt "runMain test.FullAdderGen"

其他生成verilog代码:

  • empty
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package empty

import chisel3._
import chisel3.util._

class Add extends Module {
val io = IO(new Bundle {
val a = Input(UInt(8.W))
val b = Input(UInt(8.W))
val c = Output(UInt(8.W))
})

val reg = RegInit(0.U(8.W))
reg := io.a + io.b

io.c := reg
}

object AddMain extends App {
println("Generating the adder hardware")
emitVerilog(new Add(), Array("--target-dir", "generated"))
}
  • example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
*
*
* An ALU is a minimal start for a processor.
*
*/

package simple

import chisel3._
import chisel3.util._

/**
* This is a very basic ALU example.
*/
class Alu extends Module {
val io = IO(new Bundle {
val fn = Input(UInt(2.W))
val a = Input(UInt(4.W))
val b = Input(UInt(4.W))
val result = Output(UInt(4.W))
})

// Use shorter variable names
val fn = io.fn
val a = io.a
val b = io.b

val result = Wire(UInt(4.W))
// some default value is needed
result := 0.U

// The ALU selection
switch(fn) {
is(0.U) { result := a + b }
is(1.U) { result := a - b }
is(2.U) { result := a | b }
is(3.U) { result := a & b }
}

// Output on the LEDs
io.result := result
}

/**
* A top level to wire FPGA buttons and LEDs
* to the ALU input and output.
*/
class AluTop extends Module {
val io = IO(new Bundle {
val sw = Input(UInt(10.W))
val led = Output(UInt(10.W))
})

val alu = Module(new Alu())

// Map switches to the ALU input ports
alu.io.fn := io.sw(1, 0)
alu.io.a := io.sw(5, 2)
alu.io.b := io.sw(9, 6)

// And the result to the LEDs (with 0 extension)
io.led := alu.io.result
}

// Generate the Verilog code
object AluMain extends App {
println("Generating the ALU hardware")
(new chisel3.stage.ChiselStage).emitVerilog(new AluTop(), Array("--target-dir", "generated"))

}

两种生成Verilog的方法

1
2
import chisel3._
(new chisel3.stage.ChiselStage).emitVerilog(new Hello())
1
2
import chisel3._
emitVerilog(new Add(), Array("--target-dir", "generated"))

一种getVerilogString的方法

1
println(getVerilogString(new <设计的类>))

生成Firrtl代码

这个API需要import chisel3.stage.ChiselStage.emitFirrtl,使用如下:

1
println(emitFirrtl(new <设计的类>))

(3)测试文件?

参考:

有三种常见场景需要作区分:

  1. Chisel生成器在生成电路的时候打印输出;
  2. 电路在仿真期间打印输出;
  3. 测试器在测试期间打印输出;

给主函数传递参数

Scala的类可以接收参数,自然Chisel的模块也可以接收参数。假设要构建一个n位的加法器,具体位宽不确定,根据需要而定。那么,就可以把端口位宽参数化,例化时传入想要的参数即可。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// adder.scala
package test

import chisel3._

class Adder(n: Int) extends Module {
val io = IO(new Bundle {
val a = Input(UInt(n.W))
val b = Input(UInt(n.W))
val s = Output(UInt(n.W))
val cout = Output(UInt(1.W))
})

io.s := (io.a +& io.b)(n-1, 0)
io.cout := (io.a +& io.b)(n)
}

// adderGen.scala
package test

object AdderGen extends App {
emitVerilog(new Add(), Array("--target-dir", "generated"))
}

在这里,模块Adder的主构造方法接收一个Int类型的参数n,然后用n去定义端口位宽。主函数在例化这个模块时,就要给出相应的参数。前面的帮助菜单里显示,在运行sbt命令时,可以传入若干个独立的参数。

和运行Scala的主函数一样,这些命令行的参数也可以由字符串数组args通过下标来索引。从要运行的主函数后面开始,后面的内容都是按空格划分、从下标0开始的args的元素。比如例子中的主函数期望第一个参数即args(0)是一个数字字符串,这样就能通过方法toInt转换成Adder所需的参数。

执行如下命令:

1
sbt 'test:runMain test.AdderGen 8 -td ./generated/adder'

可以在相应的文件夹下得到如下Verilog代码,其中位宽的确是8位的

给Firrtl传递参数

在运行主函数时,可以在刚才的命令后面继续增加可选的参数。例如,增加参数--help查看帮助菜单,运行命令:

1
sbt 'test:runMain test.FullAdderGen --help'

最常用的是参数-td,可以在后面指定一个文件夹,这样之前生成的三个文件就在该文件夹里,而不是在当前路径下。其格式如下:

1
sbt 'test:runMain test.FullAdderGen -td ./generated/fulladder' 

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!