--- title: Chisel敏捷开发与测试框架 ---

Chisel 敏捷开发与测试框架

                  兰州理工大学   褚骐鸣

                     2024 年 3 月 21 日

深理工一生一芯训练营

目录

  • 背景及现状
  • 敏捷开发对比 (Chisel VS HLS)
  • Chisel 测试工具
深理工一生一芯训练营

背景介绍

当前的芯片设计开发过程存在以下问题

  1. 工具门槛高(费用昂贵)
  2. 微处理器设计面临应用需求变化快、产品上市周期短的压力
  3. 大部分芯片 IP 和工具代码都未开源,缺乏透明度和可扩展性

敏捷开发优点:

  1. 缩短设计周期,降低设计
  2. 快速验证
  3. 模块化设计(可复用性)
深理工一生一芯训练营

Chisel 敏捷开发

Chisel 是一个基于 Scala 的硬件表述语言。Chisel 的设计目标是提供一种硬件构成语言,它可以利用 Scala 中强大的语法特性,同时又能够生成高效的硬件描述

  • 更方便地描述电路 (不是 HLS)
  • 支持面向对象编程
  • 支持函数式编程
  • 支持基于 Scala 的元编程
深理工一生一芯训练营

敏捷开发对比

HLS VS Chisel

深理工一生一芯训练营

使用 HLS 进行 FIR 滤波器设计

使用 HLS 设计 FIR 滤波器并烧录到 Pynq 平台:

  1. 使用 Vitis HLS 中使用 HLS 语言设计滤波器 ip 核
  2. 在 Vivado 中构建 Block Design, 导入 IP 核,生成硬件
  3. 最后烧入 overlay (Bitstream 文件) 中,完成滤波
  4. 使用 Python 或 Matlab 设计滤波器的系数,将滤波器抽头系数传入 IP 核
  5. 发出控制信号,启动 IP 核完成滤波
深理工一生一芯训练营

设计滤波器 IP 核

  • 数组存放滤波器抽头系数 (coef)
  • 使用移位寄存器存放卷积后累加的结果

https://github.com/chipsalliance/chisel?tab=readme-ov-file#fir-

深理工一生一芯训练营

FIR 滤波器 HLS 代码

#include "./fir.h"
coef_t c[N];
void fir(data_t *y, data_t x) {
#pragma HLS ARRAY_PARTITION variable=c complete dim=0
    static data_t shift_reg[N];
#pragma HLS ARRAY_PARTITION variable=shift_reg complete dim=0
    acc_t acc; int i; acc = 0;
#pragma HLS PIPELINE II=1
    for (i = N - 1; i >= 0; i--) {
        if (i == 0) {
            acc += x * c[0];
            shift_reg[0] = x;
        }
        else {
            shift_reg[i] = shift_reg[i - 1];
            acc += shift_reg[i] * c[i];
        }
    }
    *y = acc;
}
深理工一生一芯训练营

FIR 滤波器 IP 代码

void fir_wrap(data_t *y, data_t *x, int len, coef_t *coef) {
#pragma HLS INTERFACE m_axi port=coef offset=slave depth=99
#pragma HLS INTERFACE m_axi port=x offset=slave depth=100
#pragma HLS INTERFACE m_axi port=y offset=slave depth=100
#pragma HLS INTERFACE s_axilite port=len  bundle=CTRL
#pragma HLS INTERFACE s_axilite port=return bundle=CTRL

	data_t res;

	for (int i =0; i < N; i++) {
#pragma HLS PIPELINE II=1
		c[i] = *coef++;
	}

	for (int i = 0; i < len; i++) {
#pragma HLS PIPELINE II=1
		fir(&res,*x++);
		*y = res;
		y++;
	}

}
深理工一生一芯训练营

设计滤波器 IP 核

编写头文件 (fir.h)

  • 定义 N(确定滤波器的阶次)
  • 确定抽头系数、被滤波的数据以及累加器的数据类型
  • 声明 fir 和 fir_wrap 函数
#define N 99
typedef int coef_t;
typedef int data_t;
typedef int acc_t;

void fir(data_t *y, data_t x);

void fir_wrap(data_t *y, data_t *x, int len, coef_t *coef);
深理工一生一芯训练营

在 Vivado 中导入 IP

深理工一生一芯训练营

设计滤波器

深理工一生一芯训练营

滤波器效果展示


右上方为滤波前的白噪声
右下方为滤波后的白噪声


通过观察频域信息可以得出:滤波器可以正常滤除 2.8kHz 以上的频率

深理工一生一芯训练营

使用 Chisel 进行 FIR 滤波器设计

  1. 使用 python 设计 FIR 滤波器
  2. 使用 Chisel 描述电路并生成 Verilog
  3. 后续烧录到 FPGA 等步骤同上
深理工一生一芯训练营

使用 Python 设计滤波器抽头系数

import numpy as np
from scipy.signal import freqz, firwin
fs = 44100
nyq = fs / 2.0
taps = 99
# Design high-pass filter with cut-off at 2.8 kHz
hpf_coeffs = firwin(taps, 2800 / nyq, pass_zero=False)
freqs, resp = freqz(hpf_coeffs, 1)
sample_freqs = np.linspace(0, nyq, len(np.abs(resp)))

hpf_coeffs_quant = np.array(hpf_coeffs)
hpf_coeffs_hw = np.uint32(hpf_coeffs_quant/np.max(abs(hpf_coeffs_quant)) * 2**15 - 1)

with open("coeffs.txt", "w") as file:
    for coeff in hpf_coeffs_hw:
        file.write(str(coeff) + "\n")

深理工一生一芯训练营

Chisel FIR 滤波器设计

class FirFilter(bitWidth: Int, coeffs: Seq[UInt]) extends Module {  
  val io = IO(new Bundle {  
    val in = Input(UInt(bitWidth.W))  
    val out = Output(UInt(bitWidth.W))  
  })  
  val zs = Reg(Vec(coeffs.length, UInt(bitWidth.W)))  
  zs(0) := io.in  
  for (i <- 1 until coeffs.length) {  
    zs(i) := zs(i-1)  
  }  
  val products = VecInit.tabulate(coeffs.length)(i => zs(i) * coeffs(i))  
  io.out := products.reduce(_ + _)  
}  
// 生成 System Verilog
object Fir extends App {  
  val coeffs = scala.io.Source.fromFile("coeffs.txt").getLines()  
    .map(i => BigInt(i))  
    .toList  
  (new ChiselStage).execute(  
    Array("--target", "systemverilog"),  
    Seq(ChiselGeneratorAnnotation(() => new FirFilter(64, coeffs.map(_.U))),  
      FirtoolOption("--disable-all-randomization"))  
  )  
}
深理工一生一芯训练营

对比总结

HLS 抽象程度过高,新手学习时难以快速掌握其正确地使用方法:

  • 不仅需要学习 HLS, 还需要学习如何使用 pragma
  • pragma 的使用仍然需要一定经验(例如 循环 的处理需要充分考虑流水线状态机
  • 数据类型也要经过充分的考虑后,使用 pragma 指定类型
  • 不过优点是做为高层次语言加入了许多 Verilog 没有的特性,同时能快速地构造流水线并接入 AXI 总线

相比之下 Chisel 语言则更为贴近电路本身,生成的 Verilog 代码也有较好的可读性

  • Chisel 3.6 加入了基于 MLIR-based compiler(CIRCT) 后,生成的代码更加贴近源码
深理工一生一芯训练营

敏捷验证

  • Chisel 生成的 Verilog 代码可读性一直被诟病
  • Chisel 提供了 Naming Cookbook 来规范命名
    • 如何消除名称中带有_T 的信号
    • 如何合理的命名信号,以生成有良好可读性的 Verilog 代码
  • 但是仍然会生成如带有_GEN这种信号,可读性并不是很理想
  • 在一生一芯中的开发大部分时间都在使用 Difftest、ITRACE 等工具进行 Debug
  • 但是在设计 RISCV 处理器过程中一个模块的结构越来越复杂
  • 如果在这个时候重构了某些模块,Bug 可能会被越埋越深
  • 我们需要先保证一个模块的正确性,再让它加入到处理器中
  • Verilator 仿真效率高,但是需要手动编写测试激励,无法识别 Chisel 中的高级结构
深理工一生一芯训练营

在 Chisel 中测试模块

  • ChiselTest (Unit Test, Formal Verification)
  • Svsim (Verilator backend)
  • Probe (Cross Module Reference)
  • Temporal Properties (aka System Verilog Assertions)

还有很多 API

在 Chisel 写 testbench 的好处:

  • 拥有 IDE 工具的支持(自动补全、高亮提示)
  • 可以直接支持识别 Chisel 的模块结构
  • 不用编写 Verilator testbench
  • 不用翻 Chisel 生成的 Verilog 文件找端口名称 (ifu_io_bits_xxx)
深理工一生一芯训练营

ChiselTest 的缺陷

  • 对大型设计进行仿真时速度特别慢
  • Verilator 仿真效率高,ChiselTest 支持 Verilator 联合仿真
  • 与 Verilator 进行联合仿真时,由于 Java 和 C++的通信间存在瓶颈,会拖慢仿真速度

  • 那么有没有办法既能使用 Verilator 仿真,又可以让仿真测试识别 Chisel 模块的高级结构?
深理工一生一芯训练营

svsim 测试引擎

  • 直接输入 Verilog 文件和 ModuleInfo
  • 独立于后端 (Verilator,VCS)
  • 消除了与后端沟通的开销,无需借助 Java Native Access(JNA) 或者 Scala 端缓存,没有通信带宽限制
  • Chisel 文档 中介绍了如何从 ChiselTest 迁移到 ChiselSim(svsim)
  • 使用方法和 ChiselTest 几乎没有区别
2024.9.13 更新: sequencer 说 svsim 不好,不建议大家用 chisel 或者 cocotb 写 testbench 大家可以去看看 t1 的测试 flow: https://github.com/chipsalliance/t1 关于如何在 verilator 中使用 verilog 编写的 testbench, 这里有个简单点的示例
深理工一生一芯训练营

SVA (System Verilog Assertions)

在写总线协议的时候,发现软件语言的 Assertion 无法较好的描述时序逻辑。

https://ysyx.oscc.cc/slides/2205/14.html#/添加断言-error---failure/0

  • 在 System Verilog 中,可以使用 System Verilog Assertions
  • System Verilog Assertions (SVA) 是一种用于描述时序逻辑的语言,可以用于描述时序逻辑的性质。
  • 我们可以通过 SVA 来描述各个通道的时序逻辑,然后通过断言将可观测到的 Error 快速变成 Failure (不言自证)
深理工一生一芯训练营

Temporal Properties(SVA)

Chisel 中能否实现类似的 Assertion?

  • 可以!
  • Chisel6 加入的新特性
  • 支持类似于 System Verilog Assertions 的时序属性描述
AssertProperty(a |=> b ### c, clock = Some(clock), disable = Some(reset.asBool))
CoverProperty(d |=> eventually, lable = Some("cool_prop"))
assert property (@(posedge clk) disable iff (reset) a|=> b ##1 c);
cool_prop: cover property (d |-> ##1 (s_eventually e));
深理工一生一芯训练营

Cross Module Reference

  • 在设计大型模块时,模块间的引用十分复杂
  • 在顶层引用某些模块中的信号,对其加以监测
  • 可以配合 SVA 进行进行监测
  • 能够对信号强制赋值 (force)
深理工一生一芯训练营

Probes (Cross Module Reference)

  • 最终在 Chisel6 中得到支持(CIRCT中添加了原语)
class a extends Bundle {
  val x = Flipped(UInt(16.W))
  val refs = new Bundle {
    val out = RWProbe(UInt(16.W))
    val reg = RWProbe(UInt(16.W))
  }
}
class Top extends Module {
  val b = IO(new a)
  val out = IO(Output(UInt(16.W)))

  val r = Reg(UInt(16.W))
  r := b.x
  out := r
  define(b.refs.out, RWProbeValue(out))
  define(b.refs.reg, RWProbeValue(r))
}
深理工一生一芯训练营
  • 可以在 Test 模块中对dut.b.refs.out进行监测
  • 可以对dut.b.refs.reg进行强制赋值
class Test extends Module {
  val dut = Module(new Top)
  val (cycle, done) = Counter(true.B, 20)
  dut.b.x := 42.U
  chisel3.assert(dut.b.x === 42.U)
  chisel3.assert(read(dut.b.refs.out) === dut.out)
  forceInitial(dut.b.refs.reg, cycle)
  when(cycle === 0.U) {
    force(dut.b.refs.reg, cycle)
  }

深理工一生一芯训练营

Chisel 的缺点

  • 文档还不够完善(好多新功能都没有文档)
  • 商业 EDA 工具的支持不够完善
  • 抽象级变多后,影响可读性,加大验证难度
  • 加入 CIRCT(firtool) 后会对信号进行优化,加大验证难度
深理工一生一芯训练营

后续规划

  • 继续使用 Chisel 完成 NPC 的总线和 SoC 的接入
  • 在 FPGA 上构建 NPC 的 SoC(完成毕设)
  • 尝试使用 Chisel 的验证功能对 NPC 进行测试验证
深理工一生一芯训练营

                                          谢谢!

深理工一生一芯训练营