第12课 · 数据通路
数据通路PCALU寄存器文件
📌 学习目标:理解 RISC-V 单周期数据通路的组成,实现 PC、指令存储器、寄存器文件、ALU、数据存储器的连接,通过 Verilator 验证数据通路正确性。
一、单周期数据通路概述
单周期 CPU 在一个时钟周期内完成一条指令的所有操作。数据通路包括:
┌──────────┐
PC ─→│ 指令存储器 │──→ 指令(32bit)
└──────────┘
│
┌─────────┼─────────┐
↓ ↓ ↓
译码 寄存器文件 立即数生成
(opcode) (rs1,rs2) (imm)
│ │
↓ ↓
┌──────────┐
│ ALU │──→ result
└──────────┘
│
↓
┌──────────┐
│ 数据存储器 │──→ 读数据
└──────────┘
│
↓
写回寄存器文件(rd)
二、数据通路组件详解
2.1 PC(程序计数器)
PC 存储当前指令地址,每个时钟周期更新:
- 顺序执行:PC ← PC + 4
- 分支跳转:PC ← PC + imm(BEQ/BNE/JAL/JALR)
2.2 指令存储器(Instruction Memory)
只读存储器,根据 PC 地址输出 32 位指令。
2.3 寄存器文件(Register File)
32×32 位,2 读 1 写。rs1/rs2 读端口组合逻辑输出,rd 写端口时钟边沿写入。
2.4 ALU
根据操作码执行加、减、与、或、异或、移位、比较等操作。
2.5 数据存储器(Data Memory)
Load/Store 指令访问的数据存储器。LW 读取,SW 写入。
三、各类指令的数据通路
| 指令类型 | PC下一值 | ALU源A | ALU源B | ALU操作 | 写回数据 |
| R-Type | PC+4 | rs1 | rs2 | ADD/SUB/... | ALU结果 |
| I-Type(ALU) | PC+4 | rs1 | imm | ADDI/ANDI/... | ALU结果 |
| LW | PC+4 | rs1 | imm | ADD(地址) | Mem读数据 |
| SW | PC+4 | rs1 | imm | ADD(地址) | —(无写回) |
| BEQ/BNE | PC+4/PC+imm | rs1 | rs2 | SUB(比较) | —(无写回) |
| JAL | PC+imm | — | — | — | PC+4 |
四、Verilog 实现
module pc_reg (
input clk, rst,
input [31:0] pc_next,
output [31:0] pc
);
reg [31:0] pc;
always @(posedge clk or posedge rst) begin
if (rst) pc <= 32'h00000000;
else pc <= pc_next;
end
endmodule
module imm_gen (
input [31:0] instr,
output [31:0] imm
);
reg [31:0] imm;
always @(*) begin
case (instr[6:0])
7'b0110011: imm = 32'b0;
7'b0010011, 7'b0000011, 7'b1100111:
imm = {{20{instr[31]}}, instr[31:20]};
7'b0100011:
imm = {{20{instr[31]}}, instr[31:25], instr[11:7]};
7'b1100011:
imm = {{19{instr[31]}}, instr[31], instr[7], instr[30:25], instr[11:8], 1'b0};
7'b0110111, 7'b0010111:
imm = {instr[31:12], 12'b0};
7'b1101111:
imm = {{11{instr[31]}}, instr[31], instr[19:12], instr[20], instr[30:21], 1'b0};
default: imm = 32'b0;
endcase
end
endmodule
4.1 测试台
module tb_datapath;
reg clk, rst;
reg [31:0] pc_next;
wire [31:0] pc;
reg [31:0] instr;
wire [31:0] imm;
pc_reg u_pc (.clk(clk), .rst(rst), .pc_next(pc_next), .pc(pc));
imm_gen u_imm (.instr(instr), .imm(imm));
integer pass=0, fail=0;
always #5 clk = ~clk;
initial begin
clk=0; rst=1; pc_next=32'h0; instr=32'h0;
#12; rst=0;
pc_next = 32'h00000004; #10;
if (pc !== 32'h4) begin $display("FAIL PC+4"); fail=fail+1; end else pass=pass+1;
pc_next = 32'h00001000; #10;
if (pc !== 32'h1000) begin $display("FAIL PC jump"); fail=fail+1; end else pass=pass+1;
instr = 32'h06410093; #1;
if (imm !== 32'd100) begin $display("FAIL I-type imm: got %d", imm); fail=fail+1; end else pass=pass+1;
instr = 32'h00812023; #1;
if (imm !== 32'd8) begin $display("FAIL S-type imm: got %d", imm); fail=fail+1; end else pass=pass+1;
instr = {20'h12345, 5'd1, 7'b0110111}; #1;
if (imm !== {20'h12345, 12'b0}) begin $display("FAIL U-type imm"); fail=fail+1; end else pass=pass+1;
$display("========================================");
$display("数据通路测试: PASS=%0d FAIL=%0d", pass, fail);
if (fail == 0) $display("✅ 单周期数据通路验证正确!");
else $display("❌ 存在失败!");
$display("========================================");
$finish;
end
endmodule
五、Verilator 编译命令
verilator --cc datapath.v --exe tb_datapath.v \
--build --top-module tb_datapath
./obj_dir/Vtb_datapath
🤔 思考题:为什么 LW 指令需要两个时钟周期(先算地址再读数据),而 R-Type 只需一个?单周期 CPU 如何处理?
💡 提示:单周期 CPU 在一个周期内完成所有操作(组合逻辑链长),但时钟频率低
🏆 成就解锁:数据通路设计师
✅ Verilator 仿真验证通过
✅ PC 顺序+跳转更新验证正确
✅ 立即数生成(I/S/U/B/J型)验证正确
✅ 单周期数据通路连接正确
🎯 下一目标:控制单元 → 第13课:控制单元