近期在摸索如何在rocketchip中添加自定义指令,记录一下学习笔记
【更新】:项目做完了,上传一些slides方便理解
虚拟机:VMware pro 16
Linux系统:Ubuntu 18.04
chipyard:1.7.0
Vivado:2020.2
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-加速器设计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
#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 %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
运行命令编译测试程序
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下找到
这学期上了一门医疗传感器的课程(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文件移到chipyard/sims/verilator文件夹中
在chipyard/sims/verilator目录下执行下列命令
./simulator-chipyard-FIRAccelRocketConfig fir.riscv
可以看到计算结果通过了一致性测试,其中硬件加速器获得了接近16倍的提速
分析波形文件是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按钮来导入新的波形信号,保持原信号列表不变
不多说,直接上图
在generated_src文件夹中可以找到生成的v文件,但不是所有文件都是可综合的,Vivado使用过程不再展开。
综合结果如下,此处禁用了DSP单元,因为可以用LUT更高效地实现。
这里算是小小的题外话,但也记录一下。因为学校的CentOS7虚拟远程连接不支持git clone,也无法登录网页,所以只能使用wget命令从GitHub上扒自己的仓库源码下载,摸索一番后以下做个示范。
首先是从GitHub上复制自己的仓库链接
想要下载 GitHub 仓库的某个特定分支的源代码时,GitHub 提供了一种特殊的 URL 结构,形如:
https://github.com/username/repo/archive/refs/heads/branch.zip
在这个 URL 中:
上面路径中的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