Most chips are built from a small set of familiar blocks: counters, state machines, arbiters, FIFOs, pipelines, register files, datapaths and bus interfaces. Each kind has its own typical bugs and its own standard properties. This chapter takes them one at a time, with a design that has a real bug, the properties that find it, and an exercise to fix it in ChipVerify Lab.

Counters and clock dividers look too simple to need formal verification, yet off-by-one errors in them are among the most common bugs in RTL. A divider that ticks every 4 cycles instead of 3 slows a UART or a timer by a third, and a simulation that only checks "a tick happens" never notices. This article writes the standard properties for a tick divider, finds an off-by-one bug, and proves the fixed design for every cycle.

8 min read | Intermediate

What You'll Learn

  • Write the standard properties of a counter: range, wrap, step and period
  • Measure the period of a tick with a helper counter
  • Find an off-by-one bug in a clock divider
  • Prove a counter property for every cycle with a helper invariant

Standard Counter Properties

  • Range: the count stays within its legal values, cnt <= N - 1.
  • Step: it changes only by the allowed amount, cnt == $past(cnt) + 1 or a wrap to 0.
  • Wrap: it wraps exactly at the limit, $past(cnt) == N - 1 |-> cnt == 0.
  • Period: an output pulse comes exactly every N cycles, no earlier and no later.
  • Enable and hold: without the enable, the count does not change.

The period property is the one closest to the specification: it says what the user of the divider sees, without referring to the internal count at all.

The Design: a Tick Divider

This divider produces a one-cycle tick every N cycles, for example to run a slow peripheral from a fast clock:


module tick_div #(parameter N = 3) (input clk, input rst, output tick);
  reg [3:0] cnt;
  assign tick = (cnt == N - 1);
  always @(posedge clk)
    if (rst)                   cnt <= 4'd0;
    else if (cnt == N)         cnt <= 4'd0;     // bug: should wrap at N - 1
    else                       cnt <= cnt + 4'd1;
endmodule

The counter should run 0, 1, 2 and wrap. It wraps one value too late, so it also visits 3, and the ticks come every 4 cycles.

The Period Property

In SVA, "exactly N cycles between ticks" can be written as: after a tick, no tick for N - 1 cycles, then a tick.


a_period: assert property (@(posedge clk) disable iff (rst)
                           tick |=> !tick [*N-1] ##1 tick);

In the lab form, a helper counter gap measures the cycles since the last tick:


`ifdef FORMAL
  reg init = 1'b1;
  always @(posedge clk) init <= 1'b0;
  always @(*) if (init) assume (rst);
  // helper: cycles since the last tick (or since reset)
  reg [3:0] gap = 4'd0;
  always @(posedge clk) gap <= (rst || tick) ? 4'd1 : gap + 4'd1;
  // ticks are exactly N cycles apart
  always @(*) if (!init && tick && seen) assert (gap == N);
  // and never more than N cycles apart
  always @(*) if (!init) assert (gap <= N);
  reg seen = 1'b0;
  always @(posedge clk) if (!rst && tick) seen <= 1'b1; else if (rst) seen <= 1'b0;
`endif

The seen flag skips the first tick after reset, whose distance from reset is not a period. The run fails at step 7:

The first tick comes in step 3. The counter then visits 3, which it should never do, and the next tick comes in step 7, four cycles later. Change the wrap to cnt == N - 1 and the run passes.

Quick Check

Q: A simulation test checks "tick goes high at least once in 100 cycles". Does it find this bug?

Show Answer

No. The buggy divider ticks every 4 cycles, so the test passes. Only a check of the exact period, like the property here, notices that the rate is wrong.

Proving It for Every Cycle

A bounded run at depth 10 covers several periods, which is enough to trust this small design. As a proof, k-induction in prove mode returns UNKNOWN at first: the step starts from states where gap and cnt disagree, which reset never produces. One helper invariant ties them together:


// helper invariant for the induction proof
always @(*) if (!init && !rst) assert (gap == cnt + 4'd1 && cnt <= N - 1);

With it, the proof passes with k = 1. The PDR engine (abc pdr) proves the original properties without the helper. Both are shown in the strengthening invariants article for a different design.

Quick Check

Q: Why does the helper say gap == cnt + 1?

Show Answer

After reset, cnt is 0 and gap is 1, and both go up by one each cycle until the tick, when cnt wraps to 0 and gap restarts at 1. So in every reachable state, gap is one more than cnt.

Running It in ChipVerify Lab

The example below is the buggy divider with the period properties, at N = 3. It fails at step 7 within the free depth.

Common Mistakes

Mistake #1: Checking that a tick happens, not when

"Eventually a tick" passes for any period. Check the exact distance between ticks.

Mistake #2: Counting the first tick as a period

The distance from reset to the first tick may differ from the period. Skip it, as the seen flag does, or specify it separately.

Mistake #3: Testing only one parameter value

A divider with a parameter should be checked at several values. Bugs in expressions like N - 1 can hide at one value and appear at another.

Practice Exercise

Challenge: Fix It for Any N

Open the example and run it. Fix the wrap condition. Then set N = 5 and run again at depth 10. Put the bug back with N = 5 and run at depth 10, and with PRO at depth 14. What do the results tell you about depth?

Hint

With N = 5 and the bug, ticks come every 6 cycles. How many cycles from reset until the second tick?

Solution

The fix is else if (cnt == N - 1) cnt <= 4'd0;. With N = 5 the fixed divider passes at depth 10. The buggy one also passes at depth 10, because the second tick, which reveals the wrong period, comes too late: the first tick is in step 5 and the second in step 11. At depth 14 the bug is found at step 11. The depth must cover two full periods to check a period.

Open the ready-made example in the lab and run the formal check from the formal panel. The free tier allows a depth of up to 10 cycles, and PRO allows up to 100.

Summary

  • Counters need range, step, wrap, period and hold properties. The period is the one the user of the block sees.
  • A helper counter measures the distance between ticks in the lab form; in SVA, tick |=> !tick [*N-1] ##1 tick.
  • An off-by-one wrap made the divider tick every 4 cycles instead of 3, found at step 7.
  • The helper invariant gap == cnt + 1 made the property inductive, proven for every cycle.
  • Check parameterized counters at several values, with a depth that covers two periods.
Did you find what you were looking for?

Quiz

No quiz questions available for this article.