1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// An example system of an extremely simplified RISC
// (Reduced Instruction Set Computer) microcontroller
// with toy machine code, forming a system that
// can be verified by machine-check.
//
// Only the things specific to the microcontroller will
// be commented here. See the example "counter"
// for a basic description of a machine-check system.
//
// Some of the properties that hold and are verifiable:
//  - Register 1 is set to 1 before reaching the start
//    of the main loop:
//    "AF[and(eq(reg[1], 1), unsigned_lt(pc,3))]"
//  - It is always possible to reach program location 9:
//    "AG[EF(eq(pc, 9))]"
//  - Program locations above 9 are never reached.
//    "AG[unsigned_le(pc, 9)]"

#[machine_check::machine_description]
mod machine_module {
    use ::machine_check::{Bitvector, BitvectorArray};
    use ::std::{
        clone::Clone,
        cmp::{Eq, PartialEq},
        fmt::Debug,
        hash::Hash,
    };

    #[derive(Clone, PartialEq, Eq, Hash, Debug)]
    pub struct Input {
        // Proper inputs to this microcontroller
        // are addresses that can be read.
        gpio_read: BitvectorArray<4, 8>,
        // Registers and data are uninitialized
        // at microcontroller reset, which corresponds to
        // finite-state machine init.
        uninit_reg: BitvectorArray<2, 8>,
        uninit_data: BitvectorArray<8, 8>,
    }
    impl ::machine_check::Input for Input {}

    #[derive(Clone, PartialEq, Eq, Hash, Debug)]
    pub struct State {
        // Microcontroller program counter.
        // Stores the address of instruction
        // in program memory that will be executed next.
        pc: Bitvector<7>,
        // Four (2^2) 8-bit working registers.
        reg: BitvectorArray<2, 8>,
        // 256 (2^8) 8-bit data cells.
        data: BitvectorArray<8, 8>,
    }
    impl ::machine_check::State for State {}
    #[derive(Clone, PartialEq, Eq, Hash, Debug)]
    pub struct System {
        // 128 (2^7) 12-bit program memory instructions.
        pub progmem: BitvectorArray<7, 12>,
    }
    impl ::machine_check::Machine for System {
        type Input = Input;
        type State = State;

        fn init(&self, input: &Input) -> State {
            // Only initialize Program Counter to 0 at reset.
            // Leave working registers and data uninitialized.
            State {
                pc: Bitvector::<7>::new(0),
                reg: Clone::clone(&input.uninit_reg),
                data: Clone::clone(&input.uninit_data),
            }
        }
        fn next(&self, state: &State, input: &Input) -> State {
            // Fetch the instruction to execute from program memory.
            let instruction = self.progmem[state.pc];
            // Increment the program counter.
            let mut pc = state.pc + Bitvector::<7>::new(1);
            // Clone registers and data.
            let mut reg = Clone::clone(&state.reg);
            let mut data = Clone::clone(&state.data);

            // Perform instruction-specific behaviour.
            ::machine_check::bitmask_switch!(instruction {
                "00dd_00--_aabb" => { // add
                    reg[d] = reg[a] + reg[b];
                }
                "00dd_01--_gggg" => { // read input
                    reg[d] = input.gpio_read[g];
                }
                "00rr_1kkk_kkkk" => { // jump if bit 0 is set
                    if reg[r] & Bitvector::<8>::new(1)
                        == Bitvector::<8>::new(1) {
                        pc = k;
                    };
                }
                "01dd_kkkk_kkkk" => { // load immediate
                    reg[d] = k;
                }
                "10dd_nnnn_nnnn" => { // load direct
                    reg[d] = data[n];
                }
                "11ss_nnnn_nnnn" => { // store direct
                    data[n] = reg[s];
                }
            });

            // Return the state.
            State { pc, reg, data }
        }
    }
}

use machine_check::{Bitvector, BitvectorArray};

fn main() {
    let toy_program = [
        // (0) set r0 to zero
        Bitvector::new(0b0100_0000_0000),
        // (1) set r1 to one
        Bitvector::new(0b0101_0000_0001),
        // (2) set r2 to zero
        Bitvector::new(0b0110_0000_0000),
        // --- main loop ---
        // (3) store r1 content to data location 0
        Bitvector::new(0b1100_0000_0000),
        // (4) store r2 content to data location 1
        Bitvector::new(0b1100_0000_0001),
        // (5) read input location 0 to r3
        Bitvector::new(0b0011_0100_0000),
        // (6) jump to (3) if r3 bit 0 is set
        Bitvector::new(0b0011_1000_0011),
        // (7) increment r2
        Bitvector::new(0b0010_0000_1001),
        // (8) store r2 content to data location 1
        Bitvector::new(0b1110_0000_0001),
        // (9) jump to (3)
        Bitvector::new(0b0001_1000_0011),
    ];

    // load toy program to program memory, filling unused locations with 0
    let mut progmem = BitvectorArray::new_filled(Bitvector::new(0));
    for (index, instruction) in toy_program.into_iter().enumerate() {
        progmem[Bitvector::new(index as u64)] = instruction;
    }
    let system = machine_module::System { progmem };
    machine_check::run(system);
}