|
| 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 | + |
| 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 | + |
| 32 | + |
| 33 | +What is the value of `Past` before the very first time step? It is the reset value of the signal. |
| 34 | + |
| 35 | + |
| 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 | + |
| 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 | + |
| 129 | + |
| 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). |
0 commit comments