libreda_logic/truth_table/
small_lut.rs

1// SPDX-FileCopyrightText: 2022 Thomas Kramer <code@tkramer.ch>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5//! Represent boolean functions with few inputs and one output as truth tables which are compactly
6//! stored in the bits of machine-type integers.
7
8use crate::{
9    bitmanip,
10    traits::{
11        BooleanFunction, BooleanSystem, NumInputs, NumOutputs, PartialBooleanFunction,
12        PartialBooleanSystem, StaticNumInputs, StaticNumOutputs,
13    },
14    truth_table::{PartialTruthTable, TruthTable},
15};
16
17use super::TruthTableEdit;
18
19/// Small truth table with up to 6 input variables. The truth table is stored in a `u64`.
20#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
21pub struct SmallTruthTable {
22    lut: u64,
23    num_inputs: u8,
24}
25
26/// Small truth table with up to 6 input variables. The truth table is stored in a `u64`. The number of inputs is known at compile time.
27///
28/// # Parameters
29/// * `STATIC_NUM_INPUTS` : Number of inputs, known at compile time. Set to `0` for dynamic input counts.
30#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
31pub struct SmallStaticTruthTable<const NUM_INPUTS: usize> {
32    lut: u64,
33}
34
35impl<const STATIC_NUM_INPUTS: usize> From<SmallStaticTruthTable<STATIC_NUM_INPUTS>>
36    for SmallTruthTable
37{
38    /// Convert a static number of inputs into a dynamic number of inputs.
39    fn from(tt: SmallStaticTruthTable<STATIC_NUM_INPUTS>) -> Self {
40        Self {
41            lut: tt.lut,
42            num_inputs: STATIC_NUM_INPUTS as u8,
43        }
44    }
45}
46
47impl<const NUM_INPUTS: usize> TryFrom<SmallTruthTable> for SmallStaticTruthTable<NUM_INPUTS> {
48    type Error = ();
49
50    /// Convert to a truth-table with static number of inputs.
51    /// Returns an `Err` if the dynamic number of inputs does not match.
52    fn try_from(tt: SmallTruthTable) -> Result<Self, Self::Error> {
53        if tt.num_inputs() == NUM_INPUTS {
54            Ok(Self { lut: tt.lut })
55        } else {
56            Err(())
57        }
58    }
59}
60
61impl<const N: usize> NumInputs for SmallStaticTruthTable<N> {
62    fn num_inputs(&self) -> usize {
63        N
64    }
65}
66
67impl<const N: usize> NumOutputs for SmallStaticTruthTable<N> {
68    fn num_outputs(&self) -> usize {
69        1
70    }
71}
72
73// Bit-masks used for swapping inputs of a lookup-table.
74// TODO: Do a benchmark to find out if precomputation is better than computation on the fly.
75const INDEX_COLUMNS: [u64; 6] = index_columns();
76
77/// Abstraction over static and dynamic input counts.
78pub trait SmallTT: TruthTable + TruthTableEdit + Sized + Copy {
79    /// Get the the lookup-table encoded as a `u64`.
80    fn table(&self) -> u64;
81
82    /// Set the output bits.
83    fn set_table(self, table: u64) -> Self;
84
85    /// Invert the output bit iff the `condition` is `true`.
86    fn invert_if(self, condition: bool) -> Self {
87        let mask = (1 << ((condition as u64) << self.num_inputs())) - 1;
88        self.set_table(self.table() ^ mask)
89    }
90
91    /// Invert the output bit.
92    fn invert(self) -> Self {
93        let mask = (1 << (1 << self.num_inputs())) - 1;
94        self.set_table(self.table() ^ mask)
95    }
96
97    /// Compute the bitwise AND operation of the output bits.
98    fn bitwise_and(self, other: Self) -> Self {
99        bitwise_op2(self, other, |a, b| a & b)
100    }
101
102    /// Swap two inputs and permute the table accordingly.
103    fn swap_inputs(self, i: usize, j: usize) -> Self {
104        assert!(i < self.num_inputs());
105        assert!(j < self.num_inputs());
106
107        // Make sure that i <= j.
108        let (i, j) = match i <= j {
109            false => (j, i),
110            true => (i, j),
111        };
112
113        let idx_col_i = INDEX_COLUMNS[i];
114        let idx_col_j = INDEX_COLUMNS[j];
115
116        let shift_amount = (1 << j) - (1 << i);
117
118        // Select half of the bits which are affected by the permutation.
119        let select_pattern = (idx_col_i ^ idx_col_j) & idx_col_i;
120
121        // Apply permutation.
122        let permuted_output_bits =
123            bitmanip::swap_bit_patterns(self.table(), select_pattern, 0, shift_amount);
124
125        self.set_table(permuted_output_bits)
126    }
127
128    /// Create a new truth-table with the `i`-th input inverted.
129    fn invert_input(self, i: usize) -> Self {
130        let select_pattern = !INDEX_COLUMNS[i];
131        let shift_amount = 1 << i;
132
133        let permuted_output_bits =
134            bitmanip::swap_bit_patterns(self.table(), select_pattern, 0, shift_amount);
135
136        self.set_table(permuted_output_bits)
137    }
138
139    /// Return the number of `true`-values in the table.
140    fn count_ones(&self) -> usize {
141        self.table().count_ones() as usize
142    }
143}
144
145impl SmallTT for SmallTruthTable {
146    fn table(&self) -> u64 {
147        self.lut
148    }
149
150    fn set_table(mut self, table: u64) -> Self {
151        self.lut = table;
152        self
153    }
154}
155
156impl<const NUM_INPUTS: usize> SmallTT for SmallStaticTruthTable<NUM_INPUTS> {
157    fn table(&self) -> u64 {
158        self.lut
159    }
160
161    fn set_table(mut self, table: u64) -> Self {
162        self.lut = table;
163        self
164    }
165}
166
167impl<const NUM_INPUTS: usize> SmallStaticTruthTable<NUM_INPUTS> {
168    /// Construct a truth table from a boolean function.
169    /// The function can have at most 6 inputs.
170    pub fn new(f: impl Fn([bool; NUM_INPUTS]) -> bool) -> SmallStaticTruthTable<NUM_INPUTS> {
171        assert!(
172            NUM_INPUTS <= 6,
173            "Number of inputs ({NUM_INPUTS}) exceeds the maximum (6)."
174        );
175
176        let table = (0..1 << NUM_INPUTS)
177            .map(|input_bits| {
178                let mut bits = [false; NUM_INPUTS];
179                (0..NUM_INPUTS)
180                    .for_each(|bit_idx| bits[bit_idx] = (input_bits >> bit_idx) & 1 == 1);
181                (f(bits) as u64) << input_bits
182            })
183            .fold(0, |a, b| a | b);
184
185        Self { lut: table }
186    }
187}
188
189impl<const NUM_INPUTS: usize> PartialBooleanSystem for SmallStaticTruthTable<NUM_INPUTS> {
190    type LiteralId = u32;
191
192    type TermId = ();
193
194    fn evaluate_term_partial(&self, term: &(), input_values: &[bool]) -> Option<bool> {
195        Some(self.evaluate_term(term, input_values))
196    }
197}
198
199impl<const NUM_INPUTS: usize> BooleanSystem for SmallStaticTruthTable<NUM_INPUTS> {
200    fn evaluate_term(&self, _term: &(), input_values: &[bool]) -> bool {
201        // Pack booleans into an integer.
202        let bits = input_values
203            .iter()
204            .rev()
205            .fold(0, |acc, bit| (acc << 1) | (*bit as u64));
206
207        self.get_bit(bits)
208    }
209}
210
211impl<const NUM_INPUTS: usize> PartialBooleanFunction for SmallStaticTruthTable<NUM_INPUTS> {
212    fn partial_eval(&self, input_values: &[bool]) -> Option<bool> {
213        Some(self.eval(input_values))
214    }
215}
216
217impl<const NUM_INPUTS: usize> BooleanFunction for SmallStaticTruthTable<NUM_INPUTS> {
218    fn eval(&self, input_values: &[bool]) -> bool {
219        // Encode bits in integer.
220        let bits = input_values
221            .iter()
222            .rev()
223            .fold(0, |acc, &bit| (acc << 1) | (bit as u64));
224
225        self.get_bit(bits)
226    }
227}
228
229impl<const NUM_INPUTS: usize> PartialTruthTable for SmallStaticTruthTable<NUM_INPUTS> {
230    fn partial_evaluate(&self, input_bits: u64) -> Option<bool> {
231        Some(self.get_bit(input_bits))
232    }
233}
234
235impl<const NUM_INPUTS: usize> TruthTable for SmallStaticTruthTable<NUM_INPUTS> {
236    fn get_bit(&self, bits: u64) -> bool {
237        let mask = (1 << NUM_INPUTS) - 1;
238        let index = bits & mask;
239        (self.lut >> index) & 1 == 1
240    }
241}
242
243impl<const NUM_INPUTS: usize> TruthTableEdit for SmallStaticTruthTable<NUM_INPUTS> {
244    fn set_bit(&mut self, bit_index: usize, value: bool) {
245        assert!(
246            bit_index < (1 << self.num_inputs()),
247            "bit index out of range"
248        );
249        let mask = !(1 << bit_index);
250        self.lut = (self.lut & mask) | ((value as u64) << bit_index);
251    }
252}
253
254impl SmallTruthTable {
255    /// Construct a truth table from a boolean function.
256    /// The function can have at most 6 inputs.
257    pub fn new<const NUM_INPUTS: usize>(f: impl Fn([bool; NUM_INPUTS]) -> bool) -> Self {
258        SmallStaticTruthTable::new(f).into()
259    }
260
261    /// Create a truth-table with all output bits set to zero.
262    pub fn zero(num_inputs: usize) -> Self {
263        assert!(num_inputs <= 6);
264        let num_inputs = num_inputs as u8;
265        Self { lut: 0, num_inputs }
266    }
267
268    /// Create a new truth-table from the bits encoded in an `u64`.
269    pub const fn from_table(table: u64, num_inputs: usize) -> Self {
270        assert!(num_inputs <= 6);
271        Self {
272            lut: table,
273            num_inputs: num_inputs as u8,
274        }
275    }
276
277    /// Create a small truth table with up to 6 inputs from a generic boolean function.
278    ///
279    /// # Panics
280    /// Panics if the boolean function has more than 6 inputs.
281    pub fn from_boolean_function<F: BooleanFunction>(f: &F) -> Self {
282        let mut buffer = [false; 6];
283
284        let n_inputs = f.num_inputs();
285        assert!(
286            n_inputs <= 6,
287            "number of inputs must be <= 6 but is {n_inputs}"
288        );
289
290        let mut lut = 0u64;
291
292        for i in 0..(1 << n_inputs) {
293            for (j, item) in buffer.iter_mut().enumerate().take(n_inputs) {
294                *item = ((i >> j) & 1) == 1;
295            }
296
297            let output = f.eval(&buffer);
298            lut |= (output as u64) << i;
299        }
300
301        Self {
302            lut,
303            num_inputs: n_inputs as u8,
304        }
305    }
306}
307
308/// Binary bit-wise operation on truth-tables.
309fn bitwise_op2<TT: SmallTT>(tt1: TT, tt2: TT, binary_op: impl Fn(u64, u64) -> u64) -> TT {
310    assert_eq!(tt1.num_inputs(), tt2.num_inputs());
311    tt1.set_table(binary_op(tt1.table(), tt2.table()))
312}
313
314/// Generate single-bit columns of the index into the truth-table.
315/// The generated bit masks are used for efficiently permuting bits of a u64 as it is needed
316/// for swapping inputs of a lookup-table.
317///
318/// The columns are packed into `u64` with LSB being the first entry.
319///
320/// ```txt
321/// 2 1 0
322/// -----
323/// 0 0 0
324/// 0 0 1
325/// 0 1 0
326/// 0 1 1
327/// 1 0 0
328/// ...    
329/// ```
330const fn index_columns() -> [u64; 6] {
331    const N: usize = 6;
332    let mut index_columns = [0; N];
333    let mut state = !0u64;
334    let mut i = N as isize - 1;
335    while i >= 0 {
336        let shifted_state = state >> (1 << i);
337        state ^= shifted_state;
338        index_columns[i as usize] = state;
339        i -= 1;
340    }
341    index_columns
342}
343
344#[test]
345fn test_index_columns() {
346    let cols = index_columns();
347    assert_eq!(
348        cols[0],
349        0b1010101010101010101010101010101010101010101010101010101010101010
350    );
351    assert_eq!(
352        cols[1],
353        0b1100110011001100110011001100110011001100110011001100110011001100
354    );
355}
356
357#[test]
358fn test_swap_inputs() {
359    let mux_ab = SmallTruthTable::new(|[sel, a, b]| if sel { b } else { a });
360    assert_eq!(mux_ab.eval(&[false, false, true]), false);
361    assert_eq!(mux_ab.eval(&[true, false, true]), true);
362
363    let mux_ba = mux_ab.swap_inputs(1, 2);
364    assert_eq!(mux_ba.eval(&[false, false, true]), true);
365    assert_eq!(mux_ba.eval(&[true, false, true]), false);
366}
367
368#[test]
369fn test_swap_inputs_random_table() {
370    // A pseudo-random truth-table.
371    let tt = SmallTruthTable {
372        lut: 0xe3b0c44298fc1c14, // First 8 bytes of `sha256sum /dev/null`.
373        num_inputs: 6,
374    };
375
376    for i in 0..6 {
377        for j in 0..6 {
378            let tt_swapped = tt.swap_inputs(i, j);
379
380            // Exhaustively verify that the input swapping works for the given truth table and the given swap indices.
381            for inputs in 0..(1 << 6) {
382                let inputs_swapped = bitmanip::swap_bits(inputs, i, j);
383                assert_eq!(tt.get_bit(inputs), tt_swapped.get_bit(inputs_swapped));
384            }
385        }
386    }
387}
388#[test]
389fn test_invert_inputs_random_table() {
390    // A pseudo-random truth-table.
391    let tt = SmallTruthTable {
392        lut: 0xe3b0c44298fc1c14, // First 8 bytes of `sha256sum /dev/null`.
393        num_inputs: 6,
394    };
395
396    for i in 0..6 {
397        let tt_swapped = tt.invert_input(i);
398
399        // Exhaustively verify that the input swapping works for the given truth table and the given swap indices.
400        for inputs in 0..(1 << 6) {
401            let inputs_inverted_i = inputs ^ (1 << i);
402            assert_eq!(tt.get_bit(inputs), tt_swapped.get_bit(inputs_inverted_i));
403        }
404    }
405}
406
407/// Library of truth-tables for common functions.
408pub mod truth_table_library {
409    use super::SmallTruthTable;
410
411    /// constant `true`
412    pub fn one() -> SmallTruthTable {
413        SmallTruthTable::new(|[]| true)
414    }
415
416    /// constant `false`
417    pub fn zero() -> SmallTruthTable {
418        SmallTruthTable::new(|[]| false)
419    }
420
421    /// unary identity function
422    pub fn identity1() -> SmallTruthTable {
423        SmallTruthTable::new(|[a]| a)
424    }
425
426    /// boolean function which projects a selected input to the output
427    pub fn input_projection(num_inputs: usize, project_input: usize) -> SmallTruthTable {
428        assert!(project_input < num_inputs, "selected input out of range");
429        assert!(num_inputs <= 6, "no more than 6 inputs supported");
430        let num_tt_bits = 1 << num_inputs; // Number of truth-table bits.
431        let tt_bits = (0..num_tt_bits).map(|idx| ((idx >> project_input) & 1) << idx);
432
433        // pack into integer
434        let tt = tt_bits.fold(0, |a, b| a | b);
435
436        SmallTruthTable::from_table(tt, num_inputs)
437    }
438
439    /// 1-bit inverter.
440    pub fn inv1() -> SmallTruthTable {
441        SmallTruthTable::new(|[a]| !a)
442    }
443
444    /// n-ary AND
445    pub fn and(num_inputs: usize) -> SmallTruthTable {
446        assert!(num_inputs <= 6, "no more than 6 inputs supported");
447        let lut = 1 << ((1 << num_inputs) - 1);
448        let num_inputs = num_inputs as u8;
449        SmallTruthTable { lut, num_inputs }
450    }
451
452    /// n-ary OR
453    pub fn or(num_inputs: usize) -> SmallTruthTable {
454        assert!(num_inputs <= 6, "no more than 6 inputs supported");
455        let lut = !1 & ((1 << (num_inputs + 1)) - 1);
456        let num_inputs = num_inputs as u8;
457        SmallTruthTable { lut, num_inputs }
458    }
459
460    /// 2-input AND.
461    pub fn and2() -> SmallTruthTable {
462        SmallTruthTable::new(|[a, b]| a & b)
463    }
464
465    /// 2-input OR.
466    pub fn or2() -> SmallTruthTable {
467        SmallTruthTable::new(|[a, b]| a | b)
468    }
469
470    /// 2-input NAND.
471    pub fn nand2() -> SmallTruthTable {
472        SmallTruthTable::new(|[a, b]| !(a & b))
473    }
474
475    /// 2-input NOR.
476    pub fn nor2() -> SmallTruthTable {
477        SmallTruthTable::new(|[a, b]| !(a | b))
478    }
479
480    /// 2-input exclusive OR.
481    pub fn xor2() -> SmallTruthTable {
482        SmallTruthTable::new(|[a, b]| a ^ b)
483    }
484
485    /// 2-input equality (XNOR).
486    pub fn eq2() -> SmallTruthTable {
487        SmallTruthTable::new(|[a, b]| a == b)
488    }
489
490    /// A -> B.
491    pub fn implication() -> SmallTruthTable {
492        SmallTruthTable::new(|[a, b]| !a & b)
493    }
494
495    /// A <- B.
496    pub fn converse() -> SmallTruthTable {
497        SmallTruthTable::new(|[a, b]| a & !b)
498    }
499
500    /// A < B.
501    pub fn less_than() -> SmallTruthTable {
502        SmallTruthTable::new(|[a, b]| a < b)
503    }
504
505    /// A <= B.
506    pub fn less_or_equal_than() -> SmallTruthTable {
507        SmallTruthTable::new(|[a, b]| a <= b)
508    }
509
510    /// A > B.
511    pub fn greater_than() -> SmallTruthTable {
512        SmallTruthTable::new(|[a, b]| a > b)
513    }
514
515    /// A >= B.
516    pub fn greater_or_equal_than() -> SmallTruthTable {
517        SmallTruthTable::new(|[a, b]| a >= b)
518    }
519
520    /// 3-input majority function.
521    pub fn maj3() -> SmallTruthTable {
522        SmallTruthTable::new(|[a, b, c]| (a as u8) + (b as u8) + (c as u8) >= 2)
523    }
524}
525
526#[test]
527fn test_create_small_truth_table() {
528    let t = SmallTruthTable::new(|[a, b]| a ^ b);
529    assert_eq!(t.num_inputs(), 2);
530    assert_eq!(t.lut, 0b0110)
531}
532
533#[test]
534fn test_input_projection() {
535    use truth_table_library::input_projection;
536    for num_inputs in 0..=6 {
537        for selected_input in 0..num_inputs {
538            let tt = input_projection(num_inputs, selected_input);
539            for i in 0..tt.size() {
540                assert_eq!(tt.get_bit(i as u64), ((i >> selected_input) & 1) == 1);
541            }
542        }
543    }
544}
545
546#[test]
547fn test_eval_small_truth_table() {
548    let maj3 = SmallTruthTable::new(|[a, b, c]| (a as u8) + (b as u8) + (c as u8) >= 2);
549
550    assert_eq!(maj3.get_bit(0b000), false);
551    assert_eq!(maj3.get_bit(0b001), false);
552    assert_eq!(maj3.get_bit(0b100), false);
553    assert_eq!(maj3.get_bit(0b011), true);
554}
555
556impl NumInputs for SmallTruthTable {
557    fn num_inputs(&self) -> usize {
558        self.num_inputs as usize
559    }
560}
561
562impl NumOutputs for SmallTruthTable {
563    fn num_outputs(&self) -> usize {
564        1
565    }
566}
567
568impl PartialBooleanSystem for SmallTruthTable {
569    type LiteralId = u32;
570
571    type TermId = ();
572
573    fn evaluate_term_partial(&self, term: &(), input_values: &[bool]) -> Option<bool> {
574        Some(self.evaluate_term(term, input_values))
575    }
576}
577
578impl BooleanSystem for SmallTruthTable {
579    fn evaluate_term(&self, _term: &(), input_values: &[bool]) -> bool {
580        // Pack booleans into an integer.
581        let bits = input_values
582            .iter()
583            .rev()
584            .fold(0, |acc, bit| (acc << 1) | (*bit as u64));
585
586        self.get_bit(bits)
587    }
588}
589
590impl PartialBooleanFunction for SmallTruthTable {
591    fn partial_eval(&self, input_values: &[bool]) -> Option<bool> {
592        Some(self.eval(input_values))
593    }
594}
595
596impl PartialTruthTable for SmallTruthTable {
597    fn partial_evaluate(&self, input_bits: u64) -> Option<bool> {
598        Some(self.get_bit(input_bits))
599    }
600}
601
602impl TruthTableEdit for SmallTruthTable {
603    fn set_bit(&mut self, bit_index: usize, value: bool) {
604        assert!(
605            bit_index < (1 << self.num_inputs()),
606            "bit index out of range"
607        );
608        let mask = !(1 << bit_index);
609        self.lut = (self.lut & mask) | ((value as u64) << bit_index);
610    }
611}
612
613impl StaticNumOutputs<1> for SmallTruthTable {}
614
615impl<const N: usize> StaticNumOutputs<1> for SmallStaticTruthTable<N> {}
616
617impl<const N: usize> StaticNumInputs<N> for SmallStaticTruthTable<N> {}
618
619impl BooleanFunction for SmallTruthTable {
620    fn eval(&self, input_values: &[bool]) -> bool {
621        // Encode bits in integer.
622        let bits = input_values
623            .iter()
624            .rev()
625            .fold(0, |acc, &bit| (acc << 1) | (bit as u64));
626
627        self.get_bit(bits)
628    }
629}
630
631impl TruthTable for SmallTruthTable {
632    fn get_bit(&self, bits: u64) -> bool {
633        let mask = (1 << self.num_inputs) - 1;
634        let index = bits & mask;
635        (self.lut >> index) & 1 == 1
636    }
637}
638
639///// Truth-table with a number of inputs known at compile time.
640//pub struct StaticTruthTable<const NUM_INPUTS: usize> {}
641//
642//impl<const NUM_INPUTS: usize> TruthTable for StaticTruthTable<NUM_INPUTS> {
643//    fn num_inputs(&self) -> usize {
644//        NUM_INPUTS
645//    }
646//}
647//
648//pub enum Assert<const CHECK: bool> {}
649//
650//pub trait IsTrue {}
651//
652//impl IsTrue for Assert<true> {}