将前5课实现的ALU、寄存器文件、指令解码器和PC控制器组装成完整的CPU数据通路。这是第一个里程碑——我们将拥有一台能执行指令的8位CPU!虽然还缺少内存和I/O,但核心计算能力已经完整。
数据通路(Datapath)描述了数据在CPU内部流动的路径——从寄存器文件读出,经过ALU运算,结果写回寄存器文件或内存。控制信号由指令解码器产生,驱动数据通路上的每一个多路选择器和使能信号。
// ========================================
// 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阶段多周期设计:
不同指令可能跳过某些阶段。例如ADD指令不需要MEMACC阶段,NOP只经过FETCH和WRITEBACK。
以 ADD R1, #42(R1 += 42)为例:
整条指令需要4个时钟周期(跳过了MEMACC)。
追踪以下程序在CPU上的执行过程(每个时钟周期的状态、信号值):
LDI R1, #10 ; R1 = 10 LDI R2, #20 ; R2 = 20 ADD R1, R2 ; R1 = R1 + R2 = 30 STA R1, $100 ; 内存[$0100] = 30 HLT
以下指令序列存在什么数据冒险?如何解决?
ADD R1, R2 → SUB R3, R1(SUB需要R1的新值)
提示:考虑前写旁路(forwarding)机制。
计算以下程序的CPI(Cycles Per Instruction):
LDI R1, #5 ; 2字节 ADD R1, #3 ; 2字节 JMP $8000 ; 3字节 NOP ; 1字节
达成条件:
奖励:🏆 重大里程碑!你已经构建了一个完整的CPU核心!虽然还没有内存和I/O,但计算引擎已经就绪。接下来的课程将为它添加"存储"和"感官"。