ARTICLE DETAIL

资讯详情

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

组合逻辑设计实战:从真值表到门级电路搭建与调试

组合逻辑设计实战:从真值表到门级电路搭建与调试 1. 组合逻辑设计的第一步先把需求翻译成真值表做过几年数字电路设计的人应该都有同感组合逻辑电路本身的逻辑并不难难的是“把需求翻译成电路”这第一步。很多人一上来就想着画门电路、写Verilog结果需求里有个边界条件没理清后面返工返到怀疑人生。我个人的习惯是任何组合逻辑项目不管规模大小第一步永远是列真值表把输入和输出的关系完全定义清楚然后才谈得上化简和实现。1.1 为什么真值表是唯一靠谱的起点组合逻辑电路的定义很简单输出只取决于当前的输入组合和电路的历史状态没有任何关系。这句话听起来像是教科书废话但它带来的工程约束是实打实的——既然没有记忆单元那么每一个输入组合都必须对应一个确定的输出。哪一个组合没考虑清楚哪一组输出没定义电路做到后面必然出bug而且是那种仿真看不出来、上板才炸的问题。真值表的价值就在于它强迫你把所有输入组合穷举出来一个不落。比如一个3输入的逻辑函数理论上就是2的3次方等于8种组合4输入就是16种8输入就是256种。组合数量是2的n次方增长所以真值表只适用于输入数量可控的场景。工程上我一般把真值表当作“需求说明书”来用项目启动时先和需求方把真值表一条一条过一遍确认每个输入组合下的输出是否符合预期。这个动作至少能筛掉一半的需求理解偏差。建立真值表时有一个容易忽略的点不要只写0和1还要标注“无关项”dont care和“高阻态Z”。无关项指的是某些输入组合在实际电路运行中不可能出现或者即使出现了输出是什么也无所谓。这类组合在真值表里用X表示在后续卡诺图化简时可以利用它们来简化逻辑。高阻态Z则通常出现在总线结构里表示输出不对总线产生驱动。把这三类状态都标注清楚才能让真值表真正覆盖硬件的行为而不是停留在教科书理想模型里。1.2 真值表的穷举法从需求到逻辑的翻译技巧用一个实际例子来说明。假设要设计一个“家庭安防报警控制器”输入是门窗传感器A、烟雾传感器B、红外人体探测器C输出是报警信号Y。需求如下门窗被打开时报警烟雾浓度超标时报警门窗没开、没有烟雾但红外检测到人体移动时也报警。看着挺简单对吧但如果不穷举很容易漏掉“多个传感器同时触发”的情况。比如门窗被打开的同时也有烟雾这时候报警信号应该是什么逻辑上显然是报警不会因为两个条件同时成立就取消报警。把需求翻译成真值表就清晰了ABCY说明0000一切正常0011红外检测到人0101烟雾报警0111烟雾红外1001门窗被打开1011门窗红外1101门窗烟雾1111全部触发这个真值表很直观就是“或”的关系Y A B C一个三输入或门搞定。但这只是入门级例子。真正复杂的项目里真值表会暴露很多反直觉的需求问题。比如“门窗被打开时如果主人用钥匙在门口解锁则不报警”这种带条件的逻辑放到真值表里就需要增加一个“布防状态”输入表格行数就会翻倍。我自己的经验是真值表一旦超过16行手写维护就变得痛苦了这时候要么引入HDL行为描述要么用电子表格辅助管理但真值表作为设计基线文档依然建议保留下来方便后续沟通和测试点提取。2. 布尔化简的实战选择卡诺图、代数化简还是直接上工具真值表定义清楚之后下一步就是化简。网上的教程最喜欢讲代数化简那一套什么吸收律、摩根定律、分配律看着高大上实际做项目时除非表达式非常简单否则代数化简很容易化简到一半发现路径走错了越化越复杂。我的建议是掌握卡诺图作为基础技能但真正做项目时优先用工具做逻辑最小化然后人工复核。2.1 卡诺图的正确用法与常见错误卡诺图的本质是利用格雷码排列输入变量让相邻方格之间只有一个变量发生变化这样几何上相邻的1方格就可以合并消掉变化的那个变量。使用卡诺图时最常见的错误有三个。第一个错误是圈组时圈太大超出了2的幂次。卡诺图合并圈的边长必须是1、2、4、8……也就是2的幂次不能圈3个、5个或6个格子。有次带实习生做项目他圈了一个4x2的矩形里4个1格子没问题但另外一个圈圈了6个格子我说你数数6是2的幂次吗他脸一红回去改了一圈。这个细节直接决定了化简结果是否合法。第二个错误是忘了考虑“回绕相邻”。卡诺图是个立体的概念左右边界是相邻的上下边界也是相邻的。很多新手化简4变量卡诺图时会漏掉跨越边界的相邻关系导致圈组不够大化简不彻底。我的习惯是圈完第一轮后故意把图旋转90度再看一遍检查有没有被忽略的回绕相邻。第三个错误是没用利用好无关项X。前面说了真值表里标出来的X项是可以当作1来圈组的前提是输出的最终结果在X输入组合下不会影响系统行为。比如一个BCD码转七段码的译码器输入是0到9那么A到F这些非法输入就属于无关项。圈进这些X项之后表达式往往能简化不少。但要注意用无关项化简后的电路在非法输入出现时输出可能是任意值如果后续模块依赖这个输出就要谨慎了。2.2 代数化简的适用场景代数化简并没有完全退出实战它的优势是在表达式本身很简单时可以快速得到结果不需要画卡片图。比如前面那个安防报警器Y A B C一眼就能看出来就是或门没必要画卡诺图。还有一个典型的场景是化简“异或”和“同或”类逻辑。代数化简里的几个特殊公式比如A XOR B AB AB在实际设计加法器、奇偶校验电路时非常常用。这类电路如果用卡诺图反而麻烦因为异或关系的方格分布比较散圈组出来还是异或结构不如直接用代数公式来得快。我的建议是把代数化简当作“心算技能”来处理处理2到3个变量的简单表达式时用变量多了直接上卡诺图或工具别硬刚。化简的结果可以用真值表回溯验证——把化简后的表达式重新代入所有输入组合看输出是否和原始真值表完全一致。这一步不能省尤其在你用了无关项的情况下。2.3 用工具做逻辑最小化实际工程项目里表达式动辄十几个变量卡诺图手工化简根本不可行。用Verilog或VHDL写行为级描述然后交给综合工具去优化这是主流做法。EDA工具的逻辑综合过程大致是首先把HDL代码解析成布尔表达式然后调用内部算法比如Quine-McCluskey算法或Espresso算法做逻辑最小化再映射到目标工艺库或者FPGA的查找表结构上。设计者需要关心的核心参数主要有几个面积优先还是速度优先、扇出限制、最大逻辑级数。以我常用的Quartus Prime为例综合设置里有“面积/速度平衡”的滑杆默认在中间位置如果设计只追求最小时延就往速度方向拉代价是占用更多逻辑单元。这里说一个容易误解的点逻辑最小化和电路最小化不是一回事。逻辑最小化是把布尔表达式化简到最少项数但电路实现时还要考虑器件库的约束。比如在FPGA里一个6输入查找表可以实现任意的6输入布尔函数你辛辛苦苦化简到很短的表达式可能最终只占用一个查找表而一个看似复杂的表达式只要不超过6个输入也是占用一个查找表两者资源开销完全一样。所以用FPGA做设计逻辑化简的意义更多在于降低信号传播路径上的逻辑级数而不是减少资源占用。这一点和用74系列芯片搭分立电路完全不同用芯片搭电路时每少一个门就是少一颗芯片化简的收益非常直观。3. 从布尔表达式到门级电路一个4位比较器的完整设计这块我想用一个具体的例子把整个流程串起来设计一个4位二进制数比较器输入是两个4位二进制数A[3:0]和B[3:0]输出是三个信号A大于B、A等于B、A小于B。这个例子麻雀虽小五脏俱全适合展示组合逻辑设计的完整流程。3.1 需求定义与真值表推演4位比较器的输入是8个信号完整真值表有256行手写不现实。这时候要换一种思路用“按位比较”的逻辑来分解问题而不是直接展开穷举。这也是组合逻辑设计里的常用技巧先拆解功能再分别设计子电路最后拼装。4位数的大于比较可以拆成从高位到低位的逐位判断。先比较最高位A[3]和B[3]如果A[3]大于B[3]无论低位如何整体就是A大于B如果A[3]小于B[3]整体就是A小于B如果A[3]等于B[3]则继续比较A[2]和B[2]。以此类推直到最低位。这个过程本质上就是一个级联的“局部比较器”链。根据这个逻辑可以设计一个基础的1位比较器模块它有三个输出X表示本位的“大于”Y表示“等于”Z表示“小于”。输入是A_bit和B_bit以及来自更低位的比较结果。注意这里有两个版本的1位比较器做迭代比较时每个位都需要一个“本级比较结果结合低位比较结果”的逻辑。不过对于最高位它不需要参考低位可以直接根据两个最高位的大小关系输出整体比较结果但为了统一电路结构我习惯让每一位都采用相同的级联结构高位模块的“低位比较结果”输入由高一位的模块提供最低位则固定输入“等于”。3.2 表达式推导与门级实现1位比较器本级逻辑很简单设输入为Ai和Bi输出两个信号Gi表示Ai大于Bi不考虑低位Ei表示Ai等于Bi。真值表和表达式为Gi Ai · Bi也就是当前位输入为1且对方为0时本级“更大”。Ei Ai · Bi Ai · Bi也就是Ai和Bi相同通常用同或门XNOR实现。然后整体级联。设Hi表示“第i位及以上比较结果为A大于B”Li表示“第i位及以上比较结果为A小于B”EQ表示“到目前为止各位都相等”。那么从最高位开始逐位向下传递最高位H[3] G[3]L[3] Ai · Bi也就是Ai为0且Bi为1EQ[3] E[3]。下一位传递规则H[2] G[2] E[2] · H[3]L[2] A[2]·B[2] E[2] · L[3]EQ[2] E[2] · EQ[3]。最终A大于B输出就是H[0]A小于B输出L[0]A等于B输出EQ[0]。这套表达式在门级实现时一个很自然的做法是把E信号和H、L信号打包成一位的比较器单元然后级联。用芯片搭的话每个位大约需要两个与门、一个或门、一个同或门。4位总共需要大约16个门左右选用74LS08四重二输入与门、74LS32四重二输入或门、74LS86四重二输入异或门加上74LS04六反相器三四颗芯片就能搭出来。如果用Verilog一个assign语句就能搞定代码连两行都不到。这里有个设计取舍值得说级联比较器的延时会随位数线性增加。4位还好如果做到16位、32位低位结果传到最高位的路径会很长制约系统时钟频率。工业级的比较器一般会用树形结构或者进位选择结构来缩短关键路径。选用级联结构还是树形结构取决于你是用FPGA做流水线设计还是用分立芯片搭一个低频演示电路。做演示电路就用级联简单可靠做高性能设计就得考虑树形比较甚至用查找表。3.3 画原理图和选择芯片门级表达式推导出来之后就到了画原理图选芯片的环节。用74系列搭建时我的选择习惯是这样优先使用同或门74LS266或异或门74LS86配合反相器实现“等于”判断异或门输出为1表示不相等后面再接一个反相器就是相等信号。这样做的好处是芯片种类少布线清晰。画原理图时有一个细节容易被忽略门电路的闲置引脚处理。74系列芯片一个封装里有多个门比如74LS08有4个与门如果只用了3个剩下的一个与门输入引脚不能悬空必须接固定电平通常把两个输入接地输出悬空即可。悬空引脚在CMOS工艺下会引入不确定逻辑状态甚至产生振荡直接表现为电路时不时冒出一个毫无规律的脉冲。另外原理图上的信号命名也很重要。我在这个项目里会按照信号功能加前缀EQ_3、GT_3、LT_3表示第3位的比较结果CMP_GT_FINAL、CMP_LT_FINAL表示最终整体比较结果。清晰的命名能让你在焊接、调板、仿真时省下大量时间不要在这一步偷懒。4. 仿真和验证别以为电路画完就结束了很多新手最大的误区就是真值表写好了表达式化简完了原理图也画好了就觉得大功告成。实际上组合逻辑设计的验证阶段能占到整个项目工时的一半以上。尤其在用可编程逻辑器件时光看代码不看波形等于闭着眼睛开车。4.1 功能仿真的流程与要点拿到完整电路或HDL代码之后首先要做的是功能仿真。功能仿真的特点是只验证逻辑正确性不考虑门延迟、布线延迟等物理因素。用Multisim做分立元件的功能仿真时只需要把电源和地接好用逻辑分析仪或者探针去观察各路输出用Quartus和ModelSim做FPGA仿真时则需要写一个测试激励文件testbench给输入信号加激励然后检查输出波形。测试激励的编写是整个验证工作的核心。很多人写testbench就是输入随便给几种组合看看输出对不对就给过了这种做法非常危险。我的习惯是写一个穷举式testbench对整个4位比较器来说A从0到15遍历B从0到15遍历总共256个输入组合每个组合只要一个时钟周期即可完成遍历。在testbench里用两个循环控制A和B的取值然后自动比对输出和预期结果遇到不一致就打印错误信息。这种自动比对的方式比盯波形高效得多因为人工检查256组波形还不看走眼的概率约等于零。具体到4位比较器的testbench伪代码如下reg [3:0] A, B; wire GT, EQ, LT; // 实例化比较器模块 comparator_4bit uut(A, B, GT, EQ, LT); initial begin for (A 0; A 16; A A 1) begin for (B 0; B 16; B B 1) begin #10; if (GT ! (A B) || EQ ! (A B) || LT ! (A B)) $display(Mismatch: A%d, B%d, GT%b EQ%b LT%b, A, B, GT, EQ, LT); end end $display(Test complete); $finish; end这段测试的核心价值不在于检查所有组合都通过而在于当某个组合失败时能精确地把失败条件打印出来。你直接根据打印的信息定位到真值表的那一行找到是表达式推导错了还是电路连线错了。我在实际项目中遇到过的情况是发现A3、B5时GT被拉高了查了半天最后发现是原理图里某个与门的14脚电源没接上导致该门的输出一直保持在低电平。这种问题在功能仿真里就能暴露出来如果不是用了穷举自动比对盯波形很难发现。4.2 时序仿真与毛刺问题功能仿真通过之后如果做的是FPGA设计还需要做时序仿真或叫后仿真。时序仿真会带上真实的门延迟、布线延迟、查找表延迟等信息这时你可能会看到功能仿真中不会出现的现象——毛刺glitch。毛刺的本质是组合逻辑的竞争冒险。以前面4位比较器的级联结构为例当A和B同时变化时从高位到低位的“传递使能”信号E[3]、E[2]也会变化如果某一条路径上的传播延迟和另一条路径不一致在信号稳定之前的那个短暂窗口里输出可能会出现一个不该出现的脉冲。这个脉冲持续时间在纳秒级在FPGA内部可能被触发器采样到然后变成实际的功能错误。处理毛刺的常用手段有三个。最简单的是在输出端加RC滤波用一个小电容吸收窄脉冲但它会降低信号上升沿速率适合低速信号第二个方法是采用同步设计用触发器在时钟边沿采样组合逻辑输出保证采样时刻输出已经稳定这是FPGA设计的推荐做法第三个方法是在卡诺图化简时保留冗余圈就是对那些存在相邻但未合并的1方格额外加一个圈来消除竞争危险。冗余圈的代价是多一个与项但对可靠性要求高的场景是值得的。我的个人习惯是异步逻辑里尽量避免毛刺风险加上简单的RC滤波同步逻辑里直接按时序逻辑方式处理组合逻辑的输出只在时钟边沿被采样毛刺不影响功能正确性。说到底只要时钟周期大于信号传播的最长路径延迟毛刺就不是问题。5. 组合逻辑电路的常见深坑与排查思路组合逻辑看起来简单只是几个门连来连去但实际调试时遇到的坑一个比一个隐蔽。这一节把我这些年踩过的、以及帮别人排查过的典型问题整理出来当做一个速查手册用。5.1 竞争冒险最难定位的问题竞争冒险在组合逻辑设计里是最经典也最难排查的问题。它的特点是功能仿真完全正确波形干净利落但一上板或者一做时序仿真就出问题而且问题往往表现为“偶发性故障”——十次运行有九次正常偶尔错一次。我之前做过一个多路选择器电路输入选择信号S从0切换到1时输出偶尔会多出一个不存在的脉冲。最初以为是开关抖动后来用示波器抓波形才发现隐患出在S信号经过不同路径到达两个与门的时刻不同——一个路径经过了2个门另一个路径只走了1个门于是两个与门使能信号的切换错开了一个门的延迟导致先使能了一个本不该使能的通路输出了一个毛刺。排查这类问题有几个实用手段。第一看波形时要同时抓输入信号和输出信号观察输入信号跳变沿之后的10到20纳秒窗口里输出是否有异常第二用EDA工具的时序分析功能查看关键路径延迟如果发现某条路径延迟异常长优先怀疑布线和扇出问题第三对于重点信号可以人为加约束让工具做时序优化。消除竞争冒险的常用手段我在前面已经讲过这里再强调一个思路如果你是做FPGA设计最简单的根治方法其实是把组合逻辑输出接到一个触发器上在时钟边沿采样。因为时序逻辑天然规避了组合逻辑的毛刺问题你不需要在组合逻辑层面死磕。当然前提是这个信号本身处于同步时钟域里跨时钟域的场景另当别论。5.2 扇入扇出和驱动能力的工程细节组合逻辑电路不只是“逻辑对不对”的问题还有“电性能够不够”的问题。扇出系数是指一个门电路输出能够驱动多少个后续门输入这个概念在分立元件时代非常重要。74LS系列典型扇出系数是10个LS-TTL负载如果超了输出电平可能达不到标准高电平或低电平导致后续门逻辑判断错误。我记得有一次用74LS04做信号反相输出同时接到了三个后续芯片的输入还接了一个LED指示灯结果高电平时电压被拉到了2.0V以下后续芯片的阈值是2.0V于是有一部分芯片认为它是高电平另一部分认为它是低电平整个电路逻辑崩溃。当时排查了很久最后用示波器一测才发现是驱动能力的锅。解决办法是加一级缓冲器比如74LS244或者用三极管做电流放大或者减少LED的电流消耗把限流电阻加大。在FPGA设计中扇出的概念类似但不需要你手动数后面接了几个门综合工具会自动计算扇出并优化复制逻辑。不过你仍然可以在工程设置里限制最大扇出数比如限制为20工具就会自动复制高扇出信号为多份每份只驱动一部分负载从而缩短布线延迟、改善时序。5.3 常见问题速查表整理一个我在带新人时经常发给他们的排查清单希望对大家有帮助症状可能原因排查手段电路完全无输出电源没接对芯片Pin14和Pin7接反或输出引脚悬空万用表测芯片VCC和GND电压输出永远是高电平或低电平门输入端悬空CMOS芯片输入噪声触发不定态检查所有输入引脚是否连接固定电平同一设计在一部分板子上正常另一部分异常芯片批次差异高低电平阈值漂移或者电源纹波过大用示波器抓电源纹波更换芯片批次测试功能仿真正确上板后偶发错误竞争冒险或时序路径问题时序仿真查看毛刺窗口改用同步采样电路输出正确但带载后掉电压扇出系数不足加缓冲器或降低负载电流74系列芯片发烫输出短路或者CMOS输入悬空导致闩锁效应断电检查连线重点查输出是否对接地短路还有一个容易被忽略的坑逻辑分析仪和示波器的探针本身也会影响电路。探针会引入电容负载高频信号容易受影响。测量高速组合逻辑信号时建议用10倍衰减的示波器探头尽量减少对被测信号的负载影响。6. 从组合逻辑到完整项目的经验收尾回到最初的问题。组合逻辑电路设计看起来是数字电路入门的第一课但真正在项目里把它做好涉及的东西远超“列真值表、化简、画图”这三部曲。尤其是当你开始用FPGA做复杂系统时组合逻辑的思维还会影响你对时序、复位策略、异步信号处理等一系列问题的判断。我个人的体会是组合逻辑设计练的是“确定性思维”——任何输出都必须由当前输入唯一决定这个思维一旦内化后面的时序逻辑设计会顺利很多。最后分享一个小技巧。我在检查自己画的组合逻辑原理图时会先在纸面上把每个门的输入输出标一遍节点名再对照真值表逐行演算一遍确认无误后才开始连线。这个过程看起来费时间但实际上比焊完板子再拿万用表逐点查快得多。对于用HDL的朋友我的建议是每写一小段组合逻辑就立即写一段对应的穷举式testbench不要等到整个模块写完再统一验证这样能把错误消灭在萌芽阶段也避免后期调试时在大段代码里猜bug。组合逻辑的很多经验和教训是相通的希望这篇内容能帮你在自己的项目里少踩几个坑。
返回列表