ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用Proteus从零搭建8位简易CPU:寄存器、ALU与控制逻辑全解析

用Proteus从零搭建8位简易CPU:寄存器、ALU与控制逻辑全解析 说实话第一次在Proteus里把寄存器、运算器、PC和存储器全部用实打实的芯片连起来并且看到内存里的程序真的跑出结果时我盯着屏幕看了好一会儿。不是因为它多快而是因为电路里每一根线的电平变化都在告诉我CPU就是这么取指、译码、执行的所谓“程序运行”并不是黑盒子里的魔法而是一堆三态门和触发器在时钟节拍下老老实实地搬数据。这篇文章记录我最近一次用Proteus从零搭建8位简易CPU的全过程。我会把架构设计、芯片选型、控制逻辑、时序联调以及踩过的坑都掰开讲目标是让你照着思路也能在Proteus里搭出一个能执行简单指令的“真·CPU”。适合计算机组成原理刚开始上课的同学、准备课程设计或毕业设计的工科生以及任何在网上看了无数“CPU工作原理图”但还是想亲手把它做出来的朋友。1. 项目概述与整体架构设计思路1.1 为什么非要从零搭一个CPU现代FPGA和开发板已经能很容易跑一个软核处理器Keil里写几行C代码编译下载后单片机就在工作。但这种“工作”对学习者来说太透明了你很难看到指令到底怎么从内存出来、怎么拆成操作数、怎么在ALU里完成加法。用Proteus搭简易CPU本质上是把计算机组成原理教材里的数据通路图变成一张可以通电的电路图。你可以用鼠标点一下单步时钟看着IR寄存器锁存了一条指令看着PC加1看着ALU输出一个数最后一步把结果写回内存。整个过程和芯片内部真实发生的动作一一对应这种“可见性”是任何仿真软件里的成熟CPU内核都替代不了的。我这次设计的目标很明确不做复杂指令集不做流水线更不做操作系统能跑的级别只求把取指、译码、执行、访存这几个最基本的指令周期走通。在此基础上尽量让结构模块化后续想加指令、加寻址方式都可以留着扩展口。1.2 我的目标架构单总线8位CPU我最终选定的架构是8位数据总线8位地址总线程序和数据共用一片RAM。核心模块包括程序计数器PC2片74LS163级联成8位负责给出当前指令的地址。地址寄存器MAR1片74LS273锁存PC或指令中的地址去驱动RAM的地址线。RAM存储器1片6116静态RAM模型存放程序指令和运行数据。指令寄存器IR1片74LS273取指阶段锁存从RAM读出来的指令字节。累加器A与运算器ALUA寄存器保存操作数ALU负责加减运算结果写回A。控制逻辑与节拍发生器由计数器加分频和门电路构成产生各个寄存器的装载/使能信号。整体采用单总线结构所有模块的数据输出通过三态缓冲器挂到同一条数据总线上地址单独走MAR到RAM数据和地址分离开控制起来更直观。单总线和多总线的选择我特意考虑过。多总线可以让多个模块同时输出性能更好但连线复杂程度成倍上升对仿真调试极不友好。单总线虽然同一个时刻只能有一个数据源驱动总线但对入门级的CPU来说完全足够而且每一条控制信号的职责都非常清楚——谁在占用总线信号灯一目了然。做课程设计这个方案性价比最高。1.3 指令集与工作流程设计我设计了一个非常简化的指令集一共4条每条指令1个字节格式是高3位为操作码低5位为直接地址因此最多可寻址32个存储单元。指令表如下助记符操作码含义功能说明LDA000加载A M[addr]ADD001加法A A M[addr]STA010存储M[addr] AJMP011跳转PC addrHLT100停机停止时钟运行整个CPU的工作流程分两个阶段取指阶段和执行阶段。取指阶段PC输出的地址经MAR送RAM读出指令字节后锁存到IR同时PC自动加1指向下一条指令执行阶段根据IR高3位的指令类型把低5位地址再经MAR送RAM然后按LDA、ADD、STA、JMP各自的控制逻辑操作数据总线。HLT停机是通过一个T触发器和时钟门的组合把时钟信号封住仿真就能停住这也是下一步在Proteus里跑通整机最直接的验证手段。2. 核心模块原理与Proteus元件选型2.1 寄存器一片74LS273搞定锁存内部寄存器在Proteus里用74LS273最顺手。74LS273是8位D触发器带公共时钟CLK和公共清零CLR上升沿锁存输入。A寄存器、IR寄存器、MAR寄存器都可以用它。很多人一开始纠结D触发器输出会不会在锁存瞬间出现不稳定实际只要保证数据在上升沿到来之前已经稳定锁存进去的结果就是可靠的。所以控制信号设计时有个原则先开三态门让数据上总线等一段时间再给上升沿CLK。这在电路上可以通过门电路的传播延迟自然实现但在Proteus仿真里传播延迟非常小有时需要人为加RC延迟或用单步时钟来观察。IR寄存器的输入连接数据总线输出接指令译码逻辑。这里要特别提示IR的输出端最好接74LS244之类的缓冲器再送译码电路否则IR输出端直接作为控制逻辑输入负载较重时会影响总线上的微弱电平。Proteus虽然是仿真但负载问题依然可能导致逻辑错误最好按真实电路的习惯来设计。2.2 运算器74LS181的级联与功能选择ALU是整个CPU里最有“技术含量”的芯片。74LS181是一片4位ALU支持16种算术运算和16种逻辑运算通过M引脚和S0-S3引脚组合选择功能。8位运算需要两片级联低4位片的进位输出C_n4接到高4位片的进位输入C_n。在本项目中我们只用到两个功能直通A把总线上读到的数据直接传给累加器和AB。选择方式如下以数据手册功能表为准AB加法M0S3-S01001直通B或A传送按数据手册对应功能选择核心是让ALU输出等于总线输入不参与实际运算为简洁实际接线时可以把S3-S0和M作为固定电平输入用一组跳线或逻辑开关选择功能。这样虽然不够“自动”但调试时特别方便加法还是直通拨一下开关就知道相当于给ALU加了一个手动模式。另外要提醒74LS181的输出是集电极开路风格实际是推挽输出但某些接法需要配合上拉电阻在Proteus模型里一般不需要额外上拉但如果你物理搭板务必查手册确认输出结构。仿真是从简的实物要按真实电气特性处理。2.3 程序计数器PC74LS163的计数与装载PC需要两个能力自动加1以及在JMP指令时装载跳转地址。74LS163是4位同步二进制计数器带同步装载LD和同步清零CLR。两片级联即可构成8位计数器。74LS163的装载是同步的即LD有效后要在时钟上升沿才把D0-D7数据装入。这一点非常关键因为JMP指令的控制时序必须是先把跳转地址送上数据总线再拉低PC的LD信号然后在下一个时钟上升沿完成装载。如果LD信号过早消失地址没被锁存进去程序就会“乱飞”。我实际设计时用了一个技巧JMP指令执行阶段把IR的低5位直接经三态缓冲器送数据总线同时产生PC_LD有效信号再配合节拍发生器的时钟沿完成PC装载。因为PC的时钟和系统时钟共用所以装载发生的时机天然和总线上的地址稳定时间对齐。2.4 存储器与地址通路存储部分我选Proteus内置的6116RAM模型2K×8引脚包括地址A0-A10、数据D0-D7、片选CE、写使能WE、输出使能OE。对这个简易CPU实际只用低8位地址线和低8位数据线高地址位接地。RAM的读操作条件是CE0且WE1且OE0此时数据出现在D0-D7上写操作条件是CE0且WE0数据从D0-D7写入地址指向的单元。三根控制线由控制逻辑统一给出。地址通路的设计思路是MAR作为中央地址锁存器。取指阶段PC输出地址经三态缓冲器传数据总线MAR装载执行阶段IR的低5位经三态缓冲器传数据总线MAR再次装载对带地址访问类指令而言。MAR的输出直接接RAM地址线。这样一个MAR顶住了地址总线所有地址来源都通过数据总线复用结构非常干净。这种设计的缺点是地址和数据共用总线后MAR必须在数据冲突之前锁存地址控制时序要精确设计。好处是省掉了独立地址总线的连线在入门级CPU里非常常见。3. 完整搭建过程3.1 新建工程与元件放置打开Proteus 8 Professional新建一个Design文件。我建议在File菜单里选择New Project后不要选择“Firmware Project”直接选“Schematic”空工程因为我们不写固件只画纯数字电路。元件搜索路径如下在Device选择窗口直接输入名称搜索74LS2738位D触发器74LS1634位同步二进制计数器74LS1814位ALU74LS244八路三态缓冲器74LS1383-8译码器61162K×8 RAMCLOCK时钟信号源LOGICSTATE手动高/低电平LOGICPROBE电平探针LED-RED、LED-YELLOWLED显示7SEG-BCD或7SEG-MPX4-CC数码管可选放置元件后先把所有芯片的VCC和GND通过隐藏电源端子接好。Proteus默认不显示电源引脚双击芯片在Power Pins里能看到默认连接的是VCC/GND网络只要确认即可。连线建议先画一条横向总线路由把所有芯片的数据引脚通过Label标签连到对应总线网络。Proteus中总线可以画成粗线再用Wire Label定义网络名例如DB0-DB7为数据总线AB0-AB7为地址总线。3.2 数据通路连接与三态缓冲数据总线是整个CPU的核心。我划分了以下数据源每个都通过三态门接入DB0-DB7RAM数据输出D0-D7 → 经74LS244缓冲后接入。RAM读时打开。ALU输出 → 经74LS244缓冲后接入。ALU运算结果需要写回A寄存器或经总线传走。A寄存器输出 → 经74LS244缓冲后接入。执行STA指令时累加器内容经总线写RAM。PC输出 → 经74LS244缓冲后接入。取指阶段把PC值送MAR。IR低5位输出 → 经三态门接入。执行LDA/ADD/STA/JMP时把地址部分送MAR。每个三态门的OE输出使能引脚由控制逻辑单独控制同一时刻最多只能有一个使能有效。地址侧则相对简单MAR的输入端直接接数据总线DB0-DB7输出端接RAM的A0-A7。RAM的D0-D7直接接数据总线DB0-DB7RAM本身内部有三态输出读使能时输出写使能时输入。这个结构画出来其实不复杂真正花时间的是保证每一个使能信号都在正确的时间到达正确的引脚。我的建议是先分模块仿真验证不接控制逻辑用LOGICSTATE手动拉高/拉低每个使能端观察总线电平是否符合预期。先把通路调通再接控制逻辑否则全连好后一旦出错排查会很痛苦。3.3 控制逻辑设计节拍发生器与指令译码这是整个项目最核心也最容易卡住的部分。我的控制逻辑分两级节拍发生器和信号组合。节拍发生器我用的方案是74LS163计数器 74LS138译码器构成4节拍循环。计数器从0计数到3输出Q0、Q1接138的A、B输入138的Y0、Y1、Y2、Y3分别代表T0、T1、T2、T3四个节拍。计数器的时钟就是系统时钟计到3后自动清零。之所以要节拍发生器是因为一条指令最少要两个节拍T0取指T1执行。更复杂的指令可能需要T2、T3。我的指令集里所有指令都可以在T0加T1内完成JMP需要稍微调整一下T1的控制信号集合所以两个节拍完全够用。控制信号由IR的高3位经138译码后得到指令有效信号I_LDA Y0_LDA操作码000I_ADD Y1_ADD操作码001I_STA Y2_STA操作码010I_JMP Y3_JMP操作码011I_HLT Y4_HLT操作码100然后每个控制信号就是一个组合逻辑表达式例如PC_BUSPC输出到总线使能 T0MAR_LMAR装载使能 T0 (T1且(I_LDA或I_ADD或I_STA或I_JMP))RAM_RRAM读 T0 T1且(I_LDA或I_ADD)RAM_WRAM写 T1且I_STAIR_LIR锁存 T0在RAM_R有效后一拍锁存PC_INCPC加1 T0之后实际计数器的计数使能ENP和ENT在T0之后拉高一个周期PC_LDPC装载 T1且I_JMPA_LA寄存器锁存 T1且(I_LDA或I_ADD)ALU_BUSALU输出到总线使能 T1且I_ADDLDA时直接RAM_DATA经总线到A的输入路径需要说明的是IR_L锁存发生在T0节拍RAM读出的数据经过缓冲器到达数据总线然后IR在数据稳定后锁存。这里有个细节IR_L直接等于T0节拍信号和RAM_R并行在Proteus仿真中由于门电路延迟一般能正确工作。但如果发现IR锁存的指令偶尔不对可以在IR_L和T0之间加一个小的RC延迟脉冲电路效果会明显改善。PC计数器的时序也要仔细T0取指完成后PC应该在下一个上升沿加1。因为74LS163的计数使能ENP、ENT在T0结束时拉高再下一个上升沿到来时完成加1。我直接用T0信号的逻辑非控制ENP部分这样T0结束后自动计数实测很稳。控制信号真值表如下控制信号T0T1(LDA)T1(ADD)T1(STA)T1(JMP)PC_BUS10000MAR_L11111RAM_R11100IR_L10000PC_INC10000RAM_W00010A_L01100ALU_BUS00100A_BUS00010PC_LD00001上表是设计蓝图。实际接线时我强烈建议把每一路控制信号用探针接到Logic Probe观察它的时序是否正确再接目标芯片这样调试效率高很多。3.4 程序加载与运行验证搭建完成后往RAM里写入一段测试程序。我写了一个非常简单的测试读取地址0x03的数值加上地址0x04的数值结果保存到地址0x05。RAM初始化有两种方式。一种是在Proteus的RAM组件属性里找到Initial Contents选择Edit手动输入十六进制数据另一种是生成一个内存初始化文件格式为十六进制文本一行一个字节然后用Proteus的Memory Contents工具导入。建议先用手动方式把测试程序字节按地址写好地址数据含义0x000x03LDA 0x030x010x23ADD 0x030x020x45STA 0x050x030x05操作数50x040x03操作数3上面的地址编码对应指令格式LDA指令操作码000低5位地址000113即指令字节0x03ADD操作码001低5位地址3即0x23STA操作码010低5位地址5即0x45。跟我列的RAM内容一致。运行仿真使用单步时钟模式点击一次时钟给一个上升沿。按我设计的节拍第一个上升沿T0节拍PC0x00送上地址总线MAR锁存0x00RAM读出0x03IR锁存0x03PC加1变为0x01。第二个上升沿T1节拍IR高3位000执行LDAIR低5位0x03送MARRAM读出0x05A寄存器锁存0x05。第三个上升沿T0节拍PC0x01取指ADD指令0x23PC加1变为0x02。第四个上升沿T1节拍IR高3位001执行ADDIR低5位0x03送MARRAM读出0x05ALU计算0x050x030x08A寄存器锁存0x08。第五个上升沿T0节拍PC0x02取指STA指令0x45PC加1变为0x03。第六个上升沿T1节拍IR高3位010执行STAA寄存器内容0x08经总线和RAM_WE写入地址0x05。单步走完可以用RAM内容查看器看到地址0x05变成0x08也可以用LED挂在A寄存器输出端看到LED显示8二进制00001000。跑通的那一刻整个数据通路和指令流就在眼前“活”了起来。4. 常见问题与排查技巧实录4.1 总线冲突最恶心的“玄学”问题我调试过程中遇到最多的问题就是总线冲突。现象是明明每个模块单独测试都没问题一连起来数据就乱LED乱跳甚至RAM里的数据被莫名改写。排查思路非常直接看同一个时刻数据总线上有几个三态门被打开。用逻辑探针分别接每个三态门的OE引脚单步走一遍看是否出现多个OE同时有效。出现冲突时往往不是控制逻辑本身错而是两个使能信号来自同一个组合逻辑表达式例如PC_BUS在T0时一直是高但MAR_L在T1也会打开两个输出源会撞在一起。避免方法设计控制逻辑时明确“总线仲裁”原则。每写一个使能表达式都要问一句这个信号为1的时隙里还有没有别的使能也为1在真值表里把每一条控制信号的取值先全部填好再连电路能省掉90%的调试时间。4.2 时序毛刺与锁存不稳定Proteus仿真中的毛刺往往来自信号竞争。典型场景MAR锁存和RAM读使能同时有效RAM地址还没稳定OE就拉低导致RAM输出端出现瞬时不定态。解决这类问题我的经验是把“先地址后读”的时序用信号延迟体现出来。具体操作让MAR_L的上升沿在T0时钟沿之后RAM_R信号再稍微晚一点生效。在Proteus里可以用两个反向器串联做短延迟或者用一个RC低通滤波电路给RAM_R降压使RAM_R比MAR_L晚几十纳秒到达。这个技巧真实电路里也很有用。另外IR_L信号务必在RAM输出稳定后再给。我踩过一次坑T0节拍一开始就拉高IR_L结果RAM数据还没到IR就锁存了上一周期残留的垃圾数据。后来加了一个1个反向器延迟IR_L延后半拍问题立刻消失。4.3 存储器初始化失败的坑RAM内容导入要看仔细。Proteus的6116模型默认内容全FF很多人直接双击RAM输入十六进制数据格式写错会导致加载失败。我用过的方式是在RAM属性中找到Memory Contents选择Edit在弹出的编辑器里用“00 03 23 45 05 03”这样以空格分隔的十六进制格式填写注意每行地址按16字节一组显示不要把偏移地址当作数据写进去。还有一个容易忽略的点RAM的高地址位A8-A10如果悬空默认电平可能不确定导致RAM内部地址映射错乱。我建议把A8-A10全部接到GND强制只能访问低256字节。同理CE引脚要保证连接到有效电平别让它意外悬空。4.4 其他Proteus调试技巧仿真步长设置如果发现信号毛刺特别多把仿真步长从默认的50ms调小到10ms或5ms时序图会更清晰。使用逻辑分析仪Proteus内置Logic Analyzer可以直接接数据总线和控制信号同时观察多路信号时序比一次看一根探针高效得多。分模块调试强烈建议做“半成品测试”——先把PC、MAR、RAM、IR这部分取指通路搭出来跑通取指流程再往下接ALU和A寄存器。一次全搭好再排查十个问题叠在一起会让人崩溃。HLT指令实现用一个T触发器当执行到HLT时把门控时钟信号封锁。Proteus里可以直接用开关模式或者用AND门把时钟和触发器的Q非相连最省事。5. 后续扩展方向与我的建议5.1 增加指令与寻址方式当前4条指令只是一台“能动的CPU”的底线。扩展方向可以从这里入手增加立即数寻址把指令格式改成双字节第一个字节是操作码第二个字节是立即数。LDA #imm、ADD #imm这类指令对ALU的控制和现在差不多主要是取指阶段要额外取一个操作数控制逻辑要加一拍T2。增加间接寻址地址部分先取到MAR再从该地址读出真正的数据地址相当于指针。这会让控制逻辑多两个节拍但对理解“指针”这个概念非常直观。增加基本逻辑运算与、或、异或74LS181本来就支持这些只需要把S3-S0配置改成逻辑功能即可。5.2 从硬布线控制到微程序控制我这版控制逻辑是用组合逻辑来实现的也就是所谓“硬布线控制”。优点是快缺点是每加一条指令都要重新设计门电路。如果你想进一步了解CPU的控制权到底是谁在指挥可以改造为微程序控制把控制信号编码成一条条微指令存储在ROM控制存储器里每条机器指令对应一段微程序通过微地址发生器顺序执行。这个改造在Proteus里做起来其实不困难用一个ROM装微指令编码一个计数器做微地址发生器IR的输出作为微程序入口地址的高位。改完你会发现CPU的控制逻辑从一堆门电路变成了一张可以随便改写的“表格”这就是现代CPU在控制层面的核心思想。强烈推荐动手试一次。5.3 给准备动手的人的一些建议如果有人想照着做一个我的建议是不要一上来追求完整功能先从“只跑通LDA和HLT”开始。把取指通路做通看着IR锁存指令PC自动加1已经能建立起90%的体系结构感觉。之后再逐步加ADD加STA加JMP——每一步都是增量调试每一步都能看到新指令带来的行为变化这种正反馈特别适合学习。另外Proteus仿真虽然很直观但跟真实逻辑芯片还是有一个区别仿真里没有物理延迟的一致性毛刺问题往往比真实电路更明显也可能更不明显。如果你打算之后从仿真搬到面包板或者洞洞板一定在仿真阶段就养成“先稳定再锁存”的设计习惯这样实物调试会非常顺畅。我最后一次跑通这个简易CPU时正好是凌晨RAM里那个0x08静静躺在0x05地址上。那一刻我对教科书里那句“计算机是执行存储在内存中的指令序列的机器”有了真正切身的理解。所以说这个项目虽然小但我觉得它是值得每一个学计算机的人亲手做一次的东西。
返回列表