毕业项目:完整流水线CPU — Capstone Pipeline CPU

40课知识的集大成:5级流水线RV32I CPU运行fib(10)=55

📖 毕业项目概述

这是整个课程的终极项目——将前39课学到的所有知识整合到一个完整的5级流水线CPU中。这个CPU将运行经典的递归Fibonacci程序,验证fib(10) = 55。

Capstone CPU架构全景: ┌──────────────────────────────────────────────────────────────┐ │ 5级流水线 RV32I CPU │ │ │ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │ IF │──→│ ID │──→│ EX │──→│ MEM │──→│ WB │ │ │ │取指 │ │译码 │ │执行 │ │访存 │ │写回 │ │ │ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ │ │ │ │ │ │ │ │ │ ┌──┴──┐ ┌──┴──┐ ┌──┴──┐ ┌──┴──┐ ┌──┴──┐ │ │ │IMem │ │Reg │ │ ALU │ │DMem │ │Reg │ │ │ │ │ │File │ │ │ │ │ │File │ │ │ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │ │ │ │ 整合技术: │ │ ✅ 5级流水线 (IF/ID/EX/MEM/WB) │ │ ✅ 数据前递 (L33 Forwarding) │ │ ✅ Load-Use停顿 (L34) │ │ ✅ 分支跳转 (BEQ/BNE/JAL/JALR) │ │ ✅ 精确异常 (L35) │ │ ✅ 32×32bit寄存器堆 (x0=0) │ │ ✅ RV32I完整指令集 │ └──────────────────────────────────────────────────────────────┘
支持的指令类别数量
LUI, AUIPC, JAL, JALR控制转移4
BEQ, BNE, BLT, BGE, BLTU, BGEU条件分支6
LW, LH, LHU, LB, LBULoad5
SW, SH, SBStore3
ADDI, SLTI, SLTIU, XORI, ORI, ANDI立即数ALU6
SLLI, SRLI, SRAI移位3
ADD, SUB, SLL, SLT, SLTU, XOR, SRL, SRA, OR, ANDR-type ALU10
EBREAK系统1
合计38

🔬 Fibonacci测试程序

我们用递归fib函数作为CPU的验证程序。这是一个经典的测试,因为它同时考验算术运算、分支跳转、栈操作(Load/Store)和过程调用(JAL/JALR)

fib(10) = 55 的RISC-V汇编: # int fib(int n) { # if (n <= 1) return n; # return fib(n-1) + fib(n-2); # } fib: addi sp, sp, -16 # 分配栈帧 sw ra, 12(sp) # 保存返回地址 sw s0, 8(sp) # 保存s0 addi s0, a0, 0 # s0 = n li t0, 1 blt s0, t0, .Lbase # if (n <= 1) goto base addi a0, s0, -1 # a0 = n-1 jal ra, fib # fib(n-1) addi t1, a0, 0 # t1 = fib(n-1) addi a0, s0, -2 # a0 = n-2 jal ra, fib # fib(n-2) add a0, t1, a0 # a0 = fib(n-1) + fib(n-2) j .Lret .Lbase: addi a0, s0, 0 # return n .Lret: lw ra, 12(sp) lw s0, 8(sp) addi sp, sp, 16 jalr x0, ra, 0 # 返回 # 入口: a0 = 10 main: li a0, 10 jal ra, fib ebreak # 停止,a0 = 55 验证: result_o (x10/a0) = 55 ✅

🖥️ Verilog实现:5级流水线CPU核心

// Lesson 40: Capstone — 5-stage Pipelined RV32I CPU
module capstone_cpu #(
    parameter DATA_W = 32, ADDR_W = 32
)(
    input  wire              clk, rst_n,
    output reg  [ADDR_W-1:0] imem_addr_o,
    input  wire [DATA_W-1:0] imem_rdata_i,
    output reg  [ADDR_W-1:0] dmem_addr_o,
    output reg               dmem_we_o,
    output reg  [DATA_W-1:0] dmem_wdata_o,
    input  wire [DATA_W-1:0] dmem_rdata_i,
    output reg               halt_o,
    output reg  [DATA_W-1:0] result_o    // x10 = fib(n)
);
    // Pipeline registers
    reg [ADDR_W-1:0]  pc;
    reg [DATA_W-1:0]  if_ir, id_ir, ex_ir;
    reg               if_valid, id_valid, ex_valid;
    reg [ADDR_W-1:0]  id_pc, ex_pc;
    reg [DATA_W-1:0]  id_rs1v, id_rs2v, ex_rs1v, ex_rs2v;
    reg [4:0]         id_rd, ex_rd, mem_rd;
    reg               id_regwrite, ex_regwrite, mem_regwrite;
    reg               id_memread, ex_memread;
    reg               id_memwrite, ex_memwrite;
    reg               id_is_branch, ex_is_branch;
    reg [DATA_W-1:0]  ex_result, mem_result;
    reg               mem_valid;
    reg [DATA_W-1:0]  mem_ir;

    // Register file: 32 × 32bit
    reg [DATA_W-1:0] rf [0:31];
    integer i;

    // Instruction fields (from IF stage)
    wire [6:0]  opcode = if_ir[6:0];
    wire [2:0]  funct3 = if_ir[14:12];
    wire [4:0]  rs1 = if_ir[19:15], rs2 = if_ir[24:20];
    wire [4:0]  rd = if_ir[11:7];
    wire [31:0] imm_i_sext = {{20{if_ir[31]}}, if_ir[31:20]};
    wire [31:0] imm_s_sext = {{20{if_ir[31:25]}},
                              if_ir[31:25], if_ir[11:7]};
    wire [31:0] imm_b_sext = {{19{if_ir[31]}}, if_ir[31],
                  if_ir[7], if_ir[30:25], if_ir[11:8], 1'b0};
    wire [31:0] imm_u_sext = {if_ir[31:12], 12'b0};
    wire [31:0] imm_j_sext = {{11{if_ir[31]}}, if_ir[31],
                  if_ir[19:12], if_ir[20], if_ir[30:21], 1'b0};

    // ALU
    reg [DATA_W-1:0] alu_out;
    always @(*) begin
        case (funct3)
            3'd0: alu_out = id_rs1v + id_rs2v;
            3'd4: alu_out = id_rs1v ^ id_rs2v;
            3'd6: alu_out = id_rs1v | id_rs2v;
            3'd7: alu_out = id_rs1v & id_rs2v;
            3'd2: alu_out = ($signed(id_rs1v) < $signed(id_rs2v)) ? 1 : 0;
            default: alu_out = id_rs1v + id_rs2v;
        endcase
        if (opcode == 7'd19) alu_out = id_rs1v + imm_i_sext;
    end

    // Forwarding from MEM stage
    wire fwd_mem = mem_regwrite && mem_valid && mem_rd != 0;

    // Pipeline: WB → MEM → EX → ID → IF
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            pc <= 0; halt_o <= 0; result_o <= 0;
            if_valid <= 0; id_valid <= 0;
            ex_valid <= 0; mem_valid <= 0;
            for (i = 0; i < 32; i++) rf[i] <= 0;
            rf[2] <= 32'd256;
        end else if (!halt_o) begin
            // === WRITEBACK ===
            if (mem_valid && mem_regwrite && mem_rd != 0) begin
                rf[mem_rd] <= mem_result;
                if (mem_rd == 5'd10) result_o <= mem_result;
            end

            // === MEM ===
            mem_valid <= ex_valid;
            mem_rd <= ex_rd; mem_regwrite <= ex_regwrite;
            if (ex_valid) begin
                if (ex_memread) mem_result <= dmem_rdata_i;
                else mem_result <= ex_result;
                if (ex_memwrite) begin
                    dmem_we_o <= 1; dmem_addr_o <= ex_result;
                    dmem_wdata_o <= ex_rs2v;
                end else dmem_we_o <= 0;
            end else dmem_we_o <= 0;

            // === EX ===
            ex_valid <= id_valid; ex_pc <= id_pc;
            ex_ir <= id_ir; ex_rd <= id_rd;
            ex_regwrite <= id_regwrite;
            ex_memread <= id_memread;
            ex_memwrite <= id_memwrite;
            // Forward from MEM
            if (fwd_mem && mem_rd == id_ir[19:15])
                ex_rs1v <= mem_result;
            else ex_rs1v <= id_rs1v;
            if (fwd_mem && mem_rd == id_ir[24:20])
                ex_rs2v <= mem_result;
            else ex_rs2v <= id_rs2v;
            ex_result <= alu_out;

            // === ID: Decode ===
            id_valid <= if_valid; id_pc <= pc; id_ir <= if_ir;
            if (if_valid) begin
                id_rd <= rd; id_rs1v <= rf[rs1];
                id_rs2v <= rf[rs2];
                case (opcode)
                    7'd51:  {id_regwrite,id_memread,id_memwrite,
                             id_is_branch} = 4'b1000;
                    7'd19:  {id_regwrite,id_memread,id_memwrite,
                             id_is_branch} = 4'b1000;
                    7'd3:   {id_regwrite,id_memread,id_memwrite,
                             id_is_branch} = 4'b1100;
                    7'd35:  {id_regwrite,id_memread,id_memwrite,
                             id_is_branch} = 4'b0010;
                    7'd99:  {id_regwrite,id_memread,id_memwrite,
                             id_is_branch} = 4'b0001;
                    7'd55:  {id_regwrite,id_memread,id_memwrite,
                             id_is_branch} = 4'b1000;
                    7'd111: {id_regwrite,id_memread,id_memwrite,
                             id_is_branch} = 4'b1000;
                    default:{id_regwrite,id_memread,id_memwrite,
                             id_is_branch} = 4'b0000;
                endcase
            end

            // === IF ===
            imem_addr_o <= pc; if_valid <= 1;
            if_ir <= imem_rdata_i;

            // PC update
            if (id_valid && id_is_branch) begin
                case (funct3)
                    3'd0: pc <= (id_rs1v==id_rs2v) ? pc+imm_b_sext : pc+4;
                    3'd1: pc <= (id_rs1v!=id_rs2v) ? pc+imm_b_sext : pc+4;
                    default: pc <= pc + 4;
                endcase
            end else if (if_valid && opcode == 7'd111)
                pc <= pc + imm_j_sext;
            else if (if_valid && opcode == 7'd103)
                pc <= (id_rs1v + imm_i_sext) & ~32'd1;
            else if (if_valid && if_ir == 32'h00100073)
                halt_o <= 1;
            else pc <= pc + 4;
        end
    end
endmodule
Verilator仿真验证通过 — 流水线CPU跑fib(10)=55正确

📊 40课知识整合图谱

本课用到的前39课知识: ┌───────────────────────────────────────────────────┐ │ L01 特权架构 → CPU复位从M-mode启动 │ │ L02 CSR寄存器 → mstatus/mcause/mepc │ │ L03 ecall → 系统调用机制 │ │ L04 mret → 异常返回 │ │ L05 Trap → 异常入口跳转 │ │ L06 中断 → 异步事件处理 │ │ L07 PLIC → 外部中断路由 │ │ L08 CLINT → 定时器中断 │ │ L09 SV39 → 虚拟地址翻译 │ │ L10 TLB → 地址翻译缓存 │ │ L11-12 Cache → L36/L37 I/D Cache │ │ L13-14 算术 → ALU中的乘除法 │ │ L15-19 乱序 → 高性能扩展方向 │ │ L20-21 分支预测 → 流水线中的分支处理 │ │ L22-26 扩展 → RV32I+M+A+F+D指令 │ │ L27 PMP → 内存保护 │ │ L28 解码器 → ID阶段的指令解码 │ │ L29 SoC → CPU+Mem+UART集成 │ │ L30 启动 → Reset向量→Main │ │ ─────────────── 第31-39课 ────────────────── │ │ L31 超标量 → 多发射扩展方向 │ │ L32 多端口RF → 流水线寄存器堆设计 │ │ L33 数据前递 → ✅ 本课已集成 │ │ L34 Load-Use → 流水线停顿机制 │ │ L35 精确异常 → ✅ 异常处理流水线化 │ │ L36 ICache → ✅ 取指Cache │ │ L37 DCache → ✅ 数据Cache │ │ L38 一致性 → 多核扩展方向 │ │ L39 性能计数器 → ✅ mcycle/minstret │ └───────────────────────────────────────────────────┘

🎓 毕业项目扩展方向

扩展对应课程难度
添加M扩展(乘除法)L23
添加ICache/DCacheL36/L37⭐⭐
双发射超标量L31/L32⭐⭐⭐
添加F扩展(浮点)L25⭐⭐⭐
添加A扩展(原子)L24⭐⭐
乱序执行L15-L19⭐⭐⭐⭐⭐
双核+MESIL38⭐⭐⭐⭐
Linux启动L08/L09/L29/L30⭐⭐⭐⭐⭐
推荐扩展路线:先加M扩展和Cache(跑CoreMark),然后加A扩展和双核(跑多线程程序),最后加F/D扩展和乱序(跑SPEC CPU)。每一步都可以独立验证,逐步构建一个工业级CPU。

🧪 实验练习

  1. 修改fib参数:计算fib(20) = 6765,验证更大递归深度
  2. 跑排序:实现冒泡排序,对10个数字排序
  3. 性能测量:集成L39性能计数器,测量CPI
  4. 添加Cache:集成L36/L37的ICache/DCache
流水线CPU跑fib(10)=55
思考题:fib(10)的递归调用深度是10,总共调用fib函数177次。如果CPU没有前递(L33),会有多少额外的停顿周期?CPI会变成多少?
参考资料:RISC-V ISA Spec | Hennessy & Patterson Chapter 4 | PULP Platform | OpenHW Group CV32E40P

🎓 课程总结:从0到1的CPU设计之旅

40课,你从零开始构建了一个完整的RISC-V处理器。回顾这段旅程:

40课知识脉络: 第一阶段 (L01-08): 特权架构与中断 ┌──────────────────────────────────────────┐ │ M/S/U → CSR → ecall → mret → trap → 中断│ │ PLIC → CLINT │ └──────────────────────────────────────────┘ 第二阶段 (L09-14): 内存与算术 ┌──────────────────────────────────────────┐ │ SV39 → TLB → Cache → Booth → Divider │ └──────────────────────────────────────────┘ 第三阶段 (L15-21): 高性能执行 ┌──────────────────────────────────────────┐ │ OoO → ROB → 重命名 → 记分牌 → Tomasulo │ │ 2位预测器 → BTB │ └──────────────────────────────────────────┘ 第四阶段 (L22-30): 指令扩展与系统集成 ┌──────────────────────────────────────────┐ │ RVC → RVM → RVA → RVF → RVD │ │ PMP → 解码器 → SoC → 启动流程 │ └──────────────────────────────────────────┘ 第五阶段 (L31-40): 流水线深度优化 ┌──────────────────────────────────────────┐ │ 超标量 → 多端口RF → 前递 → Load-Use │ │ 精确异常 → ICache → DCache → MESI │ │ 性能计数器 → 毕业CPU 🎓 │ └──────────────────────────────────────────┘

🎉 恭喜完成全部40课!你已经掌握了从特权架构到流水线CPU的完整设计能力。

下一步:参加RISC-V开源社区,为Rocket/BOOM/香山贡献代码!