一、设计概述
这是一个完整的五级流水线 RISC-V CPU 实现,采用经典的哈佛架构,支持 RISC-V 基础指令集。设计实现了从取指令到写回的完整流水线,包含了冒险处理机制,是一个教学级的 CPU 设计范例。
二、流水线架构
2.1 五级流水线阶段划分
text
┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐
│ IF │───▶│ ID │───▶│ EX │───▶│ MEM │───▶│ WB │
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘
取指令 译码/读 执行 访存 写回
寄存器
2.2 各阶段功能详解
关键模块:PC(程序计数器)
特点:
- 支持顺序执行(PC+4)和跳转执行
- 遇到分支 / 跳转时会产生冲刷信号
- 支持指令存储器初始化
verilog
// IF阶段核心代码
always@(posedge clk) begin
if(load) begin
if(jump_sel) pc_addr <= jump_addr; // 跳转
else pc_addr <= pc_addr + 4; // 顺序执行
end
end
关键模块:
- decoder:指令解码,产生控制信号
- regfile:寄存器文件,读取源操作数
特点:
- 支持 RISC-V 所有基本指令格式(R/I/S/B/U/J)
- 立即数生成逻辑完整
- 包含数据前递逻辑解决数据冒险
verilog
// 指令解码
decoder decoder_dut(
.instruction(instruction),
.r1_addr(r1_addr), .r2_addr(r2_addr),
.imm_data(imm_data), .rd_addr(rd_addr),
.funccode(funccode), .opcode(opcode)
);
// 数据前递逻辑(解决RAW冒险)
assign r1_data = (rd_addr_d3==r1_addr)?rd_data:((rd_addr_d4==r1_addr)?rd_data_wb:r1_data_regfile);
关键模块:ALU(算术逻辑单元)
特点:
- 支持 12 种基本运算(加减乘除、逻辑运算、移位、比较)
- 包含分支条件判断
- 计算结果传递到后续阶段
verilog
// ALU运算示例
case(opcode)
7'b0110011: begin // R-type指令
case(funccode)
3'b000: rd_data <= subsra?(r1_data-r2_data):(r1_data+r2_data); // SUB/ADD
3'b001: rd_data <= (r1_data<<r2_data); // SLL
3'b010: rd_data <= stl_res[31]; // SLT
// … 其他运算
endcase
end
endcase
关键模块:data_blkmem
特点:
- 支持字节、半字、字访问
- 支持符号扩展和零扩展
- 哈佛架构,与指令存储器分离
verilog
// 存储器访问
data_blkmem data_blkmem_dut(
.addra(dmem_addr[31:2]), // 字节地址转字地址
.wea(data_ram_wen?1:dmem_wren), // 写使能
.dina(data_ram_wen?data_ram_wdata:rd_data), // 写入数据
.douta(dmem_dout) // 读出数据
);
关键模块:regfile 写端口
特点:
- 支持两种写回来源:ALU 结果和存储器数据
- 写操作在时钟下降沿进行,避免时序冲突
- x0 寄存器硬连线为 0
verilog
// 寄存器文件写操作(下降沿)
always@(negedge clk) begin
if((rd_addr==i) && rd_wren) begin
regdata[i] <= rd_data;
end
end
三、关键技术实现
3.1 数据前递(Forwarding)
问题:当后续指令需要前一条指令的结果时,会产生数据冒险(RAW)。
解决方案:数据前递技术,将结果直接传递给需要它的指令。
verilog
// 两级前递逻辑
assign r1_data = (rd_addr_d3==r1_addr)?rd_data: // EX→EX前递
((rd_addr_d4==r1_addr)?rd_data_wb: // MEM→EX前递
r1_data_regfile); // 正常寄存器读取
前递条件:
- EX→EX 前递:当前 EX 阶段指令的 rd 与 ID 阶段指令的 rs 相同
- MEM→EX 前递:前一条指令的 rd 与当前指令的 rs 相同
3.2 控制冒险处理
问题:分支 / 跳转指令改变程序流,导致已取出的指令无效。
解决方案:冲刷流水线(插入 NOP)。
verilog
// 分支判断在EX阶段
assign flush = jump_sel; // 跳转时冲刷信号
// IF阶段:用NOP替换无效指令
assign instruction = (halt|flush_d1)?0:inst_ram_out;
冲刷机制:
- 分支条件在 EX 阶段判断
- 分支成功时,冲刷 IF 和 ID 阶段(插入两个 NOP)
- 分支延迟:2 个时钟周期
3.3 流水线寄存器
作用:在流水线阶段间传递数据和信号。
verilog
// 流水线寄存器链
always@(posedge clk) begin
pc_addr_d1 <= pc_addr_d0; // IF→ID
pc_addr_d2 <= pc_addr_d1; // ID→EX
rd_addr_d3 <= rd_addr; // ID→EX
rd_addr_d4 <= rd_addr_d3; // EX→MEM
rd_data_d4 <= rd_data; // EX→MEM
end
四、指令集支持
4.1 支持的指令类型
| R-type | 0110011 | ADD, SUB, AND, OR, XOR | 寄存器运算 |
| I-type | 0010011 | ADDI, ANDI, ORI | 立即数运算 |
| S-type | 0100011 | SW, SH, SB | 存储指令 |
| B-type | 1100011 | BEQ, BNE, BLT | 条件分支 |
| U-type | 0110111 | LUI, AUIPC | 立即数加载 |
| J-type | 1101111 | JAL | 无条件跳转 |
4.2 寻址方式
- 寄存器寻址:R-type 指令
- 立即数寻址:I-type 指令
- 基址寻址:Load/Store 指令
- PC 相对寻址:分支指令
- 伪直接寻址:跳转指令
五、性能分析
5.1 时序分析
text
时钟周期: T1 T2 T3 T4 T5 T6 T7
IF: inst1 inst2 inst3 inst4 inst5 inst6 inst7
ID: inst1 inst2 inst3 inst4 inst5 inst6
EX: inst1 inst2 inst3 inst4 inst5
MEM: inst1 inst2 inst3 inst4
WB: inst1 inst2 inst3
5.2 性能指标
CPI(每条指令周期数):
- 理想情况:1.0
- 考虑分支:约 1.2-1.5
- 考虑 Load-Use 冒险:约 1.1-1.3
加速比:相对于单周期 CPU 约 3-4 倍
吞吐量:流水线满时,每个时钟周期完成一条指令
5.3 冒险分析
- 结构冒险:哈佛架构避免(指令和数据存储器分离)
- 数据冒险:
- RAW(写后读):通过数据前递解决
- WAR/WAW:RISC-V 顺序执行,不存在
- 控制冒险:分支 / 跳转时冲刷流水线
六、设计特点与创新
6.1 双时钟沿设计
verilog
// 上升沿:大多数逻辑
always@(posedge clk) begin
// 流水线寄存器更新
end
// 下降沿:寄存器文件写入
always@(negedge clk) begin
// 寄存器写操作
end
优点:
- 避免读写冲突
- 提高寄存器文件带宽
- 简化时序约束
6.2 完整的立即数生成
verilog
// 支持所有RISC-V立即数格式
case(instruction[6:0])
7'b0010011: imm_data <= { {20{instruction[31]}}, instruction[31:20] }; // I-type
7'b0100011: imm_data <= { {20{instruction[31]}}, instruction[31:25], instruction[11:7] }; // S-type
7'b1100011: imm_data <= { {19{instruction[31]}}, instruction[31], instruction[7], instruction[30:25], instruction[11:8], 1'b0 }; // B-type
// … 其他类型
endcase
6.3 灵活的存储器接口
verilog
// 支持两种访问模式
data_blkmem data_blkmem_dut(
.addra(data_ram_wen?data_ram_waddr:dmem_addr[31:2]), // 地址选择
.wea(data_ram_wen?1:dmem_wren), // 写使能选择
.dina(data_ram_wen?data_ram_wdata:rd_data) // 数据选择
);
七、测试与验证
7.1 测试程序示例
assembly
# 简单测试程序
_start:
addi x1, x0, 5 # x1 = 5
addi x2, x0, 3 # x2 = 3
add x3, x1, x2 # x3 = 8
sub x4, x1, x2 # x4 = 2
sw x3, 0(x0) # 存储到内存
lw x5, 0(x0) # 从内存加载
beq x3, x5, _end # 相等则跳转
_end:
ebreak # 停止执行
网硕互联帮助中心




评论前必须登录!
注册