FPGA——RISC-V(二):PulseRain Reindeer
一、PulseRain Reindeer
PulseRain Reindeer,源自于美国PulseRain Technology公司内部产品线的一个简化版本。在2018年由RISC-V官方组织的RISC-V Soft CPU竞赛中,该软核处理器位列季军(https://riscv.org/2018/10/risc-v-contest)。
此软核经过大佬与小脚Y团队合作,将软核处理器做了改进后,移植到了小脚丫综合实验平台上。其完整代码在Github上,文件名为Reindeer_Step-1.1.2.zip。
PulseRain Reindeer的处理器核心采用Verilog2001编写,其余的外设等部分采用System Verilog编写,并部分引用了PulseRain Technology的PulseRain RTL库。
二、Reindeer的流水线设计
Reindeer采用的是2×2的流水线设计以及内存布局采用冯·诺依曼架构
PulseRain Reindeer采用了冯·诺依曼架构,将程序代码和数据不加区分地存放于内存中。
为了追求较高的时钟主频,PulseRain Reindeer处理器中包含有4级流水线。
- 取指(Instruction Fetch)o
- 指令译码(Instruction Decode)。
- 指令执行(Execution)。
- 数据访问(Data Access)包括寄存器的更新与内存的读写。
与普通的4级流水不同的是,PulseRain Reindeer对这4个流水线阶段采用了2×2的布局,如图所示。

在这种布局下,在双数时钟周期下,只有“取指”和“指令执行”这两个阶段是活跃的。而在单数时钟周期,只有“指令译码”和“数据访问”这两个阶段是活跃的。
(一)取指器
PulseRain Reindeer是一个RV32IM处理器。通过对外部大容量DRAM内存的支持,PulseRain Reindeer避免了压缩指令集(C Extension)的实现。由于只需要支持32位的读取,取指器也不用考虑太多指令地址边界对齐的问题。
FPGA片上内存一般只有一个时钟周期的读延迟,而外部内存的读延迟则往往要大得多。通过对指令读地址的判断,内存控制器可以很快地确定是否需要读延迟,从而设立相应的握手信号来反馈给取指器,以实现FPGA片上内存和片外内存的
混合使用。

由上图可以看出,如果指令需要读取通用寄存器,则源寄存器1的地址总是在位[19:15],而源寄存器2的地址总是在位[24:20]。考虑到对通用寄存器的读取不会有其他作用,不论指令的类型是什么,PulseRain Reindeer都会以这两个位置上的数值为地址,对通用寄存器进行读取。
(二)指令译码器
从上图还可以看出,指令位[6:0]是操作码。而根据图3-2,在RV32M下,位[1:0]总是3,所以在PulseRain Reindeer中只需要对位[6:2]译码便可确定指令操作类型,并产生相应的控制信号。这些控制信号会在接下来的指令执行器中被用到。
(三)指令执行器
指令执行器需要执行以下的几类指令:
- ALU(Arithmetic Logic Unit,.算术逻辑单元)。
如下图所示,算术逻辑指令包括“加”“减”“移位”“与”“或”“异或”等。在参与算术逻辑的两个操作数中(图4-10中的寄存器X与Y),操作数X总是来自于通用寄存器,而操作数Y则可以来自通用寄存器或者指令自带的立即数。对ALU的操作选择和数据源选择都来自于指令译码器产生的控制信号。

- 乘除法(M Extension)o
PulseRain Reindeer支持RV32IM指令集。其中M Extension(硬件乘除法)可以被选择性地配置。 - 无条件跳转指令(JAL/JALR)。
对于无条件跳转,其后一条指令的地址需要被存入目标寄存器中。 - LUI/AUIPC Load Upper Immediate /Add Upper Immediate to PC
这两条立即地址构建指令(见图3-10)的结果也会被写入目标寄存器。
以上这些指令都会更新目标寄存器,其具体的写入值如下图所示。

(四)数据访问
在数据访问阶段,通用寄存器会被更新,由LOAD/STORE指令产生的内存访问也会在这个阶段产生。由于2×2的流水线布局,“内存访问”阶段和“取指”阶段被安排在了不同的时钟周期,以尽量降低内存访问冲突发生的可能性。
(五)流水线控制
流水线控制的主要目的就是对跳转指令和异常/中断的处理,对于2×2的流水线布局,取指与指令执行状态和指令译码与数据访问状态分别对应的是双数时钟周期(“取指”与“指令执行”)和单数时钟周期(“指令译码”与“数据访问”)。其中跳转指令则会将流水线控制转入初始化状态,以重新加载流水线。
而异常/中断的处理则需要一个额外的异常处理状态,以根据异常/中断的具体类别,设置异常编码,并确定异常/中断处理的返回地址(即mepc寄存器)。
三、通用寄存器
在RISC-V用户指令集标准(User-Level ISA)中提到,RV32定义了32个32位的通用寄存器(其中x0恒为零值)。在FPGA中,如果直接用触发器来实现这些通用寄存器,则需要32×32=1024个触发器。对于小脚Y平台上的Intel Cyclone10LP(10CL016YU256C8G)FPGA,则根据图2-1中的逻辑单元结构,至少需要消耗相同数量的逻辑单元才能实现所有的通用寄存器(大约占该FPGA总逻辑容量的7%)。同时,通过观察RISC-V指令格式,可以发现许多RISC-V指令都包含两个源寄存器(标记为s1和s2),即在同一指令中,需要读取两个通用寄存器。如果用触发器来实现通用寄存器,则同时还需要两个32:1的多路复用器,每个多路复用器的数据宽度都是32位。
综合以上考虑,PulseRain Reindeer中采用了两块简单双口Block RAM来实现通用寄存器,如图所示。

-
当寄存器被写入时,同样的数据会被同时写入这两块Block RAM中。而在寄存器读取时,这两块Block RAM分别对应源寄存器1与2。

-
在上图中的4个流水线阶段中,寄存器的读地址在“取指”阶段就可以确定。而寄存器的写地址和写数据会在“数据访问”阶段被确定。因为PulseRain Reindeer采用的2×2流水线设计,“取指”和“数据访问”发生在不同的时钟周期,所以不会产生由于对内存同时读写而造成的数据模糊(但是由于数据相关性而引起的流水线阶段之间的转发问题依然会发生)。
959

被折叠的 条评论
为什么被折叠?



