Skip to main content

xlsynth_g8r/transforms/
toggle_output.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use crate::aig::{AigBitVector, AigOperand, GateFn, topo};
4use crate::transforms::transform_trait::{
5    Transform, TransformDirection, TransformKind, TransformLocation,
6};
7use anyhow::{Result, anyhow};
8
9/// Primitive: Inverts a single bit in one of the primary output ports.
10/// Also inverts the `negated` attribute on the corresponding `AigOperand`.
11fn 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(&current_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, // This transform is its own inverse
85    ) -> 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                // Assert strong invariant: toggling output bit must not create cycles.
97                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}