1. Overlapping conditions are allowed
A module usually has several rules, and nothing stops two guards from being true in the same
cycle. Each rule reads the start-of-cycle state and schedules its own writes. Two rules whose
conditions are true together are not an error. Whether they can fire together depends on the
state and the methods they use, and the compiler, bsc, works that out once, at compile time;
it then generates the logic that lets compatible rules share the cycle.
For one ordinary Reg, the relationships between the method calls two rules may make are:
| Method pair | Relation | Meaning within one cycle |
|---|---|---|
| read and read | CF | No interaction |
| read and write | SB | The read is ordered before the write, so a read always sees the start-of-cycle value |
| write and write | SBR | Two writes in one rule are an error (G0004, section 7.1); two rules may each write the register in the same cycle, and the later one in schedule order shadows the earlier (bsc warns) |
These relations belong to the ordinary Reg. FIFOs, wires, and other state elements publish
their own scheduling contracts; the library primitives
chapter introduces them, and the static and
dynamic semantics chapter has the full table of relations behind the names CF, SB and
SBR.
2. Rules on different state fire together
When two ready rules read and write different registers, both fire in that cycle and both sets
of writes land at the edge. OverlapScheduling.bsv walks through two situations, one per value
of phase.
phase | Ready rules | What they touch |
|---|---|---|
| 0 | updateLeft, updateRight, advance | left, right and phase, three different registers |
| 1 | chooseOne, chooseTwo, advance | both choice rules read and write winner |
| 2 | report | prints and stops |
advance increments phase every cycle while it is below 2, and report prints the results in
phase 2.
package OverlapScheduling;
(* synthesize *)
module mkTop(Empty);
Reg#(Bit#(2)) phase <- mkReg(0);
Reg#(Bit#(4)) left <- mkReg(0);
Reg#(Bit#(4)) right <- mkReg(0);
Reg#(Bit#(2)) winner <- mkReg(0);
// Rule guards are explicit conditions; these Reg methods add no
// state-dependent implicit readiness condition.
// Both rules are ready in phase 0 and update disjoint ordinary Reg state.
rule updateLeft (phase == 0);
left <= 1;
endrule
rule updateRight (phase == 0);
right <= 2;
endrule
// Both rules read and write winner in phase 1, creating a read/write cycle.
// bsc makes a deterministic choice for this build; source order and
// fairness are not language guarantees.
rule chooseOne (phase == 1 && winner == 0);
winner <= 1;
endrule
rule chooseTwo (phase == 1 && winner == 0);
winner <= 2;
endrule
// Reads of an ordinary Reg are ordered before its write, so the ready
// phase-specific rules can share a legal subset with this advance rule.
rule advance (phase < 2);
phase <= phase + 1;
endrule
rule report (phase == 2);
$display("OVERLAP_SCHEDULING compatible=%0d,%0d winner=%0d",
left, right, winner);
$finish(0);
endrule
endmodule
endpackagePractice this concept
Use this concept in a short BSV problem as your next step.
Predict before you run. How many rules fire in the phase-0 cycle? Can winner receive both
1 and 2? Will compilation print a warning? The first question is this check; the second is the
check in section 4; the third is answered by the compile output.
Check
In the phase 0 cycle, how many rules fire?
3. Two writes to one register are not a conflict by themselves
Suppose two ready rules both write winner and neither reads it. bsc still lets both fire.
It orders one rule after the other within the cycle, and the later rule's write is the one
stored; the earlier write is shadowed. This is legal, and the compiler says so with a warning
(G0036 and G0117, "shadows the effects of") naming the rules and the register. Shadowing is
usually a mistake in the design, but it is not a conflict, and both rules do fire. That is the
SBR row of the table in section 1.
4. A conflict needs a read/write cycle
In phase 1 the picture is different. chooseOne and chooseTwo both have winner == 0 in
their guards, so each one reads winner, and each one writes it. For both to fire in one
cycle, one of them would have to come first; but then its write would come before the other's
read of the same register, and a register read must see the start-of-cycle value, never a
value written earlier in the same cycle. Neither order works. That is a conflict: the two
rules cannot share a cycle at all.
bsc resolves it at compile time by making one rule more urgent than the other and warns
(G0010); because the two guards are identical, the loser can never fire, and a second warning
says so (G0021). Which rule wins is this build's choice, not source order and not a promise.
Check
Can winner end up holding both 1 and 2 during the run?
Press Run. The panel shows three things, in this order: the two compile warnings, the
schedule report that the -show-schedule flag in this example's command writes to
mkTop.sched (the command prints it with cat), and finally the simulation line. Read them in
that order.
Check the result.
| State at cycle start | Rules that fire together | State after the edge |
|---|---|---|
phase is 0 | updateLeft, updateRight, advance | left is 1, right is 2, phase is 1 |
phase is 1 | one of chooseOne/chooseTwo, plus advance | winner is 1 or 2, phase is 2 |
phase is 2 | report | results printed, simulation ends |
| Observation | Meaning |
|---|---|
compatible=1,2 | Both rules writing different registers fired in the same cycle (section 2) |
a single winner= value | Only one of the two conflicting writes took effect (this section) |
compile warnings G0010 and G0021 | One rule was made more urgent than the other, and the suppressed rule can never fire |
4.1 Reading the warnings and the schedule report
The warnings and the report are the compiler's own statement of what it decided. Find each line
below in the panel; the text is what bsc 2026.01 prints for this source.
| Where | Text in this build | What it tells you |
|---|---|---|
first line of G0010 | Rule "chooseOne" was treated as more urgent than "chooseTwo" | urgency: when both are enabled, chooseOne gets the cycle |
reasons under G0010 | "chooseOne" cannot fire before "chooseTwo" and the reverse, each followed by calls to winner.write vs. winner.read | the read/write cycle of this section, in both directions |
G0021 | According to the generated schedule, rule `chooseTwo' can never fire. | with identical guards the less urgent rule never has a cycle of its own |
Rule: chooseTwo in the report | Blocking rules: chooseOne | chooseTwo is suppressed in every cycle in which chooseOne is enabled |
| every other rule in the report | Blocking rules: (none) | nothing suppresses updateLeft, updateRight or advance; that is why compatible=1,2 |
Predicate: | (phase == 2'd1) && (winner == 2'd0) | the rule's condition as compiled |
Logical execution order: | updateLeft, updateRight, chooseOne, chooseTwo, advance, report | the one-at-a-time order that the rules firing together in one cycle must be equivalent to; the readers of phase come before advance, which writes it |
Urgency and logical execution order are different lists. Urgency decides which of two conflicting enabled rules fires; the logical execution order is the order in which rules that do fire together are considered to happen. The third edit in section 5 changes the first and leaves the second alone.
For the same source and compiler version the choice is repeatable. It is not tied to source order, though, and a different source or compiler version may choose differently. When priority matters, make the conditions mutually exclusive or state the priority with the scheduling attributes of the attributes chapter.
Caution. Overlapping guards are not an error and not a conflict, and neither are two writes
to the same register by themselves. The sign of a conflict is a cycle in what the rules read and
write: each rule reads what the other writes, as chooseOne and chooseTwo do with winner.
When one is reported, the fix is in the design: make the guards mutually exclusive, or move the
two operations to different cycles with a phase register (section 9).
5. Change one thing
In the lab, remove && winner == 0 from both choice guards, leaving phase == 1, and run
again. The two rules now write winner without reading it.
Check
With the reads of winner gone from the guards, what happens in the phase 1 cycle?
A second edit, on the original source: change chooseTwo's write to right <= 3;. Now the two
choice rules write different registers. chooseTwo still reads winner and chooseOne writes
it, but that is one read before one write, an order that exists, so both fire in phase 1 and the
output becomes compatible=1,3 winner=1.
A third edit, again on the original source: put
(* descending_urgency = "chooseTwo, chooseOne" *) on its own line directly above
rule chooseOne. The attribute is taught in the
attributes chapter; here it only states
which of the two rules wins.
Check
With the descending_urgency attribute listing chooseTwo before chooseOne, which warnings remain and what does the run print?
5.1 What the run shows and what the compiler reports
Put the three versions side by side. Each row was compiled and run with bsc 2026.01.
| Source | Warnings | Blocking rules: in mkTop.sched | Rules that fire in the phase-1 cycle | Run prints |
|---|---|---|---|---|
| original | G0010, G0021 (chooseTwo never fires) | chooseOne under chooseTwo | chooseOne, advance | winner=1 |
reads of winner removed | G0036, G0117 (shadowing) | (none) for both | chooseOne, chooseTwo, advance | winner=2 |
descending_urgency = "chooseTwo, chooseOne" | G0021 (chooseOne never fires) | chooseTwo under chooseOne | chooseTwo, advance | winner=2 |
The last two rows print the same line for opposite reasons. In one, both rules fire and
chooseOne's write is shadowed because chooseTwo comes later in the logical execution order;
in the other, chooseOne never fires at all. A simulation is one trace from one starting state:
it shows what happened, and a rule that did not fire simply leaves no line. Whether a rule can
fire, which rule wins a conflict, and in which order rules that share a cycle take effect are
facts about every cycle, and only the warnings and the schedule report state them.
Check
Two builds both print winner=2. In which one did chooseOne fire in the phase-1 cycle?
6. A firing is one atomic transition
A rule's body is atomic: either every state change in it happens, or none does. No other rule ever observes a rule halfway through.
Note. Atomicity is a safety property: no half-applied state is ever visible. It does not promise that a ready rule eventually fires (liveness), nor that ready rules take turns (fairness). Section 4 showed a ready rule that never fires, and the guards chapter showed a design in which no rule fires at all.
6.1 <= schedules the next state
<= on a register does not change it immediately; it schedules the value to store at the next
clock edge. Reading the register again in the same rule still gives the value from the start of
the cycle.
Form
register <= expression;rule step;
count <= count + 1;
$display("%0d", count); // prints the value before the update
endrule| When | What count reads as |
|---|---|
| Any read inside the rule | The value at the start of the cycle |
| The next cycle | count + 1 |
6.2 The sequential reference semantics
BSV's reference semantics is sequential: pick one enabled rule, perform its entire body, pick another, and repeat. Because it comes from Term Rewriting Systems, it is also called TRS semantics.
One step of this model is not the same as one clock cycle. It is a reasoning tool that treats each rule as an indivisible state transition, and it guarantees one property: no observation ever shows only part of a rule's updates. Inside one rule, every read sees the start-of-cycle state and every write lands together at the edge, so the body is a function from one state to the next, not a sequence of steps.
6.3 Example: swapping two registers in one rule
swap in AtomicUpdate.bsv writes left <= right; and then right <= left;. In software, the
second line would read the already-changed left, and both variables would end up equal. Here
both right-hand sides read the same entry state. As you read, note:
- The initial state:
left = 1,right = 9,swapped = False - The guard
!swappedonswapand its three assignments - The guard
swappedonshow
Predict before you run. Does right <= left read left as 1 or as 9? What two values does
show print?
Check
left starts at 1 and right at 9. Which value does right <= left read for left?
package AtomicUpdate;
(* synthesize *)
module mkTop(Empty);
Reg#(Bit#(4)) left <- mkReg(1);
Reg#(Bit#(4)) right <- mkReg(9);
Reg#(Bool) swapped <- mkReg(False);
rule swap (!swapped);
// Both right-hand sides read the same rule-entry state before
// either register changes.
left <= right;
right <= left;
// The two writes commit together as one atomic state transition.
swapped <= True;
endrule
rule show (swapped);
$display("left = %0d, right = %0d", left, right);
$finish(0);
endrule
endmodule
endpackagePractice this concept
Use this concept in a short BSV problem as your next step.
Check the result.
| Step | State |
|---|---|
| Start of cycle 0 | left = 1, right = 9, swapped = False |
Next values computed by swap | left' = 9, right' = 1, swapped' = True; all three right-hand sides read the cycle-start state |
| Start of cycle 1 | All three land together, swapped is true, and show becomes enabled |
A line reading left = 9, right = 1 means the swap happened. You never see the same value
twice, and you never see a state where only one register changed. Atomicity promises exactly
that: no observer sees half a firing.
Try it yourself.
- Swap the two assignment lines so
right <= left;comes first. The output is stillleft = 9, right = 1; assignment order does not change what is read. - Change the initial values to
mkReg(3)andmkReg(12). The output becomesleft = 12, right = 3. - Splitting the two assignments into two separate rules is not an equivalent edit. An intermediate state becomes observable, and an ordering question appears that the single rule never had.
7. Enabled is not the same as fired
Generated hardware may fire several rules in one cycle, but only if the set of rules it fires meets two conditions:
- The rules can be lined up as steps of the sequential reference semantics.
- The parallel circuit produces the same final state as that sequence.
A rule whose condition is true is enabled; only the rules the scheduler picks actually fire. If two enabled rules use resources that cannot be combined, the scheduler suppresses one. A rule fires at most once per cycle. Rules may share a cycle only if the result equals running them one at a time in some legal order; that requirement is what lets you reason about each rule on its own.
7.1 Writing one register twice in one rule
All statements in a rule happen in parallel, so writing two values to the same register at once has no meaning.
rule bad;
value <= 1;
value <= 2; // compile error
endruleThis gives compile error G0004, reporting that the rule uses the method value.write twice in
a way that conflicts in parallel. If you want one write or the other depending on a condition,
put them on exclusive if/else branches. A conflict between two rules (a warning, one rule
suppressed) and a conflict inside one rule (an error) are handled differently.
8. Example: two rules that read and write the same registers
In RuleConflict.bsv, chooseOne and chooseTwo test the same condition, phase == 0, and
write the same two registers, phase and value. Each rule reads phase in its condition and
writes phase in its body, which is the read/write cycle of section 4; the writes to value
on their own would only shadow. Compilation and simulation both succeed, but the compiler
prints warnings. Predict the warnings and the runtime behavior separately.
Predict before you run. How many warnings appear at compile time, and what does each one
tell you? Does show print value as 1 or 2?
Check
Will both chooseOne fired and chooseTwo fired appear in the output?
package RuleConflict;
(* synthesize *)
module mkTop(Empty);
Reg#(Bit#(2)) phase <- mkReg(0);
Reg#(Bit#(4)) value <- mkReg(0);
// Both rules read and write phase, so neither can precede the other.
// The compiler must choose one and report the conflict.
rule chooseOne (phase == 0);
value <= 1;
phase <= 1;
$display("chooseOne fired");
endrule
rule chooseTwo (phase == 0);
value <= 2;
phase <= 1;
$display("chooseTwo fired");
endrule
// Correct behavior cannot depend on which rule wins this arbitrary
// priority choice.
rule show (phase == 1);
$display("value = %0d", value);
$finish(0);
endrule
endmodule
endpackagePractice this concept
Use this concept in a short BSV problem as your next step.
Check the result. The warnings and output lines in the panel show:
| Output | What it tells you |
|---|---|
Warning G0010 | bsc arbitrarily treated one rule as more urgent than the other. It lists why neither can fire before the other: calls to phase.write versus phase.read |
Warning G0021 | According to the generated schedule, the losing rule can never fire |
The ... fired line | Only one appears; only the chosen rule ran |
value = ... | The value written by the chosen rule. It reflects this compilation's priority choice, not a design contract |
The conflict unfolds like this:
| Step | Analysis or transition |
|---|---|
| 1 | In cycle 0, phase == 0, so both conditions are true |
| 2 | Each rule requests next values for phase and value |
| 3 | Each rule's write of phase would have to come before the other's read of phase, and reads come before writes, so no legal order exists and the scheduler grants one rule |
| 4 | Both writes of the chosen rule land together at the edge; phase becomes 1 |
| 5 | In cycle 1 neither competing rule is enabled, and show runs |
The winner is deterministic for one compilation, but an unrelated source edit or a compiler change may flip it. The winner you see is evidence of the current schedule, nothing more.
Try it yourself.
- Change
chooseTwo's condition tophase == 2. The two conditions can no longer be true together, so both warnings disappear, and the output showschooseOne firedandvalue = 1. - Add a second line
value <= 2;insidechooseOne. Now you get theG0004compile error from section 7.1 instead of a warning: a conflict between two rules and a conflict inside one rule are handled differently.
9. Resolving a conflict warning
"One rule never fires" is the symptom; "two rules that can be enabled together each read what the other writes" is the cause. Fix the cause:
| Situation | Fix |
|---|---|
| State already identifies which operation is meant | Give the two rules mutually exclusive conditions |
| Priority is genuinely part of the specification | State it with a scheduling attribute, and check whether the lower-priority rule can starve |
| Both operations must happen, in a fixed order | Express the order as phase state: a register that records the current step, with exactly one rule enabled in each step |
Scheduling attributes are covered in the attributes chapter; the phase-register pattern is the subject of the state machines chapter.
10. Apply it: separate the rules, then swap
Three Practice problems follow. In the first, fill and drain carry the same guard and both
write accumulator, with fill also reading state that drain writes; bsc reports the
conflict pair, and drain never fires. Give drain a guard that is true only after collection
has stopped and the single transfer has not yet happened, so the two guards are never true
together. In the second, two rules fight over the same state, producing the scheduling warnings
and a rule that never runs; decide from the warning text which registers form the read/write
cycle before you edit. The graded problem is the swap: two self-assignments that must become a
swap using the entry-state values, with no temporary register and no second rule.
11. Common mistakes
11.1 Expecting to read the new value after <=
// Wrong expectation: this does not print the updated value
rule step;
count <= count + 1;
$display("now %0d", count); // prints the old value
endrule
// Right: compute the new value and use it
rule step;
let next = count + 1;
count <= next;
$display("now %0d", next);
endrule<= only schedules what to store at the next edge. If you need the new value inside the same
rule, compute it into a local variable and use that.
11.2 Writing one register twice in one rule
// Wrong: G0004 compile error
value <= 1;
value <= 2;
// Right: put the writes on exclusive branches
if (select) value <= 1;
else value <= 2;Statements in a rule run in parallel. The software rule that "the last assignment wins" does not apply.
11.3 Relying on source order for priority
// Wrong expectation: chooseOne, written first, always wins
rule chooseOne (go); winner <= 1; endrule
rule chooseTwo (go); winner <= 2; endrule
// Right: make the conditions mutually exclusive
rule chooseOne (go && preferOne); winner <= 1; endrule
rule chooseTwo (go && !preferOne); winner <= 2; endruleWhen two rules that conflict, each reading what the other writes, are ready together, only one
fires, and the language does not use source order to decide which. Two rules that merely write
the same register can both fire; the write of the rule later in the schedule wins, and bsc
warns about the shadowed one.
11.4 Ignoring a conflict warning
// Wrong: both rules can be enabled together, and each reads and writes phase
rule chooseOne (phase == 0); value <= 1; phase <= 1; endrule
rule chooseTwo (phase == 0); value <= 2; phase <= 1; endrule
// Right: state distinguishes the two operations
rule chooseOne (phase == 0 && mode == 0); value <= 1; phase <= 1; endrule
rule chooseTwo (phase == 0 && mode == 1); value <= 2; phase <= 1; endruleEven when simulation succeeds, G0010 and G0021 are telling you that one rule never fires.
Leaving them means your design depends on a priority bsc picked arbitrarily.
11.5 Using priority to impose order
When two operations must both happen in a fixed order, giving two conflicting rules a priority picks one of them every cycle, and the other may never run. If you need order, express it as phase state, as the state machines chapter shows.
12. Summary
| Situation | What happens |
|---|---|
| two ready rules, different registers | both fire; writes land together |
| two ready rules write one register, neither reads it | both fire; the later write shadows; warnings G0036, G0117 |
| each reads what the other writes | conflict; one is suppressed; warnings G0010, G0021 |
| two writes to one register inside one rule | compile error G0004 |
| inside one firing | reads see the entry state; writes land together; the body is one transition |
| atomicity | no half-applied firing is observable; not a promise of firing (liveness) or of taking turns (fairness) |
| enabled versus fired | only a subset of the enabled rules fires, one that has a legal one-at-a-time order |
Reg relations | read/read CF, read/write SB, write/write SBR |
| a conflict warning | fix the cause: exclusive conditions, a stated priority, or phase state |
-show-schedule report | Blocking rules: names the rules that suppress this one; Logical execution order: is the one-at-a-time order of rules that share a cycle; urgency and order are separate |
| a run versus the report | a run shows one trace; whether a rule can ever fire is stated only by the warnings and the report |
13. Check your understanding
-
Why can
updateLeft,updateRight, andadvanceall fire in the phase-0 cycle?Show answer
They write
left,right, andphaserespectively, so no two write the same register. The two update rules readphase, and those reads can be ordered beforeadvancewrites it, so a legal one-at-a-time order exists. -
Why does
left <= right; right <= left;swap the two registers?Show answer
Both right-hand sides read the cycle-start state, and both writes land together at the edge. The first write has no effect on the read in the second line.
-
If the simulation output looks right, why should you still fix a
G0021warning?Show answer
G0021says a rule can never fire under the generated schedule. The current result depends on an arbitrary priority, and a source or compiler change could select the other rule. -
Two rules both write
countand neither reads it. Is that a conflict?Show answer
No. An order exists, both rules fire, and the write of the rule later in the schedule is the one stored.
bscwarns that the other write is shadowed (G0036,G0117). A conflict needs a read/write cycle between the rules.
Decide the next two from the read and write sets alone; both were compiled and run with bsc
2026.01 to confirm the answers.
Check
Rule a reads x and writes y <= x + 10. Rule b reads y and writes z <= y. Both are enabled in the same cycle, starting from x = 1, y = 2, z = 3. What happens?
Check
Rule a reads x and writes y <= x. Rule b reads y and writes x <= y. Both are enabled in the same cycle. What does bsc report?
The next chapter gives rules something other than registers to call: methods of other modules, which bring conditions of their own.