经典的逻辑函数化简算法 Espresso
经典的逻辑函数化简算法 Espresso。它虽然不是“唯一”的化简算法,但因其高效和强大的启发式能力,成为了该领域的标杆和事实标准。
1. 核心思想
Espresso 的核心思想不是像奎因-麦克卢斯基算法那样追求绝对的数学最小化(这在计算上非常昂贵),而是通过一系列局部变换和启发式搜索,快速找到一个接近最优的、非常简化的两级逻辑电路(与-或式或或-与式)。它直接处理立方体,非常适合于计算机实现。
基本概念:
立方体
代表一个乘积项。例如,在一个三变量函数中,立方体 10-
表示 A·C'
(其中 -
表示该变量不出现,1
表示原变量,0
表示反变量)。
覆盖
一组立方体的集合,完整地描述了逻辑函数。覆盖中的每一个立方体都对应与或表达式中的一个乘积项。
2. Espresso 的主要变换算法原理
Espresso 算法的核心是三个主要的变换步骤,它们被循环迭代执行,直到覆盖不再改善为止。这三个步骤是:扩张、不可略约化、降低。
步骤一:扩张
目标
让每个立方体变得尽可能大(即覆盖更多的最小项),从而可能消除掉整个立方体。
原理
对于一个立方体,尝试将其中的某个 0
或 1
变成 -
(不关心)。如果这个新的、更大的立方体所覆盖的所有最小项,仍然被整个覆盖所包含(即不包含任何不属于函数的“关断”项),那么这个扩张就是合法的。通过扩张,小的立方体会被大的立方体“吞并”,从而可以被移除。
举例说明:
假设我们有一个三变量函数的覆盖:
F = { 100, 101, 110, 111 }
观察立方体
100
。它覆盖了最小项A'B'C'
尝试扩张:将第一位
1
变成-
。得到新立方体-00
检查
-00
覆盖了哪些最小项?000
和100
100
本来就在函数中000
在不在函数中?查看原始覆盖,没有000
,所以这个扩张是非法的,因为它引入了关断项
尝试另一个扩张:将第三位
0
变成-
。得到新立方体10-
检查
10-
覆盖了100
和101
100
在覆盖中101
也在覆盖中没有引入任何关断项。所以这个扩张是合法的
用
10-
替换原来的100
和101
。现在覆盖变为F = { 10-, 110, 111 }
继续扩张
110
。将第三位0
变成-
,得到11-
。它覆盖110
和111
,都在覆盖内。合法用
11-
替换110
和111
最终覆盖变为
F = { 10-, 11- }
可以看到,通过扩张,我们从4个立方体简化到了2个立方体。10-
就是 A·B'
,11-
就是 A·B
步骤二:不可略约化
目标
找到当前覆盖的一个“最小”子集,即尝试移除某些立方体,看剩下的覆盖是否依然能完整表示该函数。
原理
按某种顺序(通常是启发式的,如从最难被其他立方体覆盖的项开始)检查覆盖中的每一个立方体。如果移除该立方体后,剩下的覆盖依然能覆盖该立方体所覆盖的所有最小项,那么这个立方体就是“可略约的”,可以安全地移除。
举例说明:
承接上例,我们有一个非常简单的覆盖:F = { 10-, 11- }
尝试移除
10-
10-
覆盖了100
和101
检查剩下的覆盖
{ 11- }
能否覆盖100
和101
?11-
覆盖110
和111
它无法覆盖
100
和101
所以
10-
是不可略约的,必须保留
尝试移除
11-
11-
覆盖了110
和111
检查剩下的覆盖
{ 10- }
能否覆盖110
和111
?10-
覆盖100
和101
无法覆盖
110
和111
所以
11-
也是不可略约的
当前覆盖
{ 10-, 11- }
已经是不可略约覆盖
再举一个需要略约的例子。假设有覆盖 F = { 00-, 1-1, -11 }
尝试移除
-11
-11
覆盖011
和111
检查
{ 00-, 1-1 }
能否覆盖011
和111
?011
不被00-
(覆盖000,001
)覆盖,也不被1-1
(覆盖101,111
)覆盖。失败。
所以
-11
不可略约。
尝试移除
1-1
1-1
覆盖101
和111
。检查
{ 00-, -11 }
能否覆盖101
和111
?101
不被00-
覆盖,也不被-11
(覆盖011,111
)覆盖。失败。
所以
1-1
不可略约。
尝试移除
00-
00-
覆盖000
和001
。检查
{ 1-1, -11 }
能否覆盖000
和001
?000
和001
显然不被1-1
或-11
覆盖。失败。
看起来都不可略约?但注意,我们的覆盖可能不是最简的。实际上,
-11
已经覆盖了111
,而1-1
也覆盖了111
,存在冗余。IRR 的步骤顺序很重要。如果我们最后检查1-1
,并且发现111
已经被-11
覆盖,101
是唯一由1-1
独覆盖的最小项吗?不是,101
只被1-1
覆盖。所以它也不能被移除。
这个例子说明了 IRR 的复杂性,它依赖于一个“目标覆盖”来计算独覆盖顶点。在 Espresso 的流程中,EXPAND 之后可能已经消除了很多冗余,IRR 再进行最后的精简。
步骤三:降低
目标
为下一次迭代的“扩张”创造机会。通过有意地将立方体“缩小”,可能会在后续的扩张中导向一个更全局优化的解。
原理
与扩张相反。尝试将立方体中的 -
变回 0
或 1
,使其仅覆盖其“独覆盖顶点”。如果一个最小项只被一个立方体覆盖,则该最小项称为该立方体的独覆盖顶点。降低操作确保该立方体仍然覆盖其至少一个独覆盖顶点。这个被缩小了的立方体在下一轮扩张时,有可能朝着一个新的、更好的方向扩张,从而找到更优的覆盖。
举例说明:
假设一个覆盖 F = { 1-1, -11 }
(这已经比较简化了)。
找出每个立方体的独覆盖顶点。
1-1
覆盖了101
和111
。101
只被1-1
覆盖吗?是的(-11
覆盖011
和111
,不覆盖101
)。所以101
是1-1
的独覆盖顶点。
-11
覆盖了011
和111
。011
只被-11
覆盖吗?是的(1-1
不覆盖011
)。所以011
是-11
的独覆盖顶点。111
被两个立方体同时覆盖,所以它不是独覆盖顶点。
执行降低。
对于
1-1
,为了使其仍然覆盖独覆盖顶点101
,我们可以将其降低为101
。对于
-11
,为了使其仍然覆盖独覆盖顶点011
,我们可以将其降低为0-11
(即011
)。
现在覆盖变成了
F = { 101, 011 }
。这看起来是更差了。但关键是下一步:现在进入下一轮循环,对
101
和011
进行扩张。扩张
101
:尝试将第一位
1
变成-
,得到-01
。它覆盖001
和101
。001
是关断项吗?我们需要参考原始函数。假设不是,那么这是一个合法的、全新的扩张方向!最终可能扩张成一个像-01
这样的项。同样,扩张
011
可能得到-11
。
新的覆盖可能变为
F = { -01, -11 }
,这可能比最初的{ 1-1, -11 }
更好(例如,文字总数更少)。
3. 完整的 Espresso 算法流程
Espresso 通过循环调用这三个步骤,并辅以输出矫正来保证结果正确。
初始化:读入函数的初始覆盖(例如,由最小项组成)。
开始循环:
a. 扩张:对覆盖中的立方体进行扩张,使其尽可能大,消除冗余。
b. 输出矫正:检查当前的覆盖是否依然正确地表示了原始函数。因为扩张和后续操作都是在“ON-Set”(函数值为1的集合)和“DC-Set”(不关心项集合)上进行的,需要确保没有覆盖到“OFF-Set”(函数值为0的集合)。这一步是质量的保证。
c. 不可略约化:从当前覆盖中移除所有冗余的立方体。
d. 降低:缩小立方体,为下一次迭代寻找新的优化路径。终止条件:当一次完整的循环后,覆盖没有再发生任何改善时,算法停止。
4. 思路总结
Espresso 的强大之处在于它巧妙的启发式思想:
扩张是贪婪的化简,追求立竿见影的效果。
不可略约化是精益求精,剔除最后的冗余。
降低是以退为进,通过暂时“变差”来跳出局部最优解,寻找全局更优解。
这种迭代优化策略使得 Espresso 能够在合理的时间内,对大规模的逻辑函数产生非常高质量(通常就是最优)的化简结果,从而成为 EDA 工具链中不可或缺的一部分。