Skip to content

Commit 86fa870

Browse files
committed
Exercise 7
1 parent b401cd0 commit 86fa870

File tree

7 files changed

+145
-0
lines changed

7 files changed

+145
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ __pycache__
33
*.il
44
*_bmc
55
*_cover
6+
*_prove

07_proof.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Exercise 7: Prove it!
2+
3+
## What you'll do:
4+
5+
Change your counter so that instead of going from 1 to 9, it goes from 1 to 999,999,999,999 and then to 1 again.
6+
7+
Use prove mode instead of bmc mode to show that your counter still does what it is supposed to.
8+
9+
## Proof by induction
10+
11+
Bounded model checking is nice, but requires you to run the circuit for as many time steps as necessary to hit all states. Clearly for a 40-bit counter like the above, it just doesn't make sense to run bounded model checking for a trillion steps.
12+
13+
Instead, we rely on proof by induction, which is a mathematical technique used to prove that something works for *any* number of steps.
14+
15+
Suppose we had some function, call it `f`, that takes an integer parameter `k`. Let's suppose the function outputs true or false, and we want to show that `f(k)` is true for every value of `k` starting with, say, 0. To do this, we first show that `f(0)` is true. This is called the *base case*. Then we show that *if we assume* that `f(n)` is true for some `n`, *then* `f(n+1)` is also true. This is called the *induction step*.
16+
17+
Notice that we don't have to prove that `f(n)` is true, just assume it is, and based on that see if `f(n+1)` is true.
18+
19+
So if we can show that the base case is true, and that the inductive step is true, we can conclude that logically, every `f(k)`, no matter what `k` is, is true (for `k >= 0`).
20+
21+
![Inductive reasoning](diagrams/induction_math.png)
22+
23+
## Formal verification by induction
24+
25+
In formal verification, we can use induction, but only if we carefully craft the base case and induction step:
26+
27+
The base case: Bounded model checking for N steps passes.
28+
29+
The inductive step: Starting from any valid state, proceeding for N+1 steps passes.
30+
31+
The `N` mentioned above is the `depth` parameter in the `[options]` section of sby file. And, instead of specifying `bmc` as the `mode` in `[options]` and in `[engines]`, we specify `prove` to perform verification by induction.
32+
33+
Formal verification by induction is a powerful technique. However, in complex circuits the state space might be too large to fully assert on, so you may need to do many rounds of debugging to find out why the inductive step found a series of N valid states followed by an invalid (i.e. assertion-breaking) state. Most likely you just need to figure out whether the first state it chose (or any of the states along the path) is valid, and if not, eliminate it with another assert.
34+
35+
![A bad induction trace](diagrams/fixing_induction.png)
36+
37+
But once you've done that, you'll know that from its initial state, your circuit will not break any assert for *any* number of time steps.
38+
39+
## Your turn
40+
41+
Use the supplied [`answers/e07_counter.sby`](answers/e07_counter.sby) file and run it in prove mode:
42+
43+
```
44+
sby -f answers/e07_counter.sby prove
45+
```
46+
47+
The depth has been kept at 22 like before. But, you can reduce that to as little as 2 steps, which covers one positive edge of the clock, and therefore any transition of the counter. Of course, if you have cover statements, you may need to increase the depth.
48+
49+
# Stumped?
50+
51+
Well, there shouldn't be any change except to your counter logic, and using prove mode. But if you need it, the answer is in [`answers/e07_counter.py`](answers/e07_counter.py)

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ Work in progress!
99
* [04 - Signs: signed signals](04_signs.md)
1010
* [05 - Synchronicity: synchronous signals](05_sync.md)
1111
* [06 - Living in the past: multi-step asserts](06_past.md)
12+
* [07 - Prove it! Formal verification by induction](07_proof.md)

answers/e07_counter.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Disable pylint's "your name is too short" warning.
2+
# pylint: disable=C0103
3+
from typing import List, Tuple
4+
5+
from nmigen import Signal, Module, Elaboratable, ClockSignal, ResetSignal, ClockDomain
6+
from nmigen.build import Platform
7+
from nmigen.asserts import Assume, Assert, Cover, Past, Initial, Rose
8+
9+
from util import main
10+
11+
12+
class Counter(Elaboratable):
13+
"""Logic for the Counter module."""
14+
15+
def __init__(self):
16+
self.count = Signal(40, reset=1)
17+
18+
def elaborate(self, _: Platform) -> Module:
19+
"""Implements the logic for the Counter module."""
20+
m = Module()
21+
22+
with m.If(self.count == 999_999_999_999):
23+
m.d.sync += self.count.eq(1)
24+
with m.Else():
25+
m.d.sync += self.count.eq(self.count+1)
26+
27+
return m
28+
29+
@classmethod
30+
def formal(cls) -> Tuple[Module, List[Signal]]:
31+
"""Formal verification for the Counter module."""
32+
m = Module()
33+
m.submodules.c = c = cls()
34+
35+
m.d.comb += Assert((c.count >= 1) & (c.count <= 999_999_999_999))
36+
37+
sync_clk = ClockSignal("sync")
38+
sync_rst = ResetSignal("sync")
39+
40+
with m.If(Rose(sync_clk) & ~Initial()):
41+
with m.If(c.count == 1):
42+
m.d.comb += Assert(Past(c.count) == 999_999_999_999)
43+
with m.Else():
44+
m.d.comb += Assert(c.count == (Past(c.count) + 1))
45+
46+
# Make sure the clock is clocking
47+
m.d.comb += Assume(sync_clk == ~Past(sync_clk))
48+
49+
# Don't want to test what happens when we reset.
50+
m.d.comb += Assume(~sync_rst)
51+
52+
return m, [sync_clk, sync_rst]
53+
54+
55+
if __name__ == "__main__":
56+
main(Counter)

answers/e07_counter.sby

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
[tasks]
2+
cover
3+
bmc
4+
prove
5+
6+
[options]
7+
prove: mode prove
8+
bmc: mode bmc
9+
cover: mode cover
10+
depth 22
11+
multiclock on
12+
13+
[engines]
14+
cover: smtbmc z3
15+
prove: smtbmc z3
16+
bmc: smtbmc z3
17+
18+
[script]
19+
read_verilog <<END
20+
module \$dff (CLK, D, Q);
21+
parameter WIDTH = 0;
22+
parameter CLK_POLARITY = 1'b1;
23+
input CLK;
24+
input [WIDTH-1:0] D;
25+
output reg [WIDTH-1:0] Q;
26+
\$ff #(.WIDTH(WIDTH)) _TECHMAP_REPLACE_ (.D(D),.Q(Q));
27+
endmodule
28+
END
29+
design -stash dff2ff
30+
read_ilang toplevel.il
31+
proc
32+
techmap -map %dff2ff top/w:clk %co
33+
prep -top top
34+
35+
[files]
36+
toplevel.il

diagrams/fixing_induction.png

128 KB
Loading

diagrams/induction_math.png

64.5 KB
Loading

0 commit comments

Comments
 (0)