第 10 章 高速缓存设计
细心的读者应该会发现,自从我们给CPU添加AXI总线接口并去除指令RAM和数据RAM之后,它的运行效率就大打折扣了,运行同样的程序需要花费更多的执行周期。那么,去除指令RAM和数据RAM是不是一种设计倒退呢?其实不然,指令RAM和数据RAM的使用要求软件人员明确掌握物理内存的容量、起始地址,增加了软件开发难度。目前,这种硬件架构仅在那些对于成本、功耗或执行延迟的确定性极为敏感的低端嵌入式领域广泛使用。这些应用领域还有一个特点是软件规模不大、程序行为相对确定,否则没有虚拟化存储管理对于应用开发来说就是“灾难”。不过,纵然指令RAM和数据RAM有这样的不足,但是性能问题也是要解决的。我们的解决思路是增加高速缓存(Cache)。
本章我们将进入最后一个设计阶段—为CPU添加Cache。这是一项很有挑战性的工作,因为围绕Cache的设计优化技术太多,导致Cache设计的复杂度的变化范围很大。在本章中,我们会把Cache的设计复杂度控制在入门级水平,兼顾性能。在Cache实现规格的细节参数上,我们也会给出一整套明确的设定。不过,读者可以放心的是,我们所选取的参数具有代表性,大多数参数即使需要调整,也只是1和2的区别,而不是从0到1的跨越。在具体实施步骤上,我们分成四个阶段:
- 阶段一 设计Cache模块。
- 阶段二 将Cache模块作为ICache(指令Cache)集成到CPU中,完成与CPU取指的配合、调整,并完成总线接口模块的设计调整。
- 阶段三 将Cache模块作为DCache(指令Cache)集成到CPU中,完成与CPU访存的配合、调整,并完成总线接口模块的设计调整。
- 阶段四 实现对Cache指令的支持。
在开始学习之前,请确保已经认真学习了《计算机体系结构基础》(第3版)中9.5.4节或其他文献中关于Cache基本概念的内容。
【本章学习目标】
- 理解Cache的组织结构和工作机理。
- 理解LoongArch架构中的Cache相关控制状态寄存器和指令。
- 掌握在流水线CPU中添加Cache支持的方法。
10.1 Cache模块的设计
10.1.1 Cache模块的数据通路设计
10.1.1.1 Cache模块功能边界划分
除了Cache表中必须要实现的数据通路,Cache模块内部还需要实现哪些数据通路取决于我们将整个读、写操作访问Cache的执行过程中余下的哪些功能放在Cache模块内实现、哪些功能放在其他模块实现。因此,我们有必要先划分出功能边界。
如前所述,我们倾向于Cache与CPU流水线之间的功能边界划分与现有“类SRAM-AXI”转接桥和CPU流水线之间的功能边界划分保持一致。简单来说,就是CPU流水线向Cache模块发送请求,Cache模块给CPU流水线返回数据或是写成功的响应。显然,这样划分之后,CPU流水线基本上不需要进行更改。此外,因为目前采用的是VIPT 的Cache访问方式,所以还需要CPU中的TLB模块将转换后的物理地址送到Cache模块中,Cache模块将该物理地址寄存一拍,然后用于Cache Tag比较。
比如,我们可以将Cache模块与CPU流水线的交互按照表10.1定义。
名称 | 位宽 | 方向 | 含义 |
valid | 1 | IN | 表明请求有效 |
op | 1 | IN | 1:WRITE;0:READ |
index | 8 | IN | 地址的index域(addr[11:4]) |
tag | 20 | IN | 经虚实地址转换后的paddr形成的tag,由于来自组合逻辑运算,故与index是同拍信号。 |
offset | 4 | IN | 地址的offset域(addr[3:0]) |
wstrb | 4 | IN | 写字节使能信号 |
wdata | 32 | IN | 写数据 |
addr_ok | 1 | OUT | 该次请求的地址传输OK,读:地址被接收;写:地址和数据被接收 |
data_ok | 1 | OUT | 该次请求的数据传输OK,读:数据返回;写:数据写入完成 |
rdata | 32 | OUT | 读Cache的结果 |
接下来,我们要考虑Cache模块通过AXI总线接口访存的这些功能如何划分。最极端的划分方式是把所有功能都放到Cache模块中。你可以认为这是把现有“类SRAM-AXI”转接桥整个替换为新的Cache模块。不过,既然名字为Cache模块,里面又包含大量AXI 协议处理的逻辑,显然这不是一种合适的模块划分方式。就算换个名字,比如改为cache_mem_module,这个模块内部的功能也确实太多了。因此,我们建议保留一个内部访存总线到AXI总线的接口转换模块。这个转换模块对CPU内部有多个端口,对CPU外部只有一个AXI总线接口。我们把多个请求之间的仲裁、AXI协议的处理都放到这个模块中,这样Cache模块与这个AXI总线接口模块之间的功能划分仍然是简洁的。Cache模块向AXI总线接口发请求,AXI总线接口模块返回数据或响应。
基于上面的划分,Cache模块向AXI总线接口模块发送的请求中的地址、操作类型、长度等信息的交互就容易设计了,这些信息都很短,在一拍之内交互完毕即可。需要重点考虑的是读或者写的数据如何交互。因为Cache行有16个字节,我们希望在AXI总线上采用突发(Burst)访问模式来进行读写访问,这样可以尽可能减少请求通道上的交互开销。那么问题是,Cache模块和AXI总线接口模块之间的数据交互是否也需要定义类似的Burst传输模式?还是两者在一拍之内把16字节交互完毕?这里的设计取决于我们设计的出发点。我们觉得,对于本书的学习目标来说,首要任务是保证功能,其次是性能过关,有条件的话再考虑一些功耗方面的优化。因此,我们给出的设计建议是:对于读操作,AXI总线接口模块每个周期至多给Cache模块返回32位数据,Cache模块将返回的数据填入Cache的Bank RAM中或者直接将其返回给CPU流水线;对于写操作,Cache模块在一个周期内直接将一个Cache行的数据传给AXI总线接口模块,AXI总线接口模块内部设一个16字节的写缓存保存这些数,然后再慢慢地以Burst方式发出去。
于是,我们可以将Cache模块与AXI总线接口模块之间的接口按表10.2定义。
名称 | 位宽 | 方向 | 含义 |
---|---|---|---|
rd_req | 1 | OUT | 读请求有效信号。高电平有效。 |
rd_type | 3 | OUT | 读请求类型。3’b000——字节,3’b001——半字,3’b010——字,3’b100——Cache行。 |
rd_addr | 32 | OUT | 读请求起始地址. |
rd_rdy | 1 | IN | 读请求能否被接收的握手信号。高电平有效。 |
ret_valid | 1 | IN | 返回数据有效信号后。高电平有效。 |
ret_last | 2 | IN | 返回数据是一次读请求对应的最后一个返回数据。 |
ret_data | 32 | IN | 读返回数据。 |
wr_req | 1 | OUT | 写请求有效信号。高电平有效。 |
wr_type | 3 | OUT | 写请求类型。3’b000——字节,3’b001——半字,3’b010——字,3’b100——Cache行。 |
wr_addr | 32 | OUT | 写请求起始地址。 |
wr_wstrb | 4 | OUT | 写操作的字节掩码。仅在写请求类型为3’b000、3’b001、3’b010情况下才有意义。 |
wr_data | 128 | OUT | 写数据。 |
wr_rdy | 1 | IN | 写请求能否被接收的握手信号。高电平有效。此处要求wr_rdy要先于wr_req置起,wr_req看到wr_rdy后才可能置上。所以wr_rdy的生成不要组合逻辑依赖wr_req,它应该是当AXI总线接口内部的16字节写缓存为空时就置上。 |
在上面定义的信号中,之所以还要考虑字节、半字、字的访问是为了支持Uncache访问。
上述接口定义好之后,大家还要对现有的类SRAM-AXI总线转接桥模块进行相应调整。请注意,此时该模块对内是两组读接口、一组写接口,第一组是指令Cache的rd_*和ret_*,第二组是数据Cache的rd_*和ret_*,第三组是数据Cache的wr_*。虽然接口看上去多了一组,但整个转接桥内部的数据通路并不需要进行大的调整。
10.2 任务与实践
完成本章的学习后,希望读者能够完成以下4个实践任务:
- 设计Cache模块,参见下面第10.2.1小节。
- 在CPU中集成ICache,参见下面第10.2.2小节。
- 在CPU中集成DCache,参见下面第10.2.3小节。
- 在CPU中添加CACOP指令,参见下面第10.2.4小节。
10.2.1 实践任务20:Cache模块设计
本实践任务的要求如下:
- 设计Cache模块。
- 利用Cache模块级验证环境对所设计的Cache进行验证,通过仿真和上板验证。
请参照第2.3.1节中介绍的方式获取本次实践任务所需的实验开发环境。具体的实验环境与之前的环境不同,是针对Cache模块的单独验证环境,位于mycpu_env/module_verify/cache_verify/ 目录下。具体目录结构及各部分功能简介如下所示:
|--cache_verify/ 目录,Cache模块级验证环境。
| |--rtl/ 目录,包含Cache模块以及验证顶层的设计源码。
| | |--cache_top.v Cache模块级验证的顶层文件。
| |--testbench/ 目录,包含功能仿真验证源码。
| | |--testbench.v 仿真顶层。
| |--run_vivado/ Vivado工程的运行目录。
| | |--constraints/ Vivado工程的设计约束。
| | |--cache_prj/ Vivado工程文件所在目录。
实验环境准备就绪后,请参考下列步骤完成本实践任务:
- 完成Cache模块的设计和RTL编写,记为cache.v,该模块名需要命名为“cache”,除时钟输入clk和低电平有效复位输入resetn以外的输入/输出端口参见本章第10.1节中表10.1和表10.2中的定义。Cache模块的设计规格要求:2路组相连,每路大小4KB,LRU或伪随机替换算法,推荐硬件初始化。将cache.v文件放入mycpu_env/myCPU/ 目录下。
- 进入 mycpu_env/module_verify/cache_verify/run_vivado/cache_prj/ 目录下启动验证cache的工程。如果该目录下尚未创建工程,请参照附录D.2节介绍的步骤,利用该目录下的 create_project.tcl 文件创建工程。如需要,请参考附录D.4节进行IP核升级。
- 在验证Cache模块的工程中运行仿真(进入仿真界面后,直接点击run all),进行功能验证与调试,直至仿真测试通过。
- 在验证Cache模块的工程中综合实现后生成bit流文件,进行上板验证。(如果无硬件实验平台,请跳过该步骤。)
10.2.1.1 仿真验证结果判断
模块级验证会从index=0的时候开始验证,针对每个index,生成四组随机的tag和data对。首先生成写请求将这四组数写进cache,然后再生成读请求读它们。如果中间没有发生错误,index递增,重新生成tag和data对进行相同的测试,直到index==ff的测试完成为止。
对于写cache请求。验证环境期望看到的结果是,写请求发出后会出现Cache miss,Cache模块会发出rd请求,验证环境返回全1值(0xFFFFFFFF)。写请求可能会引发替换操作,这时验证环境会拿wr_addr和wr_data和前述的tag/data组合做对比,如果replace的值有错,测试会中止。
写操作全部进行完之后会有读操作,验证环境会做同样的检测。当cache返回读操作的结果之后,验证环境会检测读到的结果与之前写入的结果是否相同。
在仿真时,会对每一个index生成四个cache行的先写再读的操作,所有操作都完成后会打印PASS,如下所示:
[ 2705 ns] index 00 finishd
…………
=========================================================
Test end!
----PASS!!!
如果在仿真中发现错误,请进行调试,控制台会打印出错误的原因。验证环境只会检查替换时的数据错误和Cache read的数据错误。
10.2.2 实践任务21:在CPU中集成ICache
本实践任务要求在实践任务19和实践任务20完成的基础上完成以下工作:
- 将实践任务20完成Cache模块作为ICache集成到实践任务19完成的CPU中。
- 修改CPU中的AXI转换桥,以支持Burst传输。
- 在采用AXI总线的SoC验证环境里完成exp21对应func的功能验证,要求成功通过仿真和上板验证。
请参照第2.3.1节中介绍的方式获取本次实践任务所需的实验开发环境。具体的实验环境仍位于 mycpu_env/ 目录下,且仍使用 soc_axi/ 子目录。
实验环境准备就绪后,请参考下列步骤完成本实践任务:
- 将所实现CPU的代码更新至mycpu_env/myCPU/目录中。
- 修改func配置文件——mycpu_env/func/include/test_config.h,选择exp21的配置,编译。(如果是通过压缩包exp21.zip获取实验开发环境的,请跳过该步骤。)
- 打开gettrace工程——mycpu_env/gettrace/gettrace.xpr。(该Vivado工程中的IP核是使用Vivado2019.2创建的,如果使用更高版本的Vivado打开,请参考附录D.4节进行IP核升级。)运行gettrace工程的仿真(进入仿真界面后,直接点击run all等待仿真运行完成),生成新的参考trace文件golden_trace.txt(mycpu_env/gettrace/golden_trace.txt)。要等仿真运行完成,golden_trace.txt才有完整的内容。(如果是通过压缩包exp21.zip获取实验开发环境的,请跳过该步骤。)
- 进入 mycpu_env/soc_verify/soc_axi/run_vivado/ 目录下启动验证myCPU的工程。如果该目录下尚未创建工程,请参照附录D.2节介绍的步骤,利用该目录下的 create_project.tcl 文件创建工程。如需要,请参考附录D.4节进行IP核升级。如果该目录下已有前一实践任务创建过的工程,可以在打开工程后,参照附录D.3节介绍的步骤,更新项目中CPU实现文件的列表。
- 参考第4章4.2.5.2小节,对工程中的axi_ram重新定制。(如果是通过压缩包exp21.zip获取实验开发环境的,请跳过该步骤。)
- 在验证myCPU的工程中运行仿真(进入仿真界面后,直接点击run all),进行功能验证与调试,直至仿真测试通过。
- 在验证myCPU的工程中综合实现后生成bit流文件,进行上板验证。(如果无硬件实验平台,请跳过该步骤。)
10.2.3 实践任务22:CPU中集成DCache
本实践任务要求在实践任务21完成的基础上完成以下工作:
- 将实践任务20完成Cache模块作为DCache集成到实践任务21完成的CPU中。
- 在采用AXI总线的SoC验证环境里完成exp22对应func的功能验证,要求成功通过仿真和上板验证。
请参照第2.3.1节中介绍的方式获取本次实践任务所需的实验开发环境。具体的实验环境仍位于 mycpu_env/ 目录下,且仍使用 soc_axi/ 子目录。
实验环境准备就绪后,请参考下列步骤完成本实践任务:
- 将所实现CPU的代码更新至mycpu_env/myCPU/目录中。
- 修改func配置文件——mycpu_env/func/include/test_config.h,选择exp22的配置,编译。(如果是通过压缩包exp22.zip获取实验开发环境的,请跳过该步骤。)
- 打开gettrace工程——mycpu_env/gettrace/gettrace.xpr。(该Vivado工程中的IP核是使用Vivado2019.2创建的,如果使用更高版本的Vivado打开,请参考附录D.4节进行IP核升级。)运行gettrace工程的仿真(进入仿真界面后,直接点击run all等待仿真运行完成),生成新的参考trace文件golden_trace.txt(mycpu_env/gettrace/golden_trace.txt)。要等仿真运行完成,golden_trace.txt才有完整的内容。(如果是通过压缩包exp22.zip获取实验开发环境的,请跳过该步骤。)
- 进入 mycpu_env/soc_verify/soc_axi/run_vivado/ 目录下启动验证myCPU的工程。如果该目录下尚未创建工程,请参照附录D.2节介绍的步骤,利用该目录下的 create_project.tcl 文件创建工程。如需要,请参考附录D.4节进行IP核升级。如果该目录下已有前一实践任务创建过的工程,可以在打开工程后,参照附录D.3节介绍的步骤,更新项目中CPU实现文件的列表。
- 参考第4章4.2.5.2小节,对工程中的axi_ram重新定制。(如果是通过压缩包exp22.zip获取实验开发环境的,请跳过该步骤。)
- 在验证myCPU的工程中运行仿真(进入仿真界面后,直接点击run all),进行功能验证与调试,直至仿真测试通过。
- 在验证myCPU的工程中综合实现后生成bit流文件,进行上板验证。(如果无硬件实验平台,请跳过该步骤。)
10.2.4 实践任务23:CPU中添加CACOP指令
本实践任务要求在实践任务22完成的基础上完成以下工作:
- 在实践任务22完成的CPU中增加CACOP指令实现。
- 在采用AXI总线的SoC验证环境里完成exp23对应func的功能验证,要求成功通过仿真和上板验证。
请参照第2.3.1节中介绍的方式获取本次实践任务所需的实验开发环境。具体的实验环境仍位于 mycpu_env/ 目录下,且仍使用 soc_axi/ 子目录。
实验环境准备就绪后,请参考下列步骤完成本实践任务:
- 将所实现CPU的代码更新至mycpu_env/myCPU/目录中。
- 修改func配置文件——mycpu_env/func/include/test_config.h,选择exp23的配置,编译。(如果是通过压缩包exp23.zip获取实验开发环境的,请跳过该步骤。)
- 打开gettrace工程——mycpu_env/gettrace/gettrace.xpr。(该Vivado工程中的IP核是使用Vivado2019.2创建的,如果使用更高版本的Vivado打开,请参考附录D.4节进行IP核升级。)运行gettrace工程的仿真(进入仿真界面后,直接点击run all等待仿真运行完成),生成新的参考trace文件golden_trace.txt(mycpu_env/gettrace/golden_trace.txt)。要等仿真运行完成,golden_trace.txt才有完整的内容。(如果是通过压缩包exp23.zip获取实验开发环境的,请跳过该步骤。)
- 进入 mycpu_env/soc_verify/soc_axi/run_vivado/ 目录下启动验证myCPU的工程。如果该目录下尚未创建工程,请参照附录D.2节介绍的步骤,利用该目录下的 create_project.tcl 文件创建工程。如需要,请参考附录D.4节进行IP核升级。如果该目录下已有前一实践任务创建过的工程,可以在打开工程后,参照附录D.3节介绍的步骤,更新项目中CPU实现文件的列表。
- 参考第4章4.2.5.2小节,对工程中的axi_ram重新定制。(如果是通过压缩包exp23.zip获取实验开发环境的,请跳过该步骤。)
- 在验证myCPU的工程中运行仿真(进入仿真界面后,直接点击run all),进行功能验证与调试,直至仿真测试通过。
- 在验证myCPU的工程中综合实现后生成bit流文件,进行上板验证。(如果无硬件实验平台,请跳过该步骤。)