Skip to content
code0.HDL

Guards: hold and stop

Use a rule's explicit condition to hold a register, to stop a counter exactly at its limit, and to find the silent gap when the state one rule leaves never satisfies the condition of the next.

In this chapter

  • Read a guard as the set of start-of-cycle values on which a rule may fire, and predict where a self-limiting counter stops
  • Explain holding as the absence of a write, the BSV form of an enable of 0
  • Combine an explicit condition with the implicit conditions of called methods into a rule's composite condition
  • Find a gap between the value one rule stops at and the condition the next rule waits for
Concepts in this chapter
  • Guards: hold and stop conditions
Prerequisite concepts

1. A guard chooses the cycles in which a rule may fire

1.1 The explicit condition

The Bool expression in parentheses after a rule's name is its explicit condition, usually called its guard. It becomes combinational logic over the start-of-cycle state, evaluated every cycle. When it is true, the rule may be ready, subject to the method conditions in section 3. When it is false, the rule does nothing; a register holds its value unless another rule writes it.

Form

rule name (explicitCondition);
  actions;
endrule
rule tick (running && count < 10);
  count <= count + 1;
endrule

rule always_on;          // no condition means (True)
  $display("tick");
endrule
RuleDetail
What the guard may readAny state and value in scope: registers, other values, method results
When it is evaluatedEvery cycle, against the values held at the start of that cycle
If you leave it outThe explicit condition is True; called methods may still prevent readiness
What it decidesWhether the rule may fire in this cycle; never which value it writes

A guard is an ordinary Bool expression, so the operators of Bool, Bit and pure functions combine conditions: running && count < 10 is ready only in cycles where both parts hold.

1.2 Holding is not an instruction

A register changes only when some firing rule writes it. There is no separate "hold" statement: a register that no rule writes in a cycle keeps its value through the edge. A false guard is therefore the enable of the fundamentals (Enable chooses between updating and holding), written as the condition under which the update happens rather than as a signal into the register.

FundamentalsBSV
enable = 1: the register loads Dthe guard is true and the rule fires: its <= writes are stored
enable = 0: the register keeps Qthe guard is false: no write, the register holds
resetthe initial value given to mkReg, applied before cycle 0

Note. A false guard does not reset anything. A register that a stopped rule last wrote to 5 still holds 5 many cycles later; only another rule's write, or a reset, changes it.

1.3 Ready and fired

Two words in this course stay apart: a rule is ready when its explicit and implicit conditions are true, and it fires when it actually runs in that cycle. In a design with one rule, or with rules whose guards are never true together, the two coincide. Readiness is a fact about the current state; firing is a decision the scheduler makes among the ready rules. The case where a ready rule does not fire is the subject of Rules that share state.

2. A rule that writes what its guard tests stops itself

rule step (count < 5);
  count <= count + 1;
endrule

step tests count and writes count, so each firing moves the state toward the point where the guard turns false. The last firing happens in the cycle where count is 4: it reads 4, stores 5, and from the next cycle count < 5 is false. The counter stops at 5, which is the first value the rule never fires on.

GuardValues on which the rule firesLast value the rule readsValue the register stops at
count < 50, 1, 2, 3, 445
count <= 50, 1, 2, 3, 4, 556

Choosing < or <= is the whole difference between stopping at the limit and one step past it. Read a guard by asking which values let the body run, then look at what the body stores on the last of them. The boundary cases of the fundamentals (normal, boundary and reset cases) are exactly the cycles to check: the value just below the limit, the limit, and the reset value.

3. Calls add conditions of their own

So far every guard was written out. The methods a rule calls can also carry readiness conditions of their own, called implicit conditions. A FIFO's deq and first are not ready while the FIFO is empty, and its enq is not ready while it is full.

rule move;              // no explicit condition
  outq.enq(inq.first);  // first is not ready while inq is empty
  inq.deq;
endrule

A rule is enabled only when its explicit condition and the implicit condition of every method it calls are true, including methods called from inside other methods. This course calls that conjunction the composite condition. move names no condition, yet it cannot fire while inq is empty or outq is full. A method that is not ready blocks the whole rule; BSV never runs the rest of the body while skipping just that one call.

The same holds for a method you define yourself. Below, the if (count < 3) after the method header is bump's readiness condition; how to write one is taught in Readiness, module arguments, and synthesis boundaries.

// bump is ready only while count < 3
method Action bump if (count < 3);
  count <= count + 1;
endmethod

rule drive (cycles < 5);
  c.bump;
endrule
// composite condition of drive: cycles < 5 && count < 3

Starting from count = 0 and tracing cycle by cycle:

State at cycle startExplicitbump readydrivecount after edge
cycles = 0, count = 0truetruemay fire1
cycles = 1, count = 1truetruemay fire2
cycles = 2, count = 2truetruemay fire3
cycles = 3, count = 3truefalsedisabled3
cycles = 4, count = 3truefalsedisabled3

In the last two rows, a separate rule that advances cycles keeps firing. A method that is not ready disables the rules that call it, not the whole module.

Note. An ordinary Reg has no value-dependent implicit conditions on its read or write methods. In a design made only of registers, such as every example in this chapter, readiness follows from the explicit conditions alone. If a rule's explicit condition is true but it does not fire, check the readiness of every method on its call path first.

4. A gap between two conditions stops everything silently

4.1 Matching two boundaries

When one rule is meant to take over where another stops, the state left by the first rule's last firing must make the second rule's guard true. Write the stopping rule's guard as count < S and the waiting rule's as count == F.

RelationshipWhat happens
S == Fthe second rule becomes ready in the cycle after the first stores F
S < Fcount stops at S; both guards are false and no rule fires again
S > Fin the cycle where count == F both guards are true

The S < F case produces no message of any kind. Compilation succeeds, simulation runs, and the design sits in a state where nothing happens until the simulator's time limit ends it.

4.2 Example: an unreachable condition

GuardGap.bsv sets S to 3 and F to 5 on purpose. Because finish can never become ready, the file has a separate tick counter and a watchdog rule that prints count when cycle reaches 8 and ends the run. A monitor rule, which only reads, prints cycle and count once per cycle while cycle < 8, so every row of your trace has a line to compare with. All three registers start from their mkReg values: count and cycle at 0 and done at False.

GuardGap.bsv
package GuardGap;

(* synthesize *)
module mkTop(Empty);
  Reg#(Bit#(4)) count <- mkReg(0);
  Reg#(Bool) done <- mkReg(False);
  Reg#(Bit#(4)) cycle <- mkReg(0);

  // The worker stops before the finish guard can become ready.
  rule step (count < 3);
    count <= count + 1;
  endrule

  rule finish (count == 5 && !done);
    done <= True;
    $display("FINISH: count=%0d", count);
    $finish(0);
  endrule

  // This independent watchdog keeps the broken handoff finite.
  rule tick (cycle < 8);
    cycle <= cycle + 1;
  endrule

  // Prints the start-of-cycle state once per cycle.
  rule monitor (cycle < 8);
    $display("cycle=%0d count=%0d", cycle, count);
  endrule

  rule watchdog (cycle == 8);
    $display("GUARD_GAP: count=%0d", count);
    $finish(0);
  endrule
endmodule

endpackage

Practice this concept

Use this concept in a short BSV problem as your next step.

Predict before you run.

Check

How far does count get before nothing changes it any more?

Check

Which rule ends the simulation?

Your answer

Check

The monitor prints one line per cycle. Which count does the cycle=6 line show?

Press Run, then compare every output line with this trace. Each row is one cycle; the guard columns are evaluated on the start-of-cycle values.

cyclecount at startstep guard count < 3finish guard count == 5 && !doneRules that fireLine printed
00truefalsestep, tick, monitorcycle=0 count=0
11truefalsestep, tick, monitorcycle=1 count=1
22truefalsestep, tick, monitorcycle=2 count=2
33falsefalsetick, monitorcycle=3 count=3
4–73falsefalsetick, monitorcount=3 on every line
83falsefalsewatchdogGUARD_GAP: count=3
OutputWhat it shows
count=3 from cycle=3 onstep's last firing read 2 and stored 3; with its guard false, count holds
GUARD_GAP: count=3, and no FINISH lineThe watchdog ended the run; finish never fired, because no transition leads from 3 to 5

The cause is not rule order and not a register delay; every firing was correct, and there is simply no transition from 3 to 5. Atomicity guarantees that each firing is complete, not that the design keeps making progress.

5. Change one thing

Change finish's guard to count == 3 && !done and run again. Then try count == 16 && !done.

Check

With the guard count == 3 && !done, what changes in the output?

Your answer

ChangeExpected result
Make finish's condition count == 3 && !doneThe lines cycle=0 to cycle=2, then FINISH: count=3; no GUARD_GAP line. There is no cycle=3 line: in that cycle finish and monitor are both enabled, the generated code runs finish first, and $finish stops the simulator before monitor's display
Make finish's condition count == 16 && !doneBit#(4) holds only 0 to 15, so compilation stops with T0051, Literal 16 is not a valid Bit#(4).

The second edit does not run at all. The literal rule of Types, widths and explicit conversion applies to a literal in a guard as anywhere else. A guard that can never be true because of the type is caught at compile time; a guard that can never be true because of the other rules is not.

Note. Bit#(4) arithmetic wraps modulo 16: adding 1 to 15 at run time and storing the result in four bits gives 0. A literal 16, by contrast, can never be a Bit#(4) value, so it is rejected before anything runs.

6. Apply it: stop exactly at the limit

The graded problem below is a counter with a limit register and the guard count <= limit. For the limits 5 and 2 used here, it runs one step past the limit: atLimit is true for one cycle, then false until the counter is reset. At the maximum 4-bit limit, 15, the counter instead wraps to 0 and keeps counting. Change only the comparison in the guard. Before running, use the table in section 2 to write down the last value the rule reads and the value the register settles at, for the default limit 5 and for the limit 2 that the testbench sets later.

Check

With the guard count <= limit and limit = 5, what value does count settle at?

7. Common mistakes

7.1 Running one step past the limit

// Wrong: the rule still fires when count is 5, and stores 6
rule step (count <= 5);  count <= count + 1;  endrule

// Right: the last firing reads 4 and the register stops at 5
rule step (count < 5);   count <= count + 1;  endrule

The guard decides the values the body runs on, and the register settles at what the body stores on the last of them. Check the guard against the limit value itself, not only a value in the middle.

7.2 Leaving a gap between hand-off conditions

// Wrong: count stops at 3, so finish never becomes ready
rule step (count < 3);  count <= count + 1;  endrule
rule finish (count == 5);  $finish(0);  endrule

// Right: the value where step stops is the value finish waits for
rule step (count < 5);  count <= count + 1;  endrule
rule finish (count == 5);  $finish(0);  endrule

The symptom is a design that silently stops making progress. Check the value left by the last transition of one rule against the condition of the next.

7.3 Waiting for an error when nothing fires

// Wrong expectation: a stalled design reports itself
rule finish (count == 5);  $finish(0);  endrule

// Right: an independent watchdog keeps a test finite and makes the stall visible
rule tick (cycle < 8);  cycle <= cycle + 1;  endrule
rule watchdog (cycle == 8);
  $display("stalled at count=%0d", count);
  $finish(0);
endrule

No rule firing is a legal state, not an error, so neither bsc nor the simulator reports it. A testbench that waits for a condition should bound the wait itself.

8. Summary

IdeaRule
guardcombinational condition over start-of-cycle state; chooses the cycles the rule may fire; omitted means True
holda register written by no firing rule keeps its value; a false guard is an enable of 0
ready vs firedtrue explicit and implicit conditions make a rule ready; firing is a separate decision of the scheduler
stopa rule that writes what its guard tests stops at the first value that makes the guard false
off by one< stops at the limit, <= one past it
composite conditionexplicit condition and the implicit condition of every called method; a method that is not ready blocks the whole rule
gapif the stopping value never satisfies the next guard, nothing fires and nothing reports it
impossible literala guard literal outside the type's range is T0051 at compile time

9. Check your understanding

  1. A hand-off uses count < S and count == F with S < F. What do you observe?

    Show answer

    Once count reaches S, both conditions are false and no rule fires. There is no compile error and no runtime error; the design just stops making progress.

  2. A rule calls inq.first and inq.deq and has no explicit condition. What happens while inq is empty?

    Show answer

    The implicit conditions of first and deq are false, so the whole rule is not ready and does not fire. BSV never skips only the call that is not ready.

  3. step (count < 5) stopped at 5 ten cycles ago, and no other rule writes count. What does count hold now?

    Show answer

    Still 5. A false guard means no write, and a register that is not written holds its value; a guard never resets state.

The next chapter puts two rules in the same cycle and asks what happens when both are ready.

Checkpoint

Submit the Practice problem for grading

Grading runs on the server against hidden tests; the result is recorded in this browser only.

Sources

Licences and notices for the sources this site follows