简介:这份文档是文华学院《计算机组成原理》课程设计的个人作业参考,面向正在完成模型计算机设计任务的高校学生,尤其适合使用Proteus进行硬件仿真的学习者。内容围绕基于Proteus的简单模型计算机设计与实现展开,涵盖控制信号归纳、整机电路层次化设计、开关模拟控制信号调试汇编指令、机器指令与微指令码点设计及时序安排,以及连接微程序控制器运行程序并验证指令系统等环节,并附有运算器、存储器等模块的电路图与控制信号说明。资源包共1个doc文件,约2.79MB,以课程论文形式组织,包含需求分析、逻辑图、软件设计、调试过程与结果对比等章节,可帮助读者理解模型机从逻辑电路到完整系统的搭建思路,参考微指令编码与时序分配方法,并借鉴调试与验证的排错经验。目前已有537人学习。
1. 微程序控制器:为什么一条 ADD 指令能被拆成十几个开关动作
很多人第一次看到模型计算机的微代码表会愣住:一条ADD R0, R1而已,怎么对应出十几行、每行二十几个二进制位?更反直觉的是,这些位里真正决定“加”这个动作的,只有 74LS181 的 S3~S0 和 M 五个引脚,其余全是“什么时候把哪个寄存器挂上总线、什么时候锁存”的搬运工。搞懂这件事,等于搞懂了计算机组成原理里最容易被背成玄学的那部分——控制信号不是魔法,它是一张把指令语义翻译成电平与脉冲的查找表。这份课程设计资源把整张表摊开给你看:运算器、寄存器组、指令计数器、存储器、控制器五个子电路的端口清单,每条机器指令的 MD25~MD0 与 MB5~MB0 码点,以及 T2、T4 两个节拍下哪些信号该给脉冲、哪些该保持电平。看懂它,你才知道 CPU 里那根“时钟”到底在指挥什么。
2. 控制信号的两种性格:电平信号与脉冲信号为什么必须分开
2.1 电平是“资格”,脉冲是“动作”
模型机里所有控制信号按行为分成两类,这个区分贯穿整份设计。电平信号描述的是某个部件在当前节拍内是否“被允许参与”,比如/EDR1、/EDR2低电平有效时,寄存器 R1、R2 的输出三态门才打开,数据才能上总线;/ALU-O低电平有效时,74LS181 的运算结果才允许输出。它们必须在整个数据通路上保持稳定,早一点晚一点都会让总线出现竞争。脉冲信号则是“执行瞬间”,比如D1CK、D2CK是寄存器写入的上升沿,MCK是存储器写入的上升沿,PCK是指令计数器加一的上升沿。脉冲只在时钟跳变的那一刹那起作用,之前之后是什么电平无所谓,关键是沿。
这个分工不是随意定的。常见的实现思路是:组合逻辑负责“选路”,时序逻辑负责“落盘”。选路必须提前稳定,落盘必须卡在数据稳定之后。所以你会看到调试步骤里反复出现“给 T4 脉冲打开寄存器读功能”“给 T2 脉冲将数据打入运算器暂存器”——T4 负责把源操作数挂上总线并让 ALU 算出来,T2 负责在结果稳定后打一个上升沿把它锁进目的寄存器。两个节拍一前一后,正好对应“准备”和“提交”。
2.2 从端口清单反推数据通路
资源里列出的五个子电路端口,其实就是数据通路的骨架。运算器单元对外暴露D[0..7]、S3~S0、CN、M、/ALU-O;寄存器组暴露SB、SA、/WR、RCK、/RD、D[0..7];指令计数器暴露CLR、PCK、PC+1、ELP、PC-O、IR2CK、EIR2、IR2-O、AB[0..7];存储器暴露/WM、MCK、/RM、/CS、D[0..7]、AB[0..7];控制器暴露CLR、SET、WR、SETR、RD、T2、T4、MD[0..25]、MB[0..5]。
把这些端口连起来读,一条指令的执行路径就浮出来了:PC 通过PC-O把地址送上AB总线,存储器/CS有效、/RM有效后把指令码送上D总线,IR2CK打脉冲锁进指令寄存器 IR2,IR2 再通过IR2-O把操作码送进控制器译码,控制器输出MD微代码位去驱动前面那些电平与脉冲。整条链路里没有任何“智能”部件,全是三态门和锁存器,所谓“执行指令”就是按顺序把不同的门打开又关上。
提示:读这类图时先找总线,再看每个部件到总线的出口有没有三态门、入口有没有锁存器。出口决定“能不能上”,入口决定“什么时候收”,这两件事分清了,控制信号表就不再是天书。
3. 微指令码点:MD25~MD0 与 MB5~MB0 到底编码了什么
3.1 微代码表的结构
资源里给出的微代码表,每一行对应一个微操作,列头是MD25到MD0共 26 位,加上MB5到MB0共 6 位。从表头命名能直接读出字段划分:SW-BUS、EDR1、EDR2、M、S3、S2、S1、S0、CN、ALU-O、SB、SA、WR、R、R、PC+1、ELP、PC-O、EIR2、IR2-O、RM、WM、CS、EIR1这些位,基本一位或几位管一个控制信号。MB那 6 位则是微地址或下址字段,决定这条微指令执行完跳到哪。
以IN R0, #DATA1为例,表里拆成“开寄存”“关寄存”两行。开寄存那行的有效位让SW-BUS把开关数据送上总线、EDR1选通 R0 的入口、WR置为写方向,再配合T4脉冲把数据锁进 R0;关寄存那行则把EDR1和写使能撤掉。为什么一条输入指令要两行?因为“数据在总线上稳定”和“锁存进寄存器”是两个时刻,微指令必须把它们分开,否则锁存到的可能是总线翻转过程中的中间值。这就是微程序设计和组合逻辑硬布线的核心差别:硬布线把时序揉进电路延迟里,微程序把时序显式写成一行行的码点,可读、可改、可单步。
3.2 用一段伪代码理解微指令的取指与执行
把表里的行为翻译成接近硬件的伪代码,结构会非常清楚:
# 取指阶段:所有指令共用 def fetch(): PC_O = 0 # 低有效,PC 值上地址总线 AB RM = 0; CS = 0 # 存储器读允许、片选 IR2CK.pulse() # 上升沿把 D 总线上的指令码锁进 IR2 PCK.pulse() # 上升沿让 PC 加一 PC_O = 1; RM = 1; CS = 1 # 撤掉地址与读使能,释放总线 # 执行 ADD R0, R1:对应表里连续多行微指令 def exec_add(): EDR1 = 0 # 选通 R0 输出到总线 SA = 0; SB = 1 # 寄存器组地址选择,指向 R0 / R1 M = 1 # 74LS181 算术模式 S3, S2, S1, S0 = 1, 0, 0, 1 # 加法功能码 ALU_O = 0 # ALU 结果允许输出 D2CK.pulse() # T4 节拍:结果打入 ALU 暂存器 DR2 EDR1 = 1 # 关掉 R0 输出,避免与下一次读冲突 EDR2 = 0 # 选通 R1 D1CK.pulse() # 把 R1 打入 DR1 # 两操作数就位后 ALU 求和,结果经总线写回 R0 WR = 0; SA = 0 RCK.pulse() # T2 节拍:写回 R0这段伪代码不是资源原文,而是把码点表按数据流重排后的等价描述。关键点在于:S3~S0和M决定“算什么”,EDR1、EDR2、ALU-O、WR决定“数据从哪来到哪去”,D1CK、D2CK、RCK决定“什么时候落地”。三类信号各司其职,任何一类给错,结果要么算错、要么写错、要么总线打架。
3.3 为什么加法功能码是 1001
74LS181 的功能由 S3~S0 加 M 共同决定,M=1 是逻辑运算,M=0 是算术运算。在算术模式下,S3~S0=1001 对应的是 A 加 B(无进位)这一类功能。资源里ADD那几行把M置为算术、S3~S0置为对应加法码、CN按需要给进位输入,正是标准用法。这里容易翻车的地方是CN的有效电平——表里写的是高电平有效,但 74LS181 的进位输入实际是低有效,具体以所用器件手册和仿真模型为准。这类“表里写的和芯片手册对不上”的细节,恰恰是仿真能跑通、实物会翻车的高发区,后面避坑章节会专门说。
4. 时序与层次化:T2、T4 两个节拍如何撑起整机
4.1 层次化设计解决的是“连线爆炸”
整机电路如果画成一张平铺图,五个子电路、几十根控制线、八位数据总线和八位地址总线交叉在一起,基本没法调试。资源采用的做法是层次化:运算器、寄存器组、指令计数器、存储器、控制器各自做成子图,父图只保留子图之间的端口连线。这样做的直接好处是每个子图可以单独仿真——先验证运算器在给定 S3~S0 下输出对不对,再验证寄存器组在SA、SB、/WR下读写对不对,最后才拼整机。血泪经验是:不要一上来就拼整机再调,总线冲突和时序错位混在一起,你根本分不清是哪个子图的锅。
层次化还带来一个隐性收益:端口清单本身就是接口契约。运算器子图对外只承诺“你给我 S3~S0、M、CN 和两个操作数,我在/ALU-O有效时把结果放上 D 总线”,至于内部用几片 74LS181、怎么级联进位,父图不关心。这种“契约先行”的思路,和后来 SoC 里 IP 核的接口定义是一脉相承的,只不过这里用 Proteus 子图手工实现。
4.2 T2 与 T4 的分工
控制器输出的T2、T4是整机的节拍源。从调试记录能反推出分工:T4 多用于“打开读通路、让数据上总线、启动 ALU”,T2 多用于“打写入脉冲、把数据锁进寄存器”。比如输入指令的调试步骤是“给 T2 一个脉冲并且给 T4 脉冲,将输入的数据打入寄存器 R0”,这里 T4 负责让开关数据稳定上总线,T2 负责在稳定后锁存。加法指令则是“给 T4 脉冲打开运算器写入功能并给 T2 脉冲将寄存器数据打入运算器暂存器”,同样是 T4 铺路、T2 落地。
这种两相节拍的设计动机很朴素:一个节拍做不完“选路 + 运算 + 写回”,硬拆成两个又会让电路复杂。用 T4 做“准备相”、T2 做“提交相”,正好把组合逻辑的稳定时间和时序逻辑的建立时间分开。常见的实现思路里,还会在这两个节拍之间留出死区,防止三态门关断和另一个门打开重叠造成总线短路。仿真里不一定暴露这个问题,但理解它,你才知道为什么真实 CPU 的时钟树要那么讲究。
4.3 指令执行的数据流实例
以STA (01H), R0为例,资源里把它拆成“把 R0 内容写到地址 01H”的微操作序列。数据流是:R0 经EDR1上 D 总线,同时地址 01H 经AB总线送到存储器,/WM有效、MCK打脉冲完成写入。这里地址来源和 earlier 的取指不同——取指时地址来自 PC,存储指令时地址来自指令中的立即数字段,所以控制器要切换AB总线的驱动源。这个切换在码点表里体现为某一位的置位,在电路上体现为一个多路选择器。看懂这一层,你就明白“指令中的地址字段怎么变成地址总线上的电平”不是自动的,是控制器显式选出来的。
5. 避坑与排查:仿真能跑通不等于设计对了
5.1 现象:单步能过,连续跑就错
原因:T2、T4 两个节拍的先后关系在单步调试时被人为拉长,掩盖了建立时间不足的问题;连续运行时前一个节拍的三态门还没完全关断,后一个节拍的门已经打开,总线出现瞬间竞争。解决:在仿真里给关键控制信号加微小延迟,或者严格按“先关后开”的顺序排列微指令,确保同一时刻只有一个部件驱动 D 总线。
5.2 现象:加法结果总是差一
原因:CN进位输入的有效电平搞反了。表里标注高有效,但 74LS181 器件本身是低有效进位,仿真模型若按器件行为建模,就会在需要进位时反而抑制进位。解决:对照器件手册确认CN极性,在码点表里统一按器件实际行为填写,不要照抄表格标注。
5.3 现象:写寄存器时好时坏
原因:RCK或D1CK、D2CK用了电平触发而不是上升沿触发,或者脉冲宽度不够,锁存器没采到稳定值。解决:确认这些时钟信号在仿真里配置为边沿触发,脉冲宽度至少覆盖数据建立加保持时间;调试时先用单脉冲手动给,确认一次能写对,再交给时序电路自动产生。
5.4 现象:取指后 PC 不前进
原因:PCK脉冲和PC-O的撤除顺序错了。如果PC-O还没撤就把PCK打上去,PC 可能把总线上的残留值当新地址;如果PCK打得太晚,下一条指令又覆盖了总线。解决:严格按“PC-O有效 → 存储器读 →IR2CK锁指令 →PCK加一 →PC-O撤除”的顺序排微指令,每一步之间留一个节拍。
5.5 现象:存储器写入后读出来是旧值
原因:/WM和MCK的配合不对,或者/CS片选在写周期中途被撤。解决:写周期内/CS必须全程有效,/WM先有效、MCK打脉冲、再撤/WM,读周期同理但用/RM。把读写时序画成波形图对照,比盯着码点表猜快得多。
6. 从微代码表到指令系统:这套设计的取舍在哪里
这套模型机最值得琢磨的取舍,是它选择了微程序控制而不是硬布线控制。硬布线把每条指令的控制逻辑固化成一堆门电路,速度快但改一条指令就要动电路;微程序把控制逻辑存进控制存储器,改指令只是改码点表。对一个课程设计来说,后者显然更合适——你可以坐在电脑前改二进制位,而不用重新连一堆线。代价是执行速度慢,每条微指令都要一次控制存储器访问,这也是后来 RISC 架构重新偏向硬布线的原因之一。资源里那张 MD25~MD0 的表,本质就是一台“用查表代替逻辑”的小型解释器。
第二个取舍是总线宽度。八位数据总线、八位地址总线,意味着地址空间只有 256 字节,指令码和操作数挤在一起。资源里用DB 01111111B这样的伪指令手工铺机器码,正是地址空间紧张的直接体现。这种限制在真实机器上不可接受,但在教学模型里反而有好处:所有地址都能用两个十六进制位表示,调试时一眼能看出数据流向,不用在几十位地址里找线索。
第三个取舍是节拍数。T2、T4 两相节拍够用,但不够优雅。真实 CPU 会有多级流水、多相时钟,模型机为了可观测性牺牲了并行度。你在仿真里单步走一遍ADD,能清楚看到每个节拍总线上是什么值,这种“全透明”是真实芯片给不了的。
验证这套设计对不对,最直接的办法不是看最终结果对不对,而是看中间态。资源里记录的实际结果是 58H 加 58H 得 D0H,这个结果本身说明不了太多——两个相同数相加,很多错误路径也能凑出 D0H。真正有说服力的验证是:在每个节拍暂停,检查 D 总线上的值是否等于预期操作数、ALU 输出是否等于预期和、目的寄存器的锁存时刻是否在结果稳定之后。我一般会建议把微代码表打印出来,每跑一个节拍就在对应行上打勾,全部勾完且中间态全对,才算这条指令真的通了。
从那以后我每次碰这类模型机设计,都强制自己先把端口清单和码点表对齐一遍,再动手连线——因为翻车最多的从来不是算错,而是某个三态门在该关的时候没关。希望帮到你。
本文还有配套的精品资源,点击获取