三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

FPGA五级流水线CPU设计:从零实现RISC架构处理器

FPGA五级流水线CPU设计:从零实现RISC架构处理器

这次我们来看一个面向 FPGA 初学者的五级流水线 CPU 设计项目。对于刚接触数字逻辑和计算机体系结构的朋友来说,自己动手设计一个 CPU 是理解计算机如何工作的最佳途径。这个项目的核心不是追求极致的性能,而是通过一个结构清晰、可实现的五级流水线模型,让你从零开始,在 FPGA 上跑通一个能执行指令的 CPU。它解决了从理论到实践的鸿沟,让你亲手搭建指令集、数据通路和控制单元。

最值得关注的几个特点是:第一,它基于经典的 RISC 架构,指令集精简,易于理解和实现;第二,采用标准的取指、译码、执行、访存、写回五级流水线,是学习流水线技术的绝佳范例;第三,整个设计使用 Verilog 硬件描述语言完成,可以直接在主流 FPGA 开发板上进行综合、实现和下载测试;第四,项目通常会包含完整的测试程序(如简单的排序、计算),用于验证 CPU 功能的正确性。本文将带你梳理从环境搭建、代码结构分析、仿真测试到最终上板验证的全流程,让你清楚每一步该做什么,以及如何排查可能遇到的问题。

如果你是一名电子、计算机或相关专业的学生,或者是对 CPU 内部运作机制充满好奇的硬件爱好者,这个项目非常适合你。它不需要你事先精通 FPGA,但需要你具备数字电路的基础知识和 Verilog 的基本编写能力。通过完成这个项目,你不仅能深入理解流水线、数据冒险、控制冒险等核心概念,还能获得一个可以实际运行在硬件上的成果,这对于巩固知识、丰富简历或准备硬件类面试都大有裨益。

1. 核心能力速览

能力项说明
项目类型教学型/实践型 CPU 软核设计
核心架构五级流水线(IF, ID, EX, MEM, WB)
指令集自定义或基于 MIPS 等精简指令集
实现语言Verilog / SystemVerilog
目标平台支持主流 FPGA 开发板(如 Xilinx Artix-7, Intel Cyclone IV 等)
开发环境Vivado / Quartus II 等 FPGA 厂商工具链
验证方式仿真(ModelSim/QuestaSim) + 上板测试
关键知识点流水线设计、数据前推、冒险处理、控制器设计、总线接口
硬件门槛任意一款具备足够逻辑资源的 FPGA 开发板即可,对显存无要求
适合场景计算机体系结构教学、FPGA 进阶学习、毕业设计、硬件面试准备

2. 适用场景与使用边界

这个五级流水线 CPU 设计项目主要适用于以下几个场景:

  1. 教育学习:作为《计算机组成原理》或《计算机体系结构》课程的配套实验,帮助学生将书本上的流水线、Cache、总线等抽象概念转化为可运行的硬件代码。
  2. 技能入门与巩固:对于希望进入数字 IC 设计或 FPGA 开发领域的初学者,这是一个标志性的练手项目。完成它意味着你掌握了从模块设计、仿真验证到系统集成的完整硬件开发流程。
  3. 面试与求职准备:在数字设计工程师的面试中,“设计一个简单的流水线 CPU”是高频问题。拥有一个自己实现并调试通过的项目经验,远比纸上谈兵更有说服力。
  4. 研究原型基础:你可以以此为基础,扩展更多功能,如添加中断控制器、集成乘法除法器、实现更复杂的指令集(如 RV32I),或者研究多核架构,作为科研的起点。

使用边界与注意事项:

  • 非商用级性能:这是一个教学演示性质的 CPU,主频、面积、功耗等指标未经过深度优化,不能与商业 IP 核或高性能处理器相提并论,不适用于产品级应用。
  • 功能完整性:通常实现一个最基础的整数指令子集,可能不支持浮点运算、虚拟内存、异常处理等复杂功能。
  • 工具链依赖:项目的正确运行高度依赖于特定的 FPGA 开发环境和仿真工具。不同版本的工具可能在语法检查、综合策略上存在差异。
  • 硬件平台限制:代码可能针对特定型号的 FPGA 或开发板(如特定的时钟、复位、外设接口),移植到其他平台需要一定的适配工作。

3. 环境准备与前置条件

在开始动手之前,需要准备好软硬件环境。这是项目能否顺利跑起来的基础。

硬件准备:

  1. FPGA 开发板:一块主流的 FPGA 开发板是必须的。例如:
    • Xilinx 阵营:基于 Artix-7 芯片的 Basys 3、Nexys 4 DDR、Zybo 等。
    • Intel (Altera) 阵营:基于 Cyclone IV 或 Cyclone V 芯片的 DE0-CV、DE1-SoC、DE10-Standard 等。
    • 选择哪一款取决于你手头的资源或课程要求。确保开发板有足够的逻辑单元(Logic Elements/Cells)和片上内存(Block RAM)来容纳你的 CPU 设计。
  2. 下载器:用于将编译后的程序下载到 FPGA,如 Xilinx 的 USB-JTAG 线、Intel 的 USB-Blaster。
  3. PC 机:用于安装开发软件。

软件准备:

  1. FPGA 开发套件
    • Xilinx Vivado:适用于 Xilinx 芯片。建议安装最新可用的版本(如 2022.1)。注意其体积庞大,需预留足够的磁盘空间。
    • Intel Quartus Prime:适用于 Intel 芯片。同样建议安装标准版。
  2. 仿真工具:虽然 Vivado 和 Quartus 自带仿真器,但使用专业的仿真工具如ModelSimQuestaSim进行前期验证效率更高。许多 FPGA 套件会包含免费版的 ModelSim。
  3. 文本编辑器/IDE:用于编写和查看 Verilog 代码,如 VS Code 搭配 Verilog 插件、Vim、Notepad++等。
  4. 终端/命令行工具:用于执行一些脚本命令。

知识前置条件:

  • 数字电路基础:理解组合逻辑、时序逻辑、寄存器、有限状态机等概念。
  • Verilog 语言:掌握基本的模块定义、端口声明、always 块、assign 语句、testbench 编写。
  • 计算机组成原理:了解 CPU 的基本组成(ALU、寄存器堆、控制器)、指令执行过程、流水线概念。

4. 工程建立与代码结构分析

拿到一个五级流水线 CPU 的工程代码,第一步不是盲目编译,而是先理解它的目录结构和各个模块的职责。

一个典型的项目目录可能如下所示:

five_stage_cpu/ ├── rtl/ // 存放所有可综合的 Verilog 源代码 │ ├── core/ // CPU 核心模块 │ │ ├── pc_reg.v // 程序计数器寄存器 │ │ ├── if_stage.v // 取指阶段 │ │ ├── id_stage.v // 译码阶段 │ │ ├── reg_file.v // 寄存器堆 │ │ ├── ex_stage.v // 执行阶段(含ALU) │ │ ├── mem_stage.v // 访存阶段 │ │ ├── wb_stage.v // 写回阶段 │ │ ├── hazard_unit.v // 冒险检测单元 │ │ ├── forward_unit.v // 数据前推单元 │ │ └── control_unit.v // 主控制器 │ ├── mem/ // 存储器模块 │ │ ├── inst_rom.v // 指令存储器(ROM) │ │ └── data_ram.v // 数据存储器(RAM) │ └── top.v // 顶层模块,连接CPU核心和存储器 ├── sim/ // 仿真相关文件 │ ├── testbench/ // 测试平台 │ │ └── cpu_tb.v // 顶层的Testbench │ └── scripts/ // 仿真脚本(如.do文件) ├── software/ // 软件部分 │ ├── asm/ // 汇编程序源代码 │ ├── linker_script.ld // 链接脚本 │ └── Makefile // 编译脚本,用于生成机器码 ├── constr/ // 约束文件 │ └── pins.xdc // 管脚约束文件 └── README.md // 项目说明文档

核心模块功能解读:

  1. 取指阶段 (IF):根据 PC(程序计数器)从指令存储器中读取指令,并计算下一条指令的地址(PC+4或跳转地址)。
  2. 译码阶段 (ID):解析指令,从寄存器堆中读取源操作数,并产生主要的控制信号。冒险检测单元也通常在此阶段工作。
  3. 执行阶段 (EX):由算术逻辑单元 (ALU) 执行计算操作(如加、减、与、或、比较等),并计算访存地址或跳转目标地址。
  4. 访存阶段 (MEM):如果是 load/store 指令,则访问数据存储器;否则直接传递数据。同时处理跳转指令的结果,更新 PC。
  5. 写回阶段 (WB):将执行结果或从存储器加载的数据写回到寄存器堆。
  6. 冒险处理单元:这是流水线设计的精髓。
    • 数据冒险:当后续指令需要用到前面指令还未写回的结果时发生。解决方案是“数据前推”,将 ALU 结果或访存结果直接旁路到需要的地方,避免流水线停顿。
    • 控制冒险:当遇到跳转指令时,后续已取入流水线的指令可能无效。解决方案可以是“分支预测”(简单项目可能直接停顿流水线)或“延迟槽”。

在 Vivado 或 Quartus 中新建工程时,需要将rtl/目录下的所有.v文件添加为设计源文件,将constr/目录下的约束文件添加为约束文件。

5. 仿真测试:验证逻辑正确性

在上板之前,必须通过仿真确保 CPU 的逻辑行为是正确的。这是硬件调试中最重要、成本最低的一环。

步骤 1:准备测试程序software/asm/目录下,通常会有一个用汇编语言编写的测试程序,例如test.s,它可能实现一个简单的功能,比如计算斐波那契数列、数组排序等。

# test.s - 一个简单的加法测试 .text .global _start _start: li x1, 5 # 加载立即数5到寄存器x1 li x2, 3 # 加载立即数3到寄存器x2 add x3, x1, x2 # x3 = x1 + x2 sw x3, 0(x0) # 将结果存储到内存地址0 nop nop j _start # 循环(实际测试可能用ebreak结束)

使用项目提供的Makefile或工具链(如 RISC-V 的 riscv-gcc)将该汇编程序编译、链接,并生成机器码文件(通常是.bin.hex格式)。这个机器码文件需要被加载到仿真环境中的指令存储器 (inst_rom.v) 里。

步骤 2:编写或使用现有 Testbenchsim/testbench/cpu_tb.v是这个 CPU 的测试平台。它的主要任务是:

  • 实例化顶层模块top
  • 生成时钟 (clk) 和复位 (rst) 信号。
  • 将编译好的机器码文件读入,并初始化指令存储器。
  • 在仿真运行一段时间后,检查关键寄存器或内存位置的值是否符合预期,来判断测试是否通过。

一个简单的 Testbench 时钟生成部分如下:

`timescale 1ns / 1ps module cpu_tb(); reg clk; reg rst_n; // ... 其他信号声明 top u_top ( .clk_i(clk), .rst_ni(rst_n) // ... 其他端口连接 ); // 生成时钟:周期10ns,频率100MHz initial begin clk = 0; forever #5 clk = ~clk; end // 生成复位信号 initial begin rst_n = 0; // 初始复位 #100; // 复位保持100ns rst_n = 1; // 释放复位 #5000; // 仿真运行5000ns $finish; // 结束仿真 end // 初始化指令存储器 initial begin $readmemh("../../software/test.hex", u_top.u_inst_rom.mem); end // 监控输出 always @(posedge clk) begin if (u_top.u_core.u_wb.rf_wen_i && u_top.u_core.u_wb.rf_waddr_i == 3) begin $display("Time=%t: x3 is written with value %h", $time, u_top.u_core.u_wb.rf_wdata_i); end end endmodule

步骤 3:运行仿真并分析波形在 Vivado 中,你可以直接运行行为仿真。在 ModelSim 中,你需要编译所有设计文件和 Testbench 文件,然后运行仿真。

仿真启动后,最关键的是查看波形。你需要关注:

  • pc(程序计数器):是否按顺序或正确跳转?
  • 流水线各阶段的指令 (if_inst,id_inst,ex_inst...):指令是否在流水线中正确流动?
  • 控制信号 (reg_write,mem_read等):是否在正确的周期被激活?
  • 寄存器写回数据 (wb_data) 和地址 (wb_addr):写回的值是否正确?
  • 重点观察冒险处理:当发生 RAW(写后读)冒险时,观察forward_a,forward_b等前推信号是否变为有效,以及 ALU 的操作数是否被正确的前推值替代,从而避免了流水线停顿 (stall)。
  • 最终,检查内存中特定地址(如存放结果的地址)的数据是否与软件计算的预期值一致。

只有仿真完全通过,所有关键路径的波形都符合预期,才能进行下一步的综合与上板。

6. 综合、实现与上板验证

仿真通过后,就可以将设计放到真实的 FPGA 芯片中运行了。

步骤 1:综合 (Synthesis)在 Vivado/Quartus 中点击“综合”。综合工具会将你的 Verilog 代码转换为由 FPGA 基本逻辑单元(查找表 LUT、触发器 FF 等)和连接关系组成的网表。综合报告需要重点关注:

  • 资源利用率:查看 LUT、FF、BRAM 的使用百分比。确保没有超过目标芯片的容量。
  • 时序警告:关注是否有严重的时序问题。初期可以暂时忽略一些不影响功能的警告。

步骤 2:实现 (Implementation)实现包括布局布线 (Place & Route)。工具会将网表中的逻辑单元在芯片的物理位置上进行摆放,并用芯片内部的布线资源将它们连接起来。实现报告需要关注:

  • 时序收敛:查看“时序”报告,确保建立时间 (Setup Time) 和保持时间 (Hold Time) 的裕量 (Slack) 为正。如果为负,说明当前时钟频率下时序不满足,需要降低时钟频率或优化代码。
  • 布线拥塞:如果布线拥塞严重,也可能导致时序问题。

步骤 3:生成比特流并下载当时序满足要求后,就可以生成比特流文件 (.bit.sof)。将这个文件通过下载器下载到 FPGA 开发板中。

步骤 4:上板调试上板后,CPU 就开始实际运行了。如何验证它是否在工作呢?

  1. LED/数码管显示:最简单的办法是修改测试程序,将最终的计算结果输出到开发板的 LED 或数码管上。通过观察显示值是否与预期相符来判断。
  2. 嵌入式逻辑分析仪:这是最强大的调试工具。Vivado 的 ILA (Integrated Logic Analyzer) 或 Quartus 的 SignalTap II 允许你在 FPGA 运行时,像示波器一样捕获内部信号的波形。你可以将pc、关键寄存器值、控制信号等添加到观察列表中,触发捕获条件,从而在硬件上重现仿真时的调试过程。
  3. 串口输出:如果 CPU 设计集成了 UART 模块,可以通过串口将运行状态或结果打印到 PC 的终端上,这是非常直观的调试方式。

上板验证流程示例:假设我们让 CPU 计算5+3,并将结果8写入到内存地址0x1000,同时将该地址映射到 LED 寄存器。

  • 在软件中,编写sw x3, 0x1000(x0)指令。
  • 在约束文件中,将 FPGA 的某个 8 位输出端口(连接着 8 个 LED)绑定到top模块的led_o信号,该信号在硬件上始终读取地址0x1000的值。
  • 下载程序后,观察 LED 是否显示二进制00001000(即十进制 8)。如果是,则证明 CPU 从取指、执行到访存的整个流水线工作正常。

7. 关键问题与深度排查

在设计和调试五级流水线 CPU 时,一定会遇到各种问题。以下是几个典型问题及其排查思路:

问题现象可能原因排查方式解决方案
仿真时 PC 不递增,卡在第一条指令1. 复位信号未正确释放。
2. 指令存储器初始化失败,读出的指令是全0(NOP)或非法指令。
3. PC 寄存器的时钟或复位连接错误。
1. 检查 Testbench 中复位信号的时序。
2. 在波形中查看inst_rom的输出,确认读出的指令码是否正确。
3. 检查 PC 模块的输入输出连接。
1. 确保复位信号在仿真开始后一段时间拉高。
2. 确认$readmemh路径和文件名正确,且.hex文件内容有效。
3. 逐级检查顶层到底层的连接。
流水线执行结果错误1. 数据冒险未正确处理,读到了旧值。
2. 控制信号(如RegWrite,MemRead)在流水级间传递错误。
3. ALU 功能实现有误。
1. 在波形中重点观察发生 RAW 冒险的指令附近,查看前推信号 (forward) 和 ALU 操作数。
2. 追踪错误结果在流水线中的传递路径。
3. 单独测试 ALU 模块。
1. 调试 Hazard Unit 和 Forwarding Unit 的逻辑。
2. 检查控制信号流水线寄存器的输入输出。
3. 编写 ALU 的专项测试。
上板后无任何现象1. 时钟信号未接入或频率不对。
2. 复位信号极性错误或一直有效。
3. 约束文件错误,管脚未正确分配。
4. 比特流文件下载失败。
1. 用示波器或逻辑分析仪测量时钟引脚。
2. 检查复位按钮电路和代码中的复位极性(高有效/低有效)。
3. 仔细核对约束文件中的管脚编号和电平标准。
4. 确认下载器连接正常,FPGA 型号选择正确。
1. 检查时钟生成模块(如 PLL)配置和约束。
2. 统一复位极性设计。
3. 参考开发板原理图修正约束。
4. 重新拔插下载器,尝试不同的下载模式。
时序违例,无法达到预期时钟频率1. 组合逻辑路径过长(关键路径)。
2. 高扇出网络导致布线延迟大。
3. 时钟约束过于激进。
1. 查看时序报告,找到违例的路径起点和终点。
2. 分析该路径上的逻辑层次。
1. 对关键路径进行流水线切割,插入寄存器。
2. 对高扇出信号(如全局复位)使用缓冲器或保持寄存器。
3. 适当降低时钟频率约束。
分支/跳转指令后执行流错误1. 分支判断和跳转地址计算在流水线中的阶段安排错误。
2. 控制冒险处理逻辑错误,未正确清空或冻结流水线。
3. PC 源选择逻辑(顺序、跳转、异常)有误。
1. 在波形中单步执行分支指令,观察 PC 的每个可能来源的数值和选择信号。
2. 观察分支指令后流水线中指令的变化,是否被错误地执行。
1. 重新梳理分支指令的流水线行为,修正控制信号产生时机。
2. 完善控制冒险处理单元(分支预测或停顿逻辑)。
3. 仔细检查 PC 多路选择器的逻辑。

8. 性能优化与扩展方向

当基础的五级流水线 CPU 能够正确运行后,你可以考虑以下优化和扩展,这会让你的项目更具深度和挑战性。

  1. 添加缓存 (Cache):在 CPU 和主存之间加入一级指令缓存和数据缓存,可以大幅降低访存延迟,提升性能。你需要设计 Cache 的替换策略(如 LRU)、写策略(写直达/写回)和一致性协议。
  2. 实现中断和异常:让 CPU 能够响应外部事件(如定时器、UART 接收完成)和处理内部错误(如非法指令、除零)。这需要添加中断控制器、异常处理程序入口和专用的状态寄存器。
  3. 支持乘法/除法指令:扩展 ALU 功能,实现硬件乘法器和除法器。注意它们通常是多周期的操作,需要设计好与流水线的交互,可能会引入新的数据冒险。
  4. 移植到标准指令集:将当前的自定义指令集替换为标准的、有成熟工具链支持的指令集,如RISC-V RV32I。这样你就可以使用官方的 GCC 编译器来编译 C 程序,极大地丰富测试场景。
  5. 集成片上外设:将 CPU 与 FPGA 板载的外设(如 GPIO、UART、PWM、I2C)通过总线连接,实现一个简单的 SoC。你可以编写程序来控制 LED 闪烁、读取按键、通过串口与 PC 通信。
  6. 探索更复杂的微架构:学习超标量、乱序执行等高级流水线技术,尝试在现有设计上进行修改,理解其提升性能的原理。

9. 学习路径与资源推荐

完成这个五级流水线 CPU 项目是一个重要的里程碑,但也是更深入学习硬件设计的起点。

建议的学习路径:

  1. 理解与复现:首先,彻底理解现有项目的每一行代码,确保能独立完成从仿真到上板的全部流程。
  2. 修改与调试:尝试修改一些功能,比如改变前推策略、增加一条新指令、修改分支预测策略。在这个过程中必然会引入 Bug,通过调试来加深理解。
  3. 从头设计:抛开现有代码,仅参考其架构图,尝试自己从零开始编写各个模块。这是检验你是否真正掌握的关键一步。
  4. 扩展与优化:选择上述的一个扩展方向进行深入研究并实现。

推荐资源:

  • 书籍:《计算机组成与设计:硬件/软件接口》(David Patterson & John Hennessy),《CPU 设计实战》(汪文祥、邢金璋)。
  • 在线课程:Coursera 上的 “Hardware/Software Interface”, edX 上的 “Computation Structures”。
  • 开源项目:在 GitHub 上搜索 “riscv-cpu”、“mips-cpu”、“pipeline-cpu”,有很多优秀的开源实现可供参考学习。
  • 社区:EETOP、电子工程世界等论坛的 FPGA/IC 设计板块,遇到具体问题可以在这里搜索或提问。

这个五级流水线 CPU 设计项目就像一把钥匙,为你打开了计算机体系结构和数字芯片设计的大门。它的价值不在于实现了一个多快的 CPU,而在于让你亲身体验了从指令集定义、数据通路规划、控制逻辑设计、冒险处理到最终硬件实现的完整闭环。当你看到自己编写的程序在亲手搭建的 CPU 上跑起来,并且通过逻辑分析仪观察到内部信号如预期般流动时,那种成就感是无可替代的。建议你将这个项目作为长期学习的基地,不断尝试新的想法和优化,积累的经验将会是你硬件工程师之路上最坚实的基石。

← 返回列表