Skip to content

Commit 6bf4973

Browse files
kroeningclaude
andcommitted
AIG simplifier using equivalences
Add aig_simplify() function that simplifies an AIG using a given vector of equivalent node pairs. The function: - Substitutes equivalent nodes (replacing larger var numbers with smaller) - Handles equivalences with constants - Computes transitive closure of equivalences - Performs constant propagation (a AND false = false, a AND true = a) - Simplifies trivial cases (a AND a = a, a AND !a = false) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent e0984bc commit 6bf4973

File tree

5 files changed

+640
-1
lines changed

5 files changed

+640
-1
lines changed

src/trans-netlist/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
SRC = aig.cpp \
22
aig_prop.cpp \
3+
aig_simplifier.cpp \
34
aig_terminals.cpp \
45
bmc_map.cpp \
56
build_netlist_var_map.cpp \
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/*******************************************************************\
2+
3+
Module: AIG Simplifier
4+
5+
Author: Daniel Kroening, [email protected]
6+
7+
\*******************************************************************/
8+
9+
#include "aig_simplifier.h"
10+
11+
/// Apply a substitution map to a literal
12+
/// \param l: The literal to substitute
13+
/// \param substitution: Map from variable numbers to replacement literals
14+
/// \return The substituted literal
15+
static literalt
16+
substitute(literalt l, const std::vector<literalt> &substitution)
17+
{
18+
if(l.is_constant())
19+
return l;
20+
21+
auto var_no = l.var_no();
22+
PRECONDITION(var_no < substitution.size());
23+
24+
// Get the replacement literal and apply the sign
25+
return substitution[var_no] ^ l.sign();
26+
}
27+
28+
/// Simplify an AND node after substitution
29+
/// \param a: First input literal (after substitution)
30+
/// \param b: Second input literal (after substitution)
31+
/// \param dest: The destination AIG to add the node to
32+
/// \return The literal representing the simplified AND
33+
static literalt simplify_and(literalt a, literalt b, aigt &dest)
34+
{
35+
// Constant propagation
36+
if(a.is_false() || b.is_false())
37+
return const_literal(false);
38+
39+
if(a.is_true())
40+
return b;
41+
42+
if(b.is_true())
43+
return a;
44+
45+
// a AND a = a
46+
if(a == b)
47+
return a;
48+
49+
// a AND !a = false
50+
if(a == !b)
51+
return const_literal(false);
52+
53+
// Normalize: smaller variable number first
54+
if(b.var_no() < a.var_no())
55+
std::swap(a, b);
56+
57+
// Create the AND node
58+
return dest.new_and_node(a, b);
59+
}
60+
61+
aigt aig_simplify(const aigt &src, const equivalencest &equivalences)
62+
{
63+
if(src.empty())
64+
return aigt();
65+
66+
const auto num_nodes = src.number_of_nodes();
67+
68+
// Build a substitution map: for each variable, what literal should it
69+
// be replaced with? Initially, each variable maps to itself.
70+
std::vector<literalt> substitution;
71+
substitution.reserve(num_nodes);
72+
73+
for(std::size_t i = 0; i < num_nodes; i++)
74+
substitution.emplace_back(i, false);
75+
76+
// Process equivalences. For each equivalence (l1, l2), we want to
77+
// replace references to the larger variable with the smaller one.
78+
for(const auto &eq : equivalences)
79+
{
80+
literalt l1 = eq.first;
81+
literalt l2 = eq.second;
82+
83+
// Skip invalid equivalences
84+
if(l1.is_constant() && l2.is_constant())
85+
continue;
86+
87+
// If one is constant, the other should map to that constant
88+
if(l1.is_constant())
89+
{
90+
if(l2.var_no() < num_nodes)
91+
substitution[l2.var_no()] = l1 ^ l2.sign();
92+
continue;
93+
}
94+
95+
if(l2.is_constant())
96+
{
97+
if(l1.var_no() < num_nodes)
98+
substitution[l1.var_no()] = l2 ^ l1.sign();
99+
continue;
100+
}
101+
102+
// Both are non-constant
103+
// l1 ≡ l2 means: v1 ^ s1 ≡ v2 ^ s2
104+
// We replace the larger variable with the smaller
105+
auto v1 = l1.var_no();
106+
auto v2 = l2.var_no();
107+
108+
if(v1 >= num_nodes || v2 >= num_nodes)
109+
continue;
110+
111+
// Compute the sign relationship: if l1 ≡ l2, then
112+
// v1 should map to v2 ^ (s1 ^ s2), or vice versa
113+
bool signs_differ = l1.sign() != l2.sign();
114+
115+
if(v1 < v2)
116+
{
117+
// Replace v2 with v1
118+
substitution[v2] = literalt(v1, signs_differ);
119+
}
120+
else if(v2 < v1)
121+
{
122+
// Replace v1 with v2
123+
substitution[v1] = literalt(v2, signs_differ);
124+
}
125+
// If v1 == v2, the equivalence is trivial (l1 ≡ l1 or l1 ≡ !l1)
126+
// The latter case (l1 ≡ !l1) would be a contradiction, ignore it
127+
}
128+
129+
// Compute transitive closure of substitutions
130+
// Repeat until no changes
131+
bool changed;
132+
do
133+
{
134+
changed = false;
135+
for(std::size_t i = 0; i < num_nodes; i++)
136+
{
137+
literalt subst = substitution[i];
138+
if(subst.is_constant() || subst.var_no() == i)
139+
continue;
140+
141+
// Follow the substitution chain
142+
literalt next = substitution[subst.var_no()] ^ subst.sign();
143+
if(next != subst)
144+
{
145+
substitution[i] = next;
146+
changed = true;
147+
}
148+
}
149+
} while(changed);
150+
151+
// Now rebuild the AIG with substitutions applied
152+
aigt dest;
153+
154+
// Map from old node indices to new literals
155+
std::vector<literalt> old_to_new(num_nodes);
156+
157+
for(std::size_t i = 0; i < num_nodes; i++)
158+
{
159+
const auto &node = src.nodes[i];
160+
161+
// First, check what this node is substituted with
162+
literalt subst = substitution[i];
163+
164+
if(subst.is_constant())
165+
{
166+
// This node is equivalent to a constant
167+
old_to_new[i] = subst;
168+
continue;
169+
}
170+
171+
if(subst.var_no() != i)
172+
{
173+
// This node is equivalent to another (smaller) node
174+
// Use what that node maps to, applying the sign
175+
old_to_new[i] = old_to_new[subst.var_no()] ^ subst.sign();
176+
continue;
177+
}
178+
179+
// This node is a representative (maps to itself), we need to create it
180+
if(node.is_var())
181+
{
182+
// Variable node
183+
old_to_new[i] = dest.new_var_node(node.var_no());
184+
}
185+
else
186+
{
187+
// AND node - substitute inputs and simplify
188+
literalt new_a = substitute(node.a, old_to_new);
189+
literalt new_b = substitute(node.b, old_to_new);
190+
old_to_new[i] = simplify_and(new_a, new_b, dest);
191+
}
192+
}
193+
194+
return dest;
195+
}

src/trans-netlist/aig_simplifier.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*******************************************************************\
2+
3+
Module: AIG Simplifier
4+
5+
Author: Daniel Kroening, [email protected]
6+
7+
\*******************************************************************/
8+
9+
/// \file
10+
/// AIG Simplifier using Equivalences
11+
12+
#ifndef CPROVER_TRANS_NETLIST_AIG_SIMPLIFIER_H
13+
#define CPROVER_TRANS_NETLIST_AIG_SIMPLIFIER_H
14+
15+
#include "aig.h"
16+
17+
#include <utility>
18+
#include <vector>
19+
20+
/// A pair of equivalent literals
21+
using equivalencet = std::pair<literalt, literalt>;
22+
23+
/// A vector of equivalences between literals
24+
using equivalencest = std::vector<equivalencet>;
25+
26+
/// Simplifies an AIG by substituting equivalent nodes.
27+
/// Given a set of equivalences, this function creates a new AIG
28+
/// where equivalent nodes are merged. For each equivalence (l1, l2),
29+
/// references to the node with the larger variable number are
30+
/// replaced with references to the node with the smaller variable number.
31+
/// Constant propagation is performed when an equivalence makes a node constant.
32+
/// \param src: The source AIG to simplify
33+
/// \param equivalences: Vector of pairs of equivalent literals
34+
/// \return The simplified AIG
35+
aigt aig_simplify(const aigt &src, const equivalencest &equivalences);
36+
37+
#endif // CPROVER_TRANS_NETLIST_AIG_SIMPLIFIER_H

unit/Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ SRC += smvlang/expr2smv.cpp \
1111
temporal-logic/sva_to_ltl.cpp \
1212
temporal-logic/nnf.cpp \
1313
temporal-logic/trivial_sva.cpp \
14+
trans-netlist/aig_simplifier.cpp \
1415
verilog/typename.cpp \
1516
# Empty last line
1617

@@ -25,12 +26,14 @@ CXXFLAGS += -D'LOCAL_IREP_IDS=<hw_cbmc_irep_ids.h>'
2526

2627
OBJ += ../src/smvlang/smvlang$(LIBEXT) \
2728
../src/temporal-logic/temporal-logic$(LIBEXT) \
29+
../src/trans-netlist/trans-netlist$(LIBEXT) \
2830
../src/verilog/verilog$(LIBEXT)
2931

3032
cprover.dir:
3133
$(MAKE) $(MAKEARGS) -C ../src
3234

33-
CPROVER_LIBS = $(CPROVER_DIR)/src/util/util$(LIBEXT) \
35+
CPROVER_LIBS = $(CPROVER_DIR)/src/solvers/solvers$(LIBEXT) \
36+
$(CPROVER_DIR)/src/util/util$(LIBEXT) \
3437
$(CPROVER_DIR)/src/big-int/big-int$(LIBEXT) \
3538
# Empty last line
3639

0 commit comments

Comments
 (0)