|
| 1 | +module common { |
| 2 | + // address type. |
| 3 | + type addr_t; |
| 4 | + // word type. |
| 5 | + type word_t = bv8; |
| 6 | + // list of operations supported by the CPU. |
| 7 | + type op_t = enum { |
| 8 | + op_alu, op_load, op_store, |
| 9 | + op_syscall, op_sysret |
| 10 | + }; |
| 11 | + // CPU mode. |
| 12 | + type mode_t = enum { usr_mode, sup_mode }; |
| 13 | + // CPU memory type. |
| 14 | + type mem_t = [addr_t]word_t; |
| 15 | + |
| 16 | + // define a constant that evaluates to zero. |
| 17 | + function k0_word_t() : word_t; |
| 18 | + axiom k0_word_t() == 0bv8; |
| 19 | + |
| 20 | + // the enter address for syscall. |
| 21 | + function syscall_enter_addr() : addr_t; |
| 22 | + // the exit address for syscalls. |
| 23 | + function sysret_exit_addr() : addr_t; |
| 24 | + // The above two must be different. |
| 25 | + axiom syscall_enter_addr() != sysret_exit_addr(); |
| 26 | +} |
| 27 | + |
| 28 | +module cpu { |
| 29 | + type addr_t = common.addr_t; |
| 30 | + type mem_t = common.mem_t; |
| 31 | + type word_t = common.word_t; |
| 32 | + type op_t = common.op_t; |
| 33 | + type mode_t = common.mode_t; |
| 34 | + |
| 35 | + type regindex_t; |
| 36 | + type regs_t = [regindex_t]word_t; |
| 37 | + |
| 38 | + input imem : mem_t; // program memory. |
| 39 | + var dmem : mem_t; // data memory. |
| 40 | + var regs : regs_t; // registers. |
| 41 | + var pc : addr_t; // program counter. |
| 42 | + var inst, result : word_t; // inst reg, result value |
| 43 | + var mode : mode_t; // usr/sup mode? |
| 44 | + |
| 45 | + // range of privileged memory. |
| 46 | + const sup_range_lo : addr_t; |
| 47 | + const sup_range_hi : addr_t; |
| 48 | + // predicate for the above. |
| 49 | + function in_range (a : addr_t, b1 : addr_t, b2 : addr_t) : boolean; |
| 50 | + |
| 51 | + // uninterpreted functions for decoding instructions. |
| 52 | + function inst2op (i : word_t) : op_t; |
| 53 | + function inst2reg0 (i : word_t) : regindex_t; |
| 54 | + function inst2reg1 (i : word_t) : regindex_t; |
| 55 | + function inst2addr (i : word_t, r0 : word_t, r1 : word_t) : addr_t; |
| 56 | + function aluOp (i : word_t, r0 : word_t, r1 : word_t) : word_t; |
| 57 | + function nextPC (i : word_t, pc : addr_t, r0 : word_t) : addr_t; |
| 58 | + |
| 59 | + define in_privileged_memory (a : addr_t) : boolean = |
| 60 | + in_range(a, sup_range_lo, sup_range_hi); |
| 61 | + |
| 62 | + axiom (forall (a : addr_t) :: |
| 63 | + (a == common.syscall_enter_addr()) ==> in_privileged_memory(a)); |
| 64 | + axiom (forall (a : addr_t) :: |
| 65 | + (a == common.sysret_exit_addr()) ==> !in_privileged_memory(a)); |
| 66 | + |
| 67 | + procedure exec_inst(instr : word_t, pc : addr_t) |
| 68 | + returns (pc_next : addr_t) |
| 69 | + modifies regs, result, dmem, mode; |
| 70 | + { |
| 71 | + var op : op_t; |
| 72 | + var r0ind : regindex_t; |
| 73 | + var r1ind : regindex_t; |
| 74 | + var r0 : word_t; |
| 75 | + var r1 : word_t; |
| 76 | + var addr : addr_t; |
| 77 | + |
| 78 | + // get opcode. |
| 79 | + op = inst2op(instr); |
| 80 | + // get operands. |
| 81 | + r0ind, r1ind = inst2reg0(instr), inst2reg1(instr); |
| 82 | + r0, r1 = regs[r0ind], regs[r1ind]; |
| 83 | + // get next pc (might be overwritten by syscall/sysret). |
| 84 | + pc_next = nextPC(instr, pc, r0); |
| 85 | + // get memory address |
| 86 | + addr = inst2addr(instr, r0, r1); |
| 87 | + |
| 88 | + // If we are in supervisor mode, we only set pc_next to supervisor address. |
| 89 | + assume (mode == sup_mode) ==> (in_privileged_memory(pc_next)); |
| 90 | + // If we are in supervisor mode, we only read from supervisor memory. |
| 91 | + assume (mode == sup_mode && op == op_load) ==> in_privileged_memory(addr); |
| 92 | + // If we are already in supervisor mode, we don't execute syscalls. |
| 93 | + assume (mode == sup_mode) ==> (op != op_syscall); |
| 94 | + |
| 95 | + case |
| 96 | + (op == op_alu) : { |
| 97 | + result = aluOp(instr, r0, r1); |
| 98 | + regs[r0ind] = result; |
| 99 | + } |
| 100 | + (op == op_load) : { |
| 101 | + if (mode == sup_mode || !in_privileged_memory(addr)) { |
| 102 | + // read memory. |
| 103 | + result = dmem[addr]; |
| 104 | + } else { |
| 105 | + // operation failed. return 0. |
| 106 | + result = common.k0_word_t(); |
| 107 | + } |
| 108 | + regs[r0ind] = result; |
| 109 | + } |
| 110 | + (op == op_store) : { |
| 111 | + result = common.k0_word_t(); |
| 112 | + // is writing legal? |
| 113 | + if (mode == sup_mode || !in_privileged_memory(addr)) { |
| 114 | + // write to memory. |
| 115 | + dmem[addr] = r0; |
| 116 | + } |
| 117 | + } |
| 118 | + (op == op_syscall) : { |
| 119 | + assert (mode == usr_mode); |
| 120 | + |
| 121 | + // zero out result. |
| 122 | + result = common.k0_word_t(); |
| 123 | + // zero out registers. |
| 124 | + havoc regs; |
| 125 | + assume (forall (r : regindex_t) :: regs[r] == common.k0_word_t()); |
| 126 | + // set PC. |
| 127 | + pc_next = common.syscall_enter_addr(); |
| 128 | + // set mode. |
| 129 | + mode = sup_mode; |
| 130 | + } |
| 131 | + (op == op_sysret) : { |
| 132 | + // zero out result. |
| 133 | + result = common.k0_word_t(); |
| 134 | + // zero out registers. |
| 135 | + havoc regs; |
| 136 | + assume (forall (r : regindex_t) :: regs[r] == common.k0_word_t()); |
| 137 | + // set PC. |
| 138 | + pc_next = common.sysret_exit_addr(); |
| 139 | + // set mode. |
| 140 | + mode = usr_mode; |
| 141 | + } |
| 142 | + esac |
| 143 | + } |
| 144 | + |
| 145 | + init { |
| 146 | + // reset registers. |
| 147 | + assume (forall (r : regindex_t) :: regs[r] == common.k0_word_t()); |
| 148 | + // set instruction to some deterministic value. |
| 149 | + inst = common.k0_word_t(); |
| 150 | + // start off-execution at a deterministic address. |
| 151 | + pc = common.syscall_enter_addr(); |
| 152 | + // in supervisor mode. |
| 153 | + mode = sup_mode; |
| 154 | + } |
| 155 | + |
| 156 | + next { |
| 157 | + inst' = imem[pc]; |
| 158 | + call (pc') = exec_inst(inst', pc); |
| 159 | + } |
| 160 | +} |
| 161 | + |
| 162 | +module main { |
| 163 | + // Import types. |
| 164 | + type addr_t = common.addr_t; |
| 165 | + type mem_t = common.mem_t; |
| 166 | + type word_t = common.word_t; |
| 167 | + type op_t = common.op_t; |
| 168 | + type mode_t = common.mode_t; |
| 169 | + type regindex_t = cpu.regindex_t; |
| 170 | + |
| 171 | + // Instruction memory is the same for both CPUs. |
| 172 | + var imem1, imem2 : mem_t; |
| 173 | + |
| 174 | + // Create two instances of the CPU module. |
| 175 | + instance cpu1 : cpu(imem : (imem1)); |
| 176 | + instance cpu2 : cpu(imem : (imem2)); |
| 177 | + |
| 178 | + init { |
| 179 | + // Assume same supervisor ranges. |
| 180 | + assume (cpu1.sup_range_lo == cpu2.sup_range_lo); |
| 181 | + assume (cpu1.sup_range_hi == cpu2.sup_range_hi); |
| 182 | + // Assume supervisor memory starts off identical. |
| 183 | + assume (forall (a : addr_t) :: |
| 184 | + (cpu.in_range(a, cpu1.sup_range_lo, cpu2.sup_range_hi)) |
| 185 | + ==> (cpu1.dmem[a] == cpu2.dmem[a])); |
| 186 | + assume (forall (a : addr_t) :: |
| 187 | + (cpu.in_range(a, cpu1.sup_range_lo, cpu1.sup_range_hi)) |
| 188 | + ==> (imem1[a] == imem2[a])); |
| 189 | + } |
| 190 | + |
| 191 | + next { |
| 192 | + next (cpu1); |
| 193 | + next (cpu2); |
| 194 | + // If one CPU takes a syscall, the other does too! |
| 195 | + assume (cpu.inst2op(cpu1.inst) == op_syscall || cpu.inst2op(cpu2.inst) == op_syscall) |
| 196 | + ==> (cpu1.inst == cpu2.inst); |
| 197 | + } |
| 198 | + |
| 199 | + // The two CPUs change mode in sync. |
| 200 | + invariant eq_mode : (cpu1.mode == cpu2.mode); |
| 201 | + // The two CPUs have the same PC when in supervisor mode. |
| 202 | + invariant eq_pc : (cpu1.mode == sup_mode) ==> (cpu1.pc == cpu2.pc); |
| 203 | + // In supervisor mode, the two CPUs execute the same code. |
| 204 | + invariant eq_inst : (cpu1.mode == sup_mode) ==> (cpu1.inst == cpu2.inst); |
| 205 | + // In supervisor mode, the two CPUs have the same register values. |
| 206 | + invariant eq_regs : (forall (ri : regindex_t) :: |
| 207 | + (cpu1.mode == sup_mode) ==> (cpu1.regs[ri] == cpu2.regs[ri])); |
| 208 | + // The supervisor range of memory is the same in both CPUs. |
| 209 | + invariant eq_sup_range : (cpu1.sup_range_lo == cpu2.sup_range_lo) && |
| 210 | + (cpu1.sup_range_hi == cpu2.sup_range_hi); |
| 211 | + // When in supervisor mode, the PC is also in the supervisor mode range. |
| 212 | + invariant in_pc_range1 : (cpu1.mode == sup_mode) |
| 213 | + ==> cpu.in_range(cpu1.pc, cpu1.sup_range_lo, cpu1.sup_range_hi); |
| 214 | + invariant in_pc_range2 : (cpu2.mode == sup_mode) |
| 215 | + ==> cpu.in_range(cpu2.pc, cpu2.sup_range_lo, cpu2.sup_range_hi); |
| 216 | + // The CPU memory that is private to supervisor mode is identical. |
| 217 | + invariant eq_dmem : (forall (a : addr_t) :: |
| 218 | + (cpu.in_range(a, cpu1.sup_range_lo, cpu2.sup_range_hi)) |
| 219 | + ==> (cpu1.dmem[a] == cpu2.dmem[a])); |
| 220 | + // Same for instruction memory. |
| 221 | + invariant eq_imem : (forall (a : addr_t) :: |
| 222 | + (cpu.in_range(a, cpu1.sup_range_lo, cpu1.sup_range_hi)) |
| 223 | + ==> (imem1[a] == imem2[a])); |
| 224 | + |
| 225 | + control { |
| 226 | + v = induction; |
| 227 | + check; |
| 228 | + print_results; |
| 229 | + v.print_cex( |
| 230 | + cpu.inst2op(cpu1.inst), cpu.inst2op(cpu2.inst), |
| 231 | + cpu1.result, cpu2.result, cpu1.mode, cpu2.mode, |
| 232 | + cpu1.pc, cpu2.pc, |
| 233 | + cpu1.sup_range_lo, cpu2.sup_range_lo, |
| 234 | + cpu1.sup_range_hi, cpu2.sup_range_hi, |
| 235 | + cpu.in_range(cpu1.pc, cpu1.sup_range_lo, cpu1.sup_range_hi), |
| 236 | + cpu.in_range(cpu2.pc, cpu2.sup_range_lo, cpu2.sup_range_hi)); |
| 237 | + } |
| 238 | +} |
0 commit comments