计算机组成原理模型机设计:控制信号表与Proteus调试

简介:文华学院《计算机组成原理》课程设计文档,面向计算机相关专业学生及需要完成Proteus模型机设计的学习者,提供一份个人作业参考。内容围绕“基于Proteus简单模型计算机设计与实现”展开,覆盖模型机逻辑电路与控制信号归纳、整机电路层次化设计、运算器与存储器等模块端口划分、开关模拟控制信号调试二段汇编指令、机器指令与微指令码点及脉冲时序安排,以及连接微程序控制器后整机运行与结果验证。压缩包共1个doc文件,约2.79MB,以课程论文形式组织需求分析、逻辑图、软件设计、调试过程、结果与总结,目录结构便于对照课程任务逐项查阅。已有537人学习,适合用作课程设计思路参考、实验报告框架借鉴和Proteus模型机搭建前的任务梳理,但需注意其为个人作业,宜结合自身实验独立完成。

1. 从两条指令到一台机器:模型机为什么要先确定控制信号表

「计算机组成原理课程设计」里最容易被低估的一步,不是画电路,而是把控制信号列全。很多人打开 Proteus 就先拖动 74LS181 和 6116,画到一半发现少了一根 /ALU-O,或者分不清 /WR 和 RCK 谁该是脉冲,整机推倒重来。

这份方案把顺序颠倒过来:先把模型计算机要执行的几条指令拆成微操作,再把每个微操作对应到具体的电平信号和脉冲信号,最后才动手画层次化电路。整机拆成五个子电路,配一套自定义指令系统、一张 26 位微指令码点表,最终在 Proteus 里用拨动开关手工跑通 68H + 68H = D0H。它适合要独立交一份课程设计的学生,也适合想快速复现一台可运行模型机的从业者。

2. 总体架构与信号清单:五个子电路加一条 8 位总线

2.1 层次化拆分:主电路只管时序、开关和总线

层次化设计时,真正需要克制的是「主电路」的职责范围。我的做法是给主电路只留三件事:产生时序脉冲、摆放输入开关与显示灯、把五个子电路用 8 位数据总线 D[0..7] 和 8 位地址总线 AB[0..7] 连起来。除此之外,主电路里不出现任何一片 74LS181、任何一组寄存器,它们全部下沉到子电路。

这么拆的直接好处是调试可切分。子电路可以单独打开仿真,给 DR1 手动加一个上升沿,看 ALU 的输出是不是预期的结果;等五个子图各自能跑,再回到主电路做整机联调。如果一开始就画平铺图,一根线接错,你在几百个引脚里找一个错误信号,那真是血泪经验。

拆分的依据是数据通路,分组原则是:凡是共享同一组输入输出、内部有独立时序的部件,就单独成图。运算器单元内部有两片 74LS181 和两个暂存器,自成一体;寄存器组是 R0、R1 两组 8 位寄存器;指令计数器单元是两片 74LS193 级联加一片 74LS244 和一片 74LS273;存储器单元是 6116;控制器单元负责产生微地址和全部微命令。

子电路核心器件主要职责与其他模块的接口
运算器单元74LS181 ×2、74LS273 ×2算术与逻辑运算、暂存操作数D[0..7]、S3~S0、M、CN、/ALU-O
寄存器组单元8 位寄存器 ×2存放参与运算的数据D[0..7]、SB、SA、/WR、RCK、/RD
指令计数器单元74LS193 ×2、74LS244、74LS273提供指令地址、锁存指令AB[0..7]、D[0..7]、PC+1、ELP、PC-O
存储器单元6116存放指令与数据AB[0..7]、D[0..7]、/RM、/WM、MCK、/CS
控制器单元微程序存储器、微地址寄存器产生微地址与 26 位微命令MB[0..5]、MD[0..25]

主电路上的连线只允许出现在「总线」和「控制信号干线」两类位置上。数据总线是双向的,谁驱动总线必须由 /ALU-O、PC-O、IR2-O、/RD 这类三态选通信号唯一决定;控制信号干线是单向的,从控制器单元出发,扇出到各个子电路。这条规矩定下来之后,整机图就变成了一张可以逐线核对的网,而不是一团乱麻。

2.2 电平信号与脉冲信号:一句话分清两类控制

区分电平信号和脉冲信号,只需问一句:这个信号是「保持一个状态」,还是「触发一个动作」。保持状态的叫电平信号,在整个微操作期间稳定有效,比如 /CS、/RM、/WM、SB、SA、S3~S0、M、CN、/ALU-O;触发动作的叫脉冲信号,只在写入那一瞬间出现一个上升沿,比如 D1CK、D2CK、RCK、PCK、MCK、IR2CK。

这个区分在课程设计里必须写进报告,原因是它直接决定手工调试时你该拨哪个开关。以寄存器写为例:/WR 是电平信号,它告诉寄存器组「准备写入」,但真正把总线上的数据锁进触发器的,是 RCK 的上升沿。/WR 提前打开、RCK 后到,数据才能进去;如果两个动作同时给,或者 RCK 比 /WR 早一个节拍,写进去的就是上一次总线上的残留值,表现出来就是「结果总是差一个数」。

提示:在 Proteus 里给脉冲信号配一个按钮加单稳态,或者直接用可手动触发的脉冲源,别用方波发生器。手工单步调试时你需要的是「按一次出一个沿」,而不是持续振荡。

把信号分类整理成一张表,贴在实验记录的第一页,后面每跑一条指令都在上面打勾。这张表同时也是报告里「归纳模型计算机所有控制信号」那一问的答案,先有表、后有图,前后就对得上了。

2.3 端口表先行:五个子电路的输入输出怎么定

画子图之前,先把每个子电路的端口清单确定下来,子图内部只允许出现清单上的端子。多一个端子、少一个端子,一律回到清单上改,改完再动电路。这套流程看起来啰嗦,但它能挡住一个非常常见的翻车:子图内部的信号名字和主电路上的网络名不一致,仿真时网络悬空,逻辑电平变成灰色未知态。

端口清单可以用脚本维护,改起来比在纸上涂改快得多,也方便画完图逐条核对:

# 顶层端口表:主电路只按这张表连子图,端口增删先改这里 PORTS = { "ALU": ["D1CK", "D2CK", "EDR1", "EDR2", "S3", "S2", "S1", "S0", "CN", "M", "ALU_O", "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7"], "REG": ["SB", "SA", "WR", "RCK", "RD", "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7"], "PC": ["CLR", "PCK", "PC_P1", "ELP", "PC_O", "IR2CK", "EIR2", "IR2_O", "AB0", "AB1", "AB2", "AB3", "AB4", "AB5", "AB6", "AB7"], "MEM": ["RM", "WM", "MCK", "CS", "AB0", "AB1", "AB2", "AB3", "AB4", "AB5", "AB6", "AB7", "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7"], "CTRL": ["MB0", "MB1", "MB2", "MB3", "MB4", "MB5", "T2", "T4", "CLR"] + [f"MD{i}" for i in range(26)], } # 画完子图后,逐个端口核对主电路上是否存在同名网络 def checklist(ports: dict) -> None: for unit, pins in ports.items(): print(f"[{unit}] {len(pins)} 个端口:", " ".join(pins)) checklist(PORTS)

这段脚本的作用是把端口表变成一份可打印的核对单。PORTS里的键是子图名,值是该子图对外的全部端子;checklist按子图逐个打印,画完一张子图就对着打印结果划掉一行。运算器单元那 19 个端子、存储器单元的 20 个端子,多一根少一根都会在打印结果里暴露出来。注意控制信号在子图内部是低电平有效还是高电平有效,命名时用/前缀区分,比如/RM、/WM、/CS,主电路上就不会有人误接成高有效。

端口表定完,还有一个隐含约定要写清楚:控制信号从控制器单元出来后,扇出必须在主电路上完成,不能把一根控制线先接进某个子图、再从子图里引出来接到下一个子图。这种「串联扇出」在 Proteus 里能跑,但它会让子图之间产生端口表之外的依赖,单独仿真子图时立刻失效。

3. 部件电路落地:运算器、存储器、PC 与寄存器组的接法

3.1 运算器单元:两片 74LS181 的 S3~S0 与 M、CN 编码

运算器用两片 4 位 ALU 拼成 8 位,低位片的进位输出接高位片的进位输入,两片的 S3~S0 并联,共用同一组功能选择信号。M 决定工作模式,M 为高时做逻辑运算,M 为低时做算术运算;CN 是进位输入,参与最低位的加法。ADD 指令就靠这三位配合:S3~S0 选加法功能、M 置低、CN 按需要给进位。

两个暂存器 DR1、DR2 各用一片 74LS273,它们是 ALU 的两个输入端口。D1CK、D2CK 是上升沿触发,负责把总线上的数据打进暂存器;/EDR1、/EDR2 是输出允许,低电平时暂存器内容送上总线;/ALU-O 低电平时,运算结果送上总线。这四个信号里,前两个是脉冲、后两个是电平,混用会直接导致总线冲突。

运算器单元最容易出问题的细节是总线驱动权的归属。ALU 的输出、DR1 的输出、DR2 的输出,三者都挂在内总线上,如果 /ALU-O 和 /EDR1 同时有效,两个源同时驱动 8 根线,Proteus 里就会看到一部分位是高、一部分位是低,或者干脆显示成红色冲突。做法是在微指令编码阶段就用互斥规则约束住,硬件上不做仲裁,靠编码保证同一时刻只有一个源有效。

注意:不同教材对 CN 的有效电平约定不一致,有的写成高电平表示有进位输入,有的写成低有效。以自己电路里实际接的那一片为准,把结论写进控制信号表,别照抄别人的表。

3.2 存储器单元:6116 的读允许、写允许与写入脉冲

存储器用一片 6116,容量 2K×8,本设计里只用低 8 位地址,256 个单元足够放下指令和数据。它的控制信号有四个:/CS 片选、/RM 读允许、/WM 写允许、MCK 写入脉冲。读操作是组合的,/CS 和 /RM 同时有效,数据立刻出现在 D[0..7] 上;写操作是时序的,/CS 和 /WM 有效只是让器件进入写状态,真正的写入发生在 MCK 的上升沿。

这个差异是很多人在存储器上翻车的根源。写的时候如果 /RM 还有效,器件内部输出级仍然试图驱动数据线,与总线上的写入数据打架,写进去的值就成了随机数。所以写周期的信号顺序必须是:先撤 /RM,再给 /WM,然后给 MCK 上升沿,最后撤 /WM。

读的时候也有个细节:6116 的输出是跟随地址变化的,地址一变,数据线上的值立刻跟着变。如果 PC 正在自增、地址总线还没稳定,就去锁存指令寄存器,锁到的可能是上一个地址的数据。稳妥的做法是让地址变化和读锁存分处两个节拍,T4 换地址,T2 打寄存器,这也是后面时序分配里 T2、T4 分工的由来。

3.3 指令计数器与指令寄存器:PC-O、ELP、IR2 的接力

指令计数器用两片 74LS193 级联成 8 位,PCK 的上升沿让 PC 加一,/ELP 有效时允许并行置数,用于跳转。PC 的值要送上地址总线,中间经过一片 74LS244 三态缓冲器,由 PC-O 控制:PC-O 有效,PC 的值上总线;PC-O 无效,缓冲器输出高阻,总线让给别的源。

取指流程是四个动作的接力。第一步,PC-O 有效,PC 内容上数据总线;第二步,数据总线接到地址总线(本设计里地址与数据共用一组 8 位线,靠控制信号分时复用),存储器 /CS 与 /RM 有效,指令字节被读出来;第三步,控制器把读出的字节锁进指令寄存器 IR1(一片 74LS273),同时 PC+1 让 PC 指向下一个字节;第四步,IR1 的操作码字段送去生成微地址,操作数字段经 IR2 缓冲后再上总线。

/ELP 和 PC+1 的区别要写清楚:PC+1 是顺序执行时的自增,每个取指周期都会发生;/ELP 是跳转时的强制置数,只在 STA、MOV 这类需要访问指定地址的指令里出现。两个信号如果同时有效,193 的置数优先级高于计数,PC 会被强行改成 /ELP 送进来的地址,顺序执行就断了。这也是排查时的一个入手点:程序跑飞先看 /ELP 是不是被误置。

3.4 寄存器组:SB、SA 选通和 RCK 写入的区别

寄存器组里放 R0、R1 两组 8 位寄存器,接口信号是 SB、SA、/WR、RCK、/RD。SB 和 SA 是源、目的寄存器编号的选通,属于电平信号,在整个微操作期间保持不变;/WR 是写允许,低有效,也是电平;RCK 是写入脉冲,上升沿把总线上的数据锁进被选中的寄存器。/RD 是读允许,低有效时把选中寄存器的内容送上总线。

以IN R0, #DATA1为例,完整动作是:开关数据经 SW-BUS 送上总线,SB、SA 选中 R0,/WR 拉低打开写允许,然后给 RCK 一个上升沿,数据进 R0;随后 /WR 拉高、RCK 回落。这四步里只有 RCK 那一下是脉冲,其余三个都是电平。手工拨开关时,顺序反了就会出现「按下写入按钮的瞬间,数据还没到总线上」的情况,写进去的是旧值。

寄存器组还有一个常被忽略的点:读和写不能在同一时刻对同一个寄存器生效。如果 /RD 有效把 R0 送上了总线,同时 /WR 和 RCK 又让 R0 从总线取值,R0 的输入就是自己的输出,结果是保持原值不变。仿真上看不出错,但程序逻辑会莫名其妙地少一次赋值。规避办法同样是在微指令层面保证互斥。

4. 指令系统与微程序:从汇编语句到 26 位微指令字

4.1 指令格式与数据通路:双字节 IN 为什么要两个机器周期

指令字长 8 位,高 4 位是操作码,低 4 位是寄存器号或地址低 4 位。单字节指令只有操作码,双字节指令后面跟一个立即数或地址字节。设计的两段程序,一段只做加法输出,一段带存转,用来验证存储器的读写通路:

; 程序一:两数相加后输出,操作数由拨码开关给出 IN R0, #DATA1 ; 开关数据经总线打入 R0,/WR 与 RCK 配合 IN R1, #DATA2 ; 第二个操作数打入 R1 ADD R0, R1 ; R0 + R1 -> R0,M 置低走算术加法 OUT R0 ; R0 内容送显示灯,验证结果 ; 程序二:存转之后再相加,验证存储器读写通路 IN R0, #DATA1 STA (01H), R0 ; R0 写入存储器 01H 单元,/WM 与 MCK 配合 IN R0, #DATA2 MOV R1, (01H) ; 从 01H 单元读回,/RM 与 /CS 配合 ADD R0, R1 OUT R0

IN R0, #DATA1是双字节指令,因为它要带一个 8 位立即数。第一字节是操作码,取指后 PC 自增指向第二字节;第二字节是立即数,需要再来一次读存储器把它取到总线上,然后打进 R0。所以每条双字节指令占两个机器周期,单字节的 ADD、OUT 只占一个。写报告时把「双字节指令为什么需要两次取指」讲清楚,比单纯列指令表更能说明你真的理解了数据通路。

STA (01H), R0的作用是把 R0 的值写进存储器的 01H 单元。它同样是双字节,第二字节是地址。执行时先把地址送上地址总线,再把 R0 的内容经 SB、SA 选中后送上数据总线,最后给 /WM 和 MCK 完成写入。MOV R1, (01H)是反向操作,读的时候 /RM 和 /CS 有效,数据经总线打进 R1。

4.2 微指令字段划分与微地址生成

微指令字由 26 位微命令 MD[0..25] 和 6 位微地址 MB[0..5] 组成,6 位微地址意味着最多 64 条微指令,对于几条指令的模型机绰绰有余。26 位微命令按功能字段划分,每个字段对应一组控制信号,字段之间不重叠、不遗漏,这是划分的唯一硬要求。

位段字段名对应信号说明
MD25SW-BUS开关送总线电平,低有效
MD24 / MD23EDR1 / EDR2暂存器输出允许电平,低有效
MD22MALU 模式高为逻辑、低为算术
MD21~MD18S3~S0ALU 功能选择电平,低有效
MD17CN进位输入电平
MD16ALU-O运算结果送总线电平,低有效
MD15 / MD14SB / SA寄存器选通电平
MD13 / MD12/WR / /RD寄存器写允许 / 读允许电平,低有效
MD11~MD9PC+1 / ELP / PC-O指令计数器控制前两者电平、PC-O 低有效
MD8~MD6EIR2 / IR2-O / IR1指令寄存器控制含脉冲与电平
MD5~MD2/RM / /WM / MCK / /CS存储器控制含脉冲与电平
MD1~MD0保留后续扩展预留

微地址的生成有两条路径。顺序执行时,微地址寄存器每个节拍加一,指向下一条微指令;遇到分支时,用当前指令的操作码映射到一段微程序入口。前一种靠计数器,后一种靠查找表,两者在控制器单元里用一个小规模的多路选择器切换。入口地址表要写进报告,它是「指令如何对应到微程序」的书面证据。

微指令写好之后,手拨 64 条微指令是不现实的,通常用脚本把码点拼成初始化文件,再让 Proteus 里的微程序存储器直接加载:

# 把 6 位微地址与 26 位微命令拼成 32 位字,输出 Proteus 微程序 ROM 的初始化文件 MICRO = [ (0x00, 0b11_1111_1111_0010_0110_1111_1110), # 取指:PC-O 有效,读存储器 (0x01, 0b11_1111_1111_0010_0110_1111_1101), # 锁 IR1,PC 自增 (0x02, 0b00_0000_0000_0000_0000_0000_0000), # 占位,按实际编码补齐 ] def pack(mb: int, md: int) -> int: """高 6 位放微地址,低 26 位放微命令""" return (mb << 26) | md with open("micro.hex", "w", encoding="utf-8") as f: for addr, (mb, md) in enumerate(MICRO): f.write(f"{addr:02X} {pack(mb, md):08X}\n")

pack函数把微地址左移 26 位后与微命令按位或,得到 32 位字,写出的micro.hex每行是「行号 + 32 位十六进制值」,Proteus 的存储器模型按十六进制文件初始化时可以直接读。MICRO列表里的二进制串只是位模式的占位,实际值要按你自己的字段划分逐条填,填的时候对着 4.2 的字段表一位一位核。这样做的价值在于:改一条微指令只改一行代码,不用在仿真界面里一个个拨开关,改了之后重新生成文件即可,省下的时间足够你多跑三遍程序。

4.3 时序分配:T2 与 T4 两个脉冲沿各管什么

本设计只用 T2 和 T4 两个脉冲沿,这是一种简化后的四节拍时序子集。分工原则是:T4 负责切换选通状态、改变地址和打开各种允许信号,T2 负责把已经稳定在总线上的数据打进寄存器。换句话讲,T4 让电路「准备好」,T2 让电路「落笔」。

节拍动作性质典型信号触发方式
T4状态切换PC-O、/WR、/RD、/RM、/WM、SB、SA电平变化,无需脉冲
T2数据写入D1CK、D2CK、RCK、PCK、MCK、IR2CK上升沿

这样分工的理由和前面存储器那节讲的是同一件事:地址和数据需要时间稳定,写脉冲必须晚于选通信号。T4 先让地址上线、让允许信号到位,等数据稳定了,T2 的上升沿再把数据锁进去,写入必然正确。反过来先给 T2 再给 T4,锁进去的就是上一拍残留的值,表现出来就是「程序没错、电路没错,但结果总是慢一拍」。

手工调试时,两个脉冲源要分开控制。可以给 T2、T4 各配一个按钮,按钮按下产生一个上升沿后自动回落,这样你能清楚地数出「第几个 T4、第几个 T2」,也方便在报告里记录每条机器指令耗了几个节拍。

4.4 手工拨开关跑一条 ADD:14 步操作序列

断开微程序控制器,直接拨动开关模拟控制信号,是验证微指令码点是否正确的最笨也最可靠的办法。以ADD R0, R1为例,完整操作可以拆成下面这些步骤,每一步只改一个状态,改完看总线上的值再继续:

步骤操作关键信号观察点
1打开 R0 写,给 T2 脉冲/WR、SB、SA、RCKR0 显示 DATA1
2关闭 R0 写/WR 拉高R0 保持不变
3打开 R1 写,给 T2 脉冲/WR、SA 切换、RCKR1 显示 DATA2
4关闭 R1 写/WR 拉高R1 保持不变
5关闭总线源SW-BUS 拉高总线进入高阻
6打开 R0 读/RD、SB 选中 R0总线出现 DATA1
7打开 DR1 写,给 T2 脉冲D1CKDR1 锁存 DATA1
8关闭 DR1 写、关闭 R0 读/RD 拉高总线释放
9打开 R1 读、DR2 写,给 T2 脉冲/RD、SA、D2CKDR2 锁存 DATA2
10关闭 DR2 写、关闭 R1 读D2CK 回落两个暂存器都有值
11给 ALU 送功能码,打开 /ALU-OS3~S0、M、CN、/ALU-O总线出现运算结果
12打开 R0 写,给 T2 脉冲/WR、SA、RCK结果写入 R0
13关闭 R0 写、关闭 /ALU-O/WR、/ALU-O 拉高总线释放
14打开 R0 读,看显示灯/RD、SB 选中 R0显示 D0H

输入取 68H 与 68H,两数相加得 D0H,最高位没有产生进位,8 位显示正好是 D0H。如果实际显示不是这个值,回退到第 11 步看 ALU 输出,再回退到第 7、9 步看两个暂存器的值,一般就能定位到是哪一步的脉冲没给上。

这 14 步写进报告时,建议同时标注每一步对应微指令的哪一位,格式是「步骤号 → 有效信号 → MD 位号」。这样报告里的微指令码点表和调试步骤表能互相对照,答辩时被问到「你这条微指令为什么这么编」,直接翻到对应步骤即可。

5. 调试排查:模型机跑不通时先看这五处

5.1 数据打不进寄存器:脉冲沿给早了或给晚了

现象是给 RCK 按了按钮,寄存器里的值没有变化,或者变成总线上的上一个值。原因多半是脉冲相对选通信号的位置不对:/WR 还没拉低就给了 RCK,或者数据还没上总线就给了 RCK。解决办法是把写入动作固定成「先 T4 后 T2」的顺序,T4 负责打开 /WR、选通 SB/SA、让数据源上总线,T2 负责给 RCK 上升沿,两个节拍之间至少留一次手动操作的间隔。检查时直接测寄存器输入端的 D 引脚,在 RCK 上升沿那一刻 D 上的值才是最终写进去的值。

5.2 ALU 输出全 0 或全 1:总线出现两个驱动源

现象是运算结果要么全 0、要么全 1,或者部分位对、部分位错,Proteus 里数据线显示成红色。原因是两个三态源同时驱动总线,比如 /ALU-O 和 /EDR1 同时有效,一个送运算结果一个送暂存器内容,8 根线被两套电平硬顶。解决办法是在微指令层面加互斥约束,编码时保证每个微操作只有一个源有效;调试时用脚本把每条微指令展开,检查 SW-BUS、/EDR1、/EDR2、/ALU-O、PC-O、IR2-O、/RD 这七个源里是否有两个以上同时有效:

# 微指令展开与互斥检查:把 26 位 MD 按字段切开,逐条查总线冲突 FIELD_BITS = { "SW_BUS": 25, "EDR1": 24, "EDR2": 23, "M": 22, "S3": 21, "S2": 20, "S1": 19, "S0": 18, "CN": 17, "ALU_O": 16, "SB": 15, "SA": 14, "WR": 13, "RD": 12, "PC_P1": 11, "ELP": 10, "PC_O": 9, "EIR2": 8, "IR2_O": 7, "RM": 6, "WM": 5, "MCK": 4, "CS": 3, "EIR1": 2, "LDIR": 1, "RSV": 0, } BUS_SOURCES = ["SW_BUS", "EDR1", "EDR2", "ALU_O", "PC_O", "IR2_O", "RD"] def decode(md: int) -> dict: """把一条 26 位微指令展开成 {信号名: 0/1},1 表示该信号有效""" return {name: (md >> bit) & 1 for name, bit in FIELD_BITS.items()} def check(md: int, addr: int) -> list: sig = decode(md) bad = [] active = [s for s in BUS_SOURCES if sig[s]] if len(active) > 1: bad.append(f"微地址 {addr:02X}: 总线被多个源驱动 -> {active}") if sig["RM"] and sig["WM"]: bad.append(f"微地址 {addr:02X}: 读允许与写允许同时有效") if sig["WM"] and not sig["CS"]: bad.append(f"微地址 {addr:02X}: 写存储器但片选未开,写入会丢") return bad

FIELD_BITS是位号映射,位号按你自己的 26 位划分填;BUS_SOURCES列出所有能驱动总线的信号,check先看有没有两个以上同时有效,再看读写允许有没有打架、写的时候片选开没开。把全部微指令逐条喂进去,一次性把所有冲突打印出来,比在仿真界面里一条条试快得多。注意这个脚本只检查编码层面的冲突,物理层的扇出能力、三态缓冲器方向还得自己看图。

5.3 存储器读回值不对:写允许与写入脉冲叠在一起

现象是 STA 写进去的数据,MOV 读回来变成了别的值,或者干脆是上一次写的内容。原因通常是写周期里 /RM 没撤干净,或者 /WM 与 MCK 的相对时序错了。6116 的写周期要求写允许有效覆盖整个写入脉冲,并且读允许在整个写周期内保持无效。解决办法是把写操作固定成四步:撤 /RM → 给 /WM → 给 MCK 上升沿 → 撤 /WM,读操作则相反:给 /CS、给 /RM、锁存数据、撤 /RM。调试时把地址总线固定成 01H,写一个已知值再读回来看,比在整机里瞎猜快。

5.4 微地址跑飞:MB 位宽与微指令表长度不匹配

现象是单步执行前几条微指令正常,走到某一步之后微地址跳到了不该去的地方,或者回到 00 无限循环。原因是微地址寄存器位宽和微指令表长度不匹配,6 位微地址最多 64 条,如果表里实际写了 70 条,或者入口地址表给的偏移超出了范围,计数器一加就溢出回绕。解决办法是先把每条指令占用几条微指令算清楚,在表里留出空位,写一个统计脚本核对每条指令的入口地址与占用长度之和不超过 64。另一个隐蔽原因是微地址加一的使能信号没有复位,上电后停在某个随机值上,这个靠给 CLR 一个上电复位脉冲解决。

5.5 单步能过、连续跑就乱:复位与初始状态没定死

现象是手工单步运行完全正确,一切换成连续时钟就出错,或者第一次跑对、第二次跑错。原因有两类:一是复位信号只在上电瞬间给了一次,寄存器、PC、微地址寄存器没有统一切到初始状态;二是组合逻辑里存在反馈路径,某一拍的中间态在下一拍被当成有效值采了进去。解决办法是把 CLR 接到所有需要初始化的器件上,包括 PC 的两片 193、微地址寄存器、指令寄存器,并在连续运行前强制复位一次;反馈路径则靠保证每个节拍只改变一个状态来规避,也就是前面反复强调的 T4 改状态、T2 写数据。这条坑最值得写进报告的「调试过程」一节,因为它把「单步正确不等于整机正确」这件事讲明白了。

6. 验收与答辩:怎么证明这台模型机真的跑对了

验收的核心不是「电路能跑」,而是「你能证明它跑对了」。我一般会准备三样东西:一张控制信号全表、一份微指令码点表、一段带着预期值和实际值的运行记录。三样东西互相能对上,答辩时老师随便指一条指令、指一个信号,你都能在三秒内翻到对应位置,这比嘴上说「我调通了」有说服力得多。

第一样,控制信号表要按子电路分组列,每个信号写清名称、作用、有效电平、是脉冲还是电平。第二样,微指令码点表要按微地址排序,每条微指令列出有效的信号名和它属于哪条指令的哪个节拍。第三样,运行记录按程序逐条走,记录输入、预期输出、实际输出。

验收项检查方法通过标准
单条指令功能断控制器,手拨开关走 14 步显示值与手算一致
双字节指令取指观察 PC 两次自增与 IR1 锁存立即数被正确取到总线
存储器读写STA 后 MOV 读回比对读回值等于写入值
程序一整体连续运行 68H + 68H显示 D0H
程序二整体存转后相加并输出显示值与手算一致
时序正确性单步逐拍记录 T2/T4 数量每步只有一次脉冲写入

答辩时最常被问的两个问题,一个是「为什么这个信号是脉冲不是电平」,另一个是「如果把 T2 和 T4 调换会怎样」。前一个用「保持状态还是触发动作」那句话回答,后一个直接说会锁到上一拍残留值,并且能指出在哪一步观察到。这两个问题答得出来,说明你是真的调过,而不是把别人的图抄了一遍。

还有一个容易忽略的技巧:把预期值和实际值做成一张掩码比对表,只比对有意义的位。比如 OUT 只点亮 8 个显示灯,进位位不显示,那比对时就屏蔽掉进位,避免因为一个不显示的位对不上而反复怀疑电路。

从那以后我每次做这类硬件课设,都会在画第一根线之前先把控制信号表和端口表写完,跑通之后再回头补微指令码点表,最后才写报告正文。顺序反了,报告就会变成对着电路图硬编故事。这套流程走下来,验收那半天反而最轻松。希望帮到你。

本文还有配套的精品资源,点击获取