在2023年初,达坦科技发起成立硬件设计学习社区,邀请所有有志于从事数字芯片设计的同学加入我们的学习互助自学小组,以理解数字芯片设计的精髓,强化理论知识的同时提升实操技能,继而整体提升设计能力。6.175和6.375的课程和Lab学习都有一定的难度,要求采用Bluespec语言实现RISC-V处理器,并支持多级流水、分支预测、缓存、异常处理、缓存一致性等功能。此外,Lab环节还涉及软硬件联合开发,要求基于所实现的RISC-V处理器运行真实的RISC-V程序,并给出性能评估。
继MIT6.175和MIT6.375学习笔记之后,我们又整理了到目前为止,硬件设计学习社区里大家碰到的一些共同问题,希望我们的回复以及学习贴士对于想啃下这两门高难度课程,并想从事数字芯片设计的工程师或同学有所帮助。
01. MIT Training Q & A
Q1: 如何获得这两门课程的Lab的初始代码以及对应的评测程序;
A: MIT 6.175课程的官网没有提供Lab的初始代码,但可以在GitHub上找到Lab的代码实现和评测程序:如
https://github.com/dmendelsohn/6.175
https://github.com/kazutoiris/MIT6.175
https://github.com/GTwhy/MIT_6.175
MIT 6.375课程的大部分初始代码和评测程序可在课程官网下载到, 少部分缺失的代码可以在一下仓库找到:
https://github.com/adamgallas/MIT_Bluespec_RISCV_Tutorial
Q2: 如何搭建这两门课Lab所需的软件开发环境 ?
A: 这两门课的Lab都需要在Linux环境下进行,具体依赖的软件包括:
-
BSV语言的编译和仿真器 BSC(bluespec complier):下载链接为 https://github.com/B-Lang-org/bsc/releases; 具体的安装步骤见解压后文件夹里的README文档
-
riscv工具链: 手动编译riscv工具链过程繁琐耗时且有些高版本的工具链可能并不适配Lab中的代码。推荐使用开源的预编译好的工具链,如:https://github.com/stnolting/riscv-gcc-prebuilt
-
connectal软硬件协同开发环境: connectal项目链接如下https://github.com/cambridgehackers/connectal。实验中不建议手动编译源码构建开发环境(可能会引入很多bug),可以使用一些已经配置好的docker镜像,如:
- https://hub.docker.com/r/pwang7/connectal
- https://hub.docker.com/r/kazutoiris/connectal
Q3: 对于这两门课的有没有推荐的学习顺序?
A: 这两门课的侧重点各有不同,6.175更侧重体系结构相关的内容,而6.375对BSV语法的讲解更加详尽,因此在阅读这两门课的slides和textbook时可以互相交叉,先主要学习BSV语法,然后再看体系结构相关的内容。这两门课的Lab也都可以分成两部分,第一部分是BSV基础语法,另外一部分是riscv CPU相关的内容。建议的完成顺序如下:先学习BSV基础语法部分,包括6.175的Lab0 - Lab4以及6.375的Lab1-Lab4; 然后完成和riscv相关的内容, 包括 6.175的Lab5-Lab8和project,以及MIT6.375的Lab5。
Q4: lab6 sixstage benchmarks median case fetch得到PC全为0
A: 原因是doExe stage在指令执行成功但没有mispredict的情况下才将传给下一阶段的eInst设置为Valid。但是其实即使mispredict该条指令也应该是可以正常执行的,因此需要在执行后无论predict情况如何都设置为Valid。
Q5: CPU跑通,但发现ipc只有0.5,什么导致IPC降低?
A: 在doExecute stage mispredict之后sb.remove导致IPC降低。
TA中提到了原因:
Both Exec and WB try to call sb.remove(). This will cause Exec to conflict with WB. Also, the scoreboard implementation doesn’t allow out-of-order removal.
Q6: 做完lab6后开始测各个设计的benchmark。发现很多不符合预期,该有的优化没有体现出来。
A: 问题主要出在串联各个stage的bypassfifo实际上是用一个巨大的组合逻辑把所有rule串了起来。每一拍同时触发,数据从第一个stage到最后一个依次流过。因此只要预测错误,通过exe stage重置pc则下一拍就能纠正,但看IPC就已经很高了,加入其他优化并不能使性能更高。在对fifo,sb,rfile进行更换后,换成cffifo后体现出了一定的性能差别。
Q7: 六级流水CPU加上BHT后存在冲突。
A: 观察BHT的构成和使用都只是是普通的寄存器读写,没有找到冲突源。BHT改为Bypass BHT冲突仍然存在。猜测问题是Decode预测用BHT,Exe阶段更新使用BHT,导致了冲突。
将BHT.update放到Canonicalize后BHT没问题了。但为什么Bonus采用Bypass FIFO的时候没有这些问题?
因为Bypass FIFO相当于把所有Stage连接成了一个组合逻辑电路,从Fetch到WB由组合逻辑确定执行关系。BHT在其中虽然进行了多次读但只进行了一次写,且顺序与Bypass FIFO一致。
可能的原因是存在优先级顺序的冲突。即FIFO,SB,RFile等部件包含了一个并发时Rule的执行顺序,新加入的BTB、BHT等如果放置在错误的Stage,其操作寄存器的顺序,对EHR的读写、覆写的顺序可能与FIFO等定义的顺序不符。这样一来就会引发冲突。
因此比较好的做法是,以FIFO为抓手,思考每个Stage的优先级顺序,确定之后开始选择符合优先级顺序的SB,RFile,BTB等部件。
之所以Canonicalize可以避免冲突,主要是因为其可以在所有Stage之后运行,因此避免了正向数据流和反馈回路结合导致的复杂优先级关系。这样一来反馈回路就有了一个简单可靠的设计模式。即所有Stage将需要反馈的内容写入到XXXRedirect[0]中,在Canonicalize Rule中对Redirect进行识别并完成反馈。
这样一来只需要考虑反馈作用的部件所在Stage与Canonicalize之间的优先级关系,而不是每个Stage与其要反馈的Stage之间的优先级关系,降低了问题的复杂性。
Q8: jalr和ras这两个case报错,return code!=0,但其他case正常。
A: 因为在没有进行stall判断的情况下就进行了redirect。更本质地说是没有deq就改变了电路状态,或者该原子性完成的多个操作被分开了。
在stall的情况下这一条指令会在下一拍重新执行所有该stage的流程。如果redirect pc但因为stall而没有deq,则当前这条ppc不正确的指令会在下一拍被kill掉,漏掉了一条指令,而下一次exe的指令将会是这一次reg阶段预测出来的ppc,相当于跳过了很多条指令。
由此得到的教训是:当rule中存在多个分支(判断)时,要仔细考虑哪些操作应该是一个原子事务。尤其关注一个rule中可能对其他rule产生影响的部分,如上例中的regDirect以及对fifo的enq和deq操作。
Q9: 在 Windows 上跑 docker 奇慢无比,完成一次 connectal 的编译可能要花 10 分钟以上。
A: 检查一下 CPU 的型号,在最新的 Intel CPU 中引入了大小核,后台任务会被调度至小核上,尤其是虚拟机一类的性能表现会相当差。建议搜索有关大小核调度的文档,禁用小核来取得比较好的性能。
Q10: 编译时候总是出现 schedule 的 error。
A: 不推荐使用 attribute 去指定 rule 的优先级,一般仅用于 assert 断言。可以尝试输出 rule 的 dot 文件来查看所有 rule 的依赖关系,从而判定前后顺序。由于 rule 前后级依赖相当多,很容易会写出一些前后冲突或者组合逻辑环。为此,可能需要学习一下 Pipeline FIFO / Bypass FIFO / Conflict-Free FIFO 的依赖关系。
Q11:我在做6.175的lab5的时候,这个lab需要先编译那些汇编测试用例,包括在Lab/programs/assembly/src/目录下的.S文件,和Lab/programs/benchmarks/目录下的文件,先生成.riscv的elf文件,再通过elf2hex生成.riscv.vmh的文件,我觉得好像是自己配置的elf2hex工具有问题,最后生成的.riscv.vmh文件格式不对,放到connectal模拟出来的处理器上,跑不出来。
A:elf2hex好像在binutils里被depricated了,不建议用。可以不用elf2hex,直接用Connetal给CPU加载binary。
Q12:BSV中关于一个Rule内部,不同代码之间先后顺序的差异,官方文档在哪里有描述么?
举一个例子,比如我定义一个非寄存器类型的变量,之前我一直理解的是这个变量因为不存储状态,就可以把它当做一根链接两个逻辑门的导线,这样的话,在一个rule或method内部,代码书写的顺序应该是没有关系的。但是,以下面的代码为例,对变量a的赋值操作,给a变量赋值的顺序不同,会导致仿真 结果不一样。所以之前我把它理解成导线是错的吗?或者说,这里的“赋值”其实会发生变量的覆盖?我在官方手册里找到这么一句话:Multiple assignments to the same variable are just a shorthand for a cascaded computation
如果这么理解的话,就是Bool a = ?;这一行也相当于是一次赋值,而不仅仅当做一个占位符使用,要是这么理解就能说得通了。
rule test;
Bool a = ?;
// Write a = True here?
a = True;
if (a) begin
$display("aaaa");
end else begin
$display("bbbb");
end
// Or write here
// a = True;
endrule
A:这个问题很好,对于深入理解RTL电路建模很有帮助。Rule内部的非Reg变量都会生成连线,Rule之间可以用Wire连线。Rule内部的时序逻辑如果在同一个作用域可以前后交换顺序,Rule内部的组合逻辑一般不能随意交换顺序。这个对应实际数字电路工作的场景,组合逻辑一般是有先后顺序的,时序逻辑一般是并行工作,并行工作的时序逻辑没有先后顺序。
这个问题其实对应于Verilog的时序逻辑always块和组合逻辑always块。在时序逻辑always块里的非阻塞赋值,如果在同一个作用域,可以交换顺序。在组合逻辑always块里的阻塞赋值有先后顺序关系,一般不能交换顺序。
Q13:话说我用docker搭的环境下用makefile跑bluespec仿真 总是make:killed这个问题大家有遇到过吗?
A:我目前是用docker做的实验,6175的lab1-6和6375的lab1-4目前没有遇到这个问题。不过目前github上能找到的实验项目可能跨了很多年,调试环境确实需要花些精力。
02. MIT Training 小贴士
Tips1:在做6.175的lab5实验时,发现run_asm.sh和run_bmarks.sh这两个脚本存在潜在错误的风险。我对照了群里几位同学之前的脚本,发现都存在这个问题:这个shell脚本中有如下两行:
make run.bluesim > ${log_dir}/${test_name}.log & # run bsim, redirect outputs to log
sleep ${wait_time} # wait for bsim to setup
注意第一行后面的&符号,会把仿真任务放到后台运行。第二行的wait_time默认是3秒,如果编译机器的速度比较慢,在3秒内无法完成编译,则会导致同时启动多个仿真,而这些仿真会共享bluesim/mem.vmh文件,从而导致冲突。
Tips2: 给大家同步一个坑~目前6.175的Lab5,github上能找到的一些实验源码,6.175/lab5/main.cpp里面的(1>>16) - 1应该是(1<<16) - 1,这个错误会导致在运行benchmark的时候,输出的cycle和inst数量有错误,大家做实验的时候需要小心(特别是在回答实验的question时候,计算IPC的时候需要用到这个输出结果)下图是修改后的正确书写方式。
这个是我修改后的仓库,已经推到GitHub:https://github.com/myrfy001/learn_mit_bluespec.git
03. Related Resources
达坦科技硬件设计学习社区持续开放,若想询问加入细节,请添加下方小助手微信号(DatenLord_Tech)或邮件info@datenlord.com
达坦科技(DatenLord)专注下一代云计算——“天空计算”的基础设施技术,致力于拓宽云计算的边界。达坦科技打造的新一代开源跨云存储平台DatenLord,通过软硬件深度融合的方式打通云云壁垒,实现无限制跨云存储、跨云联通,建立海量异地、异构数据的统一存储访问机制,为云上应用提供高性能安全存储支持。以满足不同行业客户对海量数据跨云、跨数据中心高性能访问的需求。
公众号:达坦科技DatenLord
DatenLord官网:www.datenlord.io
知乎账号:
https://www.zhihu.com/org/da-tan-ke-ji
B站:
https://space.bilibili.com/2017027518