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;
endrulerule tick (running && count < 10);
count <= count + 1;
endrule
rule always_on; // no condition means (True)
$display("tick");
endrule| Rule | Detail |
|---|---|
| What the guard may read | Any state and value in scope: registers, other values, method results |
| When it is evaluated | Every cycle, against the values held at the start of that cycle |
| If you leave it out | The explicit condition is True; called methods may still prevent readiness |
| What it decides | Whether 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.
| Fundamentals | BSV |
|---|---|
enable = 1: the register loads D | the guard is true and the rule fires: its <= writes are stored |
enable = 0: the register keeps Q | the guard is false: no write, the register holds |
| reset | the 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;
endrulestep 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.
| Guard | Values on which the rule fires | Last value the rule reads | Value the register stops at |
|---|---|---|---|
count < 5 | 0, 1, 2, 3, 4 | 4 | 5 |
count <= 5 | 0, 1, 2, 3, 4, 5 | 5 | 6 |
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;
endruleA 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 < 3Starting from count = 0 and tracing cycle by cycle:
| State at cycle start | Explicit | bump ready | drive | count after edge |
|---|---|---|---|---|
cycles = 0, count = 0 | true | true | may fire | 1 |
cycles = 1, count = 1 | true | true | may fire | 2 |
cycles = 2, count = 2 | true | true | may fire | 3 |
cycles = 3, count = 3 | true | false | disabled | 3 |
cycles = 4, count = 3 | true | false | disabled | 3 |
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.
| Relationship | What happens |
|---|---|
S == F | the second rule becomes ready in the cycle after the first stores F |
S < F | count stops at S; both guards are false and no rule fires again |
S > F | in 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.
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
endpackagePractice 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?
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.
cycle | count at start | step guard count < 3 | finish guard count == 5 && !done | Rules that fire | Line printed |
|---|---|---|---|---|---|
| 0 | 0 | true | false | step, tick, monitor | cycle=0 count=0 |
| 1 | 1 | true | false | step, tick, monitor | cycle=1 count=1 |
| 2 | 2 | true | false | step, tick, monitor | cycle=2 count=2 |
| 3 | 3 | false | false | tick, monitor | cycle=3 count=3 |
| 4–7 | 3 | false | false | tick, monitor | count=3 on every line |
| 8 | 3 | false | false | watchdog | GUARD_GAP: count=3 |
| Output | What it shows |
|---|---|
count=3 from cycle=3 on | step's last firing read 2 and stored 3; with its guard false, count holds |
GUARD_GAP: count=3, and no FINISH line | The 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?
| Change | Expected result |
|---|---|
Make finish's condition count == 3 && !done | The 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 && !done | Bit#(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; endruleThe 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); endruleThe 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);
endruleNo 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
| Idea | Rule |
|---|---|
| guard | combinational condition over start-of-cycle state; chooses the cycles the rule may fire; omitted means True |
| hold | a register written by no firing rule keeps its value; a false guard is an enable of 0 |
| ready vs fired | true explicit and implicit conditions make a rule ready; firing is a separate decision of the scheduler |
| stop | a 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 condition | explicit condition and the implicit condition of every called method; a method that is not ready blocks the whole rule |
| gap | if the stopping value never satisfies the next guard, nothing fires and nothing reports it |
| impossible literal | a guard literal outside the type's range is T0051 at compile time |
9. Check your understanding
-
A hand-off uses
count < Sandcount == FwithS < F. What do you observe?Show answer
Once
countreachesS, both conditions are false and no rule fires. There is no compile error and no runtime error; the design just stops making progress. -
A rule calls
inq.firstandinq.deqand has no explicit condition. What happens whileinqis empty?Show answer
The implicit conditions of
firstanddeqare false, so the whole rule is not ready and does not fire. BSV never skips only the call that is not ready. -
step (count < 5)stopped at 5 ten cycles ago, and no other rule writescount. What doescounthold 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.