The meaning of a BSV program is settled at three different moments. Compilation fixes types and structure, scheduling analysis decides which rules may run in the same cycle, and at runtime the current state decides which rules actually fire. Keeping those moments apart is what lets you tell a compile error, a scheduling warning, and a design that silently stalls from one another.
Earlier chapters met each moment on its own. Guards: hold and stop
built a rule's enabling condition from its explicit condition and the readiness
of every method it calls. Rules that share state
showed atomic updates, conflicts between rules, and the G0010 and G0021
warnings. State machines with rules
expressed a required order as phase state. This chapter names the three phases
those behaviours belong to, and then explains the scheduling relations that
primitive modules declare between their methods, which is where a conflict
warning ultimately comes from.
1. Three phases of interpretation
1.1 What each phase decides
A BSV design is interpreted by static processing, then scheduling analysis, then execution. When a design misbehaves, look for the cause in that order.
| Phase | When | What it decides | Problems that show up here |
|---|---|---|---|
| Static checking and elaboration | Compile time | Types, provisos, widths, module instances, parameters, structural loops | Type errors, unresolved provisos |
| Scheduling | Compile time | Which rules may share a cycle, method ordering, priorities | Conflict warnings, warnings about rules that can never fire |
| Runtime | Every cycle | Which conditions are true now, which rules fire, the next state | Disabled rules, unexpected state transitions |
Check
bsc prints a warning that two rules conflict and that one of them can never fire. Which phase produced that warning?
1.2 Static checking and elaboration
Elaboration unfolds the hardware structure, starting at the top module and instantiating each child in turn. Parameters are passed down, and every loop and function that builds structure is expanded.
Vector#(4, Reg#(Bit#(8))) regs <- replicateM(mkReg(0));
for (Integer i = 0; i < 4; i = i + 1)
rule clear_one (clearing);
regs[i] <= 0;
endrule| Code | What elaboration does with it |
|---|---|
replicateM(mkReg(0)) | Creates four register instances |
for (Integer i = 0; ...) | Not a loop that runs in hardware; it unfolds into four separate rules |
After elaboration the instance hierarchy and the set of rules never change. A type error is not a branch that might be taken at runtime; it is caught before any hardware exists.
Check
After elaboration of the snippet above, how many rules does the module contain?
1.3 Scheduling analysis
Scheduling analysis works on that fixed design and follows every method each rule calls, including methods of other modules and nested calls. It runs once, at compile time, and produces a scheduler circuit that picks the rules to fire in each cycle.
1.4 Runtime
At runtime the current register values feed each rule's conditions. The chosen rules compute next-state values, and stored state changes all at once at the clock edge. Simulation and synthesized hardware follow the same state-transition rules.
The rules that fire together in one cycle must give the same result as running them one at a time, in the order the scheduler chose, each rule seeing the state the earlier ones left. That is how you predict a cycle: pick the order, then run the rules in your head one by one.
1.5 Example: one rule at a time
In OneRuleAtATime.bsv, x starts at 0 and y at 9. In cycle 0 both copy
(y <= x) and incr (x <= x + 1) are enabled, and show prints the two
registers in cycle 1. copy reads x and incr writes it; a register read is
SB its write, so the only legal order is copy before incr.
Predict before you run. Run the two rules one at a time in that order and
write down x and y.
Check
What does show print?
package OneRuleAtATime;
(* synthesize *)
module mkTop(Empty);
Reg#(Bit#(8)) x <- mkReg(0);
Reg#(Bit#(8)) y <- mkReg(9);
Reg#(Bit#(2)) cycle <- mkReg(0);
// Both rules fire in cycle 0. copy reads x, and incr writes x.
rule copy (cycle == 0);
y <= x;
endrule
rule incr (cycle == 0);
x <= x + 1;
endrule
rule advance;
cycle <= cycle + 1;
endrule
rule show (cycle == 1);
$display("x=%0d y=%0d", x, y);
$finish(0);
endrule
endmodule
endpackageCheck the result. x=1 y=0, with no scheduling warning: both rules fired in
cycle 0, and the result is exactly copy followed by incr.
Try it yourself. Change incr to x <= y + 1; and predict again before you
run.
Check
With incr changed to x <= y + 1;, what happens?
2. Method scheduling relations
2.1 The relations
Primitive modules tell bsc whether two method calls can share a cycle by
declaring a scheduling relation between them.
| Relation | Meaning |
|---|---|
C | The calls conflict; they cannot fire in the same cycle |
CF | The calls are conflict-free; any order gives the same result |
SB | They can share a cycle, but must behave as if the first came before the second |
SBR | The first comes before the second, and the two calls cannot appear in one rule |
SA and SAR are the reverse-direction names for SB and SBR in scheduling
documentation; they are not BSV source attributes.
Check
Which relation lets two method calls share a cycle in a fixed order but forbids both calls from appearing in the same rule?
2.2 Register relations
| Call pair | Relation | Consequence |
|---|---|---|
_read and _read | CF | Any number of rules may read in the same cycle |
_read and _write | SB | Reads are ordered before writes, which is why reads see the old value |
_write and _write | SBR | Writes from two rules need arbitration; two writes in one rule are an error |
This table explains the phase.write versus phase.read text in the G0010
warning of the rule-conflict example in
Rules that share state. For
chooseOne to come before chooseTwo, chooseOne's write would have to
precede chooseTwo's read, but reads must precede writes. The same holds in
the other direction, so no order works.
The SBR row also explains two results from that chapter. Two writes to one
register inside a single rule are the compile error G0004, because SBR
forbids both calls in one rule. Two rules that each write the register can
still share a cycle: bsc orders them, the write ordered later takes effect,
and warning G0117 reports that it shadows the earlier one.
Check
Two different rules each write register r, and both are enabled in the same cycle. What does the SBR relation between _write and _write mean for them?
Note. Modules with similar storage behavior can still have different
scheduling relations; mkReg and mkConfigReg, for example, present different
scheduling models. Choosing a primitive changes not only area and
initialization but also which callers can share a cycle. Reach for the
alternative only when you need its relation and have verified it.
3. Summary
| Topic | Key point |
|---|---|
| Three phases | Elaboration fixes structure, scheduling fixes which rules may share a cycle, runtime computes the next state |
| Method relations | C, CF, SB, and SBR decide which calls may share a cycle |
| Register relations | Reads are CF with each other and SB before writes; writes are SBR, so two writes in one rule are an error and two writing rules need arbitration |
4. Check your understanding
-
Name one thing elaboration fixes and one thing that is re-evaluated every cycle.
Show answer
Elaboration fixes the module instance hierarchy and the set of rules; a
forloop over anIntegeris unfolded at that point. Rule explicit conditions and method readiness conditions are re-evaluated every cycle. -
Why may any number of rules read a register in one cycle, while two rules that write it need arbitration?
Show answer
_readand_readareCF: the order of two reads never changes a result._writeand_writeareSBR: the two writes must be ordered, sobschas to decide which rule comes later, and that write is the one that takes effect.