从零开始学RISC-V之存储器访问

背景介绍

存储器是一个计算机系统中最基本的组成部分之一,各种技术特性的存储介质共同组成复杂的存储器系统。(请参考"存储器山"百科词条)处理器执行的指令来自于指令RAM,所需要的数据则来自于数据RAM。另外,如果有运算结果需要保存,也需要数据RAM。但在实际应用中,我们并不会以实际的物理芯片来严格区分数据RAM和指令RAM,即这两者通常会公用同一块芯片,只是分别占用不同的地址空间。因此要确定某个存储器操作到底是访问指令RAM还是数据RAM,还需要仲裁电路,这一般是通过地址比较来实现。在本项目中,我们将单独例化两个不同的模块,并设置二者独立被访问。

RISC-V支持两类存储器访问操作,分别是读(Load)和写(Store),这两个操作都需要源寄存器1的数据作为基地址,与指令编码中的立即数作为偏移量,共同计算得出访存操作的真实目的地址。如果是Store操作,则需要源操作数2作为待写入存储器的数据;如果是Load操作,则从存储器中读出来的数据将保存到目的寄存器中。

根据上述对Load和Store两类指令的行为分析,访存指令的设计也就清晰明了了。下文将以常见的LW(Load Word)和SW(Store Word)为例,介绍其实现过程。

开始Coding

与先前所实现的指令类似,实现一条简单的ADD指令,需要以下几个EXU子模块的协同配合:

  • exu_decode:指令解码模块,负责解析具体的访存指令。

  • exu_regfile:寄存器文件模块,源操作数需要从此模块读出,读存储器的结果需要写回到此模块。

  • exu_alu:指令执行模块,具体指访存地址的生成,以及其他控制信号。

  • exu_wbck:结果写回模块,负责结果写回

基于上述划分标准,以下将详细介绍各个模块的设计细节。

指令译码

首先,我们需要从riscv-spec上找到LWSW指令所对应的编码格式,如下图所示:

6-LW-SW-编码.png

从上图我们可以看出,对于LW指令:

  • func7部分,即CODE[31:20],视为偏移量立即数,该立即数是个有符号数,在运算时时需要做符号位扩展。

  • 需要使用源寄存器1和目的寄存器

  • func3部分,即CODE[14:12],必须为010

  • opcode部分,即CODE[6:0],必须为0000011

而对于SW指令,我们也可以得出类似的信息。这就是我们在译码模块中的设计依据。在译码模块中,添加如下两行:

6-LW-SW-译码.png

此两行代码确保处理器能识别SWLW这两条指令。除此之外,还应该将对应的rsen/rsidx/rdidx这些关键信息解析出来,如下rs1en为例:

6-LW-SW-更新rs1en.png

代码中,如果rs1idx为0,即使用0号寄存器时,由于0号寄存保存常数0,不可写。因此通常意义下当idx等于0时我们认为指令不需要源寄存器。

算术运算

对于LW指令来讲,算术模块负责两个功能,一个是计算读操作的地址,另一个是将读到的数据发送到写回模块。而对于SW指令来讲,算术模块除了计算访存地址,还应该将待保存的数据发送到相应存储器的接口上。计算地址的操作如下所示,由于在译码模块中我们已经获得所需的信息,因此这里可以直接操作。

6-LW-SW-生成访存地址.png

访存地址一部分来源于源操作数1,另一部分来源于立即数,直接将二者相加即可。而生成存储器接口信息则如下:

6-LW-SW-生成访存接口信号.png

只要有LW/SW指令,则必定会产生存储器访问请求信号(req),而LW指令对应于存储器读,SW指令对应着存储器写,二者的地址由前述加法器给出(alu_adder_res)。对于存储器写操作,写的数据来源于源寄存器2。对于存储器读操作,读到的数需要送到写回接口上,如下图:

6-LW-SW-生成写回接口.png

如果当前指令是LW指令,则写回的数据来源于从存储器中读到的数据,否则对于其他指令,要么不写回寄存器,要么写回加法器的结果(仅就当前项目进度,后期会如实添加其他情况)。

造一个自己的测试程序

要快速测试之前的设计是否满足功能需求,需要设计者自己设计一个简单的测试程序。本次测试程序,在现有rv32ui/add.S基础上加以改造。首先删除原有程序中从RVTEST_CODE_BEGIN开始到RVTEST_CODE_END之间的所有内容,并在删除的地方添加测试代码,如下所示:

6-LW-SW-简单测试程序.png

如代码中的注释所描述的那样,该程序首先将指定的数据,写入到指定的地址。然后从该地址将数据读出来(保持原值),随后将该值自加,并将新的值写回到同一地址,最后将该数据再次读出来,将其保存到新的寄存器中。理论上,t1t2寄存器都应该是新的值,并且该值是原值的两倍。

编译之后的测试程序如下,仅截取有意义的测试片段:

6-LW-SW-简单测试汇编.png

特别注意

本次测试还涉及到代码中其他部分的修改,比如添加数据RAM模块(与指令RAM模块类似)各位可以自行实验。

当一切准备就绪,就是仿真开启的时刻。跑起来吧......

仿真结果及分析

仿真环境不需要更新,直接在之前的基础上运行即可。会看到如下波形:

6-LW-SW-波形.png

上图的解读如下:

  1. PC=0x80000290,t1保存指定的数据0x12345678,该值为后续程序所要操作的数,指令执行正常。

  2. PC=0x80000294,t0保存指定的数据0x10,该值为后续程序所操作的地址,指令执行正常。

  3. PC=0x80000298,第一次SW指令,将数据0x12345678写到存储器地址0x10,指令信息解码正确。

  4. 生成正常的访存请求信号,地址和数据均正确。

  5. 存储器接口接收到正确的操作信号,开始写操作,并在下一周期生效。

  6. PC=0x800002a4,将新的计算值写入到同一地址

  7. 新值写入生效

  8. 将新的值读到新的寄存器中,读到正确的值,表明读操作执行正确。

综上所述,所设计的访存机制完美实现所需功能,符合预期。

下一步,我们将实现系统控制与状态寄存器的访问,确定不看看?

同系列文章首发于微信公众号:ICLiker,愿逢有缘人

你可能感兴趣的:(从零开始学RISC-V之存储器访问)