1. A rules block is a value
Every rule you have written so far was written straight into a module body. BSV also lets you
build rules as a value, pass that value around during elaboration, and install it into a module.
A rules ... endrules block has the type Rules, and addRules installs the rules it carries
in the current module.
Form
rules
rule name (guard);
actions;
endrule
endrules
addRules(rulesExpression);A Rules value behaves like any other value while the design is elaborated: a function can
return it, a variable can hold it, and a loop can build one per iteration. What it cannot do is
run. Nothing in a Rules value fires until addRules has installed it in a module.
| Phase | What happens to a Rules value |
|---|---|
| Elaboration | The rules ... endrules expression is evaluated, functions that return it are called, and addRules installs the result in the module being elaborated |
| Simulation | The installed rules fire exactly like rules written directly in the module body: each cycle, each enabled rule whose guard is true may fire |
That split is why one function call can produce hardware that fires on many cycles. The call happens once; the rule it returned is part of the module from then on.
Check
A function returns a Rules value and the module calls addRules on it. How many times does the function run?
2. Installing an incrementing rule
RuleExpression.bsv defines incrementRules, which returns a rule that increments a register
up to 3. Read incrementRules first: the register it receives and the guard inside the returned
Rules. Then read mkTop, which installs the rules and has its own finish rule.
Simulation starts with count = 0. The installed rule fires at entry states 0, 1, and 2 and
schedules 1, 2, and 3. At entry state 3, count < 3 is false, and finish becomes enabled.
Predict before you run.
- Which value of
countdoesfinishprint? - How many times is
incrementRulescalled, and how many times does the rule it returned fire?
package RuleExpression;
function Rules incrementRules(Reg#(UInt#(4)) count);
// The function constructs a reusable Rules value during elaboration.
return rules
rule increment (count < 3);
count <= count + 1;
endrule
endrules;
endfunction
(* synthesize *)
module mkTop(Empty);
Reg#(UInt#(4)) count <- mkReg(0);
// addRules installs the returned rule in this module.
addRules(incrementRules(count));
rule finish (count == 3);
$display("count=%0d", count);
$finish(0);
endrule
endmodule
endpackagePractice this concept
Use this concept in a short BSV problem as your next step.
Check the result. finish prints count=3. That one line shows two separate timescales:
incrementRules(count) ran once while the module was elaborated, and the rule it returned fired
on three clock cycles.
Try it yourself.
- Change the guard to
count < 2. The rule stops at 2,finishwaits for exactly 3, and no rule is enabled any more. Nothing is printed and$finishis never reached, so the run ends only when the execution time limit stops it. - Change the guard to
count <= 3. At entry state 3 both rules fire in the same cycle:finishprints 3 and ends the simulation whileincrementschedules 4. The output is stillcount=3, so this testbench cannot see the overshoot; a test that ran one more cycle would.
The rule the function returned shares state with the rest of the module, so it is scheduled against the module's own rules in the usual way — see rules that share state for the relations that decide which of them share a cycle.
Apply both timescales to a loop that installs one rule per register.
Vector#(3, Reg#(UInt#(4))) counts <- replicateM(mkReg(0));
Reg#(UInt#(8)) cycle <- mkReg(0);
for (Integer i = 0; i < 3; i = i + 1)
addRules(incrementRules(counts[i]));
rule tick;
cycle <= cycle + 1;
endrule
rule finish (counts[0] == 3 && counts[1] == 3 && counts[2] == 3);
$display("cycle=%0d counts=%0d %0d %0d", cycle, counts[0], counts[1], counts[2]);
$finish(0);
endruleCheck
How many increment rules does the module hold, and what does finish print?
3. Common mistakes
3.1 Building rules without installing them
// Wrong: the Rules value is built and thrown away
Rules r = incrementRules(count);
// Right: install the rules in the module
addRules(incrementRules(count));Check
In RuleExpression.bsv you replace the addRules line with Rules r = incrementRules(count); What do bsc and the simulation show?
The wrong version compiles, but no increment rule exists, so count never moves. When a counter
stays still, check the guard, the readiness of called methods, and whether addRules was called.
4. Summary
| Question | Answer |
|---|---|
What is the type of rules ... endrules? | Rules |
| What installs it? | addRules(rulesExpression), in the module being elaborated |
| When does the function that builds it run? | Once, during elaboration |
| When do the rules it carries fire? | During simulation, when their guards, method readiness, and scheduling allow |
What happens without addRules? | The value is discarded; the design compiles and the rule never exists |
5. Check your understanding
-
In the rule-expression example, what happens during elaboration and what happens during simulation?
Show answer
During elaboration,
incrementRules(count)runs once andaddRulesinstalls the returned rule. During simulation, that rule fires on every cycle in which its guardcount < 3is true. -
With the guard
count <= 3, the example still prints 3. What does that say about the testbench?Show answer
The increment rule fires once more and schedules 4 in the same cycle in which
finishprints 3 and stops. The testbench ends too early to observe the overshoot, so a correct-looking output does not prove the bound is right. -
A module calls
incrementRulestwice, with two different registers, and passes each result toaddRules. How many rules does the module hold?Show answer
Two. Each call builds its own
Rulesvalue during elaboration, and eachaddRulesinstalls the rule it carries. They are separate rules that happen to come from the same function.