手搭计算机基础门:XOR、Mux与DMux的实现与调试指南

发布时间:2026/10/16 1:07:00
手搭计算机基础门:XOR、Mux与DMux的实现与调试指南 刚把非门、与门、或门搭完很多人的第一反应是“就这不就是三个基本门嘛”。但紧接着进入“计算机系统要素”第一章第二节我指着任务列表说了句“不至于吧”——这三个新门分别是异或门XOR、多路选择器Mux和多路分配器DMux。名字一个比一个抽象真值表一个比一个绕但等你真正动手写完 HDL、跑完测试你会意识到这仨才是后面加法器、寄存器、内存、CPU 的地基。这篇文章就是我在这一节从一脸懵到跑通全过程的完整记录三个门为什么存在、怎么用 HDL 实现、测试怎么跑、报错怎么排查以及做完之后目光能看到多远。这一节适合两类人一类是刚开始接触数字电路想用动手方式把布尔代数变成长的电路的人另一类是学过一点计算机组成原理但只知道“Mux 是选择器XOR 是异或”没亲手实现过脑子里始终差一层实感的人。看完这一篇你至少能从“背结论”进化到“自己能把结论搭出来”。1. 拆解目标三个新门到底在解决什么问题1.1 异或门真正意义上的“第一个会运算的门”异或门的规则很简单两个输入相同输出 0不同输出 1。真值表只有四行但你别小看它。生活里最常见的例子是楼梯双控开关楼上楼下两个开关控制同一盏灯两个开关状态一致时灯灭不一致时灯亮。你去按开关本质上就是在做一次异或运算。在数字系统里异或门是加法器的地基。半加器的“和”位就是两个数的异或——你不需要知道进位怎么处理先把“两数相加的结果本位”用异或表达出来。除此之外奇偶校验、模式匹配、伪随机数发生器全都绕不开 XOR。所以在第一章就让你实现它不是为了凑任务数量是因为再往下走你第一个真正意义上“算算术”的电路就是从这个门长出来的。1.2 MuxCPU 里的“铁路道岔”多路选择器 Mux 的抽象程度比 XOR 高一些。它有三个输入两路数据 a、b一个选择信号 sel输出就一路out。sel 为 0 时输出 asel 为 1 时输出 b。你可以把它想象成铁路道岔两条轨道进来到底让哪辆车开到主干道上由道岔位置决定。CPU 内部全是这种东西。寄存器要从 A 通路还是 B 通路取数ALU 的操作数来自内存还是来自寄存器下一条指令的地址是顺序加一还是跳转这些“二选一”全部要靠 Mux 来实现。没有 Mux处理器就变成了一条只能单向流动、无法做任何决策的管道。这也是为什么很多讲体系结构的书会把 Mux 称为“数据通路的闸门”。你这次亲手实现它后面看到 CPU 数据通路图里一排排 Mux就不会觉得陌生了。1.3 DMux把一条路拆成多条路的“分拣员”多路分配器 DMux 和 Mux 刚好相反一个输入 in、一个选择信号 sel、两个输出 a 和 b。sel 为 0 时in 被送到 ab 保持 0sel 为 1 时in 被送到 ba 保持 0。这就像快递分拣线一条传送带把包裹运过来前面的分拣员根据标签把包裹推到左边还是右边。数字系统里内存寻址就靠这个一条数据线上的内容要根据地址信号决定到底写入哪个存储单元。DMux 是你以后理解“译码器”“多路寻址”的第一级台阶。2. 动手之前工具链、语法与“能用的零件”2.1 工具链不需要示波器一套 HDL 仿真器足够这一节所有东西都可以在纯软件环境里完成。我实际使用的流程是在项目文件夹里放好之前已经通过的 Not.hdl、And.hdl、Or.hdl然后新建 Xor.hdl、Mux.hdl、DMux.hdl再用配套的测试脚本逐个加载、编译、跑对比。课程配套的 HDL 语言比 Verilog 简化很多没有复杂的时序控制也没有模块接口的额外语义。你只需要写清楚输入、输出、内部接线剩下的事交给仿真器。它会把你的芯片实例化按测试脚本逐行设置输入、执行一次“模拟时钟上升沿”、然后把输出结果和期望值文件对比。整个过程全是黑盒验证仿真器不关心你中间是怎么接的线只看输出是否符合预期。这种“黑盒验证”的思路正是现代芯片开发里最常见的做法。你在这次用仿真器学的这套流程将来进到更正式的 EDA 工具里核心逻辑是一样的写 RTL、写 testbench、跑仿真、对比波形。2.2 HDL 入门CHIP、PARTS 和那条“看不见的导线”如果你对 HDL 还不熟先记一个最小骨架CHIP 名字 { IN x, y; OUT out; PARTS: 子门实例; }PARTS 区里每一行都是一个子门调用比如Not(ina, outna)意味着把 Not 这个已经实现好的芯片接进去输入引脚 in 连到当前芯片的输入 a输出引脚 out 连到一根叫 na 的内部线。HDL 最核心的动作就是“给线起名字”。你声明的输入输出是外部可见的引脚而 PARTS 里那些中间的 outxxx本质上就是在当前芯片内部新增一根导线。你可以让这根线被多个子门同时读取这叫扇出但要记住一根线只能有一个来源不能有多个东西同时往这根线上写值否则就冲突了。这个点后面聊报错时还会踩到。2.3 手头零件箱Nand、Not、And、Or 全都在按照课程路线底层基元只有 Nand其余门都用 Nand 搭出来。到这里我的“零件箱”里已经有芯片说明当前状态Nand基元直接可用Not非门已实现并测试通过And与门已实现并测试通过Or或门已实现并测试通过我个人的习惯是只要有 And、Or、Not 能解决的问题就不拆回 Nand因为高层门加起来可读性更好。等到了追求最少门数、或者需要验证底层逻辑时再单独拆开。后面实现 XOR 时我会把两种风格都写一遍正好对比一下“好读”和“最省门”之间的取舍。3. 实现异或门从真值表到两份可用的 HDL 代码3.1 真值表推公式先把 XOR 拆成一句人话XOR 的定义式是“相同为 0不同为 1”。但我们需要的不是这句话而是能用已有门表达的逻辑式。把真值表完整写出来abXOR000011101110看“什么时候输出 1”两种情况a0 且 b1或者 a1 且 b0。第一种写成NOT(a) AND b第二种写成a AND NOT(b)这两个结果再求或就是 XOR。于是XOR(a, b) (NOT(a) AND b) OR (a AND NOT(b))这个推导其实是在教你一个通用方法输出为 1 的行把每个输入按“0 取反1 保留”写成子条件再把所有子条件用 OR 连起来这就是所谓积之和形式。后面遇到再复杂的逻辑门这套记法都能用。3.2 方案 A用 And、Or、Not 组合代码直观好读按照公式我用已实现的三个门直接搭HDL 长这样CHIP Xor { IN a, b; OUT out; PARTS: Not(ina, outna); Not(inb, outnb); And(aa, bnb, outanb); And(ana, bb, outnab); Or(aanb, bnab, outout); }逐行过一遍。两个 Not 先把 a、b 分别取反得到 na、nb。第一个 And 做a AND nb对应真值表里“a1 且 b0”的情况第二个 And 做na AND b对应“a0 且 b1”的情况。最后 Or 把两种情况的输出合并成最终结果。这个写法可以说完全照着公式抄几乎没有理解成本。很多人在这一步会担心“同一个 a 既连到了 Not又连到了 And没问题吗”没问题这就是扇出。一个信号被多处读取在数字电路里是常规操作只要不出现“多个信号驱动同一个线”就行。3.3 方案 B只用四个 Nand探究一下“最少门数”方案 A 总共用了 2 个 Not、2 个 And、1 个 Or文件层是 5 个子门。如果你把 Not、And、Or 再拆成 Nand实际上后台用的是十几个 Nand。但在只能使用基元 Nand 的条件下XOR 有更精简的经典实现——四个 NandCHIP Xor { IN a, b; OUT out; PARTS: Nand(aa, bb, outc); Nand(aa, bc, outd); Nand(ab, bc, oute); Nand(ad, be, outout); }这条电路我第一次看的时候也没反应过来直接盯着真值表代了一轮才明白。这里 c 是NOT(a AND b)也就是 a 与 b 的与非值d 是a NAND ce 是b NAND c最后 out 等于d NAND e。你把四种输入代一遍就能看到它确实满足 XOR 的真值表。方桵 A 和方案 B 没有绝对优劣方案 A 可读性更好方案 B 在只允许 Nand 的终极约束下更省门。我个人的建议是做这节时两种都动手写一次。你在方案 B 里获得的“同一功能可以有完全不同的电路拓扑”的感觉后面设计 ALU、优化电路时会非常值钱。3.4 踩坑提醒中间信号不能和输入引脚混用第一次写 XOR 时容易犯一个错想在同一个内部线名上反复赋值比如先Not(ina, outna)后面又想复用 na 存另一个结果。这在 HDL 里是不允许的。一根线只能有一个驱动源你需要另起名字比如 na2、n_b 这种。另一个常见问题是大小写不规范。HDL 对引脚名和线名大小写敏感a、A 是两个标识符。我的习惯是全部用小写引脚名、小写线名只在 CHIP 名字上保持首字母大写。文件名还必须和 CHIP 名完全一致否则加载时会报找不到芯片。这三个细节看着小实操里能拦住一大半报错。4. 实现 Mux 和 DMux一对互逆的控制门4.1 Mux 的真值表与选择信号sel 等于 0 走 a等于 1 走 bMux 的输入是 a、b、sel输出 out。把 8 种输入组合全部列出来selabout00000010010101111000101111001111观察这张表sel0 时out 完全跟着 a 走sel1 时out 完全跟着 b 走。于是表达式非常直接out (a AND NOT(sel)) OR (b AND sel)这个式子的物理含义是sel 先用 Not 变成互补信号分别去“开门”。sel0 时第一条路 a 经过与门放行sel1 时NOT(sel) 为 0第一条路被关死第二条路 b 放行。两个结果再 OR 到一起就实现了二选一。4.2 Mux 的 HDL 实现分两路“与”再汇成“或”对应上面公式的 HDL 代码CHIP Mux { IN a, b, sel; OUT out; PARTS: Not(insel, outnsel); And(aa, bnsel, outla); And(ab, bsel, outlb); Or(ala, blb, outout); }我给中间线起了名字 la、lb语义是“左臂”和“右臂”。别人看代码时扫一眼就知道左边这路是和 sel 的反相信号做与右边这路是和 sel 本身做与。命名是否清晰在门数多了以后会直接影响排查效率。实际测试时我还观察到一件事很多人会把And(aa, bnsel, outla)里的bnsel写成bnot(sel)这当然不合法。HDL 只接受已经存在的线名不会帮你内联计算。凡是需要取反必须先单独拉一个 Not 出来接线记住这个习惯写复杂芯片时才不会乱。4.3 DMux 的真值表与 HDL 实现把一路分发到两条路DMux 的输入是 in 和 sel输出是 a 和 b。真值表里sel 决定 in 去哪一路selinab0000011010001101可以看到sel0 时a 等于 insel1 时b 等于 in。用公式写就是a in AND NOT(sel) b in AND selHDL 实现和 Mux 几乎是对称的CHIP DMux { IN in, sel; OUT a, b; PARTS: Not(insel, outnsel); And(ain, bnsel, outa); And(ain, bsel, outb); }这个实现里四个子门两路 And 共用同一个输入 in另一条腿上分别接 sel 和 nsel。所以 DMux 从本质上就是“把 in 分别送往两个与门再靠 sel 的互补信号决定哪一扇门开着”。真值表全部行都满足测试就能一次过。4.4 从 1 位到多路DMux4Way 和 Mux4Way 的扩展思路这一节的任务只到 DMux 为止但我建议你顺手想一下如果输入有 4 路选择信号有 2 位该怎么办这就是 4-way Mux 和 4-way DMux 的雏形。4-way DMux 的做法是级联先用高位选择信号把输入分成“上半组”和“下半组”再用低位信号在组内细分。比如输入 insel[1] 先把 in 送到 A/B 这半边还是 C/D 这半边然后 sel[0] 再决定具体落到哪一条。你只需要三个 DMux 按层级接起来就完成了。Mux4Way 也是同理只是把两个 Mux 先各自二选一再用一个 Mux 从两个中间结果里选最终输出。这层“递归式拆解”的思路以后会在内存地址译码器里反复出现。你现在觉得它多余等做 16KB 的内存时地址译码就是从单个 DMux 这样一层层“分”下去的结果。5. 测试脚本与调试实录仿真器上最容易翻车的三个地方5.1 测试脚本怎么写逐行枚举所有输入组合课程配套的仿真脚本写法比较固定。拿 XOR 举例一个完整测试脚本会包含这些段落load Xor.hdl, output-file Xor.out, compare-to Xor.cmp, output-list a%B3.1.3 b%B3.1.3 out%B3.1.3; set a 0, set b 0, eval, output; set a 0, set b 1, eval, output; set a 1, set b 0, eval, output; set a 1, set b 1, eval, output;这里eval表示对当前输入组合执行一次仿真output表示把结果写入输出文件compare-to对应的 .cmp 文件里存的是期望值。仿真器跑完屏幕上会告诉你每个用例是否与期望一致。初学时最容易犯的懒病是只挑两三种输入跑一下看到没有报错就当通过了。对于 XOR 来说四个组合全跑也花不了几秒但 Mux 有 8 个组合DMux 有 4 个组合一旦漏掉某个边界情况错误当时看不见后面做加法器时会突然跳出来咬你一口。5.2 未声明内部线编译报错最常见的原因我在这三个门上遇到的报错有一半都在说“某个线没声明”。典型场景是Not(insel, outnsel); And(aa, bnsel, outa1); And(ab, bsel, outb1); Or(aa1, bb1, outout);我第一遍写 Mux 时把线名写成了 nsel、a1、b1结果在 And 里引用了 a1但 a1 是在后一行才声明出来的——HDL 不像某些顺序执行的编程语言它不管先后顺序但是要求名字拼写必须完全一致。你如果写a1和al这种长得像但不同的名字仿真器直接拒绝编译。建议所有内部线名都用简短、一致、不易混淆的字母组合比如 la、lb、na、nb。当你开始用temp1、temp2、tmpA这种名字时排查报错成本就会直线上升。5.3 悬空与冲突输出管脚重复驱动HDL 里每个子门的每个输入都必须接到一个确定的信号不允许悬空。另一个更容易踩的坑是“输出冲突”两个子门试图把结果写到同一条内部线或者你试图往芯片的最终输出引脚 out 上接两次。比如有段时间我想省事直接把中间结果接到 out后面又用另一个 And 再接到 out仿真器就会报 out 已经被驱动。记住规则最终输出引脚 out 只能被赋值一次中间线也只能有一个来源但可以被多个门读取。这就是“一源多扇出禁止多驱动”。5.4 功能全对却不稳定别忘了边界全 0 和全 1 用例有一种情况特别隐蔽你的测试脚本里每个组合都写了其中大部分结果也对但“in 等于 0”或者“sel 等于 1 且 a/b 都等于 0”这种极端组合没有覆盖导致某个输出错误没暴露。以 DMux 为例真值表里有一个很容易写错的地方sel0、in0 时a 应该是 0b 也应该是 0。有些初学版本会把 b 写成And(ain, bsel, outb)之外的形式导致 in0 时 b 跟着 a 一起输出 1。只有把 in0 的所有组合都跑一遍这个 bug 才会现形。我的习惯是每个门都把自己的完整真值表整理成测试脚本四行、八行的枚举不嫌麻烦。跑测试不是走形式是真值表在机器上的复述。5.5 调试方法论拿真值表一行一行对着排编译错误和逻辑错误要分开处理。编译错误看提示里的行号和线名逻辑错误如果不严重可以靠测试报告里失败的用例定位。更高效的定位方式是在仿真器里单步执行。你可以把某个失败用例的输入设置好然后让仿真器逐条执行 PARTS 里的子门一边执行一边观察每条内部线当前是什么值把中间结果和真值表预期一对通常一两分钟就能找到错在哪。我后来写多路选择器遇到 bug都是这样直接“看线”比盲改代码快得多。还有一点如果报错信息写着“chip not found”先检查文件名。课程仿真器对文件名和 CHIP 名是否一致比较严格大小写错了也会直接报找不到门。别问我是怎么知道的。6. 仅仅三个门怎么就成“现代计算机”的一部分了6.1 异或门怎么变成加法器拿到 XOR 之后半加器就是一行公式的事和的低位是 XOR进位是 AND。全加器则需要处理来自低位的进位多出来的一个半加器加上一个 OR 就能组合出来。所以你现在写的这个 XOR其实已经是未来 ALU 的一部分。这也是为什么我说 XOR 是第一扇“会运算”的门。Not、And、Or 都只是在做逻辑判断到了 XOR电路开始表达“两个数相加的结果”。从这里往上走你会搭出 16 位加法器、加/减法器最后把它们塞进 ALU 里完成计算机最核心的算术逻辑单元。6.2 Mux 怎么变成可控存储电路Mux 的能力是“选路”这个能力在存储单元里非常关键。寄存器往往需要从多个来源接收数据可能来自输入端口、来自 ALU 计算结果、来自内存读取结果。每个来源都是一路数据用 Mux 按控制信号决定最终存入哪一路。再往上程序计数器的下一个地址是 PC1 还是跳转目标也是靠 Mux 选择。没有 MuxCPU 就没有办法“根据当前状态决定下一步动作”整个机器就只能机械地一路跑到底。所以你现在做的这个三输入两行表达式的小门将来在 CPU 数据通路图里会以几十个、上百个的形式反复出现。6.3 DMux 怎么变成解码器的起点DMux 扮演的是“分发”的角色。内存里要写数据到的某个地址本质上就是用地址线去控制多个 DMux把数据总线上的内容分发到对应的存储单元。指令译码器也类似操作码经过一层层选择最终变成某一条控制线上的有效信号驱动后续硬件工作。你可以把 DMux 看作“最简译码器”。单个 DMux 只是 1 位地址分两路把多个 DMux 组合起来就能形成 n 位地址分 2 的 n 次方路的完整译码结构。后面做内存、做指令解码你都会反复看到这个熟悉的影子。6.4 回头看“从零开始”这条路的节奏感《计算机系统要素》第一章的精髓不是让你记住某个门的真值表而是让你习惯一种工作循环看真值表写逻辑表达式用已有组件搭 HDL跑测试过。循环往复每一轮的手段都一样但搭出来的东西却越来越靠近一个完整的处理器。我做完这三个门之后最大的感受是“原来复杂系统不是一步到位的而是像搭积木一样一层层垒上去”。每次拿到新的零件清单前面那些已经跑通的门就是新的积木你不用关心它们内部有多少个 Nand只要把它们当作黑盒去用。这个“黑盒思维”比任何一行 HDL 代码都值钱。最后分享一个实操小技巧写完 HDL先别急着点 run。花两分钟看一眼自己用了哪些中间线确认每一根线都有且只有一个驱动源确认每个子门的输入都写全了。这套“眼读”能帮你把一半的编译报错消灭在按下回车之前。我前几次偷懒跳过这一步结果反复在同一个地方花了几倍时间。老老实实眼读一遍后面跑得才顺。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询