从零构建8位RISC CPU:基于FSM的Verilog控制器设计实战

📅 2026/7/16 2:16:51
从零构建8位RISC CPU:基于FSM的Verilog控制器设计实战
1. 8位RISC CPU设计基础要设计一个8位RISC CPU首先需要明确几个核心概念。RISC精简指令集计算机的特点是指令数量少、格式统一、执行速度快。8位指的是CPU的数据总线宽度也就是一次能处理8位二进制数据。我刚开始接触CPU设计时最大的困惑就是不知道从哪里下手。后来发现从数据通路开始画起是最直观的方法。想象一下数据在CPU内部流动的路径从存储器取出指令送到控制器解码然后ALU执行运算最后写回寄存器或存储器。这个过程中需要哪些部件它们之间如何连接1.1 核心组件清单一个最基本的8位RISC CPU需要以下部件程序计数器PC16位宽度指向下一条要执行的指令地址指令存储器IM存储指令代码寄存器文件RF通常包含AC累加器和R通用寄存器算术逻辑单元ALU执行加减、逻辑运算等数据存储器DM存储数据控制器基于有限状态机FSM的硬布线控制1.2 指令集设计要点设计指令集时我建议先确定指令格式。比如采用固定8位操作码opcode这样可以简化译码逻辑。一个实用的分类方法是按指令长度// 单字指令格式 | 7:6 | 5:2 | 1:0 | | type | op | reserved | // 三字指令格式带16位地址 | 7:6 | 5:2 | 1:0 | 15:0 | | type | op | dir | address |指令类型可以划分为算术逻辑指令ADD, SUB, AND, OR等数据传送指令MOV, LD, ST等控制流指令JUMP, JZ等2. 数据通路设计与实现数据通路是CPU的高速公路决定了数据流动的路径。我在第一次设计时犯的错误是没有考虑多周期指令导致三字指令无法正确处理。2.1 多周期数据通路对于带地址的三字指令需要**增加地址寄存器AR**来暂存地址。改进后的数据通路工作流程取指阶段instr IM[PC]; PC PC 1;译码阶段case(instr[7:6]) 2b00: // 单字指令处理 2b10: // 三字指令第一周期取地址高字节 2b11: // 三字指令第二周期取地址低字节 endcase执行阶段算术运算ALU操作存储器访问DM读/写跳转指令修改PC值2.2 关键信号说明在Verilog实现中这些控制信号尤为重要信号名作用产生条件RegWrite寄存器写使能MOV, ADD, LD等MemWrite存储器写使能ST指令ALUOpALU操作选择根据指令类型NPCOpPC更新方式JUMP/JZ等3. 有限状态机控制器设计控制器是CPU的大脑我用三段式FSM实现了状态转换这是最可靠的写法。3.1 状态定义典型的状态包括parameter FETCH1 3d0; // 取指第一周期 parameter FETCH2 3d1; // 三字指令第二周期 parameter EXEC 3d2; // 执行阶段 parameter MEM 3d3; // 存储器访问 parameter WB 3d4; // 写回阶段3.2 Verilog实现示例// 状态寄存器 always (posedge clk or posedge reset) begin if(reset) state FETCH1; else state next_state; end // 下一状态逻辑 always (*) begin case(state) FETCH1: next_state (instr[7:6]2b10) ? FETCH2 : EXEC; FETCH2: next_state EXEC; EXEC: next_state (is_mem_op) ? MEM : WB; MEM: next_state WB; WB: next_state FETCH1; endcase end // 输出逻辑 always (*) begin case(state) FETCH1: {RegWrite, MemWrite} 2b00; EXEC: ALUOp (instr[5:2]4b0010) ? ADD : ...; // 其他状态输出... endcase end4. ALU与寄存器设计技巧4.1 ALU设计要点ALU需要支持的基本操作module alu( input [7:0] a, b, input [2:0] op, output reg [7:0] out, output zero ); always (*) begin case(op) 3b000: out a b; // ADD 3b001: out a - b; // SUB 3b010: out a b; // AND 3b011: out a | b; // OR // 其他操作... endcase end assign zero (out8b0); // 零标志 endmodule4.2 寄存器文件优化虽然我们的CPU只有AC和R两个寄存器但采用寄存器文件结构更利于扩展reg [7:0] regfile [0:1]; // 0:AC, 1:R always (posedge clk) begin if(RegWrite) begin if(instr[1]) regfile[1] ALUOut; // 写入R else regfile[0] ALUOut; // 写入AC end end5. 验证与调试实战5.1 测试用例设计我通常会准备三类测试单指令测试验证每条指令功能initial begin // MOV AC, #5 IM[0] 8b01_0001_01; // 操作码 IM[1] 8d5; // 立即数 #100; if(AC ! 5) $error(MOV失败); end程序流测试验证跳转指令// JUMP 5 IM[0] 8b11_0000_00; IM[1] 8d5; #100; if(PC ! 5) $error(JUMP失败);综合程序测试// 计算123 IM[0] 8b01_0010_10; // ADD AC, R IM[1] 8b01_0010_10; // ADD AC, R // 初始化AC1, R25.2 常见问题排查信号未更新检查是否缺少(posedge clk)状态机卡死添加状态监控$display时序问题检查setup/hold时间6. FPGA实现注意事项最后上板时这些经验很关键时钟管理使用全局时钟网络必要时插入时钟缓冲器IO规划// 开发板按键控制 input [1:0] sw; // 模式选择 input [7:0] dip; // 数据输入 output [7:0] led;// 状态显示调试技巧添加ILA核实时抓取信号使用VIO虚拟IO控制输入设计CPU最有趣的是看到自己写的指令真正跑起来的那一刻。记得第一次成功点亮LED时那种成就感至今难忘。虽然这个8位CPU很小但它包含了现代处理器的所有核心思想。