简介:CPU设计与体系结构实验指导手册(教师版)面向高校计算机专业师生,依托龙芯LS-CPU-EXB系列教学实验平台,适用于《数字逻辑》《计算机组成原理》《计算机体系结构》等课程的实验教学。全书从基础的数据运算起步,逐步覆盖定点加法器、定点乘法器、寄存器堆、ALU模块、存储器实现,并深入到单周期CPU与多周期CPU的设计;课程设计部分包含静态5级流水线CPU实现与优化拓展,附录还提供MIPS指令集及CP0寄存器说明,便于按教学进度分阶段选用。资源包内为1个PDF文档,共6.09MB,目录层级清晰,实验目的、设备、任务与设计流程均有完整呈现。已有680人学习,对需要系统开展CPU实验的教师和学生具有较高参考价值,也可作为自学FPGA数字系统设计与处理器体系结构的配套教材。
1. 为什么一份 CPU 设计与体系结构实验手册值得照做
做 CPU 设计与体系结构实验最怕的不是难,而是不知道每一步该做什么、做完怎么算对。很多人拿到实验箱,第一步不是写代码,而是花大量时间在工程创建、引脚约束、触摸屏调用这些外围事情上,真正留给 CPU 设计本身的时间反而没多少。这份实验指导手册的价值,正在于它把从定点加法到五级流水线的完整路线一次性铺开:实验一到实验七对应组成原理的验证型实验,课程设计及拓展对应体系结构的综合型设计,每一步都有参考代码、仿真流程和上板验证机制,属于可以照着做、做完能验收的全流程方案。
手册基于 Xilinx Vivado 开发环境、Artix-7 系列 FPGA 实验板设计和实现,用 Verilog 完成全部 CPU 实验。适合几类人:正在做计算机组成原理或体系结构课设的本科生、需要指导实验的助教和教师,以及想在自己板子上复现一套 MIPS 教学 CPU 的爱好者。下面按我的使用经验,讲清楚这套实验系统怎么跑通、哪些模块可以直接抄、哪些地方最容易翻车。
2. 实验平台与工程搭建:先从实验箱和 Vivado 环境讲起
2.1 实验箱的资源构成与接口约定
LS-CPU-EXB-002 实验箱的核心是一块 FPGA 实验板,配套 Xilinx 下载线、串口线(含 USB 转串口)和电源适配器。和普通开发板最大的区别是它实现了 LCD 触摸屏的硬件驱动,不需要处理器核就能直接使用显示和输入功能,并且提供了封装好的调用接口。这意味着上板验证时不必依赖 64 个拨码开关和 32 个 LED 来观察 32 位运算结果,而是通过触摸屏输入加数、显示结果,大大降低了外设设计的负担。
实验箱在配套资源里把 lcd_module 做成了黑盒网表文件,学生只需要按照接口调用,不用关心 LCD 时序的具体实现。我一般会提醒学生先搞清楚两层结构:顶层模块负责整个实验的对外交互(触摸屏输入输出、时钟、复位),功能模块才是实验真正要设计的对象。以定点加法为例,顶层模块 adder_display 调用两个子模块,一个是自己写的加法器 adder,另一个是封装好的 lcd_module。这种“外围展示模块 + 功能模块”的分层方式,后面几乎所有实验都可以沿用。
2.2 Vivado 工程创建的三个关键动作
新建工程时最容易踩坑的是器件型号选错。手册指定的是 Artix-7 系列、fbg676 封装下的 xc7a200tfbg676-2,这个型号和实验板上的 FPGA 芯片一一对应。如果选错,综合布局布线可能通过,但下载到板子上必然出问题,属于硬件平台的“玄学”问题,其实根因就是型号不一致。
添加源文件方面,Verilog 代码可以提前在其他编辑器里写好再添加,也可以在工程里新建。手册的参考做法是点击 Add Sources,选择 Add or create design sources,按文件或文件夹添加。工程管理区会显示模块间的层次关系,如果 lcd_module 前面带问号,说明该网表文件还没添加。这时候需要单独添加 lcd_module.dcp,注意 Vivado 2012.3 版本要先解压 dcp 文件再添加,我用的更新版本 Vivado 直接添加也能识别。这一步是很多新手第一次接触黑盒网表,容易忘记,导致综合时报找不到模块。
2.3 引脚约束文件怎么读怎么改
约束文件(.xdc)的作用是把顶层模块的端口绑定到 FPGA 板上的物理引脚。手册给出的 adder.xdc 内容看起来很长,其实按功能划分就三部分:时钟与复位、拨码开关与 LED、LCD 触摸屏接口。
set_property PACKAGE_PIN AC19 [get_ports clk] set_property PACKAGE_PIN A3 [get_ports led_cout] set_property PACKAGE_PIN Y3 [get_ports resetn] set_property PACKAGE_PIN AC21 [get_ports input_sel] set_property PACKAGE_PIN Y6 [get_ports sw_cin] set_property IOSTANDARD LVCMOS33 [get_ports clk] set_property IOSTANDARD LVCMOS33 [get_ports resetn] # LCD 触摸屏接口 set_property PACKAGE_PIN J25 [get_ports lcd_rst] set_property PACKAGE_PIN H18 [get_ports lcd_cs] set_property PACKAGE_PIN K16 [get_ports lcd_rs]PACKAGE_PIN 决定信号接到 FPGA 的哪个物理引脚,IOSTANDARD 指明电平标准。实验板上几乎全部 IO 都使用 LVCMOS33,也就是 3.3V 的 LVCMOS 电平。约束文件的修改原则是:自己设计的模块端口名必须和 xdc 里的 get_ports 名字完全一致,否则约束不生效。一般我会把约束文件里的端口名做成顶层模块的端口名对照表,检查一遍再综合,能省掉很多上板后“没反应”的排查时间。
3. 定点运算实验:从加法器到乘法器的数据通路基础
3.1 加法器参考实现与“+”号背后的设计选型
实验一是整个实验体系的入门,核心任务是让使用者熟悉实验箱和 Vivado 的设计流程。功能模块 adder.v 是典型的基础运算模块,代码如下:
`timescale 1ns / 1ps module adder( input[31:0] operand1, input[31:0] operand2, input cin, output [31:0] result, output cout ); assign {cout,result} = operand1 + operand2 + cin; endmodule这段代码有两点值得注意。第一,直接使用“+”运算符,综合工具会自动调用库里的加法器 IP,相比手动用全加器搭建,面积和时序表现通常更优。第二,输出端用拼接符把进位 cout 和 32 位结果 result 合成 33 位,语法简洁,也避免进位丢失。但在某些教学场景下,教师会要求用全加器逐位搭建以理解进位链原理。我一般建议先按手册默认的“+”实现跑通流程,再在理解原理后手动改写为行波进位结构对比综合报告,这比直接上难度更容易接受。
3.2 testbench 怎么搭才能快速验证正确性
手册强调“一个比较完备的 testbench 就是产生输入激励,送入到要测试的功能模块里,然后读出执行结果,与预期比较”,但在入门实验里只产生输入激励就足够了。testbench 的核心代码有两点值得抄:
`timescale 1ns / 1ps module testbench; reg [31:0] operand1; reg [31:0] operand2; reg cin; wire [31:0] result; wire cout; adder uut ( .operand1(operand1), .operand2(operand2), .cin(cin), .result(result), .cout(cout) ); initial begin operand1 = 0; operand2 = 0; cin = 0; #100; end always #10 operand1 = $random; always #10 operand2 = $random; always #10 cin = {$random} % 2; endmodule随机激励设计是这段代码的精华。每 10ns 给两个操作数赋一个随机 32 位数,进位输入通过拼接符 {$random} 保证非负后再取模 2,得到 0 或 1。由于加法器没有时序逻辑,随机测试配合波形观察,覆盖几百组数据后功能基本可以认为是正确的。
仿真时有两个操作习惯能显著提升效率。一个是把要观察的信号添加到波形窗口后,必须点击 Relaunch 重跑才能看到新信号数据;另一个是 32 位二进制数据在波形窗口里完全没法看,选中信号后右键 Radix 改成十六进制,一眼就能核对结果。手册里举的例子是 b897be71 + 42f24186 + 1 = fb89fff7,这类手工核对非常适合作为验收证据。
3.3 定点乘法:移位与累加的实现思路
实验二从加法进阶到乘法,复杂度提升的关键在于理解“移位加”的运算本质。乘法器在数字电路里有多种实现策略:最简单的迭代乘法器通过移位和累加完成 n 次部分积求和,面积小但需要多个周期;组合逻辑乘法器并行产生所有部分积再用加法树压缩,速度快但资源消耗大。
在组成原理课程层面,手册并没有要求实现 Booth 乘法器这类高级结构,重点是让学习者理解乘法的数据通路:被乘数左移、乘数右移、部分积累加、符号处理。这一步为后面积累 ALU 和 CPU 实现奠定基础,因为乘除指令在 MIPS 指令集中通常使用独立的多周期部件而非组合逻辑直接完成。我第一次做这个实验时总想着把乘法做成单周期并行结构,结果资源占用和时序约束都很难看,后来按迭代结构实现,综合报告立刻正常了。
4. 核心部件实现:寄存器堆、ALU 与存储器的模块化设计
4.1 寄存器堆:读写端口的设计约束
实验三实现 MIPS 寄存器堆,这是 CPU 数据通路里复用频率最高的部件。在 MIPS 架构中,寄存器堆通常需要两个读端口和一个写端口,这是双指令发射或者流水线结构能并行读两个操作数的硬件基础。手册实验要求理解通用寄存器、状态寄存器等不同类型寄存器的作用,并在硬件中构建它们。
寄存器堆的端口设计一般遵循以下约定:
| 信号名 | 方向 | 位宽 | 功能说明 |
|---|---|---|---|
| clk | input | 1 | 时钟信号,上升沿写有效 |
| we | input | 1 | 写使能,为高时允许写入 |
| raddr1 | input | 5 | 读端口 1 地址,可读 32 个寄存器 |
| raddr2 | input | 5 | 读端口 2 地址 |
| waddr | input | 5 | 写端口地址 |
| wdata | input | 32 | 写数据 |
| rdata1 | output | 32 | 读端口 1 输出 |
| rdata2 | output | 32 | 读端口 2 输出 |
实现时需要注意两点:寄存器堆要么用 always 块配合 case 语句实现,要么直接调用 Xilinx 的分布式 RAM 原语。教学实验通常要求手写 Verilog 以理解内部结构,而综合工具会把行为描述自动映射到 FPGA 的 Block RAM 或 LUTRAM 上。另外,地址 0 号寄存器在 MIPS 约定中恒为 0,写入无效,这一点会在后续指令执行中产生微妙影响,比如冒险处理时如果忽略了零寄存器恒零的特性,流水线转发逻辑会出错。
4.2 ALU 模块:功能码与运算控制的划分
实验四实现 ALU,这是 CPU 的运算核心。ALU 的接口设计直接决定后续单周期 CPU 的连线方式,一般包括两个 32 位操作数输入、一个 4 位或 5 位功能选择信号、一个 32 位结果输出,以及零标志、进位、溢出等状态输出。
功能选择信号建议用局部参数定义而非魔法数字,可读性和可维护性更好。手册虽然没有给出完整 ALU 代码,但实验要求涵盖算术运算(加减乘除)、逻辑运算(与或非异或)、移位运算(逻辑左移右移、算术右移)和比较运算(小于比较、相等比较)。这里最容易犯的错误是把所有运算都用行为级描述直接写在一个 always 块里,导致综合出优先级编码器结构。实际上 ALU 更适合用组合逻辑加多路选择器实现,把每个运算独立描述,最后通过 case 选择输出,这样综合后的电路结构清晰、时序路径也更短。
4.3 存储器实现:读时序与写时序的差异
实验五实现存储器模型,通常包括指令存储器和数据存储器两部分。教学实验里存储器可以直接用 Verilog 数组加 always 块构建行为模型,但这与真实 SRAM 的时序行为有差别:真实 SRAM 的读操作是电平触发、有地址建立时间和读延迟,而行为模型如果不加时钟控制,很容易做成组合逻辑读,导致后续 CPU 时序错乱。
手册里存储器实验的目标是理解 CPU 与存储器之间的交互关系,包括大端小端、地址对齐和读写使能控制。我通常的做法是:指令存储器做成同步读,保证取指阶段在时钟边沿之后拿到稳定指令;数据存储器做成同步写、组合读或同步读取决于 CPU 设计要求。最简单可靠的方案是让存储器的读写都同步于时钟,读地址在时钟上升沿锁存,输出在下一个时钟沿稳定。这样虽然多一个周期的延迟,但对单周期 CPU 的时序收敛非常友好。
5. 从单周期到多周期再到流水线:CPU 设计路线的核心矛盾
5.1 单周期 CPU:所有指令一个周期完成的设计约束
实验六实现单周期 CPU,这是整个手册承上启下的关键。单周期 CPU 的特点是指令周期固定为一个时钟周期,所有指令无论复杂程度如何,都在一个周期内完成取指、译码、执行、访存和写回。
这会带来两个明显问题:一是时钟周期必须按最慢指令的延迟来设定,导致整体性能被拖累;二是数据通路里所有部件只需要一套控制逻辑按指令类型输出不同控制信号,设计相对简单。单周期 CPU 的指令集范围见附录 A1,通常包含 MIPS 的 lw、sw、beq、add、sub、and、or、slt 等基础指令。实现时要把指令译码产生的控制信号逐条列成真值表,我记得当时就是对着指令真值表逐个核对控制信号,才算真正理解了每条指令在数据通路上的流动过程。
一个典型的翻车点是立即数扩展的处理。MIPS 的 beq 分支指令需要把 16 位立即数左移两位再符号扩展,而 lw/sw 只需要符号扩展到 32 位。如果复用同一个扩展模块而不区分操作类型,访存指令和分支指令的地址计算就会出错,而且这种错误仿真时偶尔能通过某些数据,上板后行为不稳定,属于比较隐蔽的 bug。
5.2 多周期 CPU:控制信号的分步拆解
实验七从单周期演进到多周期,目的在于解决单周期时钟频率受最慢指令拖累的问题。多周期 CPU 把每条指令的执行拆分成多个步骤,每个时钟周期只完成一个微操作,不同指令所需的周期数不同,控制逻辑从组合逻辑转变为有限状态机。
以 lw 指令为例,多周期执行通常分为五步:取指、译码并读寄存器、计算地址、读数据存储器、写回寄存器。每一步处于不同的状态,状态机的状态转换由指令类型决定。这意味着控制单元不再是一张组合逻辑真值表,而是一组状态转移逻辑。手册附录 A2 列出了多周期 CPU 相对单周期新增的指令,主要包括 jal、jr 等跳转类指令和乘除相关的特殊指令。
多周期设计的难点在于状态机的编写方式。如果使用一段式状态机,把所有状态转换和输出逻辑写在一个 always 块里,代码虽然短但可读性极差,后续调试很容易迷失。建议使用三段式:第一段描述状态寄存器的次态输出,第二段用组合逻辑描述状态转移条件,第三段描述各状态下的控制信号输出。这样控制信号和状态转移各自独立,出问题时能快速定位是哪一段逻辑计算错误。
5.3 流水线 CPU 与冒险处理:课程设计的验收重点
课程设计要求实现静态 5 级流水线 CPU,这是从“理解 CPU 工作原理”跨入“优化 CPU 系统”的分水岭。流水线把指令执行分为取指、译码、执行、访存、写回五个阶段,理论上吞吐率提升接近 5 倍,但实际上结构冒险、数据冒险和控制冒险会显著拉低实际性能。
手册提供的思路是静态流水线,也就是通过编译器或者程序员来避免部分冒险,硬件上只做必要的停顿和转发。数据冒险的典型处理是前向通路:在 EX 阶段计算结果出来后,直接把结果旁路到后续指令的 ALU 输入,而不是等待写回寄存器堆。控制冒险则需要分支预测或流水线冲刷,静态预测通常采用“不跳转”策略,预测错误时清除已进入流水线的错误指令。
在附录 A3 中可以看到流水线 CPU 新增的指令范围,比如支持更多逻辑运算和数据处理指令。CP0 寄存器组在附录 B 中单独列出,用于实现异常处理和处理器状态控制。我做过一次课程设计的答辩,被问到最多的问题就是“流水线冒险怎么处理的”,因此建议在提交方案时把三张图准备好:流水线数据通路图、前向通路连接图、控制冒险处理流程图。这三张图一摆,验收老师基本就能确认你是真的理解了流水线机制,而不是只改了别人的代码。
6. 常见问题排查:五个最容易翻车的细节及对策
6.1 现象:仿真波形乱七八糟,随机数据对不上
原因:testbench 里多个 always 块同时驱动同一个信号,或者 initial 块里的初始化赋值和 always 块的连续赋值冲突。
解决:检查 testbench 中每个 reg 类型变量是否只有一个驱动源。如果既在 initial 里赋值又在 always 里赋值,仿真相当于多驱动器同时作用,数据竞争不可避免。统一改成 only always 随机激励,或只在 initial 里赋值一次后靠 always 产生波形。
6.2 现象:工程综合时报找不到 lcd_module 模块
原因:dcp 网表文件没有正确添加,或者添加的网表版本与 Vivado 版本不匹配。
解决:在工程管理区确认 lcd_module 的状态图标不是问号。如果是问号,重新 Add Sources 添加 .dcp 文件。老版本 Vivado 把 dcp 当成普通文件添加后,还需要在 Sources 窗口里右键设置属性为 Design Sources。通常的排查顺序是先看文件有没有加进来,再看文件类型是否正确。
6.3 现象:上板后触摸屏无显示或不响应
原因:引脚约束没生效,或者 LCD 相关的 IOSTANDARD 缺失。
解决:检查顶层模块的端口名和 xdc 文件里的 get_ports 是否一字不差,注意大小写和数组声明方式。LCD 数据线是 16 位总线,约束文件里必须写成 {lcd_data_io[0]} 这种展开形式,不能只约束其中一个。其次检查 IOSTANDARD 是否全部设置为 LVCMOS33,漏掉任何一个都会导致 IO 电平不匹配。
6.4 现象:仿真时信号对不上,但逻辑看起来没问题
原因:波形窗口显示的是二进制,32 位数据无法直观核对。
解决:这是我写过无数次要提醒队友的“血泪经验”。仿真波形默认用二进制显示,32 位的数据一眼根本分不清对错。把要观察的信号全部改成十六进制显示,加法、存储器的地址和数据马上就能人工验算。另外新添加的信号必须 Relaunch 重启仿真才能出现波形。
6.5 现象:多周期 CPU 状态机跑飞,指令执行到一半卡住
原因:状态机的默认状态没有覆盖非法状态,或者状态转换条件里用了阻塞赋值和非阻塞赋值混用。
解决:检查 always 块里是否统一使用非阻塞赋值 <=。状态机里的次态逻辑用组合逻辑块(阻塞赋值)也没有问题,但要保证状态寄存器的更新用非阻塞赋值。建议给状态机增加一个复位状态 IDLE,任何非法状态都跳回 IDLE,避免状态机锁死在未定义状态。调试时在波形里把 state 信号加进来,单步跑几条指令看状态是否按预期切换。
7. 上板验证与答辩验收:从功能正确到交付一份完整方案
7.1 验收流程的四个层次
一套实验从写完到验收通过,我建议按以下四个层次检查。第一层是功能仿真,确保功能模块自己的逻辑正确;第二层是集成仿真,把外围展示模块和功能模块一起仿真,确认触摸屏输入和显示逻辑衔接一致;第三层是上板验证,下载到 FPGA 后通过实际输入输出确认板上行为;第四层是异常场景测试,比如加法器的进位链在最大值边界(0xffffffff + 1)时是否产生正确的进位输出。
上板验证时有个细节容易被忽略:下载完成后要手动按复位键让系统进入初始状态。很多实验箱的 FPGA 配置完成后不会自动复位,如果不按复位,触摸屏可能处于未知状态导致“屏幕亮但没有反应”。这类问题看起来像是硬件坏了,其实只是初始化顺序不对。
7.2 答辩汇报的素材组织技巧
答辩或实验检查时,不要上来就展示代码,而应按照“设计思路 → 结构框图 → 仿真结果 → 上板演示”的顺序组织。我通常会准备一组对照材料:设计框图一张、模块层次图一张、仿真波形图两到三张(覆盖正常数据和边界数据)、上板结果照片一张。实验报告里要把这几项落实为图片加文字说明,其中仿真波形图必须标注关键数据的十六进制值,让老师一眼确认结果正确。
另外一个讲不清楚的问题就是“生成的 CPU 性能怎么样”。如果被问到流水线的加速比,不要只答理论值 5 倍。实际上由于冒险停顿、分支冲刷和 load-use 停顿,静态流水线的实际加速比通常在 1.5 到 2.5 之间。可以准备一个简单的指令序列统计:汇编代码里相邻指令的依赖关系越多,停顿越多,CPI 越接近 2。如果答辩时能说出“我这个流水线在特定测试程序上 CPI 约为某个具体值”,比空泛地讲流水线原理更能证明你真的做了系统实现。
7.3 从这份手册里能拿走什么
完整做完这套实验,你手上的东西包括:一个经过仿真验证的定点加法模块、一个乘法模块、一个双读单写端口寄存器堆、一个支持基本 MIPS 指令的 ALU、一个同步存储器模型,以及单周期、多周期、静态五级流水线三套 CPU 实现。把它们组合起来就是一份完整的组成原理课设方案,再配合附录里的 MIPS 指令集和 CP0 寄存器说明,答辩材料的基本盘就已经到位了。
从那次课程设计之后,我每次做完实验都会强制自己走一遍完整流程:新建工程 → 添加源文件 → 约束引脚 → 功能仿真 → 上板验证 → 截图留证。这套流程看起来繁琐,但它能防止在最关键的上板环节才发现仿真时忽略的端口名错误或引脚约束遗漏。希望这份实验手册的解读能帮你在做 CPU 实验和课设时少走弯路。
本文还有配套的精品资源,点击获取