简介这份文华学院《计算机组成原理》课程设计文档面向正在完成Proteus模型计算机设计与实现作业的本科生提供一份可对照参考的个人作业范例。压缩包仅含1个doc文件约2.79MB内容为完整课程论文涵盖需求与总体设计、模型机逻辑图、运算器与存储器电路及控制信号、整机层次化设计和软件设计。文中整理了74LS181运算器、6116存储器等模块的信号名称与有效电平并记录时序分析、指令数据流向、微指令与微代码设计、编程调试过程及应得结果与实际结果对比便于读者理解控制信号归纳、脉冲信号与电平信号区分、微程序控制器连接等关键环节。已有537人学习适合需要搭建Proteus模型机、梳理课程设计报告结构或查漏补缺的计算机相关专业学生参考但应结合自身方案独立完成。1. 一台只会做加法的计算机反而最考验你对时序的理解在 Proteus 里把 6116 的 /WE 和 /CS 顺序接反数据就是写不进去灯不亮、数码管不动你根本分不清是片子坏了、网线断了还是电平给错了。计算机组成原理这门课最容易卡住的地方就在这里教材上真值表会背、功能表会填可真要把 74LS181、74LS273、6116 拼成一台能跑程序的机器中间隔着几百根连线和一堆脉冲、电平的区别。这份课程设计做的就是这样一件事用 Proteus 8.9/8.11 搭一台 8 位单总线模型计算机自己定义指令、自己排微代码、自己拨开关对时序最后跑通一段 IN/ADD/OUT 的程序58H 加 58H 得到 D0H。它适合两类人正被课设卡住、需要一份能对照着连线的同学以及想把“CPU 到底怎么动起来”这件事从抽象补成具体画面的自学者。2. 整机层次化拆分五张子图、8 位单总线与端口连线2.1 为什么先拆层次再连线直接在顶层画一张包含全部器件的整机图几乎是自找麻烦。这份设计走的是层次化路子主电路只保留按钮开关、显示灯、时序部分、指令寄存器 IR174LS273以及各子电路的方框把运算器、寄存器组、指令计数器、存储器、控制器这五块各自做成一张子图Proteus 里的子电路顶层靠端口把信号接进去。好处很实在调运算器的时候只看运算器那张图噪声少、连线少出错范围也小等五个子图各自能动了再往顶层一拼问题基本只剩端口连错或者控制信号漏接。单总线结构是这套设计的骨架。整机只有一组 8 位数据总线 D[0..7] 和一组 8 位地址总线 AB[0..7]任何两个部件之间传数据都必须“一个放、一个收”同一时刻总线上只能有一个源。这条约束决定了后面所有微指令的写法你要把 R0 送进 ALU就得先把 R0 打开到总线、再把 ALU 暂存器的写允许打开中间不能有任何第二个源抢总线否则仿真里看到的就是一片 X 或者全 0。很多人第一次调不通不是器件坏是总线打架。我曾经把“先拆子图”当成形式主义觉得顶层一把画完更省事结果一个 8 位加法算错排查了整整两天最后发现是子图里一根 /EDR2 没引出来。从那以后我坚持一个原则顶层只允许出现端口和方框任何器件都下沉到子图里。2.2 五张子电路和它们的端口清单端口命名最好一次定死后面写微指令、拨开关全靠它。这份设计的端口整理出来大概是下面这样我做了一点归类方便你连线和对照子电路主要端口信号说明运算器单元D2CK、D1CK、/EDR1、/EDR2、S3、S2、S1、S0、CN、M、/ALU-O、D[0..7]两片 74LS181 组 8 位 ALUDR1/DR2 为两个暂存器寄存器组单元SB、SA、/WR、RCK、/RD、D[0..7]SA/SB 选读写寄存器RCK 为写脉冲指令计数器单元CLR、PCK、PC1、ELP、PC-O、IR2CK、EIR2、IR2-O、D[0..7]、AB[0..7]两片 74LS193 级联成 8 位 PC244 做三态输出273 做 IR2存储器单元/WM、EMCK、/RM、/CS、D[0..7]、AB[0..7]6116 静态 RAM读写分离控制控制器单元CLR、SETW、SETR、DT2、T4、MD[0..25]、MB[0..5]微指令寄存器输出控制字段与下地址这张表看着朴素实际是整个项目的“接口契约”。子图内部你怎么画都行但端口名字一旦定成 /EDR1顶层、微指令表、调试记录里就必须都叫 /EDR1不要一会儿写 EDR1、一会儿写 DR1EN。血泪经验名字不统一最后对微代码的时候你会怀疑人生。2.3 把所有控制信号归纳成一张表题目要求“归纳此模型计算机的所有控制信号”这一步其实是整个设计的分水岭。信号归纳清楚了微指令字段就有了归纳不清楚后面全是补丁。按功能分这批信号大致是四类第一类是数据源选择比如 SW-BUS把开关数据送上总线、/ALU-OALU 结果送上总线、PC-OPC 送地址总线、IR2-O指令寄存器送总线。第二类是数据目的写允许比如 /EDR1、/EDR2、/WR、EIR2、ELP、MCS。第三类是功能选择比如 S3~S0、M、CN 决定 74LS181 做什么运算SA/SB 决定读写哪个通用寄存器。第四类是时序与脉冲比如 D1CK、D2CK、RCK、PCK、EMCK这些是上升沿有效的打脉冲。这四类里前两类在微指令里表现为电平位第三类也是电平位第四类是真正意义上的“脉冲”。区分这两者的意义在于电平可以一直保持你慢慢拨开关都没事脉冲必须靠时序电路自动产生因为人手拨开关根本拨不出一个足够窄的上升沿。这也是为什么这份设计必须有时序部分——T2、T4 两相时序一个负责出脉冲、一个负责切换电平。3. 运算器与存储器子电路74LS181 和 6116 的控制信号怎么给3.1 两片 74LS181 拼 8 位S3~S0、M、CN 怎么配74LS181 是 4 位 ALU做 8 位就得两片级联低 4 位那片的进位输出接到高 4 位那片的进位输入两片的 S3~S0 和 M 并联由同一组控制位驱动。这样一条微指令就能同时决定 8 位运算的功能。功能选择的关键是这几位S3~S0 选运算类型这片子的选择输入是低有效所以微指令里写 0 才表示“选中”M 决定算术还是逻辑高电平走逻辑运算低电平走算术运算CN 表示进位输入。常见组合我整理成下面这张表注意极性实际以你手上那份数据手册的功能表为准仿真模型偶尔也会有出入运算S3 S2 S1 S0MCN说明A 加 B1 0 0 101课设里的 ADD 走这一组A 减 B0 1 1 001结果带借位补偿需要再看进位标志A 与 B1 0 1 11x逻辑运算时 CN 无关A 或 B1 1 1 01x同上A 异或 B0 1 1 01x逻辑异或A 取反0 0 0 01x单目运算B 端数据无所谓这张表的价值在于把 ADD 换成 SUB、AND、OR、XOR、COM硬件一根线都不用动只改微指令里那 6 个位。这也是这份课设天然留出的扩展口最后一章会展开。写微代码时我习惯用宏把控制位定义出来避免直接写一串 0 和 1/* 微指令控制字段按位定义位号与设计中的 MD 字段顺序一致 */ #define MD_SWBUS (1u 25) /* 开关数据上总线 */ #define MD_EDR1 (1u 24) /* 打开 DR1 写允许电平 */ #define MD_EDR2 (1u 23) /* 打开 DR2 写允许电平 */ #define MD_M (1u 22) /* ALU 方式1 逻辑0 算术 */ #define MD_S3 (1u 21) /* ALU 功能选择低有效 */ #define MD_ALUO (1u 16) /* ALU 结果送总线低有效 */ #define MD_MW (1u 4) /* 存储器写允许低有效 */ #define MD_MCS (1u 3) /* 存储器片选低有效 */ /* ADD R0,R1 的执行微指令算术加法结果送上总线写回 R0 */ #define UCODE_ADD_EXEC (MD_S3 | MD_ALUO) /* 其余位按寄存器选择补齐 */这段代码不是拿来编译运行的它的作用是让微指令表变得可读。用宏之后一条微指令写出来是“打开了哪些源、打开了哪些写允许”而不是0b0110...。参数上要特别注意有效电平/ALU-O、/EDR1、/WR 这些都是低有效宏里如果直接按“置 1 表示打开”写最后填到 ROM 里就得整体取反这一步非常容易翻车建议宏名和实际位值严格对应仿真里的电平宁可多写一行注释。3.2 D1CK、D2CK 与 /EDR1、/EDR2 的配合运算器这一块最容易被误解的组合就是“写允许 打脉冲”。/EDR1、/EDR2 是电平原语它只表示“我允许数据进入 DR1 或 DR2”真正把数据锁进去的是 D1CK、D2CK 这两个上升沿。只给电平不给脉冲暂存器里永远是上一次的值你在仿真里会看到 ALU 输出一直是老数据怎么改 S3~S0 都不对。所以 IN R0,#DATA1 这条指令要拆成两条微指令第一条把开关数据送上总线SW-BUS 有效、打开 DR1 写允许/EDR1 低、同时选通目标寄存器然后在 T2 相给出 D1CK 脉冲数据落进暂存器第二条把写允许关掉。原稿的调试步骤里专门有“再给 T2 一个脉冲关闭寄存器写端”这一步就是在补第二拍。很多人觉得这一步多余其实不然写允许一直开着后面任何一次总线上的数据抖动都会灌进寄存器尤其是你手拨开关的时候抖动是常态。寄存器组那边同理/WR 是写允许RCK 是写脉冲两个都得给。/RD 是读允许只给电平就行因为读是把寄存器内容打到总线上不需要锁存。这种“写要脉冲、读只要电平”的不对称是单总线结构里最典型的规律。3.3 存储器/CS、/WM、/RM、EMCK 的读写时序6116 是 2K×8 的静态 RAM11 根地址线而这份设计里地址总线只引出了 AB[0..7] 八根等于只用了 256 字节的空间。对课设来说够用但心里要有数这是个简化不是片子只有这么大。存储器相关信号四个/CS 片选、/WM 写允许、/RM 读允许、EMCK 写入脉冲。写一个字节的正确顺序是先把地址稳定在 AB[0..7] 上再拉低 /CS 选中片子然后拉低 /WM 并给出 EMCK 上升沿数据在上升沿被锁进去最后依次释放 /WM、/CS。顺序错了就写不进去或者写进去一个错值。读的时候更简单地址稳定、/CS 低、/RM 低数据就出现在 D[0..7] 上全程不需要脉冲。这里有个细节值得单独说/RM 和 /WM 绝对不能同时有效。仿真里两个都拉低6116 模型的行为是不确定的有的模型输出高阻有的模型直接把总线拉成某个值表现出来就是“程序跑到访存就乱”。所以微指令里这两位要互斥写的时候读关掉读的时候写关掉。我在微代码表里给每条访存指令都单独留了一条“关闭写/关闭读”的微指令就是这个原因。3.4 脉冲信号和电平信号一张表分清题目明确要求区分脉冲和电平这不是文字游戏它直接决定了你的调试方式。电平信号你可以用面板上的开关慢慢拨拨完停下来看灯脉冲信号必须由时序电路给你手拨出来的“脉冲”宽度可能是几百毫秒也可能带着抖动逻辑上不可靠。信号类型有效条件调试方式SW-BUS、/ALU-O、PC-O、IR2-O电平低有效选通类拨开关保持即可/EDR1、/EDR2、/WR、EIR2、ELP电平低有效写允许拨开关保持S3~S0、M、CN、SA、SB电平见功能表拨开关保持/CS、/WM、/RM电平低有效拨开关保持注意互斥D1CK、D2CK、RCK、PCK、EMCK脉冲上升沿由 T2 时序自动产生PC1、CLR脉冲/电平上升沿 / 高有效PC1 走时序CLR 手动把这张表贴在屏幕边上调试的时候少走很多弯路。凡是“拨了开关没反应”的先看这个信号在表里是电平还是脉冲如果是脉冲那拨开关本来就不该有反应。4. 微程序控制器从汇编指令到微指令码点4.1 微指令格式26 位控制字段加 6 位下地址控制器这块的核心是一张微指令表。这份设计的微指令字长 32 位高 26 位是控制字段 MD[0..25]低 6 位是下地址字段 MB[0..5]所以微程序最多能放 64 条微指令足够跑通课设里的几条指令。控制字段的排布大致是MD25 起是 SW-BUS、EDR1、EDR2、M、S3、S2、S1、S0、CN、ALU-O再往后是 SB、SA、WR、读寄存器、PC1、ELP、PC-O、EIR2、IR2-O、MW、MCS、EIR1、P 等。不同版本的表格位号可能差一两位动手前先拿自己的表对一遍不要照抄别人的位号。下地址字段的作用是“这条微指令执行完下一条去哪儿”。它有两种典型写法顺序执行时直接写下一条的地址遇到分支比如指令译码后跳转到对应指令的微程序入口时由指令操作码拼出地址。课设规模不大一般用后者IR 里的操作码经译码后作为微地址的高几位低位由微程序计数器给这样一条机器指令就能定位到自己那段微程序。微指令的存储介质在 Proteus 里通常用 EEPROM 模型比如 2816、28C16 这类把 32 位拆成 4 个字节按地址烧进去。原稿里 CM1CM4 用DB伪指令一行写 8 位一条微指令正好四行这个写法不是随便定的它就是 ROM 的内容格式。4.2 逐条拆IN R0,#DATA1 为什么要两条微指令拿最简单的输入指令开刀。IN R0,#DATA1干的事是把开关上的 8 位数据读进 R0。拆成微操作是开关数据送上总线SW-BUS 有效选通 R0SA/SB 指向 R0打开寄存器写允许/WR 低打开 ALU 暂存器 DR1 的写允许/EDR1 低准备后续运算T2 相给 D1CK 或 RCK 脉冲把总线数据锁进目标关掉写允许释放总线。所以它至少占两条微指令一条“开写 打脉冲”一条“关写”。原稿的调试步骤第 1、2 步就是这两条“给 T2 一个脉冲信号并且给 T4 脉冲将输入的数据打入寄存器 R0 中”“给 T2 一个脉冲关闭寄存器写端”。IN R1,#DATA2完全一样只是把 SA/SB 换成 R1。这里有个容易忽略的点第一条微指令里的 SW-BUS 是电平必须在脉冲来之前就稳定。如果你让 SW-BUS 和脉冲在同一拍产生仿真里可能采到上一个值。稳妥的做法是让电平在 T4 相切换脉冲在 T2 相产生两相错开。4.3 ADD R0,R1 的数据流向与微代码加法是这份设计的重头戏也是微指令条数最多的一条。数据流向是R0 和 R1 的内容分别送进 ALU 的两个暂存器 DR1、DR2ALU 做加法结果经总线写回 R0。原稿记录的过程拆得很细步骤动作关键控制信号1关总线选 R0打开 DR1 写SW-BUS 无效SB/SA 选 R0/EDR1 有效2关 DR1 写/EDR1 无效3选 R1打开 DR2 写SB/SA 选 R1/EDR2 有效4关 DR2 写/EDR2 无效5ALU 执行加法结果上总线写回 R0S3~S0 1001M 0CN 1/ALU-O 有效/WR 有效6关寄存器写/WR 无效对应到汇编层面程序长这样; 两数相加并输出DATA1/DATA2 由开关给出 IN R0, #DATA1 ; 读第一个操作数到 R0 IN R1, #DATA2 ; 读第二个操作数到 R1 ADD R0, R1 ; R0 R0 R1结果写回 R0 OUT R0 ; 把 R0 内容送显示灯 HLT ; 停机这段程序的参数说明很短DATA1、DATA2 是开关输入的立即数R0、R1 是通用寄存器ADD 走的是 74LS181 的算术加法模式。注释里我特意标了每个操作数从哪来、到哪去因为调试的时候你盯着灯看唯一能判断对错的就是“这一步总线上应该是谁”。第二条程序用到了存储器IN R0, #DATA1 ; 读第一个数 STA (01H), R0 ; 存入存储单元 01H IN R0, #DATA2 ; 读第二个数 MOV R1, (01H) ; 从 01H 取回第一个数 ADD R0, R1 ; 相加 OUT R0 ; 输出结果这条程序多了 STA 和 MOV等于多验证了存储器的写和读两条通路是整机联调时最有价值的一段测试。跑通它说明数据总线、地址总线、存储器读写时序、微地址跳转都是对的。4.4 微代码数组的落地把微指令表写成数组便于对照和生成 ROM 内容。原稿里的写法是一条微指令四行DB我这里用 C 数组表达同样的结构/* 一条微指令 32 位高 26 位控制字段低 6 位下地址 */ typedef unsigned int u32; const u32 ucode_add[] { /* 取 R0 到 DR1选 R0、开 DR1 写下地址指向下一条 */ (MD_SWBUS_OFF | MD_EDR1 | SA_R0 | SB_R0 | NEXT(0x08)), /* 关 DR1 写 */ (MD_SWBUS_OFF | SA_R0 | SB_R0 | NEXT(0x09)), /* 取 R1 到 DR2 */ (MD_EDR2 | SA_R1 | SB_R1 | NEXT(0x0A)), /* 关 DR2 写 */ (SA_R1 | SB_R1 | NEXT(0x0B)), /* ALU 加法结果上总线并写回 R0 */ (MD_S3 | MD_ALUO | MD_WR | SA_R0 | SB_R0 | NEXT(0x0C)), /* 关写允许微程序结束 */ (SA_R0 | SB_R0 | NEXT(0x00)), };代码里的NEXT()就是 MB 下地址字段SA_R0、SB_R0是寄存器选择位的组合MD_*是上一章定义的字段宏。参数上要盯住两件事一是每条微指令的下地址必须指向正确的下一条中间漏一条整段就跑飞二是最后一条的下地址要么回到取指入口要么指向停机循环不能随手写 0 了事——0 通常就是取指入口等于让程序从头再跑一遍表现出来就是“结果对了但灯一直闪”。5. 整机联调与常见问题排查5.1 时序分配T2 管打T4 管选这份设计只用了两相时序T2 和 T4。我的理解是T4 负责切换电平类控制信号选通、写允许、ALU 功能位T2 负责产生打入脉冲D1CK、D2CK、RCK、PCK、EMCK。两相错开的好处是电平先稳定脉冲后到达采样不会踩在跳变沿上。节拍安排上一条微指令占一个或两个时钟周期。简单指令比如关写允许一个周期就够访存和 ALU 运算这类需要“先建立后打入”的就给两个周期。原稿的调试记录里反复出现“给 T4 脉冲打开……再给 T2 脉冲打入……”就是这套约定的直接体现。真正上整机之后T2、T4 由时序电路自动产生你只需要按“启动”按钮。手动拨开关的阶段是把时序拆开、一步一步验证两者不能混着来。5.2 用开关模拟控制信号的十三步操作手动调试阶段把时序电路断开用拨动开关逐个给控制信号按原稿记录的顺序走一遍 ADD给 T2 脉冲、T4 脉冲把开关数据打入 R0给 T2 脉冲关闭寄存器写端给 T2、T4 脉冲把第二个数据打入 R1给 T2 脉冲关闭寄存器写端给 T2 脉冲关闭总线SW-BUS 无效给 T4 脉冲打开寄存器读默认先读 R0给 T4 脉冲打开运算器写给 T2 脉冲把 R0 打入 ALU 暂存器 DR1给 T4 脉冲关闭运算器写端给 T4 脉冲打开运算器写、改选 R1给 T2 脉冲把 R1 打入 DR2给 T4 脉冲关闭运算器写和寄存器读给 T4 脉冲打开 ALU 输出读出运算结果给 T4 脉冲打开寄存器写给 T2 脉冲把结果写回 R0给 T4 脉冲关闭寄存器写再打开显示读 R0 内容。这一串操作看着啰嗦但它把“一条指令在硬件上到底发生了几件事”摊开给你看。做完一遍你对微指令表的理解会完全不一样。结果对上了58H 加 58H显示 D0H说明运算器和总线通路没问题。5.3 常见问题排查五条踩坑记录现象一数据打不进寄存器灯一直不亮。原因只给了 /EDR1 或 /WR 这样的写允许电平没给 D1CK、RCK 脉冲。允许是“门开着”脉冲才是“人进去”。 解决用示波器或者 Proteus 的逻辑探针看脉冲脚有没有上升沿确认 T2 相确实产生了脉冲而不是被 T4 的电平盖住。现象二加法结果总是差 1或者全 F。原因CN 的极性搞反了。原设计里 CN 记为高电平表示有进位输入而 74LS181 本身的进位输入是低有效中间如果没加反相就等于一直在借位。 解决按你手上片子的功能表确认极性在子图里加一级反相或者直接在微指令里把这一位取反。这个坑很典型改完记得把减法也重测一遍。现象三写进存储器的数读出来不对。原因/WM 和 /RM 同时有效或者地址还没稳定就给了 EMCK 上升沿。 解决写时序严格按“地址 → /CS → /WM → EMCK 上升沿 → 释放”走读写允许互斥。仿真里可以把地址总线加个探针确认上升沿那一刻地址是对的。现象四程序跑飞PC 乱跳。原因PC1 和 ELP 同时有效。ELP 是让 PC 装入新值PC1 是让计数器加一两个一起来装进去的值下一秒就被加了 1。 解决这两位在微指令里必须互斥跳转指令用 ELP顺序执行用 PC1别图省事一起给。现象五微代码烧进去机器不执行。原因字节序或位序反了。Proteus 的 ROM 模型按字节寻址一条 32 位微指令占四个地址你把高字节放低地址还是低字节放低地址结果完全不同。 解决固定一种约定写进注释用脚本统一生成 hex别手工一行行敲。手工敲过一次 64 条微指令之后我就再也没手敲过。6. 二次开发把加法机扩成一台能算加减与逻辑的机器6.1 改指令集只动 S3~S0 和 M 位这套设计最舒服的地方在于扩展运算能力几乎不用动硬件。ADD、SUB、AND、OR、XOR、COM 的差别全在 74LS181 的 6 个功能位上你在微指令表里给每种运算各开一段微程序译码器把操作码映射到不同的微地址入口硬件一根线都不用改。原稿在 ADD 后面特意注了SUB、AND、OR、XOR、COM其实已经把这个扩展口留出来了。具体做法先扩充操作码字段比如原来 4 位操作码只用了 3 个编码现在把 SUB0100、AND0101、OR0110、XOR0111、COM1000 填进去再在微指令表里复制 ADD 那段六条微指令只改第 5 条的 S3~S0 和 M最后把译码器的输出接到微地址高位。验证方法很简单拿同一组数先算 ADD 记下结果再算 SUB两个结果应该满足 AB 与 A-B 的关系AND、OR、XOR 用 0FH 和 0F0H 这种边界值试能一眼看出对不对。6.2 微代码自动化一个几十行的脚本手工维护微代码表是二次开发最大的阻力。我一般会写个小脚本把“字段名 位宽”的描述转成 32 位十六进制再输出成 Proteus 能直接加载的 hex# 把微指令字段拼成 32 位字并按字节输出 hex FIELDS [(SWBUS, 1), (EDR1, 1), (EDR2, 1), (M, 1), (S3, 1), (S2, 1), (S1, 1), (S0, 1), (CN, 1), (ALUO, 1)] def pack(bits: dict, next_addr: int) - int: word 0 # 按字段顺序从高位往低位拼缺省补 0 for name, width in FIELDS: word (word width) | (bits.get(name, 0) ((1 width) - 1)) word (word 6) | (next_addr 0x3F) # 低 6 位是下地址 return word def to_hex(words): out [] for w in words: # 一条微指令 4 字节高位在前与 ROM 地址递增方向一致 out.append(:.join(f{(w s) 0xFF:02X} for s in (24, 16, 8, 0))) return out ucode [pack({EDR1: 1, S3: 1}, 0x08), pack({ALUO: 1}, 0x00)] print(\n.join(to_hex(ucode)))脚本的输入是字段名到 0/1 的映射输出是每行四字节的十六进制。参数上要注意两点FIELDS的顺序必须和微指令表的位序严格一致错一位整张表就废了下地址只占 6 位超过 63 的地址会被截断微程序写太长就会绕回开头表现出来是“跑着跑着又从头开始”。这类问题在仿真里极难看出来用脚本生成至少能保证格式一致。我现在的习惯是微指令表永远只维护字段描述不维护二进制。每次改指令集改完描述跑一次脚本生成的 hex 直接拖进 Proteus 的 ROM 模型再对着显示灯核一遍结果。从那以后我每次搭总线型电路都先把所有控制信号列成一张“脉冲/电平”两列表再动手连线——省下来的时间远比列表本身多。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?