背景介绍
存储器是一个计算机系统中最基本的组成部分之一,各种技术特性的存储介质共同组成复杂的存储器系统。(请参考"存储器山"百科词条)处理器执行的指令来自于指令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上找到LW
和SW
指令所对应的编码格式,如下图所示:
从上图我们可以看出,对于LW
指令:
func7部分,即CODE[31:20],视为偏移量立即数,该立即数是个有符号数,在运算时时需要做符号位扩展。
需要使用源寄存器1和目的寄存器
func3部分,即CODE[14:12],必须为010
opcode部分,即CODE[6:0],必须为0000011
而对于SW
指令,我们也可以得出类似的信息。这就是我们在译码模块中的设计依据。在译码模块中,添加如下两行:
此两行代码确保处理器能识别SW
和LW
这两条指令。除此之外,还应该将对应的rsen/rsidx/rdidx
这些关键信息解析出来,如下rs1en
为例:
代码中,如果rs1idx
为0,即使用0号寄存器时,由于0号寄存保存常数0,不可写。因此通常意义下当idx
等于0时我们认为指令不需要源寄存器。
算术运算
对于LW
指令来讲,算术模块负责两个功能,一个是计算读操作的地址,另一个是将读到的数据发送到写回模块。而对于SW
指令来讲,算术模块除了计算访存地址,还应该将待保存的数据发送到相应存储器的接口上。计算地址的操作如下所示,由于在译码模块中我们已经获得所需的信息,因此这里可以直接操作。
访存地址一部分来源于源操作数1,另一部分来源于立即数,直接将二者相加即可。而生成存储器接口信息则如下:
只要有LW/SW
指令,则必定会产生存储器访问请求信号(req),而LW
指令对应于存储器读,SW
指令对应着存储器写,二者的地址由前述加法器给出(alu_adder_res)。对于存储器写操作,写的数据来源于源寄存器2。对于存储器读操作,读到的数需要送到写回接口上,如下图:
如果当前指令是LW
指令,则写回的数据来源于从存储器中读到的数据,否则对于其他指令,要么不写回寄存器,要么写回加法器的结果(仅就当前项目进度,后期会如实添加其他情况)。
造一个自己的测试程序
要快速测试之前的设计是否满足功能需求,需要设计者自己设计一个简单的测试程序。本次测试程序,在现有rv32ui/add.S
基础上加以改造。首先删除原有程序中从RVTEST_CODE_BEGIN
开始到RVTEST_CODE_END
之间的所有内容,并在删除的地方添加测试代码,如下所示:
如代码中的注释所描述的那样,该程序首先将指定的数据,写入到指定的地址。然后从该地址将数据读出来(保持原值),随后将该值自加,并将新的值写回到同一地址,最后将该数据再次读出来,将其保存到新的寄存器中。理论上,t1
和t2
寄存器都应该是新的值,并且该值是原值的两倍。
编译之后的测试程序如下,仅截取有意义的测试片段:
特别注意
本次测试还涉及到代码中其他部分的修改,比如添加数据RAM模块(与指令RAM模块类似)各位可以自行实验。
当一切准备就绪,就是仿真开启的时刻。跑起来吧......
仿真结果及分析
仿真环境不需要更新,直接在之前的基础上运行即可。会看到如下波形:
上图的解读如下:
PC=0x80000290,t1保存指定的数据0x12345678,该值为后续程序所要操作的数,指令执行正常。
PC=0x80000294,t0保存指定的数据0x10,该值为后续程序所操作的地址,指令执行正常。
PC=0x80000298,第一次
SW
指令,将数据0x12345678
写到存储器地址0x10
,指令信息解码正确。生成正常的访存请求信号,地址和数据均正确。
存储器接口接收到正确的操作信号,开始写操作,并在下一周期生效。
PC=0x800002a4,将新的计算值写入到同一地址
新值写入生效
将新的值读到新的寄存器中,读到正确的值,表明读操作执行正确。
综上所述,所设计的访存机制完美实现所需功能,符合预期。
下一步,我们将实现系统控制与状态寄存器的访问,确定不看看?
同系列文章首发于微信公众号:ICLiker,愿逢有缘人