Skip to content

Commit b401cd0

Browse files
committed
Exercise 6
1 parent 2bb0472 commit b401cd0

File tree

11 files changed

+310
-0
lines changed

11 files changed

+310
-0
lines changed

06_past.md

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# Exercise 6: Living in the past
2+
3+
## What you'll do:
4+
5+
In the previous exercise, you showed that the output of a counter that counted from 1 to 9, going back to 1, was never 0 and was never greater than 9. You also covered the case where the output was 3. Hopefully you saw that the output started at 1, went to 2, and then to 3.
6+
7+
Show that each positive edge of the clock increments the counter by one, and that the counter goes from 9 back to 1.
8+
9+
## The Past
10+
11+
In formal verification, you can refer to the value of a signal as it was one time step ago by using `Past`:
12+
13+
```python
14+
from nmigen.asserts import Past
15+
16+
m.d.comb += Assert((x == 2) & (Past(x) == 1))
17+
```
18+
19+
The above asserts that `x` is now 2, but one time step ago it was 1.
20+
21+
![Past, waveforms](diagrams/past1.png)
22+
23+
We can also look at the value of a signal N steps ago by giving `clocks=N` to `Past`:
24+
25+
```python
26+
m.d.comb += Assert((x == 2) & (Past(x, clocks=2) == 1))
27+
```
28+
29+
The above asserts that `x` is now 2, but two time steps ago it was 1. The default for `clocks` is 1.
30+
31+
![Past, waveforms](diagrams/past2.png)
32+
33+
What is the value of `Past` before the very first time step? It is the reset value of the signal.
34+
35+
![Past before time begins](diagrams/past_t0.png)
36+
37+
## The clock and reset signals
38+
39+
So far you haven't used the clock or reset signals in anything. But you can access them using `ClockSignal("domain-name")` and `ResetSignal("domain-name")`.
40+
41+
```python
42+
from nmigen import ClockSignal, ResetSignal
43+
44+
sync = ClockSignal("sync")
45+
with m.If(sync):
46+
m.d.comb += Assert(x == 2)
47+
```
48+
49+
The above checks that `x` is always 2 when the `sync` clock is high, but makes no assertion about what `x` is when the `sync` clock is low.
50+
51+
## Other past-looking functions
52+
53+
```python
54+
from nmigen.asserts import Stable, Rose, Fell
55+
```
56+
57+
`Stable(x)` is equivalent to `x == Past(x)`.
58+
59+
`Rose(x)` is equivalent to `(x == 1) & (Past(x) == 0)`.
60+
61+
`Fell(x)` is equivalent to `(x == 0) & (Past(x) == 1)`.
62+
63+
`Stable(x, clocks=N)`, `Rose(x, clocks=N)`, and `Fell(x, clocks=N)` are equivalent to using `clocks=N+1` in the `Past` expression, so the default `clocks` value is 0.
64+
65+
![Waveforms showing Past and friends](diagrams/past_and_friends.png)
66+
67+
## Initial
68+
69+
The `Initial()` signal is 1 when we are on the very first time step, and 0 otherwise. This is useful for determining whether `Past` is going to give you the reset value.
70+
71+
```python
72+
from nmigen.asserts import Initial
73+
```
74+
75+
## Assumptions
76+
77+
There's a hidden assumption that you're making when you ask the formal verification engine to verify a circuit with clocks in it: that the clocks actually clock.
78+
79+
Earlier we said that there is a built-in clock domain, `sync`. However, you still need to explain to the engine how that input signal (for it is an input signal to your circuit) is supposed to operate. We do that with assumptions.
80+
81+
Assumptions force the verification engine to ensure that the assumptions are true. So for example, suppose `x` were an input to some module that you want to verify:
82+
83+
```python
84+
from nmigen.asserts import Assume
85+
86+
x = Signal(16) # An input signal
87+
m.d.comb += Assume(x < 0xD000)
88+
```
89+
90+
The above would force the engine not to consider cases where `x >= 0xD000`. As a less direct assumption:
91+
92+
```python
93+
x = Signal(4) # An input signal
94+
y = Signal(4) # Another input signal
95+
z = Signal(4)
96+
m.d.comb += z.eq(x + y)
97+
m.d.comb += Assume(z < 10)
98+
```
99+
100+
Now the engine can only consider `x` and `y` such that `x + y < 10`. By the way, this is where the prover's built-in theories can really help. With a theory of linear arithmetic built in, the Z3 solver is able to short-cut a brute-force solution to the equation.
101+
102+
The fun continues when you have complex circuits with signals that indicate complex conditions. You can simply assume that such a signal goes high, and the solver will be forced to make that complex condition happen.
103+
104+
This may sound similar to the Cover statement, but covering only tries to find one path to making the condition happen. Assumption works with bounded model checking (and later, induction) to find all such paths.
105+
106+
## Assuming a clock
107+
108+
So, because we need to assume that the `sync` clock actually clocks, we add this to the `formal` function in our skeleton code (see [`skeleton_sync.py`](skeleton_sync.py)):
109+
110+
```python
111+
...
112+
113+
sync_clk = ClockSignal("sync")
114+
sync_rst = ResetSignal("sync")
115+
116+
# Make sure the clock is clocking
117+
m.d.comb += Assume(sync_clk == ~Past(sync_clk))
118+
119+
# Include this only if you don't want to test resets
120+
m.d.comb += Assume(~sync_rst)
121+
122+
# Ensure sync's clock and reset signals are manipulable.
123+
return m, [sync_clk, sync_rst, my_class.my_input]
124+
```
125+
126+
Without this assumption, formal verification can feel free to simply not clock `sync`, and then your counter will be stuck at its initial value.
127+
128+
![Waveforms for clock with assumptions](diagrams/sync_clk_assume.png)
129+
![Waveforms for clock without assumptions](diagrams/sync_clk_no_assume.png)
130+
131+
## Bad assumptions
132+
133+
Sometimes you might get too clever and write some assumptions that can't all simultaneously work. For example, consider the assumption that `sync_clk == ~Past(sync_clk)`. Remember we said that the `Past` of any signal before the initial time step is its reset value? The reset value for clocks is `0`. Therefore, at the first time step, `Past(sync_clk)` is `0` and so `~Past(sync_clk)` is `1`. And because of our assumption, `sync_clk` on the first time step will be `1`.
134+
135+
However, if you wanted to force the clock to be `0` on the first time step using an assumption:
136+
137+
```python
138+
with m.If(Initial()):
139+
m.d.comb += Assume(~sync_clk)
140+
```
141+
142+
Now you have two contradictory assumptions. One says that the clock must be the opposite of its past value, the past value on the first step is 0, so the clock must be 1 on the first step.
143+
144+
The other assumption says that the clock must be 0 on the first step.
145+
146+
If you run into contradictory assumptions, formal verification will give you a message like this:
147+
148+
```
149+
SBY 10:42:59 [e06_counter_bmc] engine_0: ## 0:00:00 Assumptions are unsatisfiable!
150+
SBY 10:42:59 [e06_counter_bmc] engine_0: ## 0:00:00 Status: PREUNSAT
151+
```
152+
153+
## Your turn
154+
155+
Armed with all the above, you should now be able to construct your assumptions and assertions to meet the problem posed at the beginning of this exercise. Remember that sequential positive edges on the `sync` clock are two time steps apart!
156+
157+
## Stumped?
158+
159+
The answer to this exercise is in [`answers/e06_counter.py`](answers/e06_counter.py).

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ Work in progress!
88
* [03 - Life finds a way: parts of signals](03_parts.md)
99
* [04 - Signs: signed signals](04_signs.md)
1010
* [05 - Synchronicity: synchronous signals](05_sync.md)
11+
* [06 - Living in the past: multi-step asserts](06_past.md)

answers/e06_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(4, 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 == 9):
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 <= 9))
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) == 9)
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/e06_counter.sby

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

diagrams/past1.png

42.2 KB
Loading

diagrams/past2.png

68.8 KB
Loading

diagrams/past_and_friends.png

59.8 KB
Loading

diagrams/past_t0.png

50.3 KB
Loading

diagrams/sync_clk_assume.png

64.6 KB
Loading

diagrams/sync_clk_no_assume.png

76.7 KB
Loading

0 commit comments

Comments
 (0)