保姆级Rocket-chip自定义指令/加速器教程-chipyard学习笔记

近期在摸索如何在rocketchip中添加自定义指令,记录一下学习笔记

【更新】:项目做完了,上传一些slides方便理解

环境搭建

虚拟机:VMware pro 16

Linux系统:Ubuntu 18.04

chipyard:1.7.0

Vivado:2020.2

学习添加自定义RoCC协同处理器(Accelerator)

RoCC模块基础知识

跑Rocket自带的custom0~3指令配置(RoccExampleConfig)

chipyard/generators/rocket-chip/src/main/scala/tile路径下包含的LazyRoCC.scala文件下自带accumulator、translator、counter和blackbox这四个自定义模块,

可以在和tile同级的subsystem/Configs.scala文件中找到WithRoccExample的调用。 

在同级的system/Configs.scala文件中可以找到RoccExampleConfig的顶层配置

 尝试运行如下命令

cd sims/verilator
make CONFIG=RoccExampleConfig -j

make失败并出现如下错误

 后来发现通过chipyard调用配置文件时,system/Configs.scala并不在路径上,需要在chipyard/generators/chipyard/src/main/scala/config/RocketConfigs.scala中进行顶层配置。

在RocketConfigs.scala添加如下代码

class RoccExampleConfig extends Config(
	new freechips.rocketchip.subsystem.WithRoccExample ++
	new freechips.rocketchip.subsystem.WithNBigCores(1) ++ // single rocket-core 
	new chipyard.config.AbstractConfig
) 

 再次尝试运行make CONFIG=RoccExampleConfig -j

make成功,生成的文件可以在chipyard/sims/verilator/generated-src下找到

复现求最小公倍数的自定义RoCC指令(Least Common Multiple)

原博链接:rocc-加速器设计1-rocket-寄存器访存之求最小公倍数

原博客因为发布时间较久,chipyard版本较旧,推测发布版本为1.1.0之前(因其包含的chipyard/generators/example文件在1.2.0版本后被取消合并)目前一些依赖路径与文件目录发生变化,给复现带来一些难度,遂记录一下修改过程。

代码实现

在chipyard/generators下新建路径LCM_RoCC/src/main/scala/LCMRoCCAccel.scala

cd chipyard/generators
mkdir -p rocc_simple_use_register/src/main/scala
touch LCMRoCCAccel.scala

将下面三部分代码放在LCMRoCCAccel.scala中:

1.导入的库文件

import Chisel._
import freechips.rocketchip.tile._ // 导入LazyRoCC
import freechips.rocketchip.config._ // 导入Config object
import freechips.rocketchip.diplomacy._ // 导入LazyModule

2.求最小公倍数模块的逻辑实现代码

class LCM(val w : Int) extends Module{
    val io = IO(new Bundle{
        val in1 = Flipped(Valid(UInt(w.W)))
        val in2 = Flipped(Valid(UInt(w.W)))
        val out = Decoupled(UInt(w.W))
    })
    
    val x = Reg(UInt(w.W))
    val y = Reg(UInt(w.W))
    val a = Reg(UInt(w.W))
    val b = Reg(UInt(w.W))   
    
    val s_idle::s_dataIn::s_gcdComp::s_lcmComp::Nil = Enum(4)
    val state = RegInit(s_idle)

    state := MuxCase(state,Seq(
        (((state===s_idle)&&io.in1.valid&&io.in2.valid) -> s_dataIn),
        ((state===s_dataIn) -> s_gcdComp),
        (((state===s_gcdComp)&&(x===y)) -> s_lcmComp),
        (((state===s_lcmComp)&&io.out.ready) -> s_idle)))

    when(state===s_dataIn){
        x := io.in1.bits
        y := io.in2.bits
        a := io.in1.bits
        b := io.in2.bits
    }

    when(state===s_gcdComp){
        when(x>=y){ // 相等表明找到了最大公约数
            x := y
            y := x
        }.otherwise{
            y := y - x
        }       
    }
 
    io.out.bits := a * b / x
    io.out.valid := state===s_lcmComp   

3.译码及数据通路设计代码

class LCMRoCCAccel(opcodes: OpcodeSet, val w : Int)(implicit p: Parameters) extends LazyRoCC(opcodes){
    override lazy val module = new LazyRoCCModuleImp(this){//作为隐式类,也可显式写在外面
        // LazyRoCCModuleImp 已经定义好 IO        
       
        val rd = RegInit(0.U(5.W))        
        val rs1Value = RegInit(0.U(w.W))
        val rs1Enable = RegInit(false.B)
        val rs2Value = RegInit(0.U(w.W))
        val rs2Enable = RegInit(false.B)
        
        val busy = RegInit(false.B)
        val canResp = RegInit(false.B)
        io.cmd.ready := !busy
        io.busy := busy

        val canDecode = io.cmd.fire() && (io.cmd.bits.inst.funct===0.U)        
        when(canDecode){ // 每当fire时候会Rocket-core送一条指令过来
            busy := true.B
            rs1Value := io.cmd.bits.rs1
            rs1Enable := true.B 
            rs2Value := io.cmd.bits.rs2
            rs2Enable := true.B
            rd := io.cmd.bits.inst.rd
        }
        val lcm = Module(new LCM(w))  
        lcm.io.in1.bits := rs1Value
        lcm.io.in2.bits := rs2Value
        lcm.io.in1.valid :=  rs1Enable
        lcm.io.in2.valid := rs2Enable

        val lcmRes = RegInit(0.U(w.W))
              
        lcm.io.out.ready := Mux(lcm.io.out.valid, true.B, false.B)
        when(lcm.io.out.valid){
            lcmRes := lcm.io.out.bits
            canResp := true.B
        }

        io.resp.valid := canResp
        io.resp.bits.rd := rd
        io.resp.bits.data := lcmRes
        when(io.resp.fire()){
            canResp := false.B
            busy := false.B
            rs1Enable := false.B
            rs2Enable := false.B
            rs1Value := 0.U
            rs2Value := 0.U
            lcmRes := 0.U
        }  
       
    }
}

将如下代码添加至chipyard/generators/rocket-chip/src/main/scala/subsystem/Configs.scala

class WithLCMRoCCAccel extends Config((site,here,up) => {
    case BuildRoCC => Seq(       
        (p:Parameters) => {
            val regWidth = 64 // 寄存器位宽
            val lcmAccel = LazyModule(new LCMRoCCAccel(OpcodeSet.all, regWidth)(p))
            lcmAccel
        }
    )
})

将如下代码添加至chipyard/generators/chipyard/src/main/scala/config/RocketConfigs.scala

class LCMAccelRocketConfig extends Config(
	new freechips.rocketchip.subsystem.WithLCMRoCCAccel++
	new freechips.rocketchip.subsystem.WithNBigCores(1) ++ // single rocket-core 
	new chipyard.config.AbstractConfig) 

将如下代码添加至chipyard/build.sbt 

lazy val LCMRoCCAccel = (project in file("generators/LCM_RoCC"))
  .dependsOn(rocketchip, midasTargetUtils, testchipip)
  .settings(commonSettings)

运行如下指令

cd sims/verilator
make CONFIG=LCMAccelRocketConfig -j

 在chipyard/sims/verilator/generated-src下生成如下文件

generated-src文件夹包括生成的Verilog文件和一些测试用的文件,verilator文件夹包含仿真工具verilator的源码和安装文件。

题外话:若显示compile error: not found LCMRoCCAccel,可尝试另一种更直截了当的方法,将LCMRoCCAccel.scala源码直接添加进LazyRoCC.scala文件中,可避免一些因依赖调用问题产生的error

测试代码

1、Rocket 性能评估

可以通过读取 CSR 专用寄存器查看性能,CSR 访问封装在 encoding.h :

  • cycle(64bits) :自CPU复位以来用了多少个时钟周期

  • time(64bits):自CPU复位以来用了多长时间,驱动频率固定

  • instert(64bits): 自CPU复位以来完成了条指令

2、RoCC 指令

ROCC_INSTRUCTION_DSS 封装在 rocc.h 当中,对于R类指令进行编码。

3、添加 fence 及 fence:::memory 汇编指令可以保证写回结果后,再取下条指令。

将下列代码添加至chipyard/tests/lcm.c

#include<stdio.h>
#include "rocc.h"
#include "encoding.h"
#define SIZE 10

unsigned long long  gcdCompute(unsigned long long  a, unsigned long long  b){
	long long unsigned temp;
	while(a != b){
		if(a>b){
			temp = b;
			b = a;
			a = temp;
		}
		b = b - a;
	}
	return a;
}

int main(void){
	unsigned long long  randNum1[SIZE] = {
            26985, 84546, 46198, 38570, 46417, 49941, 8138, 8827, 99324, 96819}; 
	unsigned long long  randNum2[SIZE] = {
            2826, 77394, 39239, 46078, 43985, 43458, 34337, 66575, 76502, 17900};
	unsigned long long  swLcmRes[SIZE] = {0};
	unsigned long long  hwLcmRes[SIZE] = {0};	
	unsigned long long  start, end;
	// 软件计算
	start = rdcycle();
	for(int i=0; i<SIZE; i++){
		swLcmRes[i] = randNum1[i]*randNum2[i]/gcdCompute(randNum1[i], randNum2[i]);
	}
	end = rdcycle();
	// printf("LCM compute:\n");
	// for(int i=0; i<SIZE; i++){
	// 	printf("(%lld, %lld) -> %lld\n", randNum1[i], randNum2[i], swLcmRes[i]);
	// }
	printf("SW average cycles used:  %lld\n", (end-start)/SIZE);
	
	// RoCC 加速
	start = rdcycle();
	for(int i=0; i<SIZE; i++){
		asm volatile ("fence"); // 保证数据都存会内存
		ROCC_INSTRUCTION_DSS(0, hwLcmRes[i], randNum1[i], randNum2[i], 0);
		asm volatile ("fence" ::: "memory");
	}	
	end = rdcycle();

	for(int i=0; i<SIZE; i++){
		if(swLcmRes[i] != hwLcmRes[i]){
			printf("test failed! %lld and %lld LCM: swLcmRes: %lld, hwLcmRes: %lld\n", randNum1[i], randNum2[i], swLcmRes[i], hwLcmRes[i]);
			return 0;
		}
	}	
	printf("HW average cycles used:  %lld\n", (end-start)/SIZE);
	printf("test successed! \n");
	return 0;
	
}

运行命令编译测试程序

riscv64-unknown-elf-gcc lcm.c -o lcm

生成同名riscv可执行文件

将生成的lcm可执行文件放到verilator工作目录,同级下的simulator-chipyard-LCMAccelRocketConfig是可执行文件,是测试程序的入口。

在此工作目录下执行命令 

./simulator-chipyard-LCMAccelRocketConfig pk lcm

等待几分钟后,终端输出测试结果

success! 

参考资料:

Chipyard生成Boom核运行程序并生成波形文件

运行以下代码可以生成日志文件和vcd波形文件

make CONFIG=LCMAccelRocketConfig run-binary-debug BINARY=lcm

若lcm.riscv不在当前目录下,需在文件前指定对应路径,比如BINARY=../../tests/lcm

生成的波形文件可以在verilator/output下找到

设计一个支持双采样模式的FIR自定义指令(ECG验证)

项目指标设计

流程图

加速器内部结构图

文件流

FIR滤波器参数设计(MATLAB)

 生成滤波输入信号

这学期上了一门医疗传感器的课程(Sensors in medical instrumentation),对ECG(Electrocardiogram)心电图信号产生了一定兴趣,打算研究下关于心电信号的滤波预处理。

关于心电图信号的源数据可以从MIT-BIH网站上开源下载。https://archive.physionet.org/cgi-bin/atm/ATM

可参考教程:

读取MIT-BIH数据库中的心电数据[MATLAB]

  • MIT-BIH Arrhythmia Database是一个广泛使用的心电图数据库,由MIT和波士顿医院的合作团队在上世纪80年代创建。该数据库收集了来自多位患者的长时间心电图记录,以及带有心律失常和正常心律的短时间片段。它包含多种心律失常的样本,如室上性心动过速、室性心动过速、房颤等。它已成为心电信号处理和心律失常检测算法开发的标准基准。该数据库提供了详细的心电图标注和注释,使研究人员可以验证和比较不同的心律失常检测算法。                                                             ——chatGPT

心电图的噪声主要包括三大类:低频率的基线漂移,50/60Hz的工频干扰,以及高频的肌电噪声

以下为提取MIT-BIH数据导入Matlab

clear
clc
Fs=360;        %采样频率
[filename, pathname] = uigetfile('*.dat', 'Open file .dat');% only image Bitmap
if isequal(filename, 0) || isequal(pathname, 0)   
    disp('File input canceled.');  
   ECG_Data = [];  
else
fid=fopen(filename,'r');
end
time=10;
f=fread(fid,2*360*time,'ubit12');
M=f(1:2:length(f));
M = M-1024;                             
M=0.005*(M);
t=(0:1:length(M)-1)/Fs;
N=length(M);

 生成滤波器具体参数

可以使用matlab自带的工具箱设计滤波器参数,在命令行窗口直接输入fdatool即可调用

分别设计8阶与16阶的滤波器并导出参数

从VMware中导出数据到matlab中作图时,我采用的是将输出数据打印出来,并重定向到特定文件,之后从该文件直接读取数据就可以导入matlab作为数据源。

将Linux下编译的warning警告信息输出到文件中

代码实现

Tips: 编写代码时统一缩进格式,Scala官方推荐是每级缩进两个空格,千万不要tab和空格混用!!可能导致出现嵌套错位,在编译时出现一些奇奇怪怪的error

分别设计了三种类型的FIR滤波器架构(其实是被导师催着不断优化)

内部实现FIR算法的状态机长这样

功能验证与仿真测试

编写测试文件

这里我们采用chipyard自带的编译方式,实际测试中速度提高了四到五倍,节省很多debug等待时间。原博客链接:rocket-chip generator介绍及其仿真使用

在chipyard/toolchains/riscv-tools/riscv-tests/benchmarks中新建测试文件夹,放入写好的fir.c,连同必要的头文件。

打开benchmark目录下的Makefile,修改其bmarks变量,添加新增的文件夹名字fir

 执行make命令

cd chipyard/toolchains/riscv-tools/riscv-tests/benchmarks
make

得到.riscv可执行文件

 将.riscv文件移到chipyard/sims/verilator文件夹中

软硬件计算周期数对比

在chipyard/sims/verilator目录下执行下列命令

./simulator-chipyard-FIRAccelRocketConfig fir.riscv

可以看到计算结果通过了一致性测试,其中硬件加速器获得了接近16倍的提速

分析vcd波形文件

分析波形文件是debug中很重要的一环,运行下列代码即可生成vcd后缀的波形文件。

但不用的vcd文件记得删,在测试3600个输入信号时生成的波形文件有40G,直接给磁盘干爆了。

make CONFIG=FIRAccelRocketConfig run-binary-debug BINARY=fir.riscv

文件位于chipyard/sims/verilator/output/chipyard.TestHarness.FIRAccelRocketConfig/fir.vcd

同目录下还有.log文件和.out文件,分别存放运行程序的输出和Core内存相关的输出。

有时候用rm指令清理完文件,磁盘空间并没有被释放,此时无法运行其他程序

运行如下代码查看磁盘占用率

df -h

可以看到主分区sda1占用率100%(此时重启会导致虚拟机无法开机) 

 可通过删除被挂起的进程释放磁盘空间,指路 👉linux系统删除文件后,仍占用磁盘空间

如果已安装GTKWave,双击vcd文件即可打开波形

打开GTKwave后初始界面是没有信号的,下一步是在庞大的信号树中找到我们需要的信号波形,可以使用Search下的Signal Search Tree (SST) 或者Signal Search Regexp帮助寻找对应信号

我们需要的rocket-core关键信号位置如下

Top->TestHarness->chiptop->system->tile_prci_domain->tile_reset_domian->tile

选中需要的信号并点击insert,出现波形界面如下所示

可以通过上方蓝色reload按钮来导入新的波形信号,保持原信号列表不变

滤波精度/资源分析

Matlab精度分析

不多说,直接上图

FPGA逻辑综合(Vivado 2020.2)

在generated_src文件夹中可以找到生成的v文件,但不是所有文件都是可综合的,Vivado使用过程不再展开。

综合结果如下,此处禁用了DSP单元,因为可以用LUT更高效地实现。

使用wget下载GitHub文件压缩包(主题无关可跳过)

这里算是小小的题外话,但也记录一下。因为学校的CentOS7虚拟远程连接不支持git clone,也无法登录网页,所以只能使用wget命令从GitHub上扒自己的仓库源码下载,摸索一番后以下做个示范。

首先是从GitHub上复制自己的仓库链接

想要下载 GitHub 仓库的某个特定分支的源代码时,GitHub 提供了一种特殊的 URL 结构,形如:

https://github.com/username/repo/archive/refs/heads/branch.zip

在这个 URL 中:

  • username 是仓库所有者的用户名
  • repo 是仓库的名称
  • branch 是你想要下载的分支的名称(例如 "master" 或 "main")

上面路径中的archive/refs/heads并不在我的真实路径中,需要手动添加,不然会出现ERROR 404:Not Found报错。

也可以用-O指定下载的压缩包名称为DC_syn.zip,因此下载我自己仓库的完整命令如下

wget -O DC_syn.zip https://github.com/yaoyao0927/FIRRocketConfig/archive/refs/heads/master.zip

下载完成

 然后运行下列指令即可将压缩包解压到相应路径(FIR是同目录下另一个文件夹)

unzip DC_syn.zip -d FIR

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值