简介:面向哈尔滨工程大学计算机组成原理课程设计场景,压缩包提供基于Quartus完成的16条指令模型机完整工程与实验报告。包内共340个文件,既有qpf/qsf/bdf工程原理图、vwf仿真波形,也有v/vhd/tdf硬件描述源码、doc/txt报告等,7.06MB的体积便于整包下载;工程按顶层、控制器、ALU等模块拆分,便于对照数据通路、控制器和运算器的设计实现。已有345人学习下载,适合正在做同类课程设计或希望掌握Quartus建模流程的同学参考。读者可直接打开工程查看原理图与源码,配合实验报告理解16条指令的译码、执行与仿真验证过程,也可借鉴其模块划分与报告写作思路。 每次到这个时间,课程群里就会被同一类问题刷屏:“模型机设计课上用的16条指令到底怎么定?”“微程序写进去跑不起来怎么办?”说实话,哈尔滨工程大学的计算机组成原理课程设计里,“模型机设计(16条指令)”是一道非常经典的综合题。它不要求你做出能跑操作系统的CPU,但要求你把“指令系统、数据通路、控制器”这一整条链路真正贯通。这篇文章就围绕这个课设,把我当时从拿到题目到验收通过的全过程、踩过的坑、以及后来帮同学排查时的经验整理出来,给正在做这个题目的你一个可复现的参考思路。
1. 拿到“模型机设计”题,先搞明白课设真正想考什么
1.1 一套模型机到底由哪几块拼起来
很多同学一上来就急着查“16条指令是哪16条”,这种思路其实反了。模型机设计这个课设真正想考的不是背诵指令表,而是下面这几件事:
- 指令系统的设计:操作码怎么分配,寄存器字段怎么安排,寻址方式怎么表达。
- 数据通路的连接:CPU内部的寄存器、ALU、存储器、总线之间怎么连,数据从哪儿到哪儿。
- 控制器的实现:每条指令分几步执行,每一步产生什么控制信号。
- 验证与调试:能不能用一条条指令的实际运行结果证明你的设计正确。
把这四件事翻译成物理模块,就是一台模型机的标准配置:程序计数器PC、指令寄存器IR、通用寄存器组、算术逻辑单元ALU、内存RAM、地址寄存器MAR、数据寄存器MDR,以及产生控制信号的控制器。这个规模说大不大,但麻雀虽小五脏俱全。
1.2 哈工程实验环境下的常见实现路径
哈工程这边的课设实现方式,不同老师带的不同班会有差别。我们当时用的是实验室里的硬件实验箱,整机采用“排线连线 + 微程序控制器”的方式,微程序存放在RAM里,通过拨码开关或上位机写入。也有同学用Verilog把模型机写到FPGA里跑仿真,两种路线各有各的难点。
如果你用的是实验箱,最痛苦的不是逻辑设计,而是连线和时序。一根线插错、一个三态门没控制好,都是问题。如果你用Verilog,逻辑好改,但仿真波形里的组合竞争、寄存器写时序同样容易翻车。这篇文章我会以“实验箱 + 微程序控制器”为主线来讲,因为这是哈工程最常见的形式;如果你做的是FPGA版本,核心思想完全一样,只是排查手段从示波器变成了波形图。
2. 16条指令的指令集设计:格式、分类和取舍逻辑
2.1 指令格式怎么定才不会被自己绕晕
模型机毕竟是课设,不需要像真实CPU那样搞复杂变长指令。我强烈建议你做定长16位指令字,并且把字段位置固定下来。这是我在设计时最终确定的格式:
OP(4位) | DR(4位) | SR(4位) | ADDR(4位)
各位字段的含义:
- OP:操作码,4位,最多支持16条指令,题目要求16条,刚好用满。
- DR:目的寄存器编号,4位,可以访问16个寄存器。
- SR:源寄存器编号,4位。
- ADDR:地址或立即数字段,4位。
你可能会问,4位地址只能访问16个内存单元,够用吗?对于模型机验证来说够了。我们当时配套的存储阵列就是16个单元,跑一个演示程序绰绰有余。如果指导老师要求更大的存储空间,可以把指令字改成OP(4) + DR(4) + ADDR(8),牺牲SR字段来换地址空间,这是后话。
2.2 16条指令的分类与最终清单
16个操作码刚好占满4位二进制。我当时把指令分成了四类,这样无论是写微程序还是画状态图都清晰很多:
| 类别 | 操作码 | 指令 | 功能 | 格式 |
|---|---|---|---|---|
| 控制类 | 0000 | HALT | 停机 | 无操作数 |
| 数据传送类 | 0001 | MOV Rd, Rs | 寄存器间传送 | DR, SR |
| 数据传送类 | 0010 | LDR Rd, [Addr] | 从内存读入寄存器 | DR, ADDR |
| 数据传送类 | 0011 | STR [Addr], Rs | 从寄存器写入内存 | SR, ADDR |
| 运算类 | 0100 | ADD Rd, Rs | 加法 | DR, SR |
| 运算类 | 0101 | SUB Rd, Rs | 减法 | DR, SR |
| 运算类 | 0110 | AND Rd, Rs | 逻辑与 | DR, SR |
| 运算类 | 0111 | OR Rd, Rs | 逻辑或 | DR, SR |
| 运算类 | 1000 | XOR Rd, Rs | 逻辑异或 | DR, SR |
| 运算类 | 1001 | NOT Rd | 取反 | DR |
| 运算类 | 1010 | INC Rd | 加一 | DR |
| 跳转类 | 1011 | JMP Addr | 无条件跳转 | ADDR |
| 跳转类 | 1100 | JZ Addr | 零标志为1时跳转 | ADDR |
| 跳转类 | 1101 | JC Addr | 进位标志为1时跳转 | ADDR |
| 输入输出类 | 1110 | IN Rd | 读入开关值到寄存器 | DR |
| 输入输出类 | 1111 | OUT Rd | 寄存器值输出到指示灯 | DR |
这套指令集有几个刻意为之的取舍。第一,我没有单独设NOP空操作指令,因为MOV R0, R0完全可以充当空操作,省出一个操作码给输入输出。第二,LDR和STR都显式带上地址字段,而不是用寄存器间接寻址,这样取指后可以直接把指令中的ADDR字段送上地址总线,控制器的节拍会少一拍,写起来更省心。第三,JZ/JC这种条件跳转在模型机里非常经典,它要求控制器能够读取ALU产生的零标志Z和进位标志C,这是很多同学一开始容易忽略的。
2.3 为什么一定要把“执行周期不同”这件事提前想清楚
RISC风格里大家都在谈单周期或多周期,模型机几乎默认采用多周期方式。这就意味着不是每条指令都走同样多的节拍。按我的设计,各类指令的执行周期差异如下:
- 取指周期是公共的,每条指令都必须先经过:送PC到MAR、读内存、PC自增、指令装入IR。
- MOV、NOT、INC这类纯寄存器操作,在取指之后一般只需要1到2个周期。
- LDR、STR这类访问内存的指令,需要额外做MAR送地址、读或写内存,周期会长一点。
- JMP/JZ/JC这类跳转指令,需要把目标地址回写到PC,最关键的是条件跳转还要判断标志位。
这个差异直接决定了微程序的长度和微地址的分配。如果你一开始没做好规划,后面微程序跳转关系会乱成一团。我的习惯是先在纸上列出一个“指令周期表”,每条指令占几行、每行做什么,写清楚再动手编微程序。
3. 数据通路与微操作序列:从“能画框图”到“能跑起来”
3.1 单总线还是多总线:我选单总线的理由
模型机数据通路最基础的问题是总线结构。我记得当时有两种选择:单总线和双总线。单总线结构下,所有部件共享一条数据总线,同一时刻只能有一个源向总线发送数据;双总线则允许两个源同时往ALU的A端和B端送数。
我最终选了单总线。原因很现实:单总线控制信号少,连线少,调试时观察总线上的值非常直观。代价是取指和运算需要多拆几个节拍,性能差一点,但课设完全不在乎这一点性能,稳定和好查错才是第一位的。如果你用FPGA,单总线也能有效避免“到处都能写数据”的竞争问题。
单总线模型机里,每个能够向总线输出数据的部件前面都必须加三态门,由各自的控制信号决定是否驱动总线。对应关系大概是:
| 部件 | 输出控制信号 | 说明 |
|---|---|---|
| PC | PC_OUT | 送PC值到总线 |
| MDR | MDR_OUT | 送MDR内容到总线 |
| ALU的结果寄存器 | ALU_OUT | 送运算结果到总线 |
| IR字段提取器 | IR_OUT | 送指令中的地址字段到总线 |
| 寄存器组 | R_OUT | 根据SR选择寄存器并输出 |
“同一时刻只能有一个输出控制信号有效”,这句话我后来在排错时反复验证过很多次。谁违反了,总线上就打架,数据全是乱的,这属于模型机调试里最经典的坑之一。
3.2 取指周期的微操作序列
不管控制器是微程序还是硬布线,取指周期都长一个样。我习惯用箭头描述微操作,每一行表示一个节拍内同时发生的动作:
T1: PC -> MAR; PC + 1 -> PC T2: RAM -> MDR T3: MDR -> IR说明一下:PC加1和PC送MAR放在同一拍,是因为这两个动作互不冲突;RAM读出的数据出现在T2,经过一个存取周期后才能稳定被MDR捕获;T3把指令内容锁存进IR。这里有个容易被忽略的小细节:模型机的PC自增必须在取指早期完成,否则跳转指令执行时,PC其实是“下一条指令的地址”,如果跳转判断失败,PC保持这个值才能继续执行下一条。
3.3 用三条典型指令拆微操作序列
只说框架难免空,我拿ADD、STR、JZ三条指令举例,你感受一下微操作序列长什么样。
ADD Rd, Rs的执行序列:
T4: SR -> ALU_B T5: DR -> ALU_A; ALU_OP = ADD T6: ALU_OUT -> DR; 更新Z标志和C标志ADD的关键在于,它取指时已经把Rd和Rs的编号从IR里提取出来了。T4先读源寄存器,T5再读目的寄存器到ALU的A端,同时让ALU执行加法,T6把结果写回目的寄存器并更新标志位。为什么不能T4同时送两个寄存器到单总线?因为单总线一次只允许一个源出现,这也是单总线的代价。
STR [Addr], Rs的执行序列:
T4: IR的Addr字段 -> MAR T5: SR -> MDR T6: RAM写使能有效, MDR -> RAM这里有个先后顺序必须死记:**先送地址到MAR,再送数据到MDR,最后才发写使能。**我刚做的时候把T5和T4的顺序写反了,结果每次执行STR,数据总是写进上一次操作留下的旧地址里,排查了将近半小时。这个坑太典型,后面专门讲。
JZ Addr的执行序列:
T4: 判断Z标志 T5: 若Z=1: IR的Addr字段 -> PC; 控制器转移到下一条 T6: 若Z=0: PC保持上一步取指后的值条件跳转最微妙的是“要不要修改PC”这个动作。无条件跳转JMP无论在什么情况下都要在T5把Addr送到PC;JZ则必须先看Z标志。控制器里需要设计一个条件选择信号,我习惯叫它COND:当COND为0时强制条件成立,当COND为1时看Z,当COND为2时看C。这样微程序可以复用同一段跳转流程,只是COND字段不同。
3.4 微程序控制器怎么组织:字段直接控制
最后说一下控制器本身。我用的微程序控制器,本质上就是一块专用的“微指令存储RAM”,每条微指令是一个固定宽度二进制数,每一位或每一组字段控制一个操作。
我的微指令字段大致这样划分:
- 源选择字段:控制PC_OUT、MDR_OUT、ALU_OUT、IR_OUT、R_OUT等。
- 目的写使能字段:控制PC_LD、IR_LD、MAR_LD、MDR_LD、寄存器写使能等。
- ALU操作字段:选择加法、减法、与、或、异或、取反、加一。
- 内存读写字段:控制RAM的读/写。
- 微地址迁移字段:指出下一条微指令地址,以及是否根据Z/C标志跳转。
使用微程序而不是硬布线的原因很简单:微程序把“每条指令对应一串微操作”这件事变得高度可定制。你发现ADD时序不对,只需要改ROM里ADD对应区域的微指令内容,而不需要重新搭一堆门电路。这个设计思路和真实CPU里的微码控制也是一脉相承的,所以课设里弄懂它,价值和意义不止于应付验收。
4. 微程序跑飞了怎么办:一条完整的排错链路
4.1 第一条指令就翻车:现象、定位、修复
我印象最深的一次排错发生在第一次往实验箱里写微程序。当时我只写了HALT和MOV两条指令,想先把基本链路跑通。结果一上电,指示灯乱跳,IR里读出来的指令根本不是我预置的那条。
排查顺序是这样的:
- 先看总线上跑的值。实验箱上一般有总线指示灯,把单节拍模式打开,一拍一拍看总线数据。
- 第一拍PC送MAR,地址正确。
- 第二拍RAM读数据,发现RAM输出端有数据变化。
- 第三拍MDR送IR,问题出现了:IR里的值和RAM输出对不上。
最终定位下来说是MDR的装载时序问题:RAM数据在T2已经稳定,但MDR的装载脉冲在T3才有效,中间数据早就变了。换句话说,我在写微程序时把“MDR锁存”和“MDR输出到IR”混在了同一拍。正确做法是在T2结束前把RAM数据锁存进MDR,T3再执行“MDR -> IR”。调整微程序后,取指链路立刻正常。
这个案例给我的教训是:模型机调试不要靠猜,一定要打开单节拍模式,逐拍观察总线。实验箱上的LED灯就是最廉价的“示波器”。
4.2 从冒烟测试到覆盖测试:指令测试矩阵
指令全部写完以后,不要上来就跑复杂的演示程序。我当时总结了一个测试顺序,非常管用:
- 先测不需要访问内存的纯寄存器指令:MOV、ADD、SUB、AND、OR、XOR、NOT、INC。
- 再测访问内存的指令LDR和STR。
- 接着测输入输出IN和OUT。
- 最后测跳转指令HALT、JMP、JZ、JC。
每测一条指令,都做两件事:记录初始寄存器值、看运行后的寄存器和内存值、和预期值比对。手工比对很繁琐,但模型机就16条指令,用不了多少时间。
我习惯把结果整理成一张测试矩阵,大致长这样:
| 测试指令 | 初始条件 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|
| MOV R1, R2 | R2=5 | R1=5 | R1=5 | 通过 |
| ADD R3, R4 | R3=1, R4=2 | R3=3 | R3=3 | 通过 |
| STR [3], R1 | R1=5, 内存[3]=0 | 内存[3]=5 | 内存[3]=0 | 失败 |
这个表格随便做,但记录的每一行都有价值。验收的时候老师问“你测过哪些情况”,你直接把这个矩阵递过去,比你空口说“都测了”有说服力得多。
4.3 三个高频坑,几乎每届都会有人踩
第一个坑是总线竞争。表现是运行同样的指令,结果时好时坏,随机出错。原因是某一个节拍里同时打开了两个输出控制信号,比如ALU_OUT和R_OUT同时有效,两个部件往一根总线上灌数据。修复方法很简单:把每条微指令的所有输出控制信号列个清单,检查同一拍里是否出现两个1。
第二个坑是写使能时序。寄存器写使能和ALU结果到达的时间对不上,会出现写进去的是旧数据。这个问题的排查要点是看写脉冲和数据到达的先后关系。数据必须先稳定,写脉冲再有效,而且写脉冲宽度要足够,否则寄存器可能只捕获到一半数据。
第三个坑是条件跳转后PC没变化。表现是JZ该跳没跳,或者不该跳却跳了。原因通常是控制器里没有把“条件成立”和“PC装载”两个条件合在一起。我的办法是把PC_LD信号设计成:PC_LD = 控存跳转条件成立 AND 当前跳转指令条件满足。检查这一步,需要先确认Z标志有没有生成了,再看COND字段有没有送进控制器。标志位生成慢、或者标志位在ALU运算后没有更新,也是常见原因。
4.4 实验箱上机与Verilog仿真的差异
如果你用的是Verilog实现,排错手段比实验箱更舒服。ModelSim或Vivado的波形图可以看任意一根内部信号,所以遇到问题先定位是哪一拍出错,再往回追数据来源。
但Verilog也有自己的坑:仿真里信号都是理想时序,不会有实验箱上那种电平竞争和接触不良的问题,所以仿真通过不代表上板能过。我见过很多同学的FPGA版本在仿真里一切正常,烧到板子上LED就是不亮,后来发现是时钟没有做同步处理,或者复位信号没有释放。这部分建议你在做Verilog版本时,多注意时钟域、异步复位和按键消抖,模型机虽然简单,这些基本功一样不能含糊。
5. 验收准备与文档:课设分数不只看通电那一下
5.1 演示脚本要提前演练
验收的时候,大概率会让你现场跑一遍程序。不要现场翻手册,提前准备一个两分钟演示脚本。我当时准备的程序是:
- 用IN从开关读入两个数到R1、R2。
- 用ADD算和。
- 用OUT把结果显示到LED灯。
- 用一个循环例子演示JMP和JZ,比如从某个内存地址累加,加到0跳出。
这个脚本覆盖了输入、运算、输出、跳转四类指令,演示时长刚好,老师看了也觉得思路完整。演示程序不要贪多,关键是中间每一步能讲清楚“现在执行到哪条指令、寄存器里的值为什么变了”。能在演示的同时指着实验箱说清楚这一步,比默默跑完整个程序要高一个层次。
5.2 文档里最少要画的三张图
哈工程对课设报告的格式要求通常比较严,但不管格式怎么变,下面三张图必须有:
第一张是指令格式图,标明4位操作码、4位DR、4位SR、4位ADDR。旁边配上16条指令的编码表。
第二张是数据通路图,把所有寄存器的输入端、输出端、控制信号、三态门画全。这张图是你设计的地图,也是老师判断你懂不懂模型机的重要依据。画的时候记得标清楚每根线的方向,尤其是MAR和MDR的输入来源。
第三张是控制器状态转移图或微程序流程图。我建议画成“取指公共部分 + 每条指令的执行子流程”的形式,一眼就能看出每条指令分几步。如果条件跳转有多个分支,把分支条件标在箭头上。
这三张图齐全,报告的可信度就会有保障。反过来,如果文档里连数据通路都没画,运行结果再漂亮,老师也可能认为你就是别人代做的。
5.3 老师最爱问的几个“防御性提问”
验收最怕的不是机器跑不出来,而是老师问设计思路时答不上来。下面几个问题我几乎每次帮同学模拟验收都会问,你可以提前想好答案:
- “为什么用微程序而不用硬布线?”答:微程序修改方便,适合教学验证;硬布线速度快但设计复杂度高。
- “如果让你扩展到32条指令,哪些地方要改?”答:操作码从4位扩到5位,控存容量需要增加,指令格式可能需要加长或变长,其余数据通路不用大改。
- “单总线有什么缺点?”答:同一时间只能传输一个数据,所以运算和访存要拆成多个节拍,性能较低。
- “为什么STR要先送地址再送数据?”答:MAR地址先行可以避免写使能有效时由于地址未稳定导致写入错误单元,同时单总线结构也不允许地址和数据同时出现在总线上。
这些问题本身不难,但很能检验你是不是真的理解了设计。建议你在验收前把设计文档从头到尾翻一遍,尤其是自己当时觉得绕的地方,多问自己几遍“为什么”。能答出“为什么”,往往比答出“是什么”更让老师认可。
5.4 最后一点个人体会
从完成模型机设计到后来帮同学调实验,我最大的体会是:这个课设并不需要多高深的理论,它真正在训练的是你“把一条指令从脑子里变成机器上可执行动作”的能力。很多同学卡住,不是卡在不懂原理,而是卡在“原理书上的话和实验箱上的线对不上”。破解方法只有一个,就是逐拍跟着数据走一遍,把每个控制信号都看明白。这个工作量前期显得有点枯燥,但等你把16条指令的微程序全部调通,再回头看组成原理书上的控制器章节,会突然觉得书上写的每一句话都变成了看得见摸得着的东西。这种感觉,大概就是这个课设最值得的部分了。
本文还有配套的精品资源,点击获取