Skip to content
code0.HDL

Rules as values

Build a rule as a Rules value, install it with addRules, and tell the one elaboration-time call apart from the many cycles the installed rule fires on.

In this chapter

  • Write a function that returns a Rules value and install it with addRules
  • Separate what happens during elaboration from what happens during simulation for one installed rule
  • Diagnose a counter that never moves because its Rules value was built but never installed
Concepts in this chapter
  • Rules as values
Prerequisite concepts

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.

PhaseWhat happens to a Rules value
ElaborationThe rules ... endrules expression is evaluated, functions that return it are called, and addRules installs the result in the module being elaborated
SimulationThe 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?

Your answer

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.

  1. Which value of count does finish print?
  2. How many times is incrementRules called, and how many times does the rule it returned fire?
RuleExpression.bsv
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

endpackage

Practice 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.

  1. Change the guard to count < 2. The rule stops at 2, finish waits for exactly 3, and no rule is enabled any more. Nothing is printed and $finish is never reached, so the run ends only when the execution time limit stops it.
  2. Change the guard to count <= 3. At entry state 3 both rules fire in the same cycle: finish prints 3 and ends the simulation while increment schedules 4. The output is still count=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);
endrule

Check

How many increment rules does the module hold, and what does finish print?

Your answer

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?

Your answer

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

QuestionAnswer
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

  1. In the rule-expression example, what happens during elaboration and what happens during simulation?

    Show answer

    During elaboration, incrementRules(count) runs once and addRules installs the returned rule. During simulation, that rule fires on every cycle in which its guard count < 3 is true.

  2. 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 finish prints 3 and stops. The testbench ends too early to observe the overshoot, so a correct-looking output does not prove the bound is right.

  3. A module calls incrementRules twice, with two different registers, and passes each result to addRules. How many rules does the module hold?

    Show answer

    Two. Each call builds its own Rules value during elaboration, and each addRules installs the rule it carries. They are separate rules that happen to come from the same function.

Checkpoint

Run the example and compare it with your prediction

Sources

Licences and notices for the sources this site follows