
简介本资源是浙江大学计算机体系结构课程配套的MIPS流水线CPU硬件实现项目面向高校计算机/电子类专业本科生及数字电路设计初学者解决从理论指令集架构到可综合Verilog代码落地的关键实践问题。压缩包共56个文件含24个核心Verilog模块如IfStage.v、ExStage.v、Cpu.v等、5个COE内存初始化文件、5个XISE工程配置文件、4个PNG/JPG原理图与调试界面截图以及Python自动生成脚本、ASM汇编测试用例和详细README文档整体大小仅685KB轻量但结构完整。已有230人学习下载项目采用清晰长命名规范、模块化分阶段设计取指/译码/执行/访存/写回及推荐Verilog编码实践所有逻辑集中于*Stage.v系列文件Registers.v与Cpu.v由脚本生成辅以UCF约束与Nexys3开发板适配说明便于移植与深度理解流水线控制逻辑、Stall处理与Forwarding机制。1. 这不是教学演示而是一颗跑在 Nexys3 上的、带 VGA 输出的完整 MIPS 流水线 CPU你手头那块积灰的 Nexys3 开发板真能跑起一个有指令解码、寄存器读写、ALU 运算、数据内存访问、分支预测Stall/Forwarding、甚至还能把执行过程实时渲染到 VGA 屏幕上的 CPU 吗浙江大学计算机体系结构课的这份实验包给出了明确答案能而且是可综合、可调试、可验证的工业级 Verilog 实现。它不依赖任何黑盒 IP除基础 PLL 和 Block RAM 初始化文件所有流水线阶段IF/ID/EX/MEM/WB全部手写模块31 条 MIPS I 类指令覆盖算术、逻辑、移位、跳转、加载/存储全谱系更关键的是它把传统“烧进 FPGA 就完事”的实验升级为可观测系统——通过Debugger.v和Terminal.v模块CPU 当前 PC、各寄存器值、指令译码结果、ALU 输入输出、MEM 阶段数据通路状态全部以 ASCII 字符形式实时输出到 VGA 显示器。这不是玩具模型而是用命名即文档如IdStage_InstructionRegister_Output、模块职责隔离*Stage.v处理逻辑*Registers.v仅做寄存器堆建模、Verilog-2001 规范显式端口声明、always (posedge clk)标准触发构建的工程化实践。适合刚学完《计算机组成原理》想动手验证流水线冲突、正在准备 PAT 或考研复试需要硬件项目背书、或想从零理解 RISC CPU 如何落地为真实数字电路的工程师。2. 从顶层模块 Cpu.v 到五级流水线 Stage.v结构拆解与信号流闭环2.1 Cpu.v顶层设计与时钟域划分Cpu.v是整个系统的入口它不实现具体运算逻辑而是完成三类关键连接时钟与复位管理接收外部 100MHz 时钟clk_100mhz经Pc.v中的计数器分频生成clk_25mhz供给 VGA 控制器同时通过Anti_jitter.v消抖后驱动 CPU 主时钟cpu_clk模块实例化与信号粘连按 IF→ID→EX→MEM→WB 顺序例化IfStage.v、IdStage.v等并用IfIdRegisters.v、IdExRegisters.v等寄存器模块连接相邻阶段外设桥接将VgaController.v的像素数据总线vga_r/g/b与Terminal.v的字符缓冲区char_buffer关联使 CPU 内部状态可映射为屏幕坐标。提示Cpu.v中未使用initial块或#delay所有行为均基于同步时序符合 FPGA 综合要求。若需移植到其他开发板只需修改nexys3.ucf中的引脚约束Cpu.v本身无需改动。2.2 五级流水线 Stage.v 模块分工与关键信号定义每个*Stage.v模块遵循统一接口规范输入为上一级寄存器输出如IfId_*输出为本级计算结果如IdEx_*。以IdStage.v为例其核心职责是指令译码与寄存器堆读取关键信号如下// IdStage.v 片段已简化 module IdStage ( input wire clk, input wire rst_n, // 从 IF 阶段接收的指令和 PC input wire [31:0] IfId_Instruction, input wire [31:0] IfId_PcPlus4, // 输出到 EX 阶段的译码结果 output reg [31:0] IdEx_ReadData1, output reg [31:0] IdEx_ReadData2, output reg [4:0] IdEx_WriteRegister, output reg [31:0] IdEx_Immediate, output reg [2:0] IdEx_AluOp, output reg IdEx_RegWrite, output reg IdEx_MemRead, output reg IdEx_MemWrite, output reg IdEx_Branch, output reg IdEx_Jump );IfId_Instruction是从IfIdRegisters.v输出的 32 位原始指令IdStage需解析其opcode6bit、rs/rt/rd5bit、shamt5bit、funct6bit等字段IdEx_ReadData1/2来自RegisterFile.v的同步读端口IdEx_Immediate是符号扩展后的立即数sign_extend模块处理IdEx_AluOp是三位控制码决定 EX 阶段 ALU 执行 ADD/SUB/AND/OR/XOR/SLT 等操作其值由ControlUnit.v根据opcode和funct查表生成。2.3 寄存器文件 RegisterFile.v 与前递Forwarding实现RegisterFile.v是双端口同步 RAM支持同一周期内读两个寄存器rs,rt并写一个rd。其关键设计在于解决 RAW 冲突当某条指令I1在 MEM 阶段写入rd而下一条I2在 ID 阶段需读取同一rd时I2不能等待I1完成 WB必须从 MEM 或 EX 阶段的输出中“截获”最新值。项目通过ExMemRegisters.v和MemWbRegisters.v的输出信号在IdStage.v内部实现前递逻辑// IdStage.v 中前递判断逻辑伪代码 if (IdEx_RegWrite (IdEx_WriteRegister IfId_rs)) begin IdEx_ReadData1 ExMemRegisters_WbData; // 从 MEM 阶段取值 end else if (IdEx_RegWrite (IdEx_WriteRegister IfId_rt)) begin IdEx_ReadData2 ExMemRegisters_WbData; end else begin // 正常从 RegisterFile 读取 IdEx_ReadData1 RegisterFile_Out1; IdEx_ReadData2 RegisterFile_Out2; end注意前递路径仅覆盖 MEM→ID 和 EX→ID 两种情况因 WB 阶段输出已稳定无需再前递且IdEx_WriteRegister必须非零$zero不参与写回该逻辑在IdStage.v中硬编码实现避免额外多路选择器延迟。2.4 控制单元 ControlUnit.v指令集完备性的决策中枢ControlUnit.v是纯组合逻辑模块输入为 6-bitopcode输出为RegWrite、MemRead、MemWrite、Branch、Jump、AluSrc、AluOp等 9 个控制信号。其本质是一个 64 项 ROM 查表器对应 MIPS I 全部 64 种 opcode 编码。项目实现了其中 31 条常用指令包括R 型指令add,sub,and,or,xor,slt,sll,srlfunct字段决定具体操作I 型指令addi,andi,ori,xori,lw,sw,beq,bne,luiJ 型指令j,jal。查表逻辑用 case 语句实现例如lw指令opcode100011的输出为100011: begin RegWrite 1b1; // 写回目标寄存器 MemRead 1b1; // 从内存读取 MemWrite 1b0; Branch 1b0; Jump 1b0; AluSrc 1b1; // ALU 第二输入来自立即数 AluOp 3b000; // ALU 执行 ADD基地址偏移 end3. VGA 实时调试系统Terminal.v 与 Debugger.v 的协同机制3.1 Terminal.v字符缓冲区与 VGA 像素映射Terminal.v是 VGA 显示的核心中介它维护一个 60×80 字符的缓冲区char_buffer[4799:0]60 行 × 80 列每个字节存储 ASCII 码。其工作流程分三步CPU 状态写入Cpu.v将Pc.v的当前 PC 值、RegisterFile.v的 32 个寄存器内容、Disassembler.v的反汇编指令字符串通过write_addr和write_data接口写入缓冲区指定位置如 PC 显示在第 0 行第 0 列VGA 时序生成内部集成VgaController.v的时序逻辑产生hsync、vsync、blank信号并根据当前扫描位置row/col计算缓冲区索引addr row * 80 col字符→像素转换查Font.xcoXilinx Core Generator 生成的 8×16 点阵字体 ROM将 ASCII 码映射为 16 行 × 8 列的像素位图最终输出vga_r/g/b三原色信号。提示Font.xco文件定义了标准 ASCII 字符集0x20–0x7E若需显示中文需替换为 GB2312 字体 ROM 并修改Terminal.v的地址映射逻辑。3.2 Debugger.v指令级单步与断点触发Debugger.v提供两种调试模式自动刷新模式每 10ms 从Cpu.v采集一次PC、IR指令寄存器、Reg[0:31]、ALU_Out、MEM_DataOut等信号格式化为固定宽度文本如PC: 0x00000000 | IR: 0x00000000 | R1: 0x00000000写入Terminal.v缓冲区断点模式用户通过debugger-input.txt预设断点地址如0x00000010Debugger.v在每个时钟上升沿比对PC值命中时拉高debug_halt信号冻结Pc.v的计数器使 CPU 停在断点处。此时可通过串口需外接 UART 模块发送命令继续运行或查看寄存器。3.3 调试信息生成链从汇编到屏幕的全流程项目提供generate-debugger.py脚本将InstructionMemory.asm中的汇编指令自动转换为InstructionMemory.coeCOE 文件是 Xilinx Block RAM 初始化格式并生成disassembly.txt反汇编列表。Disassembler.v模块在运行时实时解析IR调用HexCharacterConverter.v将 4-bit 十六进制数转 ASCII和BooleanTextConverter.v将 1-bit 信号转 0/1 字符最终拼接成人类可读的指令字符串如add $t0,$s0,$s1。该字符串被Debugger.v采集后经Terminal.v渲染到 VGA 屏幕第 2 行。调试信号源Verilog 模块输出格式VGA 显示位置当前 PCPc.v32-bit hex (0x00000000)第 0 行列 0–9指令寄存器 IRIfStage.v32-bit hex Disassembler.v反汇编第 1 行列 0–29通用寄存器 R0–R31RegisterFile.v32-bit hex每行 4 个寄存器第 3–11 行ALU 运算结果Alu.v32-bit hex第 12 行列 0–94. 实验复现从 Vivado 工程构建到 Nexys3 烧录的完整步骤4.1 Vivado 工程创建与约束文件配置使用 Vivado 2018.3兼容 Nexys3 的最低版本创建新工程选择RTL Project→Do not specify sources at this time在Default Part中选择xc7a35ticsg324-1LNexys3 的 Artix-7 FPGA 型号添加所有.v文件Cpu.v,IfStage.v, ...,Terminal.v和.xco文件Font.xco,Background.xco关键步骤在Constraints下添加nexys3.ucf该文件已预定义所有引脚# nexys3.ucf 片段 NET clk_100mhz LOC E3 | IOSTANDARD LVCMOS33 | PERIOD 10.000 ns; NET vga_hsync LOC G19 | IOSTANDARD LVCMOS33; NET vga_vsync LOC H19 | IOSTANDARD LVCMOS33; NET vga_r0 LOC D19 | IOSTANDARD LVCMOS33; NET vga_g0 LOC D20 | IOSTANDARD LVCMOS33; NET vga_b0 LOC E19 | IOSTANDARD LVCMOS33;注意nexys3.ucf中vga_r/g/b各定义 3 位RGB333 格式若需更高色深需修改约束并重写Terminal.v的像素打包逻辑。4.2 Block RAM 初始化COE 文件生成与内存加载InstructionMemory.coe、DataMemory.coe、Background.coe是初始化 FPGA 片上 RAM 的关键文件。项目提供generate-stage-instantiation.py脚本但实际使用需手动操作编写InstructionMemory.asmMIPS 汇编例如.text 0x00000000: lui $t0, 0x1234 0x00000004: addi $t0, $t0, 0x5678 0x00000008: sw $t0, 0($zero)运行python generate-debugger.py InstructionMemory.asm生成InstructionMemory.coe在 Vivado 中右键InstructionMemory.xo→Edit in IP Packager→Re-customize IP→Port Configuration→Load COE file选择生成的.coe。4.3 综合、实现与烧录验证执行标准 Vivado 流程Synthesis检查Critical Warning是否为 0如有通常是未连接的open端口可忽略Implementation重点观察Timing Summary中WNS (Worst Negative Slack)是否 ≥ 0若为负需优化时序如在Pc.v中插入寄存器打拍Bitstream Generation成功后生成archexp.runs/impl_1/archexp.bit烧录使用 Adept 2.2.1 工具Nexys3 官方配套选择Program Device→Select .bit file→Program。烧录完成后VGA 显示器应出现 60×80 字符界面顶部显示PC,IR,R0–R31等实时值。若屏幕无输出用示波器测vga_hsync引脚G19是否有 31.5kHz 方波检查Terminal.v中vga_blank信号是否恒为 0应为周期性脉冲在Cpu.v中临时将vga_r/g/b全置为3b111确认显示器能否显示白屏。5. 进阶技巧指令集扩展与性能瓶颈定位5.1 添加新指令以nop和syscall为例扩展指令需同步修改三处ControlUnit.v在case语句中新增opcode分支设置控制信号。nopopcode0x00的输出全为 0syscallopcode0x00, funct0x0c需置RegWrite0,MemRead0,MemWrite0,Branch0,Jump0,AluSrc0,AluOp3b000Disassembler.v在casez语句中添加32h0000000c对应syscall字符串Testbench编写CpuTest.v的新测试用例例如// CpuTest.v 片段 initial begin reset 1; #100 reset 0; // 测试 nopPC 应4无寄存器变化 wait (pc 32h00000000); #100 assert (pc 32h00000004) else $error(nop failed); end5.2 Stall 检测与流水线气泡注入当lw指令后紧跟使用该加载数据的add指令时RAW冲突IdStage.v必须插入气泡stall_id 1。项目通过以下逻辑检测// IdStage.v 中 stall 判断 assign stall_id (IdEx_MemRead (IdEx_WriteRegister ! 5d0) (IdEx_WriteRegister IfId_rs || IdEx_WriteRegister IfId_rt));若stall_id为高则IdStage输出全为 0且IfIdRegisters.v的clk_en被拉低暂停 IF 阶段取指。此时 VGA 屏幕上PC值将停滞 1 个周期IR显示为0x00000000气泡指令这是验证 Stall 机制是否生效的最直观证据。5.3 性能瓶颈分析关键路径时序报告解读在 Vivado 的Reports → Timing → Report Timing Summary中重点关注Slack若WNS -1.2ns说明最差路径比时钟周期慢 1.2nsPath Group通常瓶颈在PC InstructionMemory Address→IfStage→IdStage的组合逻辑链Critical Path点击Report Timing Details查看延时最大的单元常见为InstructionMemory的 Block RAM 读取约 2.5ns或Disassembler.v的多级 case 语句约 1.8ns。优化建议将InstructionMemory改为双时钟域clk_100mhz读clk_25mhz写利用 Block RAM 的异步读特性在Disassembler.v前插入一级寄存器缓存IR将长组合路径拆分为两级时序。VGA 调试界面本身即是性能探针——当PC更新频率明显低于 25MHz即每 40ns 更新一次说明流水线存在严重阻塞需优先检查stall_id信号是否被意外拉高。本文还有配套的精品资源点击获取