整体架构
我设计的RV64-CPU总共有14个模块:
- PC:更新下一条指令地址。
- PCincrement:将PC加4,作为PCNext的可能值。
- PCNext:根据Jump和CndCode信号,判断PC是跳转还是累加,计算出PCNext值。
- InstMem:指令内存。根据PC值取出指令并解析。
- RegFile:寄存器文件。执行读取和写回操作。
- ImmGen:立即数生成器。将立即数补位成64位。
- Control:控制模块。判断指令类型,并输出控制信号。
- ALU_A:选择ALU端口A的数据来源。
- ALU_B:选择ALU端口B的数据来源。
- ALU_control:选择ALU功能。
- ALU:执行算术逻辑运算。
- DataMem:数据内存。执行写入和读取内存操作。
- Conditional:计算Conditional Code。
- RegBack:选择写回寄存器的数据来源。
其中控制信号有以下:
- Branch:是否是Branch类指令
- Jump:是否是
jal
指令 - JumpReg:是否是
jalr
指令 - MemRead:是否读取内存
- MemWrite:是否写入内存
- ALUop:指令类型
- ALUSrc:ALU_B数据来源
- RegWrite:是否写回寄存器
- Halt:停止执行
我没有设置MemtoReg信号。这是因为:目前,MemtoReg信号和MemRead信号一致,读取内存一定是存进寄存器里。
RISC-V指令集
我设计的CPU支持所有RV32I Base Integer Instruction(除ecall
、ebreak
外)。
代码实现
PC
在时钟上升沿更新PC值。
1 | module PC( |
PCincrement
计算PC累加值,作为
PCNext
的可能值。
1 | module PCincrement( |
PCNext
根据
Conditional Code
、Jump
、JumpReg
信号,计算PCNext
值。
1 | module PCNext( |
Control
根据
OpCode
判断指令类型,并产生控制信号,指导:ALU运算、寄存器读写、内存读写、PC更新。
ALUop | 指令类型 |
---|---|
000 | R-type |
001 | I1-type |
010 | I2-type(Load类指令) |
011 | S-tpye |
100 | B-type |
101 | J-type |
110 | U-type |
1 | module Control( |
InstMem
根据
PCaddress
读取指令,并拆解为OpCode
、Rs1
、Rs2
、Rd
。Inst
是完整指令,用于生成立即数。
为了后续便于验证正确性,这里预先写入两条指令。
1 | module InstMem( |
RegFile
对寄存器文件进行读写操作。
1 | module RegFile( |
ImmGen
根据
Inst
判断指令类型,根据不同指令,采取不同方式补位立即数。
funct7_30
是 funct7 的1个 bit ,指令中的第30个 bit 。funct7 中其余部分都是0,真正有区分作用的仅funct7_30
一位。(区分srai
和srli
时)
1 | module ImmGen( |
ALU_control
根据指令类型,选择ALU具体功能
ALUfunc
。
1 | module ALU_control( |
ALU_A
选择
AluA
的数据来源。
1 | module ALU_A( |
ALU_B
选择
AluB
的数据来源。
1 | module ALU_B( |
ALU
执行算术逻辑运算。
1 | module ALU( |
DataMem
执行对数据内存的读写操作。
1 | module DataMem( |
Conditional
根据ALU计算得到的符号位
Zere
、SignedLess
、UnsignedLess
,判断是否跳转,得到CndCode
。
1 | module Conditional( |
RegBack
选择写回寄存器的数据来源。
1 | module RegBack( |
arch
在顶层模块,完成连线。
1 | module arch( |
正确性验证
为检验正确性,我向 InstMem 中写入了两条指令:
lui x5, 0x12345
:0x126802b7add x7, x5, x6
:0x005303b3
然后我们在 Vivado 中进行 synthesis 和 stimulation 。
1 | module sim(); |
我们可以看到:指令都被正确解析。
我们查看Regfile中的5号、7号寄存器。它们都被正确地写入和读取。