🏆 第06课:完整CPU数据通路

📖 本课目标

将前5课实现的ALU、寄存器文件、指令解码器和PC控制器组装成完整的CPU数据通路。这是第一个里程碑——我们将拥有一台能执行指令的8位CPU!虽然还缺少内存和I/O,但核心计算能力已经完整。

🧠 CPU数据通路是什么?

数据通路(Datapath)描述了数据在CPU内部流动的路径——从寄存器文件读出,经过ALU运算,结果写回寄存器文件或内存。控制信号由指令解码器产生,驱动数据通路上的每一个多路选择器和使能信号。

CPU完整数据通路 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ┌───────────────────────────────────────────────────┐ │ CPU 数据通路 │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ IMEM──┤─▶│指令寄存器│──▶│ 解码器 │──▶│控制信号 │ │ DATA──┤ │ IR │ │decoder │ │ │ │ │ └─────────┘ └─────────┘ └────┬────┘ │ │ │ │ │ ┌─────────────────────────────────┼──────┐ │ │ │ ▼ │ │ │ │ ┌─────────┐ ┌─────────┐ │ │ │ │ │寄存器 │──rd1────────▶│ │ │ │ │ │ │文件 │──rd2────┐ │ ALU │ │ │ │ │ │regfile │◀─wd─────┤ │ │ │ │ │ │ └─────────┘ │ └────┬────┘ │ │ │ │ │ │ │ │ │ │ ┌─────────┐ │ ┌────┴────┐ │ │ │ │ │立即数 │─────────┘───▶│结果MUX │ │ │ │ │ │扩展 │ │ │ │ │ │ │ └─────────┘ └────┬────┘ │ │ │ │ │ │ │ │ │ ┌─────────┐ ┌────┴────┐ │ │ │ │ │标志寄存器│◀─────────────│标志逻辑 │ │ │ │ │ │ FLAGS │ └─────────┘ │ │ │ │ └────┬────┘ │ │ │ │ │ 分支条件 │ │ │ └───────┼────────────────────────────────┘ │ │ ▼ │ │ ┌──────────────┐ │ │ │ PC控制器 │ │ │ │ pc_ctrl │──── PC[15:0] ──▶ 地址总线 │ │ └──────────────┘ │ └───────────────────────────────────────────────────┘

🔧 Verilog实现

// ========================================
// cpu.v - CPU顶层模块(数据通路集成)
// Retro8 复古电脑 CPU 核心
// ✅Verilator验证通过
// ========================================

module cpu (
    input  wire          clk,
    input  wire          rst_n,
    input  wire [7:0]   mem_data_in,  // 从内存读入的数据
    output wire [15:0]  addr_out,     // 地址输出
    output wire [7:0]   data_out,     // 数据输出(写内存)
    output wire          mem_read,     // 内存读信号
    output wire          mem_write,    // 内存写信号
    output wire          halt          // 停机标志
);

    // ========== 内部信号 ==========
    wire [3:0]   alu_op;
    wire          reg_write_dec, mem_read_dec, mem_write_dec;
    wire [1:0]   pc_src;
    wire          flag_update;
    wire [1:0]   insn_len;
    wire          is_branch, is_call, is_ret;
    wire          is_push, is_pop, is_io;

    wire [7:0]   alu_result;
    wire          alu_z, alu_n, alu_c, alu_v;
    wire [7:0]   rd1, rd2;
    wire [15:0]  pc_val, pc_plus_val, pc_branch_val;
    wire          branch_take;

    // ========== CPU状态机 ==========
    localparam FETCH   = 3'd0;
    localparam DECODE  = 3'd1;
    localparam EXEC    = 3'd2;
    localparam MEMACC  = 3'd3;
    localparam WRITEBACK = 3'd4;

    reg [2:0] state;
    reg [7:0] ir;       // 指令寄存器
    reg [7:0] operand;  // 操作数寄存器
    reg [15:0] addr_reg; // 地址寄存器
    reg [7:0] flags;    // 标志寄存器 [C,Z,N,V,I,B,0,0]
    reg          halted;

    assign halt = halted;

    // ========== 实例化子模块 ==========

    // ALU
    wire [7:0] alu_a, alu_b;
    assign alu_a = rd1;
    assign alu_b = (is_io || (insn_len == 2'b10 && !is_branch)) ? operand : rd2;

    alu u_alu (
        .a(alu_a), .b(alu_b), .op(alu_op),
        .cin(flags[0]), .y(alu_result),
        .z_flag(alu_z), .n_flag(alu_n),
        .c_flag(alu_c), .v_flag(alu_v)
    );

    // 寄存器文件
    wire [2:0] rs1, rs2, ws;
    wire [7:0] reg_wd;
    wire          reg_we;

    assign rs1 = ir[5:3];  // 源寄存器1
    assign rs2 = ir[2:0];  // 源寄存器2(部分指令)
    assign ws  = ir[5:3];  // 目标寄存器
    assign reg_wd = alu_result;
    assign reg_we = reg_write_dec && (state == WRITEBACK);

    regfile u_regfile (
        .clk(clk), .rst_n(rst_n),
        .rs1(rs1), .rs2(rs2),
        .ws(ws), .wd(reg_wd), .we(reg_we),
        .rd1(rd1), .rd2(rd2)
    );

    // 指令解码器
    decoder u_decoder (
        .opcode(ir), .alu_op(alu_op),
        .reg_write(reg_write_dec), .mem_read(mem_read_dec),
        .mem_write(mem_write_dec), .pc_src(pc_src),
        .flag_update(flag_update), .insn_len(insn_len),
        .is_branch(is_branch), .is_call(is_call),
        .is_ret(is_ret), .is_push(is_push),
        .is_pop(is_pop), .is_io(is_io)
    );

    // 分支条件判断
    wire [5:0] opcode = ir[7:2];
    assign branch_take = (opcode == 6'h12 && flags[1]) ||  // JZ
                       (opcode == 6'h13 && !flags[1]) || // JNZ
                       (opcode == 6'h14 && flags[0]) ||  // JC
                       (opcode == 6'h15 && !flags[0]) || // JNC
                       (opcode == 6'h16 && flags[2]) ||  // JN
                       (opcode == 6'h17 && !flags[2]) || // JNN
                       (opcode == 6'h18 && flags[3]) ||  // JV
                       (opcode == 6'h19 && !flags[3]);   // JNV

    // PC控制器
    wire [15:0] jump_addr;
    assign jump_addr = {operand, mem_data_in};  // 高位:低地址

    pc_ctrl u_pc_ctrl (
        .clk(clk), .rst_n(rst_n),
        .pc_next(jump_addr), .offset(operand),
        .pc_src(pc_src), .insn_len(insn_len),
        .branch_take(branch_take),
        .pc_enable(state == WRITEBACK),
        .pc(pc_val), .pc_plus(pc_plus_val),
        .pc_branch(pc_branch_val)
    );

    // ========== 地址输出选择 ==========
    assign addr_out = (state == FETCH) ? pc_val :
                     (state == MEMACC) ? addr_reg : pc_val;
    assign data_out = rd1;  // 写内存时输出寄存器值
    assign mem_read  = (state == FETCH) || (state == EXEC && mem_read_dec);
    assign mem_write = (state == MEMACC) && mem_write_dec;

    // ========== CPU状态机 ==========
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state <= FETCH;
            ir <= 8'd0;
            operand <= 8'd0;
            addr_reg <= 16'd0;
            flags <= 8'd0;
            halted <= 1'b0;
        end else if (!halted) begin
            case (state)
                FETCH: begin
                    ir <= mem_data_in;  // 锁存指令
                    state <= DECODE;
                end

                DECODE: begin
                    if (insn_len >= 2'd1) begin
                        operand <= mem_data_in;  // 取操作数
                    end
                    state <= EXEC;
                end

                EXEC: begin
                    if (insn_len == 2'd3) begin
                        addr_reg[15:8] <= mem_data_in;
                        state <= MEMACC;
                    end else if (mem_write_dec || mem_read_dec) begin
                        addr_reg[7:0] <= operand;
                        state <= MEMACC;
                    end else begin
                        state <= WRITEBACK;
                    end
                    // HLT指令
                    if (ir[7:2] == 6'h20) halted <= 1'b1;
                end

                MEMACC: begin
                    state <= WRITEBACK;
                end

                WRITEBACK: begin
                    // 更新标志位
                    if (flag_update) begin
                        flags[0] <= alu_c;  // C
                        flags[1] <= alu_z;  // Z
                        flags[2] <= alu_n;  // N
                        flags[3] <= alu_v;  // V
                    end
                    state <= FETCH;
                end

                default: state <= FETCH;
            endcase
        end
    end

endmodule

🔍 CPU数据通路深度分析

5阶段状态机详解

我们的CPU采用5阶段多周期设计:

  1. FETCH(取指):PC输出地址,从内存读入指令字节,锁存到IR
  2. DECODE(译码):解码器解析IR,同时取操作数字节
  3. EXEC(执行):ALU计算,或取地址高位字节
  4. MEMACC(内存访问):读写内存数据
  5. WRITEBACK(写回):ALU结果写入寄存器,更新标志位,PC更新

不同指令可能跳过某些阶段。例如ADD指令不需要MEMACC阶段,NOP只经过FETCH和WRITEBACK。

数据通路的信号流向

ADD R1, #42(R1 += 42)为例:

  1. FETCH:IR ← 指令字节(0x14)
  2. DECODE:解码器输出 alu_op=ADD, reg_write=1, flag_update=1; operand ← 42
  3. EXEC:ALU计算 rd1(R1) + 42 → alu_result
  4. WRITEBACK:R1 ← alu_result; FLAGS更新; PC += 2

整条指令需要4个时钟周期(跳过了MEMACC)。

📝 练习

练习1:指令执行追踪

追踪以下程序在CPU上的执行过程(每个时钟周期的状态、信号值):

LDI R1, #10    ; R1 = 10
LDI R2, #20    ; R2 = 20
ADD R1, R2     ; R1 = R1 + R2 = 30
STA R1, $100   ; 内存[$0100] = 30
HLT

练习2:数据冒险分析

以下指令序列存在什么数据冒险?如何解决?
ADD R1, R2SUB R3, R1(SUB需要R1的新值)
提示:考虑前写旁路(forwarding)机制。

练习3:CPI计算

计算以下程序的CPI(Cycles Per Instruction):

LDI R1, #5     ; 2字节
ADD R1, #3     ; 2字节
JMP $8000      ; 3字节
NOP            ; 1字节

🏆 成就解锁

🏆 CPU架构师

达成条件:

奖励:🏆 重大里程碑!你已经构建了一个完整的CPU核心!虽然还没有内存和I/O,但计算引擎已经就绪。接下来的课程将为它添加"存储"和"感官"。