IC的帆哥 - chisel 2021-12-11T21:38:00+08:00 Typecho https://icfgblog.com/index.php/feed/atom/tag/chisel/ <![CDATA[“香山”路上的几个坑]]> https://icfgblog.com/index.php/Digital/309.html 2021-12-11T21:38:00+08:00 2021-12-11T21:38:00+08:00 icfg66 http://www.icfgblog.com 由于项目需要,希望了解开源的香山处理器,希望先跑通一波,虽然有清晰的教程,但还是遇到不少问题,特来记录一番。

一、mill 安装

香山的编译不是基于sbt而是另一个叫mill的工具,我用的是ubuntu 18.04 ,但硬是没找到对应的mill安装指令,apt-install也不行,网上找了一圈,确实官网有,说是Manual安装:

sudo sh -c "curl -L https://github.com/com-lihaoyi/mill/releases/download/0.10.0-M4/0.10.0-M4 > /usr/local/bin/mill && chmod +x /usr/local/bin/mill"

二、github 连不上

运行make init后,需要下载子电路模块以及其他github仓库,具体有这些:

  • timingScripts
  • berkeley-hardfloat
  • api-config-chipsalliance
  • rocket-chip
  • block-inclusivecache-sifive
  • chisel-testers2

不能科学上网的我只能干瞪眼。

网上搜到一个比较有效的办法linux连Github

第一步 : 通过Ping检测最快的节点http://ping.chinaz.com/github.com 第二步 : 选择最快节点添加到hosts文件:

sudo vim /etc/hosts
140.82.114.3 github.com

但最后还是把这些仓库先移到gitee,然后逐个git clone。

三、香山make verilog不成功

当万事具备,运行make verilog,已经出现“编译成功”,但几分钟之后就报error退出。搜到论坛说内存要大于32G,或者修改build.sc,有两个地方Xmx64G,均改成10G,就可以跑通了,最后生成的verilog代码竟然有100M!

附、设置环境变量

在~/.bashrc下添加几个环境变量

vim ~/.bashrc
# 行末添加
export NEMU_HOME=/home/tom/Documents/chisel_test/NEMU
export AM_HOME=/home/tom/Documents/chisel_test/nexus-am
export NUTSHELL_HOME=/home/tom/Documents/chisel_test/nutShell
export NOOP_HOME=/home/tom/Documents/chisel_test/XiangShan
]]>
<![CDATA[chisel 虐我千百遍(一)]]> https://icfgblog.com/index.php/Digital/301.html 2021-11-17T07:48:00+08:00 2021-11-17T07:48:00+08:00 icfg66 http://www.icfgblog.com 前几天想将一个大小端转换的模块转成chisel,感觉挺简单,但真写起来就各种bug。

module EndianCnvt #(parameter NUMLENGTH = 128)(
	input [NUMLENGTH-1:0]	NumIn,
	output [NUMLENGTH-1:0]	NumOut
	);
	localparam BYTENUM = NUMLENGTH/8;
	genvar i;
	for (i=0; i

一开始就是简单直译,但会保存,因为UInt类型不能切片赋值,在一点在官网cookbook其实已经说了,是我没认真看~

class EndianCnvt (NUMLENGTH :Int)extends Module{
  val io = IO(new Bundle {
    val NumIn = Input(UInt(NUMLENGTH.W))
    val NumOut= Output(UInt(NUMLENGTH.W))
  })
  val BYENUM = NUMLENGTH>>3
  val temp = Wire(Vec(BYENUM,UInt(8.W)))
  for (i <- 0 until BYENUM){
    io.NumOut[(i+1)*8-1:i*8] := io.NumIn((BYENUM-i)*8-1,(BYENUM-i-1)*8)//UInt不能切片赋值
  }
}

需要先转换类型才能赋值:

class EndianCnvt (NUMLENGTH :Int)extends Module{
  val io = IO(new Bundle {
    val NumIn = Input(UInt(NUMLENGTH.W))
    val NumOut= Output(UInt(NUMLENGTH.W))
  })
  val BYENUM = NUMLENGTH>>3
  val temp = Wire(Vec(BYENUM,UInt(8.W)))//转换成8bit的数组,分别赋值,再转回UInt
  for (i <- 0 until BYENUM){
    temp(i) := io.NumIn((BYENUM-i)*8-1,(BYENUM-i-1)*8)
  }
  io.NumOut := temp.asUInt
}

后来大神说可以用reverse的方法,还是很好用的,最后再添加例化模块的apply方法

class EndianCnvt (NUMLENGTH :Int)extends Module{
  val io = IO(new Bundle {
    val NumIn = Input(UInt(NUMLENGTH.W))
    val NumOut= Output(UInt(NUMLENGTH.W))
  })
  val BYENUM = NUMLENGTH>>3
  val temp =  io.NumIn.asTypeOf(Vec(BYENUM,UInt(8.W)))
  val temp2 = Wire(Vec(BYENUM,UInt(8.W)))
  temp2 := temp.reverse  //reverse方法,这里必须借助中间变量temp2,不然也会报错
  io.NumOut := temp2.asUInt()
}
object EndianCnvt{
  def apply(in1:UInt,NUMLENGTH:Int) :UInt={
  val inst = Module(new EndianCnvt(NUMLENGTH))
  inst.io.NumIn := in1
  inst.io.NumOut
}
}

生成的verilog如下:

module EndianCnvt(
  input          clock,
  input          reset,
  input  [127:0] io_NumIn,
  output [127:0] io_NumOut
);
  wire [7:0] temp_0 = io_NumIn[7:0]; // @[sub_module.scala 32:32]
  wire [7:0] temp_1 = io_NumIn[15:8]; // @[sub_module.scala 32:32]
  wire [7:0] temp_2 = io_NumIn[23:16]; // @[sub_module.scala 32:32]
  wire [7:0] temp_3 = io_NumIn[31:24]; // @[sub_module.scala 32:32]
  wire [7:0] temp_4 = io_NumIn[39:32]; // @[sub_module.scala 32:32]
  wire [7:0] temp_5 = io_NumIn[47:40]; // @[sub_module.scala 32:32]
  wire [7:0] temp_6 = io_NumIn[55:48]; // @[sub_module.scala 32:32]
  wire [7:0] temp_7 = io_NumIn[63:56]; // @[sub_module.scala 32:32]
  wire [7:0] temp_8 = io_NumIn[71:64]; // @[sub_module.scala 32:32]
  wire [7:0] temp_9 = io_NumIn[79:72]; // @[sub_module.scala 32:32]
  wire [7:0] temp_10 = io_NumIn[87:80]; // @[sub_module.scala 32:32]
  wire [7:0] temp_11 = io_NumIn[95:88]; // @[sub_module.scala 32:32]
  wire [7:0] temp_12 = io_NumIn[103:96]; // @[sub_module.scala 32:32]
  wire [7:0] temp_13 = io_NumIn[111:104]; // @[sub_module.scala 32:32]
  wire [7:0] temp_14 = io_NumIn[119:112]; // @[sub_module.scala 32:32]
  wire [7:0] temp_15 = io_NumIn[127:120]; // @[sub_module.scala 32:32]
  wire [63:0] lo = {temp_8,temp_9,temp_10,temp_11,temp_12,temp_13,temp_14,temp_15}; // @[sub_module.scala 35:28]
  wire [63:0] hi = {temp_0,temp_1,temp_2,temp_3,temp_4,temp_5,temp_6,temp_7}; // @[sub_module.scala 35:28]
  assign io_NumOut = {hi,lo}; // @[sub_module.scala 35:28]
endmodule

功能确实是对的,如何去除这些烦人的注释呢?写了一个脚本remove.py,还挺好用

# python3 remove.py -f yourfile.v
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-f','--file',dest='file',type=str,default='yourfile.v')

args = parser.parse_args()
file_data = ""
remove = 0
with open(args.file,"r",encoding="utf-8") as f:
    for line in f:
        if(line[1:3]=="if" or line[0:7]=="initial"):
            remove += 1
            temp = ""
        elif(line[1:4]=="end" or line[0:6]=="end //"):
            remove -= 1
            temp = ""
        else:
            if(remove!=0):
                temp=""
            else:
                temp = line.split('/')[0]
                if((len(temp)!=0) and (temp[-1]!='\n')) :
                	temp += '\n'
        file_data += temp
with open(args.file,"w",encoding= "utf-8") as f:
    f.write(file_data)

处理之后瞬间清爽了:

module EndianCnvt(
  input          clock,
  input          reset,
  input  [127:0] io_NumIn,
  output [127:0] io_NumOut
);
  wire [7:0] temp_0 = io_NumIn[7:0]; 
  wire [7:0] temp_1 = io_NumIn[15:8]; 
  wire [7:0] temp_2 = io_NumIn[23:16]; 
  wire [7:0] temp_3 = io_NumIn[31:24]; 
  wire [7:0] temp_4 = io_NumIn[39:32]; 
  wire [7:0] temp_5 = io_NumIn[47:40]; 
  wire [7:0] temp_6 = io_NumIn[55:48]; 
  wire [7:0] temp_7 = io_NumIn[63:56]; 
  wire [7:0] temp_8 = io_NumIn[71:64]; 
  wire [7:0] temp_9 = io_NumIn[79:72]; 
  wire [7:0] temp_10 = io_NumIn[87:80]; 
  wire [7:0] temp_11 = io_NumIn[95:88]; 
  wire [7:0] temp_12 = io_NumIn[103:96]; 
  wire [7:0] temp_13 = io_NumIn[111:104]; 
  wire [7:0] temp_14 = io_NumIn[119:112]; 
  wire [7:0] temp_15 = io_NumIn[127:120]; 
  wire [63:0] lo = {temp_8,temp_9,temp_10,temp_11,temp_12,temp_13,temp_14,temp_15}; 
  wire [63:0] hi = {temp_0,temp_1,temp_2,temp_3,temp_4,temp_5,temp_6,temp_7}; 
  assign io_NumOut = {hi,lo}; 
endmodule

附上寄存器、向量初始化的语句备忘

val regs = RegInit(VecInit(Seq.fill(32)(0.U(32.W))))//寄存器向量初始化
val reg  = RegNext(updateData,resetData,enable)
val reg2 = ShiftRegister(in , n , en )
val reg3 = ShiftRegister(in ,n ,resetData,en)
val reg4 = Reg(Vec(32,SInt(64.W)))
val array = Wire(Vec(4,SInt(8.W)))
val array1 = array.asTypeOf(Vec(2,UInt(16.W)))
val array2 = VecInit(Seq.fill(4)(false.B))
val array3 = VecInit(Seq.fill(4)(0.S))
//rom
val mem = VecInit("h11".U,"hff".U,"h15".U,"h31".U)
]]>
<![CDATA[chisel中级技巧]]> https://icfgblog.com/index.php/Digital/282.html 2021-10-22T09:59:00+08:00 2021-10-22T09:59:00+08:00 icfg66 http://www.icfgblog.com 过了一遍chisel book和chisel tutorial的内容,学了chisel基本搭电路的语法,能把verilog的代码翻译成chisel,但在翻译的过程中发现仅有这些基本技能还不够,完全没有发挥出scala这种高级语言的优势,知乎看到一篇用Chisel快速搭建FFT流水线电路,发现好多方便的操作,总结下来有以下几点:

  • local parameter
  • 同类线网Bundle
  • 实例化模块

一、局部变量local parameter

verilog 经常会有局部变量,有些定义位宽,有些定义循环次数,chisel里也能用val 定义变量,但总感觉“不干净”,而且不好复用,有什么办法呢?scala的trait很好用!

可以在Config.scala里定义参数:

package FFT

trait HasDataConfig {
  val DataWidth = 32
  val BinaryPoint = 16
}

trait HasElaborateConfig {
  val FFTLength = 512
  val useGauss = false
  val supportIFFT = false
}

然后定义时,在需要这些局部变量的模块加上with就可以访问,非常方便:

//用with增加局部变量
class MyComplex extends Bundle with HasDataConfig {  
  val re = FixedPoint(DataWidth.W, BinaryPoint.BP)
  val im = FixedPoint(DataWidth.W, BinaryPoint.BP)
}

二、Bundle线网

实际电路的输入输出其实会分数据线和控制线,还有一些信号属于同类型的,但verilog只能一视同仁,只能在命名上加以区分,但chisel里的Bundle类可以把相同的信号“绑”在一起,既便于阅读,也便于调用。

先定义一个复数的Bundle,然后在Switch模块里就可以直接调用MyComplex,而无需再定义实部虚部,更加简洁。

//Bundle“绑定”相同类型的信号
class MyComplex extends Bundle with HasDataConfig { 
  val re = FixedPoint(DataWidth.W, BinaryPoint.BP)
  val im = FixedPoint(DataWidth.W, BinaryPoint.BP)
}

class Switch extends Module {
  val io = IO(new Bundle{
    val in1 = Input(new MyComplex)
    val in2 = Input(new MyComplex)
    val sel = Input(Bool())
    val out1 = Output(new MyComplex)
    val out2 = Output(new MyComplex)
  })
  io.out1 := Mux(io.sel, io.in2, io.in1)
  io.out2 := Mux(io.sel, io.in1, io.in2)
}

三、伴生对象之工厂方法

别被这个名词唬了,verilog调用子模块时,都需要再连线,很繁琐,代码又长又臭。chisel伴生对象的工厂方法可以很好解决这个问题。

class Switch extends Module {
  val io = IO(new Bundle{
    val in1 = Input(new MyComplex)
    val in2 = Input(new MyComplex)
    val sel = Input(Bool())
    val out1 = Output(new MyComplex)
    val out2 = Output(new MyComplex)
  })
  io.out1 := Mux(io.sel, io.in2, io.in1)
  io.out2 := Mux(io.sel, io.in1, io.in2)
}
//定义伴生对象,apply方法
object Switch {
  def apply(in1: MyComplex, in2: MyComplex, sel: Bool): (MyComplex, MyComplex) = {
    val inst = Module(new Switch)
    inst.io.in1 := in1
    inst.io.in2 := in2
    inst.io.sel := sel
    (inst.io.out1, inst.io.out2)
  }
}

这样就可以很方便例化模型,不需要再连线,元素访问也很简单:

//不需要再连线
val sw12 = Switch(in1,in2,swCtrl)
//元素访问
val out1 = sw12._1
val out2 = sw12._2

除此之外,用map生成正弦余弦rom,scala测试代码中还有一些高效操作,还没有完全掌握,得慢慢消化。

]]>
<![CDATA[Chisel 从入门到放弃]]> https://icfgblog.com/index.php/Digital/263.html 2021-09-05T20:53:00+08:00 2021-09-05T20:53:00+08:00 icfg66 http://www.icfgblog.com 吃鸡卷王钦哥在暑假期间入坑Chisel,仍然吃鸡,已然抛弃羽毛球。现在已经可以用Chisel替代verilog做工程项目,这转身真是迅速!诚邀钦哥总结一路踩过的坑,因此有了这篇汇总博文。


官网

1. GitHub - chipsalliance/chisel3: Chisel 3: A Modern Hardware Design Language

2. Chisel/FIRRTL: Introduction (chisel-lang.org)

3. Chisel 中文社区 (chiselchina.com)

4. 官方 API

很多问题在上述链接 2 都能解答,主要在 Resource 和 Cookbooks 目录下,以下给出 General Cookbook 目录下一个常见问题的解答 (很可能遇到的 , 部分位的赋值问题):

Chisel3 does not support subword assignment . The reason for this is that subword assignment generally hints at a better abstraction with an aggregate/structured types, i.e., a Bundle or a Vec.

If you must express it this way, one approach is to blast your UInt to a Vec of Bool and back:

 import chisel3._

 class Foo extends Module {
   val io = IO(new Bundle {
     val in = Input(UInt(10.W))
     val bit = Input(Bool())
     val out = Output(UInt(10.W))
   })
   val bools = VecInit(io.in.asBools)
   bools(0) := io.bit
   io.out := bools.asUInt
 }

环境配置

1. intellij IDEA 下载

Download IntelliJ IDEA: The Capable && Ergonomic Java IDE by JetBrains

选择社区版本够用了

2. 插件安装

image-20210904093328-c8vyj8c.png

chisel 插件提供了 Chisel Project 模板,可以快速配置 scala 版本和 sbt(scala build tool)

3. 工程创建

image-20210904093511-rdukw00.png

路径: project - new project - chisel - empty chisel project,假如没有 SDK 就安装图示的 openjdk.

图中的 GCD Example 是官方提供的一个样例程序,同时提供了设计和内建测试的代码

image-20210904093826-8us5978.png

目前个人的配置如上,sbt 版本最好就是按照官网版本提供的,否则编译过程中报奇怪的错很难界定原因.假如后续 chisel 更新之后代码莫名跑不通了,可以参照官网的说明以及样例程序,检查自己的工程中 sbt 或者 scala verision 是否过时了

4. 文件结构 & chisel 设计模板创建

image-20210904094143-j7pt3k0.png

上图给出了工程文件的结构示意图, 主要关注的是 /src 路径下的文件,以及根目录(Project)下的 /test_run_dir 和 生成的 .v 文件(编译通过才有),其中 /src 文件夹下的主要内容如下图 image-20210904094517-97kp9nc.png

  1. /src/main/resource 文件夹存放来自外部的 verilogIP,此路径是 chisel 默认的 blackBoxResource(即此处的 verilogIP) 存放路径,以下给出 1 个示例
    class data_scheduler(DI_W: Int, DO_W: Int) extends BlackBox 
    with HasBlackBoxResource 
    with ParamTable {
      val io = IO(new Bundle() {
        val params_intl_ch_num    = Input(UInt(2.W))
        val params_cpi            = Input(UInt(SLOW_TIME_CNT_W.W))
        val params_frame_interval = Input(UInt(SLOW_TIME_CNT_W.W))
        val params_samples_per_tr = Input(UInt(FAST_TIME_CNT_W.W))
        val params_total_samples_per_ch  = Input(UInt(CH_SAMPLE_CNT_W.W))
        val params_fft_in_inv_order      = Input(Bool())
    
        ...  //按照verilogIP端口声明所有的变量
             //这里声明的变量名就是chisel例话IP时候的端口名
             //另外,chisel并不会检查verilogIP的端口与这里声明的变量是否同名
             //因此,这里声明的变量名一定要跟verilogIP端口名完全对应,
             //否则会出现"port nof found"错误
      })
    
      addResource("/data_scheduler/rtl/data_scheduler.v") 
     //默认目录为/resource,这里非默认路径,需要进一步指明文件位置
    }
    
  2. /src/main/scala 路径下存放设计文件 假如设计文件分类较多,可以采用 package 来管理,如下图所示

image-20210904100453-gcjj63k.png

在创建了 Package 之后,例如上图的 SoCComponents,类似地,选中该 Package,右键菜单可以进一步创建 Package,或者是直接就创建一个 Chisel Module 的模板.

5. 生成 verilog

参照官网教程完成设计之后,可以采用如下语句生成 verilog(当前的最新版本),这个生成 verilog 的方法官方一直在迭代,网上的博客很多给的是比较落后的方法

object SoCGen extends App {
  val verilogString = (new chisel3.stage.ChiselStage).execute(Array(""), 
                        Seq(ChiselGeneratorAnnotation(() => (new SoCTop)))) 
                                //SoCTop是设计文件(Class)
  /*
  val targetDir      = "test_run_dir/SoCTop"
  (new ElkStage).execute(
    Array("-td", targetDir),
    Seq(ChiselGeneratorAnnotation(() => new SoCTop))  //这是生成架构图的,可以先不用
    )*/
}

参考博客

入门

以下博客给的生成 verilog 方法基本都已过时 !

如何在 windows 上搭建 chisel 环境(除了 chisel,作者还提供了很多其他数字设计内容)

初学 Chisel 语言,看这篇就够了:最方便简洁的入门资料整理 - 知乎 (zhihu.com)

知乎 chisel 专栏

官网 chiselTest

Introduction | Tour of Scala | Scala Documentation (chisel 内语法看不懂时的参考,且有些 scala 语法提供的语法糖对于减少设计也有帮助))

进阶

用 Chisel 快速搭建 FFT 流水线电路 - 知乎 (zhihu.com)

第二十二章 Chisel 基础——多时钟域设计__iChthyosaur 的博客-CSDN 博客

【Chisel 学习笔记 1】隐式参数(比较高级的传参,可以略过)

ChiSel 学习笔记 - (提供了一个端口列表参数化的方法)

chisel 基础教程 (提供了一个位操作的方法)

算法函数_Chisel 实践——利用 CORDIC 算法计算对数函数_街机时代的博客-CSDN 博客

参数化的 Bundle 的方法以及存在问题的解决

高阶应用

Scala进阶

chisel 高阶用法简介--rocket-chip generator - 知乎 (zhihu.com)

(27 封私信 / 80 条消息) 如何深入理解 rocket-chip core 的代码? - 知乎 (zhihu.com)

RocketChip 分析,内容比较详细

RocketChip 官方文档

RocketChip 学习笔记系列 - 知乎 (zhihu.com)

Verilator 仿真器入门 | Hu '' s Blog (hubohan.space)

Verilator_timewh 的专栏 -CSDN 博客_verilator

]]>
<![CDATA[Chisel,说爱你不容易]]> https://icfgblog.com/index.php/Digital/253.html 2021-08-29T19:26:00+08:00 2021-08-29T19:26:00+08:00 icfg66 http://www.icfgblog.com 很早就听说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 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文件。 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,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"))
  )
}
]]>