ARTICLE DETAIL

资讯详情

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

从逻辑门到CPU:手把手教你搭建一台8位教学处理器

从逻辑门到CPU:手把手教你搭建一台8位教学处理器 想把手头的电脑从“能用”拆到“能懂”最硬核也最过瘾的一条路就是自己从零开始搭一个CPU出来。别一听就慌了这个“手搓”不是让你去光刻机下造芯片而是用最底层的逻辑门一点一点拼出一台能跑程序的计算机。我前阵子刚把一个8位教学CPU完整跑通从画逻辑门符号到最后看着自己写的程序在仿真里算对结果那个瞬间的满足感比看任何架构书都来得实在。这篇文章就准备把这套从逻辑门到CPU的完整路线原原本本捋一遍适合正在学计算机组成原理、准备考计算机相关专业或者纯粹想搞清楚“CPU到底是怎么思考问题”的开发者收藏。这条路线看起来长其实是一层一层垒上去的。你不需要一开始就懂流水线、乱序执行这些复杂玩意儿只需要跟着四个阶段走先认识逻辑门再做出存储单元然后拼出运算器和控制器最后把数据通路串起来一台能跑指令的计算机就成了。1. 整体路线与核心思路1.1 为什么要从逻辑门开始而不是直接学CPU很多同学学计算机组成原理一上来就是指令周期、寻址方式、流水线结果越听越虚。原因很简单CPU不是做出来的是“长”出来的。它由一大堆功能部件组成功能部件由寄存器、ALU、控制器组成而这些部件的底层全部是寄存器堆、加法器、译码器再往下就是逻辑门。你没看错整台计算机的根基就是与门、或门、非门、异或门这几种再简单不过的东西。它们的组合能构成加法器加法器加上寄存器能构成运算器运算器加上控制器和存储器就是一台完整的计算机。这是我强烈建议你跟着走一遍手搓路线的第一个理由倒着学只会背概念顺着做才能真正通透。我自己当年学计算机组成原理时总觉得“控制信号”是个玄学直到亲手在仿真软件里搭出单总线CPU看到每个时钟沿控制器发出的那串1和0才发现原来译码、执行本来就是一张查表流程。1.2 四个阶段把大目标拆成能落地的小任务一台可以运行的计算机可以粗略拆成四个递进的实现阶段。我按这个顺序推进每个阶段都设了明确的产出物做完一个阶段再往下走心里特别有底。阶段一逻辑门与组合逻辑。先搞清楚每种门的符号、真值表、布尔表达式然后用它们搭出多路选择器、译码器、加法器。产出物能算1位加法、能选择数据通路的组合电路。阶段二时序逻辑与存储单元。从D触发器开始做寄存器、计数器、寄存器堆。产出物能保存数据、能在时钟驱动下工作的存储电路。阶段三数据通路与运算器。把寄存器堆、ALU、PC、存储器用总线连起来。产出物能按要求读取指令、计算数据、写回结果的核心部件。阶段四控制器与整机运行。设计指令集写微程序或者用硬布线状态机生成各部件控制信号。产出物能自动执行一段程序并得到正确结果的完整CPU。这个拆法也呼应了热词里最常见的“单总线CPU设计实验”“多周期MIPS CPU设计Logisim”——它们本质都是阶段三和阶段四的融合练习。1.3 工具选型为什么用 Logisim 这类仿真器我见过不少人一上来就买FPGA开发板或者真芯片不是说不行但第一次做设计我强烈建议先用仿真软件。原因特别现实调试成本低。真芯片出了问题你得拿示波器量、拿逻辑分析仪抓初学者大概率直接懵。仿真器可以随时暂停、单步、观察每根线的电平出错了能回退重连。元件库齐全。RAM、寄存器、ALU、多路选择器甚至内置的ROM都有拉出来就能用能让你把精力集中在架构设计而不是搭门电路上。反馈直观。一眼看到数据从哪个部件流到哪个部件控制信号怎么驱动总线这种可视化的理解效率是任何文字描述都比不了的。我用的是 Logisim-evolution 这个分支版本相比老版 Logisim 多了不少修复和特性支持子电路、隧道标签、时钟源频率配置手搓CPU刚刚好。2. 打好地基8种逻辑门与布尔代数2.1 八种逻辑门的符号、真值表与数学表达式这一节算是最基础的知识但我还是想按自己的理解重新过一遍。因为后面你搭的一切从加法器到ALU本质上只是在反复组合这几种门的运算规则。为了方便对照我把八种常见逻辑门整理成了表格。这里的符号我按教科书里最常用的表述来写与门是“D型门”的矩形符号逻辑表达式用乘号表示与加号表示或上划线表示非。门名称英文缩写布尔表达式真值表要点A、B为输入Y为输出典型用途与门ANDY A·B只有A、B都为1Y才为1使能控制、位屏蔽或门ORY ABA、B任一为1Y就是1信号合并、事件判断非门NOTY ¬A输入取反取反操作、逻辑求补与非门NANDY ¬(A·B)与门结果再取反万能门可搭出任何逻辑或非门NORY ¬(AB)或门结果再取反万能门RS触发器的核心异或门XORY A⊕BA、B不同则Y为1相同则为0加法、比较、校验同或门XNORY ¬(A⊕B)A、B相同则Y为1不同则为0等值比较器缓冲器BufferY A输出等于输入但起隔离/放大作用驱动增强、总线隔离画符号的时候要注意同一种门有两种表示法一种是矩形符号国家标准和IEEE都推荐输入在左、输出在右门类型写在框内另一种是“烟斗形”符号很多老教材和图里还在用看老资料时容易混淆但只要认准真值表就不会乱。举个立刻能用的例子1位半加器就是把异或门和与门同时接在A、B两个输入上。异或门的输出就是“和”与门的输出就是“进位”。这就是组合逻辑最简单的应用后面全加器、加法器都是这么扩出来的。2.2 逻辑门背后的物理实现与三态门了解逻辑门不可能绕开物理层。现实中绝大多数逻辑门用CMOS工艺实现CMOS管分PMOS和NMOS两类一个用来拉高电平、一个用来拉低电平配合起来就能实现反相器。你再把反相器组合起来就能得到与非门、或非门、传输门等等。这里最关键的物理概念是“高阻态”。普通逻辑门的输出只有0和1两种状态而三态门额外提供第三种状态——高阻态Z。高阻态的意思是输出端对外相当于断路不会主动驱动总线。这个特性在单总线CPU里太重要了因为多个部件要共享同一组数据线某时刻只能允许一个部件把数据放到总线上其他部件必须处于高阻态放行否则两路信号打架仿真器直接报红色冲突实物就直接烧毁。2.3 组合逻辑设计的小技巧动手设计组合逻辑时有几个心得特别值钱优先用与非门或或非门当万能基础件。NAND门自身就是“完备”的任何布尔函数都能只用与非门搭出来。有时候用库里最方便的门去化简比硬凑某种门更省事。MUX多路选择器是数据通路的开关。一个2选1MUX本质上就是两个与门加一个或门用一个选择信号决定哪路输入通过。CPU里的数据通路到处是MUX比如PC是选下一条地址还是选跳转地址。德摩根定律要刻在脑子里。¬(A·B) ¬A ¬B这条定律在做逻辑化简时高频出现尤其是把“与非”翻译成“或非”的时候能帮你省下一大片门电路。3. 从组合逻辑到“会记忆”的电路触发器与寄存器3.1 组合逻辑的最大短板记不住状态前面的加法器、选择器输入一变输出立刻跟着变这叫组合逻辑。可CPU不能光靠组合逻辑工作——它需要在执行完一条指令后记住当前状态到下一条指令时再更新。存储能力是CPU和纯粹计算器的本质区别。能“记住”的最小单元是触发器。最简单的版本是RS触发器它可以由两个或非门或者两个与非门交叉连接构成。RS触发器有两个输入置位S和复位R输出端Q和反相端Q当S和R同时为0时它能保持原状态不变当输入改变时输出翻转并锁存。但RS触发器有个输入组合S1且R1不允许出现使用时需要特别小心。改进后的D触发器就优雅得多一个时钟输入Clk一个数据输入D。时钟有效沿到来时输出更新为当前D的值其余时间输出保持不动。这个“边沿触发”的特性是数字电路的分水岭它让所有部件有了统一的节拍就像乐队有了指挥。3.2 从D触发器到寄存器、寄存器堆和PCn位寄存器就是n个D触发器并联共享同一个时钟每个触发器存一位。给寄存器加上“写使能”信号后写使能为1且时钟沿到来时才更新否则保持不变这在CPU里用来控制哪些寄存器该写入数据。寄存器堆则是多个寄存器组成的小型存储阵列一般有两个读端口和一个写端口。为什么需要多端口因为一条指令往往要同时读出两个源操作数送给ALU。你如果自己用触发器搭寄存器堆会发现这个“同时读两个数”的需求直接决定了内部线路的组织方式。PC程序计数器本质是一个计数器每个时钟周期加1让PC指向下一条指令。遇到跳转指令时把目标地址直接装入PC。这里有个小设计如果用加法器实现PC1就可以省掉专门的计数器芯片这也是单总线CPU实验里最常见的做法。3.3 时序逻辑设计中的高频踩坑点我自己第一次搭时序电路时被坑得最惨的就是时钟处理。你可能会遇到这几个典型问题电平触发和边沿触发搞混。如果使用高电平锁存在高电平期间输入的任何抖动都会进入寄存器最后数据像喝醉酒一样乱跳。遇到这种情况检查时钟端口属性是否是“上升沿触发”。所有寄存器必须用同一个时钟源。有人图方便给不同模块单独加时钟脉冲结果各部件节拍不一致整个CPU就像几个不同步的人在合唱怎么调都乱。别忘了复位信号。PC初值如果不初始化为0而是随机值程序一启动就会乱跑。设计电路时一定要加一个全局复位并且保证上电一瞬间PC、寄存器堆、控制器状态机都清零。4. 算得动也要算得对ALU与加法器的设计4.1 从半加器到全加器再到并行加法器加法器是ALU的核心。1位半加器只能处理两个一位数相加结果有“和”与“进位”。但你处理多位二进制数时低位的进位必须传给高位所以完整的一位加法器实际上是全加器输入是A、B还有来自低位的进位Cin输出是和S以及向高位的进位Cout。全加器的布尔表达式是S A ⊕ B ⊕ Cin Cout (A·B) (Cin·(A⊕B))多位加法器怎么搭最简单的串行加法器就是n个全加器从低到高一字排开前一级的Cout接到后一级的Cin。这种设计虽然简单直接但走一条进位链下去位数越长延迟越高速度提不上来。所以才有了超前进位加法器利用逻辑公式把进位并行计算出来这也是现代CPU里高频加法器的原型。手搓CPU时用串行加法器就好教学目的完全够用。4.2 ALU的架构与一个多功能ALU实例ALU是CPU的“计算心脏”它本质上是一个能执行多种运算的组合逻辑电路输入两个操作数A和B通过控制信号F选择运算模式输出结果Result还附带几个标志位。ZZero结果为0时置1。NNegative结果最高位为负时置1。CCarry无符号加法产生进位时置1。VOverflow有符号数加减法发生溢出时置1。一个公共教学版ALU可以支持八种运算加减、与、或、异或、非、左右移位、直通。内部实现一般是先写一个真值表然后用译码器或MUX选出不同运算结果中的一种。比如可以做一个4位控制信号分别控制加法器、与门、或门、异或门的输出最后用一个多路选择器根据高位控制线选通。4.3 设计ALU时最容易犯的错标志位的判断是我认为整个ALU设计里最烦的部分。Z标志你得用一个大的或非门把所有结果位并起来结果全是0时才输出1。V溢出标志的更严格必须检查两个操作数符号和结果符号。溢出和进位是完全不同的概念两个正数相加结果为负说明是符号数溢出最低位之外多出来一位那是无符号数溢出。很多人在这里开始犯迷糊我建议先只做无符号运算跑通后再加有符号判断。5. 存储与总线CPU和内存之间的“信息高速公路”5.1 存储器的结构地址线、数据线、控制线存储器已经不是逻辑门这个级别的部件了但它是CPU必不可少的“外挂大脑”。RAM的核心结构其实是一个“巨大无比”的寄存器阵列通过地址译码器选中某一行然后由读写控制决定把这一行读出到数据线上或者把数据线上的值写入这一行。我理解存储器连接要点时习惯把它简化成三组线地址线Address busCPU给出想访问的单元编号宽度决定能访问多少地址。比如16位地址能访问64K地址空间。数据线Data bus用于传数据双向。8位数据线一次能传一个字节。控制线Control bus读信号、写信号、时钟信号等控制什么时候读、什么时候写。这里有个特别容易混淆的点地址线是单向的CPU发出去RAM接收数据线是双向的读的时候RAM往CPU传写的时候CPU往RAM传。所以数据线必须通过三态门接入总线否则读写状态冲突时两路输出会硬碰硬。5.2 单总线结构的组织原理单总线CPU的核心特征是所有功能部件的输入输出都连接到一组公共数据总线上数据在一组线上传来传去。举个直观的画面寄存器堆、PC、ALU、内存、指令寄存器、地址寄存器它们各自通过三态门挂在同一条总线上。这种结构的好处是极其简洁清晰。一次总线操作只允许一个部件把数据放到总线上而其余部件必须把输出置为高阻态。比如要把PC的值送给MAR控制器就打开PC输出端的三态门同时确保其他设备全部“闭口”数据就顺顺利利流进了MAR。单总线的代价也很明显同一时刻只能做一个数据传递多条数据流必须串行分时进行所以一个多周期指令会被拆成好几个微步骤执行但教学演示恰恰需要这种“慢而清晰”。5.3 连接CPU和存储器时的接口细节在实际连接中给存储器加上读使能和写使能引脚后要注意时序顺序先给地址等地址稳定后再给读使能让数据总线上的数据有足够时间稳定出来。如果读和写同时拉高或者地址和数据线的方向没控制好结果就是读出垃圾数据或者写入错位。另外我习惯单独做一个MAR寄存器来锁存地址。为什么不能直接把PC或寄存器堆的输出接到RAM的地址线上因为单总线上同一时刻只允许一个数据源如果PC一边输出到地址线一边还要往总线上发下一条指令地址总线就冲突了。用一个MAR先把地址从总线锁存下来后面的RAM地址读取就用MAR里的稳定状态CPU的其他部件可以继续忙别的事。这是理解和设计单总线CPU时非常关键的一环。6. 指挥中心控制器与指令的执行流程6.1 指令从哪儿来取指、译码、执行CPU能“思考问题”靠的是按指令办事。这里的思考本质是查表加搬运从内存里取一条指令解析出操作码和操作数按操作码对应的流程去打开各种三态门、置位所用寄存器、触发ALU计算。最基本的执行流程是取指PC把地址通过总线送到MARRAM根据MAR地址读出指令指令进入IR指令寄存器。译码控制逻辑分析IR里的操作码确定这是一条加法指令还是跳转指令然后生成对应的控制信号。执行控制信号驱动数据通路完成运算比如把寄存器A和寄存器B送入ALU结果写回目标寄存器PC加1或跳转。这个过程在全周期里重复执行就是所谓“CPU的思考”。你看着它傻乎乎地按步执行会觉得一点都不神秘但它确实就是现代计算机最底层的模样。6.2 硬布线控制器和微程序控制器的思路对比控制器本身也有两种主流实现路线手搓的时候二选一。硬布线控制器就是用一个有限状态机用逻辑门直接生成控制信号。状态转换用组合逻辑和触发器实现速度很快但设计起来复杂稍改指令集就要重新改逻辑电路典型应用是RISC和精简架构。微程序控制器换了个思路把每条指令对应的控制信号序列预先写进一个叫控制存储器的ROM里每条控制信号序列叫“微指令”。CPU执行指令时不过是从ROM里逐条取出微指令再把微指令的各个位直接接到各个控制端上。这种实现方式灵活得多新增指令只需要往ROM里多写几行数据也是Logisim单总线CPU实验里最常用的一种。两种思路各有利弊硬布线执行速度快适合追求性能微程序控制结构规整适合教学、适合扩展到复杂指令。我建议第一次做单总线CPU用微程序因为逻辑门设计状态机实在太烧脑。6.3 单总线CPU微程序控制器的关键细节微程序控制器有三个核心字段理解它们就掌握了大半控制字段每一位对应一个控制信号比如“寄存器堆写使能”“PC加载”“ALU操作选择”“总线数据源选择”。这一串位就是真正的“命令”。下地址字段指定下一条微指令在控制ROM中的地址用于实现顺序执行或跳转。判别条件字段决定是否根据外部状态比如ALU的Z标志或指令操作码来改变下一条微指令的地址。条件判别测试逻辑是这里最容易犯晕的地方。它的本质是回答一个问题下一条微指令地址是怎么算出来的它可以是顺序地址1可以直接跳转到下地址也可以根据某个条件位的值进行选择。比如指令译码后要根据操作码跳到对应执行流程的起始微地址就需要用操作码作为索引去查一个散转表选出正确的入口。我用一个简单的加法指令ADD $R3, $R1, $R2来演示微程序流程1. PC - MAR ; 把PC值送到MAR锁存 2. RAM - IR ; 读出指令到IR 3. PC PC 1 ; PC加1准备取下一条指令 4. 译码操作码 ; 根据IR高几位查表跳到ADDroutine 5. 寄存器堆读出R1、R2 ; 把两个源操作数送到ALU输入锁存器 6. ALU加运算 ; 执行加法 7. ALU结果写入R3 ; 写回目标寄存器 8. 回到取指流程 ; 开始下一条指令这些微步骤在微程序ROM里对应着8条微指令每条微指令的控制字段负责打开对应的三态门、使能对应的写信号。这一套跑通了你真的会觉得CPU的“智能”不过是一张精心设计的查表。7. 动手实操用Logisim从零搭建一个可跑程序的单总线MIPS CPU7.1 项目规划与指令集选择我建议一条指令的宽度和寄存器都用8位先跑通指令集只选最基础的五类八条加法、减法、与、或、装数从内存读、存数写内存、相等跳转、无条件跳转。编写指令集时我给每类操作分配了不同操作码。例如0000ADD Rd, Rs, Rt0001SUB Rd, Rs, Rt0010AND Rd, Rs, Rt0011OR Rd, Rs, Rt0100LW Rt, Offset(Rs)0101SW Rt, Offset(Rs)0110BEQ Rs, Rt, Offset0111J Target这个精简指令集足够写一段有意义的程序了。我在Logisim里建了主电路和几个子电路模块包括寄存器堆RegFile、ALU、控制ROMControlStore、总线接口等每一层单独独立调试最后再拼装。7.2 关键连通步骤与中间检查搭建不是一次性把所有部件拖进去而是分部件做完再集成。我按下面顺序来每个环节都要有测试基准做寄存器堆先搭4个8位寄存器验证写使能有效时寄存器能在时钟沿更新读端口数据正确。做ALU写好加减与或四个运算验证控制信号分别选通时结果正确Z标志能输出。做PC与MAR确认PC初始化为0每个时钟沿加1能从总线上装载跳转地址。接单总线把寄存器堆输出、ALU输出、PC输出、内存数据输出分别通过三态门挂到总线上确保某个时刻只有一个源输出驱动总线否则线会变红。写控制ROM根据前面微程序表把每一条微指令的二进制控制字段填进ROM的十六进制值里。联调把控制器输出的控制信号连到各个使能端用模拟时钟跑程序。这里我想特别强调使用Tunnel隧道标签。单总线CPU的控制信号非常多如果全部拉线连到各个使能到最后会乱成一团。用Tunnel标签给它命名比如PC_LOAD、MEM_WR、BUS_SRC等再在部件引脚上放相同名字的Tunnel信号就对上了线路图干净了不止一个量级。7.3 写一段测试程序并跑通我把一段“把内存两个数相加存回内存”的汇编程序翻译成指令机器码先存放在ROM或RAM里作为测试程序LW R1, 0(R2) ; 从内存地址R2处读取第一个数到R1 LW R3, 4(R2) ; 从内存地址R24处读取第二个数到R3 ADD R1, R1, R3 ; R1 R1 R3 SW R1, 8(R2) ; 把结果存到内存地址R28处 J HALT ; 停机通过单步执行我可以看到每个周期总线上的数据、PC值、微指令控制信号。当我看到第三步时总线上出现两个数相加后的结果并且写回R1成功那种“原来CPU就是这么算过来的”的感觉确实只有亲手搭一遍才能体会。8. 常见问题排查与经验教训8.1 仿真中的典型故障与排查思路手搓CPU几乎不可能一次跑通遇到过的问题五花八门我把高频率的几个整理在下面方便你照着排查。表现最可能的原因排查方法总线出现红色或冲突多个源同时驱动总线检查三态门使能信号确认同一时刻只有一路源有效PC不乱跳但指令执行乱IR锁存时机不对时钟沿控制不当检查IR的时钟相位是否和其他部件一致运算结果差1或者多1进位链处理错误或者PC提前加了1画一个小例子手动推一步检查每个微步骤的顺序控制信号一直为0控制ROM没写入或者微地址没归零查看控制ROM的初始数据确保译码入口正确程序只执行一遍没有循环或停机的跳转目标写错检查BEQ/J指令的目标地址编码排查工具上我强烈建议开着Logisim的“时钟单步”功能。一开始不要用连续高速时钟而是手动按一次时钟走一步盯着关键信号走完一条指令。你会在这种慢速推演中发现至少一半问题都出在某一个控制信号没在正确节拍上。8.2 从仿真到FPGA与物理实现的补充仿真跑通后如果你还想更进一步把设计烧到FPGA上是一个很好的延伸。但要注意几个变化点仿真里用Tunnel传递的逻辑关系要换成物理引脚的连接FPGA开发工具会帮你自动布线但约束文件必须写清楚时钟、复位和总线冲突的处理。时钟频率要受时序约束约束。仿真中时钟随便设都没问题但FPGA有实际的建立时间和保持时间要求组合逻辑延迟太长会导致时序违例实际运行就会随机出错。三态门在FPGA里通常对应输出使能信号千万不要随便用代码写高阻态赋值因为有些内部总线结构对高阻态的映射并不高效。如果你连FPGA的板子都准备好了还可以把Logisim工程手动用Verilog或VHDL重写一遍这反而是学习硬件描述语言的好素材。8.3 给新手的不定期建议最后再分享几个我踩过坑后总结出来的经验一次只改一个变量。改完某段控制逻辑后先只测试跟它有关的一条指令不要同时调三条不同指令的微代码。把模块做小、分步验证。寄存器堆单独验证、ALU单独验证、PC单独验证这些验证结果满意了再谈整机联调。把控制信号命名规范化。信号名一乱排查时两眼一抹黑我习惯用“部件_动作”的格式比如PC_LOAD、REG_WR、MEM_RD。保留一份能正常工作的中间版本。每次重大改动前先复制一个工程文件万一改崩了还能回退省得重头拼。结尾一点真实体会说实话这条路走下来最大的收获不是“我会用Logisim”而是彻底理解了一个道理CPU尽管看起来无所不能但它的“思考”本质上是一环扣一环的信号搬运和查表。所谓手搓CPU真正锻炼的是把复杂问题拆碎、再在正确层级组合起来的能力。计算机组成原理里那些抽象名词到了亲手点亮跑通的那一刻全都有了具体的形象。如果你也在学的路上卡壳不用急从逻辑门开始一步一步往上搭等到看见自己写的程序在电路里算出正确答案很多原先想不通的问题都会突然变得通透。按照这个路线走下去你会慢慢发现那台看起来遥不可及的计算机其实比你想象的简单得多。
返回列表