xlsynth_g8r/transforms/
toggle_output.rs1use crate::aig::{AigBitVector, AigOperand, GateFn, topo};
4use crate::transforms::transform_trait::{
5 Transform, TransformDirection, TransformKind, TransformLocation,
6};
7use anyhow::{Result, anyhow};
8
9fn do_toggle_output_bit(g: &mut GateFn, output_idx: usize, bit_idx: usize) -> Result<()> {
12 if output_idx >= g.outputs.len() {
13 return Err(anyhow!(
14 "Output index {} out of bounds ({} outputs)",
15 output_idx,
16 g.outputs.len()
17 ));
18 }
19 let output_spec = &mut g.outputs[output_idx];
20 if bit_idx >= output_spec.bit_vector.get_bit_count() {
21 return Err(anyhow!(
22 "Bit index {} out of bounds for output '{}' ({} bits)",
23 bit_idx,
24 output_spec.name,
25 output_spec.bit_vector.get_bit_count()
26 ));
27 }
28
29 let mut current_ops: Vec<AigOperand> =
30 output_spec.bit_vector.iter_lsb_to_msb().copied().collect();
31 if bit_idx < current_ops.len() {
32 current_ops[bit_idx].negated = !current_ops[bit_idx].negated;
33 output_spec.bit_vector = AigBitVector::from_lsb_is_index_0(¤t_ops);
34 Ok(())
35 } else {
36 Err(anyhow!(
37 "Bit index {} out of bounds for collected ops ({} ops) for output '{}'",
38 bit_idx,
39 current_ops.len(),
40 output_spec.name
41 ))
42 }
43}
44
45#[derive(Debug)]
46pub struct ToggleOutputBitTransform;
47
48impl ToggleOutputBitTransform {
49 pub fn new() -> Self {
50 ToggleOutputBitTransform
51 }
52}
53
54impl Transform for ToggleOutputBitTransform {
55 fn kind(&self) -> TransformKind {
56 TransformKind::ToggleOutputBit
57 }
58
59 fn find_candidates(
60 &mut self,
61 g: &GateFn,
62 direction: TransformDirection,
63 ) -> Vec<TransformLocation> {
64 log::trace!(
65 "Finding candidates for ToggleOutputBitTransform; direction: {:?}",
66 direction
67 );
68 let mut candidates = Vec::new();
69 for (output_idx, output_spec) in g.outputs.iter().enumerate() {
70 for bit_idx in 0..output_spec.bit_vector.get_bit_count() {
71 candidates.push(TransformLocation::OutputPortBit {
72 output_idx,
73 bit_idx,
74 });
75 }
76 }
77 candidates
78 }
79
80 fn apply(
81 &self,
82 g: &mut GateFn,
83 candidate_location: &TransformLocation,
84 _direction: TransformDirection, ) -> Result<()> {
86 log::trace!(
87 "Applying ToggleOutputBitTransform to {:?}",
88 candidate_location
89 );
90 match candidate_location {
91 TransformLocation::OutputPortBit {
92 output_idx,
93 bit_idx,
94 } => {
95 let res = do_toggle_output_bit(g, *output_idx, *bit_idx);
96 topo::debug_assert_no_cycles(&g.gates, "toggle_output_bit");
98 res
99 }
100 _ => Err(anyhow!(
101 "Invalid location type for ToggleOutputBitTransform: {:?}",
102 candidate_location
103 )),
104 }
105 }
106
107 fn always_equivalent(&self) -> bool {
108 false
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use crate::{
116 aig::AigRef,
117 gate_builder::{GateBuilder, GateBuilderOptions},
118 };
119
120 #[test]
121 fn test_toggle_output_bit_transform_applies_and_reverses() {
122 let mut gb = GateBuilder::new("f".to_string(), GateBuilderOptions::no_opt());
123 let i0 = gb.add_input("i0".to_string(), 1).get_lsb(0).clone();
124 gb.add_output("o0".to_string(), i0.into());
125 let g_original = gb.build();
126 let mut g_transformed = g_original.clone();
127
128 let mut transform = ToggleOutputBitTransform::new();
129 let candidates = transform.find_candidates(&g_transformed, TransformDirection::Forward);
130 assert!(!candidates.is_empty(), "Should find candidates");
131
132 let candidate_loc = &candidates[0];
133
134 let original_output_negation = g_transformed.outputs[0].bit_vector.get_lsb(0).negated;
135 transform
136 .apply(
137 &mut g_transformed,
138 candidate_loc,
139 TransformDirection::Forward,
140 )
141 .unwrap();
142 let transformed_output_negation = g_transformed.outputs[0].bit_vector.get_lsb(0).negated;
143 assert_ne!(
144 original_output_negation, transformed_output_negation,
145 "Forward transform should change negation"
146 );
147
148 transform
149 .apply(
150 &mut g_transformed,
151 candidate_loc,
152 TransformDirection::Backward,
153 )
154 .unwrap();
155 let reverted_output_negation = g_transformed.outputs[0].bit_vector.get_lsb(0).negated;
156 assert_eq!(
157 original_output_negation, reverted_output_negation,
158 "Backward transform should revert negation"
159 );
160 assert_eq!(
161 g_original.to_string(),
162 g_transformed.to_string(),
163 "Graph should be identical after forward and backward transform"
164 );
165 }
166
167 #[test]
168 fn test_toggle_output_bit_transform_invalid_location() {
169 let mut gb = GateBuilder::new("f".to_string(), GateBuilderOptions::no_opt());
170 let i0 = gb.add_input("i0".to_string(), 1).get_lsb(0).clone();
171 gb.add_output("o0".to_string(), i0.into());
172 let mut g = gb.build();
173 let transform = ToggleOutputBitTransform::new();
174 let invalid_loc = TransformLocation::Node(AigRef { id: 0 });
175 assert!(
176 transform
177 .apply(&mut g, &invalid_loc, TransformDirection::Forward)
178 .is_err()
179 );
180 }
181
182 #[test]
183 fn test_do_toggle_output_bit_out_of_bounds() {
184 let mut gb = GateBuilder::new("f".to_string(), GateBuilderOptions::no_opt());
185 let i0 = gb.add_input("i0".to_string(), 1).get_lsb(0).clone();
186 gb.add_output("o0".to_string(), i0.into());
187 let mut g = gb.build();
188
189 assert!(do_toggle_output_bit(&mut g, 1, 0).is_err());
190 assert!(do_toggle_output_bit(&mut g, 0, 1).is_err());
191 }
192}