ARTICLE DETAIL

资讯详情

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

从SR锁存器到RAM:图灵完备第三章存储电路构建指南

从SR锁存器到RAM:图灵完备第三章存储电路构建指南 开头前面两章我们把与门、或门、非门这些基础元件一路搭到了加法器再拼出一个能算加减法的算术逻辑单元。看着信号从输入端进去一路被各种门处理最后在输出端给出了正确答案那种感觉很爽感觉“电脑也不过如此”。但游戏第三章标题一出来我卡住了。它要的不是“算”而是“存”——让我做一个能保存1位数位的电路。我盯着面板看了半天随手接了个AND门、OR门信号一撤输出立刻归零什么都没留下。那一刻我才意识到前面所有的组合逻辑电路本质上都是“没记忆”的。而在数字电路里要让一个电路“记住”一点东西唯一的出路就是制造循环依赖——把输出接回输入。这是图灵完备第三章最核心的知识点也是很多人第一次对“计算机为什么能记住数据”产生真实体感的地方。这一篇我想完整记录我从循环依赖到优雅存储的构建过程以及过程中的坑和心得希望能帮你少走几步弯路。1. 为什么“记住”这件事这么难组合逻辑的天花板1.1 前几章的算法世界组合逻辑前面几章我们搭的电路学名叫组合逻辑电路。组合逻辑有个核心理念任意时刻的输出只由当前时刻的输入决定。加法的进位、ALU的运算结果都是这个理念的产物。输入一变输出跟着变没有延迟没有记忆也没有“上一次是什么状态”的概念。说白了组合逻辑就像一个只知道按菜单做菜、但完全不记得顾客上次点了什么的厨师。这种特性在做算术时完全没有问题——你要的是“这里输入34输出永远是7”。但等关卡要求你把一个结果“留到下一步再用”时组合逻辑就崩了。你没法把刚才运算得到的值保存下来因为你没有任何元件可以“钉住”一个电平。这时候你才意识到计算机里那部分叫“存储”的东西并不是“算”出来的它需要另一种完全不同的电路结构。而那个结构绕不开一条看上去很奇怪的路把电路的输出重新接回它自己的输入。1.2 循环依赖的第一印象既是要避开的坑也是唯一的解法玩到第三章时我第一次被迫把一个门的输出接回另一个门的输入然后那个门的输出又接回来。面板上立刻出现了一条环状连接的线看起来就像电路在“咬自己的尾巴”。我当时的第一反应是这能行吗逻辑门这不得卡死这里要解释一个关键概念循环依赖也叫反馈回路。在组合逻辑的世界里这条回路是不被允许的——因为如果你从A门输出绕一圈又回到A门的输入A门就会陷入“输出影响输入、输入又反过来决定输出”的无限循环最后要么振荡要么状态完全不可预测。但在时序逻辑里这个环恰恰是存储的核心。为什么因为逻辑门本身带着一点延迟信号从输入传到输出需要时间反馈回路让这个延迟变成了一种“承上启下”的机制门输出的结果在下一小段时间里反过来约束了它自己的输入于是电路就可以“保持”住一个状态而不是会立刻被外界的任何信号冲掉。如果你玩过物理层面的类比就更容易理解把麦克风放到音箱前面音箱输出的声音被麦克风拾取又放大再从音箱播出来——这就是一个反馈回路。处理不好是啸叫处理好了就是延迟效果器。电路的反馈也一样处理不好会振荡处理对了就会进入一种“锁定”状态。而“锁定状态”就是存储的雏形。理解到这一层你才算迈过图灵完备第三章的第一道心理门槛不要怕导线成环但成环之后你必须搞清楚这个环想稳定在哪个状态。2. SR锁存器存储器的第一块砖2.1 两个NAND门交叉耦合的搭建过程游戏里的第一个存储关卡给到的核心提示基本就是让你自己用NAND门去搭一个SR锁存器。我刚开始一头雾水后来才明白SR锁存器其实是反馈回路最朴素的形态它只用两个NAND门交叉连接就能实现1位数据的保存。具体搭法如下准备两个NAND门第一个NAND门的输出接到第二个NAND门的一个输入端第二个NAND门的输出再接回第一个NAND门的一个输入端。剩下的两个输入端一个叫SSet置位一个叫RReset复位。输出有两个一个叫Q一个叫~Q正常情况下它们是互补的——Q是1时~Q是0反过来也一样。顺手一提为什么用NAND而不是AND、OR因为NAND是通用门而且在这种交叉结构里用低电平触发置位/复位天然更简洁。游戏的基础电路库只有NAND等几个基础门你不可能绕过它去实现存储。2.2 一张表看懂SR锁存器的四种状态搭完之后下一步是验证它的真值表。我截一张等效逻辑表你一眼就能看懂这个锁存器的行为S置位R复位Q保存结果~Q反相输出说明11保持上一状态保持上一状态存储模式数据被锁住0110置位把Q拉成11001复位把Q清成00011非法状态禁止出现这里最神奇的是S1、R1那行。两个输入都不生效时输出靠什么确定答案就是那条反馈回路。Q保持上一个时刻的值然后通过回路把这个值反过来钉住自己如果Q当前是1那这个1经过反馈又维持住1如果Q当前是0它也维持住0。这就是“锁存”名字的由来——它把上一次写入的数值锁死在电路里。你可以做一个小实验先让S变0再变1Q会变成1并保持住再让R变0再变1Q会变成0并保持住。这就像你用笔在一个开关上做了个标记标记不会自己消失。这个“不会自己消失”就是存储。2.3 置位/复位背后的门级原理只看真值表不过瘾我建议你动手走一遍电平变化真的看着电路图推理一次这样记得牢。以置位为例S给0R给1。这时第一个NAND的两个输入端分别是S0和~Q来自第二个NAND的输出我们暂时不知道它是几但NAND只要有一个输入是0输出就强制为1。所以第一个NAND输出Q1。Q1送到第二个NAND的输入端第二个NAND的一个输入是R1另一个是Q1两个都是1NAND输出就是0于是~Q0。这时把S拉回1会发生什么第一个NAND现在的输入是S1和~Q0只要有一个0输出还是1。也就是说Q被第二个NAND的输出~Q0锁住了——哪怕S已经恢复成1Q依然稳稳地停在1。复位的过程完全对称R给0时~Q被强制为1这个1反馈到第一个NAND让Q变为0而Q0又维持住~Q1于是状态被锁在“Q0”。我真正动手搭完这四条路径以后第一次产生了“电路里有东西被记住”的实感。它不是靠某个门单独完成的而是几个门互相牵制、形成了一种平衡。这种“牵制”在数字电路里就叫稳定态。SR锁存器有两个稳定态Q1或Q0所以它能存1位数据——这个结论值得你亲手验证后再往下走。3. 给锁存器装一个“写开关”从SR到D锁存器3.1 门控SR锁存器为什么需要使能信号SR锁存器能存东西但它有个很别扭的问题S和R是两个外部输入想写入数据你得同时控制两路信号更麻烦的是S和R不能同时为0否则进入非法状态。在现实的大电路里这种要求太苛刻了。咱们得给它加上一个统一的“写入开关”——让S和R的改动只在某个控制信号有效时才起作用。这个控制信号叫使能信号通常写作EEnable或CLK。怎么实现做法不复杂在SR锁存器前面加两个AND门S信号经过第一个AND门再接到原来的S端R信号经过第二个AND门接到原来的R端然后把两个AND门各剩下的一个输入端并在一起接E。当E0时两个AND输出都是0而SR锁存器对SR0这个组合注意这里我说的S/R是内部信号的处理是保持原状态所以锁存器一动不动。当E1时外部S/R信号可以通过AND门送入锁存器正常写入。我在游戏里搭完这一步之后最大的感受是存储电路从此从“裸奔”变成了“可控”。你可以先准备好要写入的数据然后让E信号像拍手一样在某个时刻“啪”地一下打开写入通道让数据灌进去再把E撤掉数据就稳稳地留下来了。这个“拍手”的瞬间已经是后续所有时序逻辑的雏形。3.2 D锁存器消除非法状态一个输入搞定全部虽然门控SR已经能“按需写入”但数据输入仍然是S和R两条线使用起来还是很别扭。谁能保证操作者永远不犯SR0这种错误于是D锁存器出现了。它的思路很聪明把输入合并成一条线DData然后把D同时接到S端和R端——其中R端接的是经过一个非门取反后的D。这样S和R永远是一对互补信号D1时S1且R0D0时S0且R1。你永远不可能构造出SR0或SR1那两行麻烦事。D锁存器的真值表非常干净E使能D数据输入Q输出0任意保持上一状态100111这个行为理解起来特别直观E是“写入开关”D是“要写入的数据”。E打开时输出跟随D走E关闭时输出被锁住不管D再怎么变Q都不动。打个比方D就像一扇门后面站的人E就是门锁——锁打开时你能看到里面的人是谁锁一拧上就算门口换了个穿不同衣服的人你看进去的仍然是刚才那一位。我在游戏里搭D锁存器时连续接错了好几次原因都是把取反的D信号漏接了结果S和R同时为1整个锁存器死死保持在原状态写什么都进去。这一步的关键就一句话D信号要先分出两个支路一条直接走一条取反再走两个支路分别去控制原来的S和R千万别省那个非门。3.3 游戏中的实操心得封装、命名、模块化到D锁存器这里你手里已经有一个完整的1位存储单元了。这个单元说大不大但也有两个NAND锁存器 两个AND 一个NOT 五个逻辑门左右。如果后面的每一关都把这些门摊开在面板上线会乱到你自己都不认识。所以我的建议是一旦信号正常立刻在游戏里把它封装成一个自定义元件给它取个清楚的名字比如“Latch_1bit”只暴露三四个引脚出来。封装这一步绝不只是为了好看。它强迫你思考“这个元件的接口是什么”D进、E进、Q出、~Q出如果你需要反相输出的话。接口清楚了记忆一起封装起来后续搭多位寄存器时你只需要复制粘贴这个黑盒而不需要在脑子里重新过一遍内部门的连接。这是我踩了好几关的布线坑之后才真正学会的一课大型电路不是靠聪明搭出来的是靠“封装得够不够干净”撑出来的。4. 从1位到一堆位寄存器与随机存取存储器4.1 多个D锁存器拼出8位寄存器1位存储单元听起来太可怜了游戏里很快就要求你做一个能一次存8位甚至更多位的电路。做法在思想上很简单把8个D锁存器并排放在一起每个锁存器拿一位数据。8个锁存器的D输入分别接数据总线的D0到D7Q输出分别接到Q0到Q7最关键的是所有锁存器的E输入并成一条线一起控制。E为1时8位数据同时写入8个锁存器E为0时8个锁存器一起进入保持状态。我搭的时候一度觉得这太“粗暴”了这不就是复制粘贴了8份吗但后来想想硬件设计里很多“大进步”真就是这种复制粘贴式的扩展。单个D锁存器解决了“存1位”的质变多复制几份就能解决“存多位”的量变。游戏里整个关卡的核心逻辑就是让你意识到这一点寄存器的本质就是一组共享同一个写入使能信号的D锁存器阵列。它是一次性保存一个多位数字的“手抄本”CPU里给运算结果暂存的寄存器就是这个结构。4.2 地址译码器从“存一堆”到“定位一个”有了一个8位寄存器下一步自然是要有一堆寄存器——毕竟你不能只存一个数。游戏到这会要求你做一个类似“内存组”的东西比如8个寄存器每个能存8位数据。这时候问题来了内存里摆了8个房子我往里面存数据或读数据时得先告诉电路“我要的是第几个房子”。这个“第几个”就是地址。怎么实现“选房子”靠的是译码器。地址线如果只有3位二进制正好可以表示0到7对应8个寄存器。你把3位地址喂给一个“3-8译码器”它会让8条输出线里恰好一条为高电平其他都为低。这条唯一的高电平就作为对应寄存器的写入使能信号。比如地址是011十进制3译码器的第3号输出端就变高于是只有第3号寄存器的E被拉高其他7个都不动。再配合统一的数据总线“往0x03地址写数据”这个操作就变成了把数据放在D0~D7上把地址设为二进制011然后给一个写信号数据就进了3号寄存器。这里我第一次感受到“地址”这个概念不是抽象的内存地址而是实实在在由导线和门电路选择出来的。所谓内存地址的本质就是若干根用来控制哪条使能线生效的输入信号。想明白这一点“指针”、“寻址”这些词都不神秘了。4.3 随机存取存储器雏形读多路选择器写译码器地址译码器解决了“往哪个寄存器写”的问题但“从哪个寄存器读”是另一个方向的问题所有寄存器都在不停地把自己的Q0~Q7输出到线上你怎么只摘出你想要的第3号读的方向用的是一个完全对称的电路多路选择器MUX。8个寄存器的Q0输出全接到一个8选1多路选择器上Q1接另一个Q2接再一个……一共需要8个多路选择器每个负责一位。然后用同样的3位地址信号去控制这8个多路选择器它就会把选中寄存器的第0位、第1位直到第7位分别选送到最终的数据线上。至此一个最小化的随机存取存储器RAM就成型了。它有三组主要的接口地址线访问哪个单元、数据线输入/输出内容、控制线决定是读还是写。地址译码器管写入多路选择器管读取两者共用一个地址输入同步工作。为什么叫“随机存取”因为你可以随意挑任意一个地址直接读或写不必像磁带那样从头扫到尾——这里的“随机”英文是Random意思是“任意、按需”不是概率意义上的随机。游戏里搭完这个结构后看着面板上整整齐齐的译码器、寄存器阵列、多路选择器我终于体会到什么叫“优雅的存储”它不靠什么魔法全靠让信号在恰当的时机走到恰当的路径上。4.4 真实世界的存储器它如何连接到CPU游戏里搭出来的这个RAM虽然看起来离现代电脑的DDR5内存雕琢了十万八千里但骨架已经完全对上了。现实中CPU访问内存就是靠三组信号地址总线告诉内存“我要哪个位置”数据总线负责搬运数据控制总线读/写信号决定数据流动的方向。CPU把地址发到地址总线上拉高读信号内存就把那个地址的数据放到数据总线上让CPU取走——这跟游戏里你用小开关模拟“读”操作没有本质区别。学过单片机的人应该更眼熟51单片机的存储器扩展就是这个思路的应用单片机有P0、P2等引脚作为地址/数据复用总线配一个锁存器把地址低位锁存再接SRAM芯片。连接的本质就是“地址对地址、数据对数据、控制信号对控制信号”。而现代内存里用的存储单元静态RAMSRAM使用的仍然是锁存器结构只是工程上做成了6个晶体管构成的6T单元动态RAMDRAM则改用电容存电荷所以需要周期性刷新防止电荷漏光。但无论工程实现如何演进只要你理解D锁存器是“电平驱动、数据被反馈回路钉住”你就掌握了所有现代存储器的思想源头。5. 常见问题与排查技巧实录5.1 输出一直在闪延迟与振荡问题我第一次搭SR锁存器面板上明明按逻辑推演应该稳定的Q实际运行时却像抽风一样地闪。排查半天发现问题出在游戏设置里的逻辑门延迟。逻辑门必须有延迟反馈回路才能稳定——如果没有延迟信号在环里瞬间循环模拟器根本没法收敛。图灵完备默认给的延迟可能偏小或偏随机导致锁存器在高频闪动。你把延迟设成固定且稍大一点的值再跑一次通常就稳了。还要提醒一句如果延迟设得太大又会出现“写入要等半天才看到效果”的现象感觉就像电路迟钝。所以做存储类关卡时延迟设置要么用默认值别乱动要么统一固定别混合随机否则你会在振荡和迟钝之间反复横跳根本分不清是哪一步出了问题。5.2 存不住数据找出“总是能写”的元凶一个很常见的现象你给D锁存器写入数据E改回0之后Q居然跟着D变了。这说明锁存器根本没有进入“保持”状态。最典型的原因就是E的使能信号接反了极性如果你是低电平有效但电路里默认高电平有效那意味着E平时一直是1锁存器永远处于“跟随”模式D一变Q就变从来没有“锁”过。另一个可能是你的E线不小心接了高电平后面忘了断开整条总线一直是激活的。遇到这种问题千万别急着乱改拿一个电平指示灯接在E线上动手拨动控制开关先确认E是不是真的按你预期在高/低之间变化。很多时候不是逻辑设计错而是某条控制线被另一条不该有输出的线路拖着一直处于错误电平。5.3 写入后读出来是错的看看Q和~Q有没有接反在把多个D锁存器拼成寄存器时特别容易犯一个低级错误把Q和~Q接反。Q和~Q总是互补的如果你把~Q当Q接到了数据总线上那写进去0存出来变成1写进去1存出来变成0看起来就像“数据损坏”一样。排查方法很简单单独测一个锁存器写入1看输出是不是1写入0看输出是不是0。如果结果总是反的优先怀疑你把锁存器的Q和~Q接反了而不是怀疑后面的选择器。类似的还有数据线顺序接乱D0接了锁存器1Q0却接到了数据总线的第1位。这种问题在8位寄存器里太常见了肉眼盯线很难发现建议按号码逐位核对或者用游戏里的“总线”功能做成一个位宽一致的数据线束能大幅降低此类错位。5.4 布线地狱让电路可读的几条实战规则到寄存器阵列和RAM这一步游戏面板上的线多到已经像一碗面了。我自己的经验是三条第一方向统一。数据线一律从左边进、右边出控制线从下往上或从右往左走别交叉。第二分色管理。高电平用红、低电平用蓝或者固定颜色习惯数据通路一个颜色控制信号一个颜色一眼能分清用途。第三按模块留间隔。不要把元件贴得太密每个模块之间留出专门走线的通道宁可面板大一点也别为了省位置让连线疯狂绕弯。另外游戏里的自定义元件功能真的要用起来。把D锁存器封装好把8位寄存器封装好再去做RAM时你面对的就是几个清晰的积木块而不是一片逻辑门海洋。我见过太多人卡关不是因为想不出逻辑而是因为线太乱找不到哪根接错了。6. 关于这一章我想对卡关的你说几句6.1 别再用“数据流”的脑子理解存储电路第三章最大的认知门槛其实是心态层面的。前两章一直接触组合逻辑你已经习惯了“信号从输入流到输出”的思维。但存储电路完全是另一套逻辑你关注的不是“信号流到哪了”而是“电路处于哪个状态”。一个D锁存器不关心D信号本身只关心“当前被锁住的值是0还是1”。这两个视角的转换是很多玩家卡关的根本原因。我自己的切换方法是每次分析锁存器不要从输入开始顺推而是先问一句“如果Q现在是0那其他几个节点的电平应该是多少”。把“假设一个状态验证它稳不稳”当成思考工具你会发现存储电路没那么玄乎。本质上它就是一个能用自身输出把自己钉住的小系统。6.2 卡关时的万能解法从1位开始抠如果你在某个存储关卡卡了半个多小时最有效的策略不是去硬拆大电路而是先回到1位存储单元把D锁存器的行为从头确认一遍E1时是不是能跟上DE0时是不是能锁住1位没问题再去做8位8位没问题再去做带地址的。很多大电路的错误都是底部的小单元行为不端导致的。把基础元件当成可以信任的积木而不是每个大结构都从头验证一遍你才有精力去处理更高层的问题。还有一个小技巧用游戏里的正常通关测试不要自己口头推演就算过了。就算你觉得逻辑天衣无缝也把题目要求的输出都点亮一遍游戏会用真实结果告诉你哪里漏了。6.3 存储之后的道路触发器、时钟和CPU的雏形第三章的结束不是终点。锁存器是电平驱动的它有一个弱点只要E是1数据一直能灌进去。如果你想让“写入”只发生在某一个瞬间就需要把电平驱动升级成边沿触发——这就是触发器的由来。触发器再加时钟信号你的电路就开始有了“节拍”可以像乐谱一样精确控制每一步什么时候执行。一旦有了寄存器、有了基于时钟的读写控制你距离组装一个完整CPU已经很近了。后面你会看到指令解码器怎么从内存里取出指令寄存器怎么暂存中间结果运算器怎么把结果再写回内存。所有这一切都建立在你第三章搭出的那个“能记住1位数的电路”之上。这也是我为什么愿意花这么多篇幅把这个看似简单的存储器关卡讲透——它不是一堆零散的知识点而是整个计算机大厦的地基。最后再分享一个小技巧如果你实在想不通某个存储电路的行为可以把它的输出接到一个LED灯上然后把延迟调大一点点观察状态跳变的过程。一旦你亲眼看到“写入瞬间电平从0跳到1并稳稳停住”那种理解会比你读十遍原理都刻骨铭心。我当时就是靠这一招把SR锁存器和D锁存器的所有行为彻底印在了脑子里后面的关卡一路顺到底。
返回列表