Skip to content

Commit 2d3aa2f

Browse files
kroeningclaude
andcommitted
AIG simplifier using equivalences
Adds the 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) - Basic backward propagation from AND output to inputs Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 9c8c9cb commit 2d3aa2f

5 files changed

Lines changed: 981 additions & 0 deletions

File tree

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: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
/*******************************************************************\
2+
3+
Module: AIG Simplifier
4+
5+
Author: Daniel Kroening, dkr@amazon.com
6+
7+
\*******************************************************************/
8+
9+
#include "aig_simplifier.h"
10+
11+
literalt apply_substitution(literalt l, const substitutiont &substitution)
12+
{
13+
if(l.is_constant())
14+
return l;
15+
16+
auto var_no = l.var_no();
17+
PRECONDITION(var_no < substitution.size());
18+
19+
// Get the replacement literal and apply the sign
20+
return substitution[var_no] ^ l.sign();
21+
}
22+
23+
/// Simplify an AND node after substitution
24+
/// \param a: First input literal (after substitution)
25+
/// \param b: Second input literal (after substitution)
26+
/// \param dest: The destination AIG to add the node to
27+
/// \return The literal representing the simplified AND
28+
static literalt simplify_and(literalt a, literalt b, aigt &dest)
29+
{
30+
// Constant propagation
31+
if(a.is_false() || b.is_false())
32+
return const_literal(false);
33+
34+
if(a.is_true())
35+
return b;
36+
37+
if(b.is_true())
38+
return a;
39+
40+
// a AND a = a
41+
if(a == b)
42+
return a;
43+
44+
// a AND !a = false
45+
if(a == !b)
46+
return const_literal(false);
47+
48+
// Normalize: smaller variable number first
49+
if(b.var_no() < a.var_no())
50+
std::swap(a, b);
51+
52+
// Create the AND node
53+
return dest.new_and_node(a, b);
54+
}
55+
56+
std::vector<literalt>
57+
aig_substitution(const aigt &src, const equivalencest &equivalences)
58+
{
59+
if(src.empty())
60+
return std::vector<literalt>();
61+
62+
const auto num_nodes = src.number_of_nodes();
63+
64+
// Build a substitution map: for each variable, what literal should it
65+
// be replaced with? Initially, each variable maps to itself.
66+
std::vector<literalt> substitution;
67+
substitution.reserve(num_nodes);
68+
69+
for(std::size_t i = 0; i < num_nodes; i++)
70+
substitution.emplace_back(i, false);
71+
72+
// Process equivalences. For each equivalence (l1, l2), we want to
73+
// replace references to the larger variable with the smaller one.
74+
for(const auto &[l1, l2] : equivalences)
75+
{
76+
// Skip invalid equivalences
77+
if(l1.is_constant() && l2.is_constant())
78+
continue;
79+
80+
// If one is constant, the other should map to that constant
81+
if(l1.is_constant())
82+
{
83+
if(l2.var_no() < num_nodes)
84+
substitution[l2.var_no()] = l1 ^ l2.sign();
85+
continue;
86+
}
87+
88+
if(l2.is_constant())
89+
{
90+
if(l1.var_no() < num_nodes)
91+
substitution[l1.var_no()] = l2 ^ l1.sign();
92+
continue;
93+
}
94+
95+
// Both are non-constant
96+
// l1 ≡ l2 means: v1 ^ s1 ≡ v2 ^ s2
97+
// We replace the larger variable with the smaller one
98+
auto v1 = l1.var_no();
99+
auto v2 = l2.var_no();
100+
101+
if(v1 >= num_nodes || v2 >= num_nodes)
102+
continue;
103+
104+
// Compute the sign relationship: if l1 ≡ l2, then
105+
// v1 should map to v2 ^ (s1 ^ s2), or vice versa
106+
bool signs_differ = l1.sign() != l2.sign();
107+
108+
if(v1 < v2)
109+
{
110+
// Replace v2 with v1
111+
substitution[v2] = literalt(v1, signs_differ);
112+
}
113+
else if(v2 < v1)
114+
{
115+
// Replace v1 with v2
116+
substitution[v1] = literalt(v2, signs_differ);
117+
}
118+
// If v1 == v2, the equivalence is trivial (l1 ≡ l1 or l1 ≡ !l1)
119+
// The latter case (l1 ≡ !l1) would be a contradiction, ignore it
120+
}
121+
122+
// Compute transitive closure of substitutions and propagate constraints
123+
// backwards: if an AND node is false, both inputs must be false
124+
// Repeat until no changes
125+
bool changed;
126+
do
127+
{
128+
changed = false;
129+
130+
// Transitive closure
131+
for(std::size_t i = 0; i < num_nodes; i++)
132+
{
133+
literalt subst = substitution[i];
134+
if(subst.is_constant() || subst.var_no() == i)
135+
continue;
136+
137+
// Follow the substitution chain
138+
literalt next = substitution[subst.var_no()] ^ subst.sign();
139+
if(next != subst)
140+
{
141+
substitution[i] = next;
142+
changed = true;
143+
}
144+
}
145+
146+
// Backward propagation: if AND(a, b) = false, then a = false and b = false
147+
for(std::size_t i = 0; i < num_nodes; i++)
148+
{
149+
const auto &node = src.nodes[i];
150+
151+
// Check if this is an AND node that's been set to false
152+
if(node.is_and() && substitution[i].is_false())
153+
{
154+
// Both inputs must be false
155+
// For node.a: if it's literalt(var, sign), we need var to map to
156+
// const_literal(false) XOR sign, so that var XOR sign = false
157+
if(!node.a.is_constant())
158+
{
159+
auto var_a = node.a.var_no();
160+
// We want node.a to evaluate to false
161+
// node.a = var_a XOR sign_a
162+
// For this to be false, var_a must be (false XOR sign_a)
163+
literalt new_val = const_literal(false) ^ node.a.sign();
164+
if(substitution[var_a] != new_val)
165+
{
166+
substitution[var_a] = new_val;
167+
changed = true;
168+
}
169+
}
170+
171+
// Same for node.b
172+
if(!node.b.is_constant())
173+
{
174+
auto var_b = node.b.var_no();
175+
literalt new_val = const_literal(false) ^ node.b.sign();
176+
if(substitution[var_b] != new_val)
177+
{
178+
substitution[var_b] = new_val;
179+
changed = true;
180+
}
181+
}
182+
}
183+
}
184+
} while(changed);
185+
186+
return substitution;
187+
}
188+
189+
std::pair<aigt, substitutiont>
190+
apply_substitution(const aigt &src, const substitutiont &substitution)
191+
{
192+
if(src.empty())
193+
return {aigt{}, {}};
194+
195+
const auto num_nodes = src.number_of_nodes();
196+
PRECONDITION(substitution.size() == num_nodes);
197+
198+
// Now rebuild the AIG with substitutions applied
199+
aigt dest;
200+
201+
// Map from old node indices to new literals
202+
std::vector<literalt> old_to_new(num_nodes);
203+
204+
for(std::size_t i = 0; i < num_nodes; i++)
205+
{
206+
const auto &node = src.nodes[i];
207+
208+
// First, check what this node is substituted with
209+
literalt subst = substitution[i];
210+
211+
if(subst.is_constant())
212+
{
213+
// This node is equivalent to a constant
214+
old_to_new[i] = subst;
215+
continue;
216+
}
217+
218+
if(subst.var_no() != i)
219+
{
220+
// This node is equivalent to another (smaller) node
221+
// Use what that node maps to, applying the sign
222+
old_to_new[i] = old_to_new[subst.var_no()] ^ subst.sign();
223+
continue;
224+
}
225+
226+
// This node is a representative (maps to itself), we need to create it
227+
if(node.is_input())
228+
{
229+
// primary input
230+
old_to_new[i] = dest.new_input();
231+
}
232+
else
233+
{
234+
// AND node - substitute inputs and simplify
235+
literalt new_a = apply_substitution(node.a, old_to_new);
236+
literalt new_b = apply_substitution(node.b, old_to_new);
237+
old_to_new[i] = simplify_and(new_a, new_b, dest);
238+
}
239+
}
240+
241+
// Re-number the labeling
242+
for(const auto &[label, lit] : src.labeling)
243+
{
244+
dest.labeling[label] = apply_substitution(lit, old_to_new);
245+
}
246+
247+
return {dest, old_to_new};
248+
}
249+
250+
std::pair<aigt, substitutiont>
251+
aig_simplify(const aigt &src, const equivalencest &equivalences)
252+
{
253+
auto substitution = aig_substitution(src, equivalences);
254+
return apply_substitution(src, substitution);
255+
}

src/trans-netlist/aig_simplifier.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*******************************************************************\
2+
3+
Module: AIG Simplifier
4+
5+
Author: Daniel Kroening, dkr@amazon.com
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+
using substitutiont = std::vector<literalt>;
21+
22+
/// Applies a substitution map to a literal.
23+
/// \param l: The literal to substitute
24+
/// \param substitution: Map from variable numbers to replacement literals
25+
/// \return The substituted literal
26+
literalt apply_substitution(literalt l, const substitutiont &);
27+
28+
/// A pair of equivalent literals
29+
using equivalencet = std::pair<literalt, literalt>;
30+
31+
/// A vector of equivalences between literals
32+
using equivalencest = std::vector<equivalencet>;
33+
34+
/// Simplifies an AIG by substituting equivalent nodes.
35+
/// Given a set of equivalences, this function creates a new AIG
36+
/// where equivalent nodes are merged. For each equivalence (l1, l2),
37+
/// references to the node with the larger variable number are
38+
/// replaced with references to the node with the smaller variable number.
39+
/// Constant propagation is performed when an equivalence makes a node constant.
40+
/// \param src: The source AIG to simplify
41+
/// \param equivalences: Vector of pairs of equivalent literals
42+
/// \return The simplified AIG
43+
std::pair<aigt, substitutiont>
44+
aig_simplify(const aigt &, const equivalencest &);
45+
46+
#endif // CPROVER_TRANS_NETLIST_AIG_SIMPLIFIER_H

unit/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ SRC += smvlang/expr2smv.cpp \
1212
temporal-logic/nnf.cpp \
1313
temporal-logic/trivial_sva.cpp \
1414
trans-netlist/aig.cpp \
15+
trans-netlist/aig_simplifier.cpp \
1516
verilog/typename.cpp \
1617
# Empty last line
1718

0 commit comments

Comments
 (0)