第12课 · 数据通路

数据通路PCALU寄存器文件

📌 学习目标:理解 RISC-V 单周期数据通路的组成,实现 PC、指令存储器、寄存器文件、ALU、数据存储器的连接,通过 Verilator 验证数据通路正确性。

一、单周期数据通路概述

单周期 CPU 在一个时钟周期内完成一条指令的所有操作。数据通路包括:

         ┌──────────┐
    PC ─→│ 指令存储器 │──→ 指令(32bit)
         └──────────┘
              │
    ┌─────────┼─────────┐
    ↓         ↓         ↓
  译码     寄存器文件   立即数生成
  (opcode)  (rs1,rs2)   (imm)
              │    │
              ↓    ↓
            ┌──────────┐
            │   ALU    │──→ result
            └──────────┘
              │
              ↓
         ┌──────────┐
         │ 数据存储器 │──→ 读数据
         └──────────┘
              │
              ↓
         写回寄存器文件(rd)

二、数据通路组件详解

2.1 PC(程序计数器)

PC 存储当前指令地址,每个时钟周期更新:

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源AALU源BALU操作写回数据
R-TypePC+4rs1rs2ADD/SUB/...ALU结果
I-Type(ALU)PC+4rs1immADDI/ANDI/...ALU结果
LWPC+4rs1immADD(地址)Mem读数据
SWPC+4rs1immADD(地址)—(无写回)
BEQ/BNEPC+4/PC+immrs1rs2SUB(比较)—(无写回)
JALPC+immPC+4

四、Verilog 实现

// datapath.v — 单周期数据通路核心组件 // 程序计数器 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; // R-type 7'b0010011, 7'b0000011, 7'b1100111: imm = {{20{instr[31]}}, instr[31:20]}; // I-type 7'b0100011: imm = {{20{instr[31]}}, instr[31:25], instr[11:7]}; // S-type 7'b1100011: imm = {{19{instr[31]}}, instr[31], instr[7], instr[30:25], instr[11:8], 1'b0}; // B 7'b0110111, 7'b0010111: imm = {instr[31:12], 12'b0}; // U-type 7'b1101111: imm = {{11{instr[31]}}, instr[31], instr[19:12], instr[20], instr[30:21], 1'b0}; // J default: imm = 32'b0; endcase end endmodule

4.1 测试台

// tb_datapath.v 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 测试 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; // 立即数生成测试 // ADDI x1, x2, 100 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; // SW x3, 8(x2) — S-type 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; // LUI x1, 0x12345 — U-type 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课:控制单元