以下内容摘自《步步惊芯——软核处理器内部设计分析》一书
14.1 SB模块的作用与工作过程
ICache是直接与Wishbone总线接口单元WB_BIU相连的,但是在DCache与Wishbone总线接口单元WB_BIU之间插入了一个Store Buffer(SB)模块,如图1.6所示。
SB的作用是通过缓冲存储操作,从而加快存储操作。其原理是这样的:当执行存储操作时,可能需要通过WB_BIU将要写的数据写入外部Memory,尤其是在通写法模式下,每次执行存储操作都要将数据写入外部Memory,这样会等待外部Memory完成存储操作,在此期间,CPU处于暂停状态,降低了CPU的效率,引入SB后,如果是存储操作,那么SB模块将本次操作保存起来,同时立即向DCache返回一个存储完成信号(dcsb_ack_o为1),使得CPU可以继续执行,然后SB模块会接着完成被其保存起来的存储操作。在SB内部有一个FIFO(先入先出队列)作为缓冲,如果是连续的多个存储操作,那么会将每个存储操作都存放在FIFO中,并向DCache返回存储完成信号,然后SB从FIFO中取出要保存的数据,完成存储操作。图14.1是存储操作时SB的工作过程。
上述设计会带来两个问题:
(1)SB直接向DCache返回存储完成信号,但如果在SB通过WB_BIU模块向外部Memory存储的过程中发生错误如何处理?
(2)如果存储操作还在SB中缓存,也就是SB没有完成存储操作,但此时DCache又发起了加载操作,如何处理以避免可能出现的数据不一致的情况?
SB模块没有处理第一个问题,读者在使用SB模块的时候需要注意,处理第二个问题的方法是:在加载操作执行前进行判断,只有FIFO为空才可以进行加载操作,这样就可以避免出现数据不一致的情况。并且加载操作不会被SB保存到FIFO中,而是直接交给WB_BIU模块。图14.2是加载操作时SB的工作过程。
SB是存储缓冲模块,从名称上理解只是缓冲存储操作,而ICache不会有存储操作,只有读取指令操作,所以在ICache与WB_BIU模块之间不需要插入SB模块。
14.2 SB模块的结构
14.2.1 SB模块的对外连接关系
为了对SB模块有一个整体的认识,本小节给出SB模块的对外连接关系,如图14.3所示。从图中可知,SB与DCache、SB与WB_BIU之间都是Wishbone总线接口,并且CPU有一个sb_en信号输入到SB,该信号为SB使能信号,当该信号为1时,表示SB使能,如果sb_en为0,那么SB禁止,此时相当于DCache与WB_BIU模块直接相连。
此外,SB与DCache之间的接口名称都是dcsb_xxx_x的形式,SB与WB_BIU之间的接口名称都是sbbiu_xxx_x的形式,因此,通过接口名称就可以知道该接口位于哪两个模块之间。
14.2.2 SB模块内部结构
构成SB的源代码文件有:or1200_sb.v、or1200_sb_fifo.v,分别对应SB、FIFO模块,FIFO模块实现了一个先入先出队列,其在SB模块内例化,例化语句如下:
or1200_sb.v …… or1200_sb_fifo or1200_sb_fifo ( .clk_i(clk), //输入的时钟信号 .rst_i(rst), //输入的复位信号 .dat_i(fifo_dat_i), //要保存到FIFO中的数据 .wr_i(fifo_wr), //FIFO写操作 .rd_i(fifo_rd), //FIFO读操作 .dat_o(fifo_dat_o), //从FIFO中读出的数据 .full_o(fifo_full), //FIFO满标志位,为1表示FIFO满,反之FIFO不满 .empty_o(fifo_empty) //FIFO空标志位,为1表示FIFO空,反之FIFO中有数据 ); ……
参考上述例化语句得到图14.4,其中给出了FIFO模块的接口,以及各个接口连接到SB的对应变量,每个模块的左边是输入接口,右边是输出接口,每个模块内部是接口名,外部引脚上的名称代表SB中的对应变量。本章后面在分析SB时需要参考该图,图中各个变量的含义在上面例化语句的注释中已经说明。
14.2.3 SB模块有关的宏定义
OR1200中有关SB模块的宏定义如下:
or1200_defines.v `define OR1200_SB_IMPLEMENTED //默认没有实施SB,需要去掉注释才可以使用SB `define OR1200_SB_ENTRIES 4 //FIFO的深度 `define OR1200_SB_LOG 2 //OR1200_SB_ENTRIES以2为底的对数
14.3 示例程序
本节将给出一个简单的示例程序以验证SB模块的效果,因为这里主要验证SB模块对存储指令的影响,希望消除取指操作的影响,所以还是使用第13章分析DCache时修改过的简单SOPC作为验证平台,此时,代码存储在QMEM中,取指操作可以在一个时钟周期完成,不影响指令执行。
有一点需要注意,要修改OR1200中的一个错误,参考图14.3可知,SB使能信号sb_en来自CPU模块,参考CPU模块可知sb_en等于如下:
or1200_cpu.v `ifdef OR1200_SB_IMPLEMENTED //可见即使实施了SB,但是sb_en的代码被注释掉了 //assign sb_en = sr[`OR1200_SR_SBE]; `else assign sb_en = 1'b0; `endif
这是OR1200的一个错误,作者可能原本希望使用特殊寄存器SR的一个标志位OR1200_SR_SBE来控制SB的启动与停止,但是在OR1200的特殊寄存器SR定义中并没有OR1200_SR_SBE这个标志位,将上述代码改为如下:
or1200_cpu.v `ifdef OR1200_SB_IMPLEMENTED assign sb_en = 1'b1; `else assign sb_en = 1'b0; `endif
这样只要定义了宏OR1200_SB_IMPLEMENTED,sb_en就为1,也就是SB使能。
示例程序如下:
.section .text,"ax" .global _start .org 0x0 #修改后的简单SOPC在复位后,会从QMEM的0x0处读取指令,所以代码从0x0开始 _start: l.movhi r0,0x0 #初始化r0、r1、r2、r3,分别设置为0x0、0x1、0x2、0x3 l.addi r1,r0,0x1 l.addi r2,r0,0x2 l.addi r3,r0,0x3 l.sw 0x0(r0),r0 #依次将0x0、0x1、0x2、0x3保存到RAM的地址0x0处 l.sw 0x0(r0),r1 l.sw 0x0(r0),r2 l.sw 0x0(r0),r3
上述代码很简单,就是向RAM的0x0处依次存入0x0、0x1、0x2、0x3。
在Ubuntu中新建文件Example.S,内容就是上述代码,拷贝ram.ld、Makefile、Bin2Mem.exe到Example.S所在目录,其中的Makefile选择在10章中修改过后的Makefile,也就是不会使用OR1KSim进行模拟。打开终端,调整路径到上述文件所在目录,输入“make all”得到可以在ModelSim仿真中使用的存储器初始化文件mem.data。修改后的简单SOPC就使用该文件初始化QMEM。为了便于知道仿真波形中if_insn、id_insn、ex_insn等信号对应的指令,下面列出指令与其对应的二进制,分为三列,分别是指令地址、指令、指令对应的二进制。
指令地址 指令 指令对应的二进制 0x800000 l.movhi r0,0x0 0x18000000 0x800004 l.addi r1,r0,0x1 0x9c200001 0x800008 l.addi r2,r0,0x2 0x9c400002 0x80000C l.addi r3,r0,0x3 0x9c600003 0x800010 l.sw 0x0(r0),r0 0xd4000000 0x800014 l.sw 0x0(r0),r1 0xd4000800 0x800018 l.sw 0x0(r0),r2 0xd4001000 0x80001C l.sw 0x0(r0),r3 0xd4001800
注释掉OR1200_SB_IMPLEMEMTED这个宏定义,也就是不使用SB模块,ModelSim仿真波形如图14.5所示。不注释掉OR1200_SB_IMPLEMEMTED这个宏定义,也就是使用SB模块,ModelSim仿真波形如图14.6所示。从两个波形比较可知,SB模块确实减少了存储操作所需要的时钟周期,达到了设计目的。
本书光盘的Chapter14目录下包括ModelSim仿真工程,Chapter14/Code目录下包括示例程序源代码。由于SB模块比较简单,所以本章接下来直接对SB模块的源代码进行分析,按照自底向上的顺序分析,先分析FIFO模块,再分析SB模块。