ARTICLE DETAIL

资讯详情

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

只用逻辑门怎么搭出CPU?从门电路到最小处理器的完整路径

只用逻辑门怎么搭出CPU?从门电路到最小处理器的完整路径 “从0手搓CPU”这个系列做到第11集终于讲到最关键的问题只用基础逻辑门怎么搭出一台计算机先说结论这件事完全可行它不是比喻而是数字电路教材里真实存在的递进路径。适合两类人看一类是刚学完逻辑门、触发器想把知识串成整机的人另一类是写了几年代码但对CPU内部如何取指、译码、执行始终没有实感的人。最值得先明确的一点是这里说的“计算机”不是一台能装操作系统、能上网的完整电脑而是一个具备取指、译码、执行、写回能力的处理器雏形。逻辑门只是积木真正的难点在于怎样用这些只会输出0和1的积木搭出能读懂指令、保存数据、完成运算的机器。我建议不要一上来就埋头画电路。先把整条路径分成几个层次再逐个层次落地。下面按我实际推演过一遍的顺序来拆。1. 先搞清楚只用逻辑门搭“计算机”难点到底在哪1.1 你把门电路看成“函数”组合逻辑就好理解了基础逻辑门就那几种与门、或门、非门、与非门、或非门、异或门、同或门。严格来说与非门和或非门都能单独组成所有其他门所以“只用基础逻辑门”这句话其实可以压缩成“只用一种门也能搭”但这属于理论上的极简实操中没必要自虐。第一层要做的是组合逻辑。组合逻辑的特点是输出只取决于当前输入不关心之前发生过什么。用它做固定运算很合适。半加器一个异或门出和位一个与门出进位。全加器两个半加器加一个或门把低位进位串进来。四位加法器把四个全加器的进位像锁链一样串起来。多路选择器用选择信号决定哪一路输入能送到输出。译码器把几条输入线扩展成多条输出线常用于指令译码。我在模拟器里第一次把四个全加器串成四位加法器时最直观的感受是所谓“CPU能做加法”其实到这里就已经完成了百分之七八十。真正的CPU里ALU算术逻辑单元的核心就是这些加法器和其他运算电路周围再配上选择器、移位器。组合逻辑的验证非常简单给定输入看输出。不需要时钟不需要复位只要接线对结果就是确定的。所以新手入门建议先在组合逻辑这一层多花时间。1.2 没有时序逻辑就存不住状态CPU就谈不上“执行程序”如果只有组合逻辑电路永远处于“当前输入对应当前输出”的状态。这带来一个致命问题你没有地方保存中间结果。程序里每个变量的值、当前执行到哪条指令、上次运算有没有进位这些都需要存储。于是第二层必须引入时序逻辑。时序逻辑的特点是输出不仅依赖当前输入还依赖历史状态。建立历史状态的最小单元是锁存器进一步演化成边沿触发的D触发器再组合成寄存器。寄存器和组合逻辑配合才形成真正的处理器模型程序计数器PC是一个寄存器保存下一条要执行的指令地址。指令寄存器IR保存当前正在执行的指令。通用寄存器组存放参与运算的数据。控制单元本质上是组合逻辑加上有限状态机时序部分仍然要靠触发器。所以“只用基础逻辑门搭出计算机”这句话准确说应该是“用基础逻辑门先搭出组合逻辑和时序逻辑再用这两类电路搭出CPU”。很多人卡住就是因为跳到“从门直接到CPU”中间漏掉了寄存器和状态机。这一层最难理解的是时钟边沿。D触发器只在时钟上升沿或下降沿采样输入边沿前后的一小段时间里输入不能变化否则会产生竞态。后面讲到排查很多奇奇怪怪的输出错误都出在这里。2. 搭建路线选哪种仿真、面包板还是继电器2.1 软件仿真最适合讲原理也最适合这集内容主流的教学仿真工具有Logisim、Logisim-evolution、Digital等。它们的特点是用鼠标拖器件、连线就能得到一个可运行的电路模型。这类工具一般需要Java环境运行在Windows、macOS、Linux上都行普通电脑就能带起来。为什么建议先用仿真因为搭建CPU的过程里排错成本最高的不是逻辑而是“连接错误”。真实面包板上几十根跳线有一根插错就可能让你排查一晚上。仿真器里接线可撤销、可修改还能直接在导线上加探针看电平。仿真器更适合本集还有一个原因逻辑门抽象本身是平台无关的。你在仿真器里用它自带的与门、或门、D触发器搭出来的CPU和真实芯片搭出来的CPU在逻辑结构上完全一致。本集要讲的是原理不是焊接工艺。第一次用仿真器时我建议先做三件小事搭一个半加器搭一个D触发器搭一个四位计数器。这三件事都跑通说明你已经具备搭整机的基础。2.2 74系列芯片和面包板真实但很费时间如果想摸到真实芯片常见选择是74系列TTL逻辑门芯片。比如74LS00装四个与非门74LS04装六个反相器74LS08装四个与门74LS86装四个异或门。再配上面包板、跳线、5V电源、按钮开关、LED和时钟源。真实电路的好处是能亲手摸到“门”的存在LED一亮一灭很直观。但搭到CPU级别连线会爆炸式增长。一个四位加法器可能要很多芯片一个完整的最小CPU动辄几十块芯片面包板上几乎没有容纳空间。我的建议是折中整机用仿真器搭但单独拿几个模块到面包板上实现。比如把半加器、D触发器、计数器在面包板上各搭一遍感受一下真实器件的电平、引脚和上电行为。这样既理解了物理世界又不会被连线的工程量劝退。2.3 继电器逻辑门看得见但别指望搭整机用继电器组成逻辑门是很多早期科普视频会展示的做法。继电器本质是一个电磁开关线圈通电后吸合触点天然就是一个能放大和反转信号的器件所以可以组成与门、或门、非门。它最大的优点是直观你能看到机械结构动作非常适合教学演示“门到底是什么”。但继电器有几个很现实的限制响应速度是毫秒级比晶体管慢几个数量级线圈一直通电会发热功耗大体积大噪音也不小。历史上确实有人用继电器搭出过能跑程序的计算机但那是博物馆级别的工程对普通人来说用来理解“继电器→逻辑门”这一步就够了没必要真用它搭整机。2.4 三条路线怎么选路线需要什么时间成本适合目标软件仿真普通电脑 仿真软件低可反复修改理解逻辑门到CPU的原理面包板 74系列芯片芯片、面包板、电源、跳线高连线繁琐亲手感受模块级电路继电器搭门继电器、电池、导线高且不适合整机展示“门”的物理本质如果你是为了看懂本集内容选第一条如果想要一点物理实感选第二条的模块级实验。我见过不少人直接冲第三条最后大部分停在十几个继电器组成的加法器上没有再前进。方向本身没错但要控制预期。3. 从门电路到最小处理器我建议按这四步走3.1 第一步先把指令集定下来别急着画电路很多人犯的错误是一上来就想画一个“CPU电路图”结果不知从何下手。换一个顺序就清楚了先定义指令集再根据指令格式设计硬件。指令集就是这台机器能识别的指令列表。最小情况下几条指令就够了ADD R1, R2把寄存器R1和R2相加结果存回某个寄存器。SUB R1, R2做减法。LOAD R1, addr从内存地址addr读数据到寄存器R1。STORE R1, addr把寄存器R1的值写到内存地址addr。JZ addr如果上一条运算结果为0跳转到addr。HALT停机。每条指令必须有一个固定格式例如高4位是操作码中间是寄存器编号低4位是地址或立即数。操作码决定控制单元产生哪些控制信号寄存器编号决定寄存器组读取哪个口地址决定内存访问哪一行。先定指令集的原因很简单硬件是被指令“逼”出来的。每条指令需要什么运算、需要读几次寄存器、需要写几次内存这些都决定数据通路里要放哪些模块。指令不定电路无从谈起。3.2 第二步画出数据通路数据通路是CPU里负责“搬数据和算数据”的部分。最小处理器的数据通路通常包含程序计数器PC指向当前指令地址。指令存储器根据PC输出指令字。指令寄存器IR锁存当前指令。寄存器组4个或8个通用寄存器需要提供两个读口和一个写口。ALU完成加减运算同时输出零标志、进位标志。数据存储器执行LOAD/STORE时访问。内部总线把各个模块的输出接入ALU输入或寄存器写口。以ADD指令为例数据流动是寄存器组读出两个数送到ALUALU计算结果再写回寄存器组。以STORE指令为例寄存器组读出一个数送到数据存储器的写入端口同时控制单元给出写使能信号。这里还要决定指令存储器和数据存储器是分开还是共用。分开的话叫哈佛结构取指和读写数据可以同时进行逻辑更简单适合教学。共用一个存储器叫冯·诺依曼结构更接近现代计算机但需要处理“取指和访存冲突”控制逻辑复杂一些。第一版建议用分开的方式减少变量。3.3 第三步用状态机把各个模块串起来数据通路只是“路”真正指挥车辆怎么走的是控制单元。控制单元根据当前指令的操作码决定每个模块在每个时刻做什么。最简单的控制方式是多周期状态机把一条指令拆成几步取指状态PC输出地址指令存储器读出指令PC自增到下一个地址。译码状态把指令字放进IR控制单元根据操作码决定后续状态。执行状态寄存器组读数据ALU计算结果。访存/写回状态需要访问内存的指令在这里访存需要写寄存器的指令在这里写入。这个状态机本身也是逻辑门搭出来的。状态寄存器用触发器保存当前在第几步状态转移逻辑是组合逻辑根据当前状态和操作码计算出下一个状态。也就是说控制单元不是一块神秘芯片它就是“一组触发器 一组与或门”。这里有一个容易忽略的点PC的自增要在“取指”这一步完成而不是整条指令结束时。否则执行完一条指令后PC可能还在旧地址。跳转指令更特殊它要在判断条件成立后把目标地址直接塞进PC。3.4 第四步从一条加法指令开始整体联调整机联调不要一上来就写复杂程序。我一般会先准备一段最简单的程序验证“取指—译码—执行—写回”闭环能跑通。比如在指令存储器里放两条指令LOAD R1, 0x00从内存地址0读一个数到R1。LOAD R2, 0x01从内存地址1读另一个数到R2。ADD R1, R2相加。STORE R1, 0x02把结果写到地址2。HALT。用仿真器的单步时钟功能一步一步地跑。每跑一个时钟周期检查PC值、IR内容、寄存器组的值和控制信号。全部都符合预期再考虑连续跑多个周期。这一集最打动人的地方也在这里你会亲眼看到一堆与门、或门、触发器连成的电路真的能按照你写好的指令一步步把两个数加起来再存回内存。从这一刻起“CPU是逻辑门组成的”就不再是抽象说法。4. 怎么验证从模块测试到整机跑通一段小程序4.1 先测模块再测整机整机跑不通时最怕的是不知道哪一层出了问题。所以我的习惯是先做模块级测试把每个模块单独钉死再拼整机。ALU测试给固定输入A4、B3选择ADD模式输出应为7选择SUB模式输出应为1。寄存器组测试写使能拉高输入数据5时钟上升沿后读口能读到5写使能拉低后数据保持不变。PC测试复位后PC为0每个时钟上升沿PC加1JZ条件满足时PC能装载目标地址。指令存储器测试给定几种地址分别读回预期的指令字。每个模块的输入输出都在可控范围内整机出问题时就可以把怀疑范围缩小到“模块之间的连接”和“控制信号的时序”而不是从零开始猜。4.2 用一段“23”程序验证闭环我建议的第一段整机程序就是计算23并把结果存到指定地址。为什么选这个因为它覆盖了LOAD、ADD、STORE、HALT四条核心指令几乎把最小数据通路都走了一遍。程序跑完后判断标准是检查项正常结果内存地址0初始值2内存地址1初始值3内存地址2程序运行后为5PC最终状态停在HALT指令地址不再增加寄存器R1最终值5如果PC不停止、地址2不是5就说明有问题。接下来用单步时钟回放看每一拍PC和IR是否按预期变化。另一种好用的验证是加一个“运行完成”信号灯。控制单元进入HALT状态后点亮一个LED或探针。这样即使程序跑了很多周期你也能一眼知道当前是否已经停机。4.3 仿真器里看波形和探针的实操习惯很多人搭完电路就直接点“连续运行”然后看最终结果。结果对就以为万事大吉结果错完全无从下手。更稳妥的方式是分层观察。第一层看静态状态在寄存器、PC、IR、内存输出端口上拉出探针或标签单步执行记录每个周期这些值的变化。第二层看控制信号把寄存器写使能、内存读写使能、ALU选择信号、PC装载信号都显式引到探针上。ADD指令执行的那一拍写使能必须是高电平STORE指令执行的那一拍内存写使能必须是高电平。第三层看时钟和复位确认时钟信号确实在翻转复位信号在启动后保持正确的电平状态不会周期性干扰寄存器。我在仿真器里踩过的最无语的一个问题是某个控制信号在组合逻辑中形成了回路导致输出在0和1之间来回震荡。单步看不出来连续运行后寄存器值变成随机的。后来把每个控制信号都单独引出观察才发现逻辑表达式写反了。所以多拉探针永远比少拉好。注意连续运行前先用单步方式跑通一条指令单步都稳定了再连续跑十条、一百条。直接连续跑只会让你面对一个黑盒。5. 最容易卡住的三个问题和我的排查顺序5.1 卡点一输出忽对忽错先怀疑时序而不是逻辑表现形式是单步执行结果正确连续执行几个周期后寄存器里突然出现一个错误值再往后又恢复正常或者从此乱掉。这类问题最容易被误判成“某个门接错了”。实际上时序电路对时钟边沿附近的数据稳定性有严格要求。如果某个组合逻辑的延迟太长导致寄存器输入在时钟边沿来临时还没稳定就会采到中间值。排查办法先看时钟频率仿真器里如果时钟太快就把频率调低或者手动单步如果是真实电路检查时钟源是否有毛刺、是否设置了合适的去抖。再看关键路径尤其是ALU输出到寄存器的数据路径上是否多串了太多层门。另一个常见原因是使能信号和时钟顺序不对。寄存器写使能本应在数据稳定后拉高如果它与时钟边沿同时变化就可能出现“该写的没写、不该写的写了”。5.2 卡点二PC跑飞大概率是复位、跳转或位序问题PC是最容易出错的模块之一。表现为程序没有停在该停的地方或者不断跳到乱地址。先查复位。仿真器里触发器初始值可能是未定义状态如果不给一个显式复位信号PC上电后可能是随机值。所有时序模块都要接入同一个复位信号并且在程序启动前拉高一段有效时间。再查PC自增。常见错误是取指状态里PC更新和指令读取用了同一个时钟边沿导致读到的还是旧地址的指令。更稳妥的设计是在取指状态的上升沿锁存指令到IR同时用另一个逻辑或先保存旧PC值再把PC加1。最后查跳转条件。JZ需要依赖ALU的零标志如果零标志在ALU输出之后才更新而跳转逻辑立刻读它就可能读到上一轮的值。跳转指令通常要晚一拍判断条件或者把零标志先锁存到专门的标志寄存器里。5.3 卡点三仿真正常换成实体芯片就乱如果你按前面建议把某个模块搬到面包板上用74系列芯片搭可能遇到仿真里没有的问题。首先是供电和接地。每个芯片的VCC和GND都要接好共地必须一致。建议靠近每个芯片的电源脚放一个0.1微法的去耦电容吸收瞬时电流变化。然后是悬空输入。高速CMOS或TTL输入引脚不能悬空悬空相当于一个不稳定电平会带来随机输出。不用的输入脚要么接高、要么接地不能露出光脚。再来是扇出限制。一个门输出能驱动的输入个数有限如果输出端连接了太多后续门电平可能被拉低高电平不再稳定。这种问题很隐蔽因为用万用表量电压时可能只是略低于正常值。最后是按钮时钟抖动。用手动按按钮产生时钟触点闭合时会连续跳动很多次等于一次按键产生了多个时钟脉冲。要么用带施密特触发器的去抖电路要么用专门的时钟模块。仿真器里没有抖动概念但真实电路里这是新手翻车重灾区。5.4 我的排查顺序时钟→复位→指令→控制信号→数据通路遇到整机不干活时我坚持按固定顺序排查不打乱不凭感觉猜先看时钟和复位波形是否正常翻转所有时序模块是否都被复位到已知状态。再看指令存储内容确认PC输出的地址对应能读回正确的指令字注意位序和字节顺序。再看IR每个周期锁存的值是不是当前指令有没有提前或延后。再看控制信号该状态下的写使能、读使能、ALU选择是否为预期值。最后才看数据通路从寄存器读口到ALU再到写回逐段加探针。这个顺序的逻辑是先确认“系统跑起来了”再确认“读到的程序对了”再确认“电路知道该干什么”最后才确认“数据有没有被正确搬运”。大多数情况下问题都出在前四步真正出在数据通路里的反而少。6. 这一集之后还能往哪个方向继续加东西6.1 从最小CPU到完整一点的计算机本集搭出来的最小CPU能执行加法、减法、读内存、写内存、条件跳转、停机已经具备“可编程”的核心。下一步可以扩展的东西很多增加指令类型逻辑与、逻辑或、左移右移、立即数加载。增加标志位零标志、进位标志、负标志方便条件跳转更多样。增加栈和调用指令CALL、RET为支持子程序打基础。增加输入输出接一组拨码开关和LED让程序能和人交互。增加中断外部事件到来时CPU能自动跳转到中断处理程序。每加一项都要回到指令集设计再修改数据通路和控制状态机。这个时候你会体会到指令集架构和硬件设计真的是相互绑定的一对。6.2 从逻辑门到FPGA和HDL如果你觉得鼠标连线已经满足不了复杂度下一步可以转到FPGA上用硬件描述语言Verilog或VHDL重新实现这个CPU。这不是从头再来而是换一种更高效的表达方式。用Verilog描述逻辑门级别的电路时你会写类似的逻辑assign表示组合逻辑比如加法器输出、选择器输出。always块配合时钟边沿描述寄存器和状态机。模块化设计把寄存器组、ALU、控制单元分成独立模块。在FPGA开发板上实现CPU和仿真器里的结构完全对照但运行速度、资源占用、调试手段都更接近真实硬件。之后再回头看逻辑门你的理解会更深一层。6.3 我的建议重画一遍别只抄一遍这一集内容如果你只看不练收获会很有限。如果你打算照着教程搭我也建议不要原封不动复制连接。更好的做法是先看明白结构图然后关掉参考自己从半加器开始重画。自己重画时你会发现几个问题寄存器组两个读口怎么同时读数据ALU的输出怎么同时送到寄存器和内存写口控制状态机遇到HALT之后怎么停住。这些问题在“看懂讲解”的时候不会出现只有动手才会冒出来。踩过几个坑之后你会得到一个属于自己的判断所谓“只用基础逻辑门搭出计算机”本质上不是靠某一个复杂发明而是靠把简单器件一层层抽象成可用的模块。这个抽象能力才是这一集真正想练的东西。如果只留一个建议我会说别从“搭一台计算机”开始先从“搭一个能边沿触发的D触发器”开始。门电路到计算机的距离其实没那么远但中间每一层都需要你亲手走过去。
返回列表