Skip to content

Commit 5897658

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 5897658

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

src/trans-netlist/aig_simplifier.h

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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> aig_simplify(const aigt &, const equivalencest &);
44+
45+
#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)