第四讲:组合逻辑还原基础——从网表中提取与或非逻辑表达式
各位同学,今天我们来聊聊组合逻辑还原中最基础、也是最核心的一步——从网表里把与或非表达式给“挖”出来。
说实话,我刚入行那会儿,拿到一个综合后的门级网表,看着满屏的AND、OR、INV、NAND、NOR,脑袋都是大的。后来带我的老工程师跟我说了一句话,我到现在还记得:“网表就是一堆门,门就是布尔代数,布尔代数就是与或非。你只要会看门,就会写表达式。”
嗯,今天我就把这句话拆开揉碎了讲给你听。
4.1 网表里的“门”到底长什么样?
我们先看一个最简单的例子。假设你拿到了一段这样的网表描述(Verilog格式):
// 网表片段
AND2 U1 (.A(a), .B(b), .Y(n1));
OR2 U2 (.A(n1), .B(c), .Y(n2));
INV U3 (.A(n2), .Y(z));
这玩意儿怎么读?
- U1 是一个两输入与门(AND2),输入是 a 和 b,输出是内部节点 n1。
- U2 是一个两输入或门(OR2),输入是 n1 和 c,输出是 n2。
- U3 是一个反相器(INV),输入是 n2,输出是 z。
所以,从输入到输出的逻辑关系就是:
n1 = a & b
n2 = n1 | c = (a & b) | c
z = ~n2 = ~((a & b) | c)
你看,这不就是最基础的与或非表达式吗?
核心思想:网表里的每一个标准单元(Standard Cell),本质上就是一个布尔运算器。你只需要顺着连线,把门的输出表达式代入到下一级的输入里,就能得到最终的逻辑表达式。
4.2 提取表达式的“三步法”
我在项目中总结了一套方法,叫“三步法”。你照着做,基本不会出错。
- 第一步:找输出端口
先找到网表的顶层输出端口(比如 z、y、data_out 等)。这是你的起点。 - 第二步:反向追踪
从输出端口出发,沿着连线往回找。遇到一个门,就把它的输入表达式写出来。遇到内部节点,就继续往前追。 - 第三步:代入化简
把所有中间节点的表达式,逐级代入到输出端口的表达式里。最后得到的就是一个只包含原始输入端口(比如 a、b、c、clk 等)的布尔表达式。
举个例子,我们看一个稍微复杂一点的网表:
NAND2 U4 (.A(d), .B(e), .Y(n3));
NOR2 U5 (.A(n3), .B(f), .Y(n4));
AOI21 U6 (.A(n4), .B(g), .C(h), .Y(z));
这里出现了一个 AOI21(与或非门),它内部结构是:Y = ~( (A & B) | C )。
好,我们按三步法来:
- 输出端口是 z,由 U6 驱动。
- U6 的输入是 n4、g、h。所以
z = ~( (n4 & g) | h )。 - n4 由 U5 驱动,U5 是 NOR2:
n4 = ~(n3 | f)。 - n3 由 U4 驱动,U4 是 NAND2:
n3 = ~(d & e)。
现在代入:
n4 = ~( ~(d & e) | f )
z = ~( ( ~( ~(d & e) | f ) & g ) | h )
嗯,这个表达式看起来有点长。但别急,后面我们会讲怎么化简。今天你只要学会“提取”这一步就行。
我的小技巧:在纸上画一个简单的逻辑图。把每个门画成一个符号,连线标上节点名。这样反向追踪的时候,眼睛跟着线走,比盯着文本网表舒服多了。我早期做项目时,网表动辄几十万行,不画图根本理不清。
4.3 常见门单元的表达式对照表
为了方便你快速提取,我把最常用的门单元及其布尔表达式整理成了一张表。建议你收藏一下,工作中随时翻看。
| 单元名称 | 功能描述 | 布尔表达式(Y = ...) |
|---|---|---|
| AND2 | 两输入与门 | A & B |
| OR2 | 两输入或门 | A | B |
| INV | 反相器 | ~A |
| NAND2 | 两输入与非门 | ~(A & B) |
| NOR2 | 两输入或非门 | ~(A | B) |
| XOR2 | 两输入异或门 | A ^ B |
| XNOR2 | 两输入同或门 | ~(A ^ B) |
| AOI21 | 与或非门(2输入与 + 1输入或 + 反相) | ~( (A & B) | C ) |
| OAI21 | 或与非门(2输入或 + 1输入与 + 反相) | ~( (A | B) & C ) |
| AOI22 | 与或非门(两组两输入与 + 反相) | ~( (A & B) | (C & D) ) |
注意:AOI 和 OAI 这类复合门,在网表里非常常见。它们本质上就是把“与或”和“非”打包成了一个单元。提取表达式时,千万不要把它拆成多个门来写,直接套用上表中的公式即可。我曾经见过有同事把 AOI21 拆成 AND + OR + INV 三步写,结果表达式越写越乱,最后对不上功能。
4.4 一个完整的实战例子
我们来看一个真实项目中遇到的场景。假设网表如下:
// 模块:data_check
// 输入:a, b, c, d, en
// 输出:valid
INV U1 (.A(en), .Y(n_en));
NAND2 U2 (.A(a), .B(b), .Y(n1));
NOR2 U3 (.A(c), .B(d), .Y(n2));
AOI21 U4 (.A(n1), .B(n2), .C(n_en), .Y(valid));
我们来提取 valid 的表达式。
第一步,输出是 valid,由 U4 驱动。
U4 是 AOI21:valid = ~( (n1 & n2) | n_en )。
第二步,追 n1、n2、n_en:
n1 = ~(a & b)n2 = ~(c | d)n_en = ~en
第三步,代入:
valid = ~( ( ~(a & b) & ~(c | d) ) | ~en )
嗯,这个表达式看起来有点复杂。但没关系,我们后面会讲怎么用布尔代数化简它。今天你只要能做到这一步,就已经掌握了组合逻辑还原的“基本功”。
总结一下:从网表提取与或非表达式,说白了就是“看门、写式、代入”六个字。你只要认识常见的门单元,会写布尔表达式,再顺着连线反向追踪,就能把任何组合逻辑网表翻译成数学公式。
4.5 本章知识体系图
下面我用一张 SVG 图来总结本章的核心逻辑。你可以把它当作一个“思维导图”来用。
4.6 避坑指南
最后,我分享几个自己踩过的坑,希望能帮你少走弯路。
- 不要忽略反相器。 网表里经常有大量的 INV 单元,它们会改变信号的极性。如果你漏掉一个反相器,整个表达式可能就反了。我曾经在调试一个数据通路时,因为漏掉了一个 INV,导致表达式结果跟仿真对不上,查了整整两天才找到问题。
- 注意多扇出节点。 如果一个内部节点(比如 n1)被多个门使用,你要确保在代入时,这个节点的表达式只写一次,不要重复计算。否则表达式会变得非常冗余。
- 复合门不要拆开写。 像 AOI、OAI 这种门,直接套用上表的公式。拆开写不仅容易出错,而且会让表达式变得冗长,不利于后续化简。
好了,今天的内容就到这里。你只要把“三步法”练熟,以后看到任何组合逻辑网表,都能像看数学公式一样清晰。
公众号:蓝海资料掘金营,微信deep3321