Skip to content
code0.HDL

Rules that share state: atomicity, shadowing and conflicts

Put two rules in the same cycle: both fire when they touch different state, a second write to one register shadows the first with a warning, and a genuine conflict needs a read/write cycle; every firing is one atomic transition.

In this chapter

  • Predict which of several ready rules fire in one cycle from the registers they read and write
  • Tell a shadowed write (both rules fire, warnings G0036 and G0117) from a conflict (one rule is suppressed, warnings G0010 and G0021)
  • Trace a rule body as one atomic transition in which every read sees the entry state
  • Read the conflict warnings and choose the fix: exclusive conditions, a stated priority, or phase state
  • Read a -show-schedule report and tell what the compiler decided (urgency, blocking, execution order) from what one simulation run happened to show
Concepts in this chapter
  • Independent rules and shared state
Prerequisite concepts

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 pairRelationMeaning within one cycle
read and readCFNo interaction
read and writeSBThe read is ordered before the write, so a read always sees the start-of-cycle value
write and writeSBRTwo 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.

phaseReady rulesWhat they touch
0updateLeft, updateRight, advanceleft, right and phase, three different registers
1chooseOne, chooseTwo, advanceboth choice rules read and write winner
2reportprints and stops

advance increments phase every cycle while it is below 2, and report prints the results in phase 2.

OverlapScheduling.bsv
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

endpackage

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

Your answer

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 startRules that fire togetherState after the edge
phase is 0updateLeft, updateRight, advanceleft is 1, right is 2, phase is 1
phase is 1one of chooseOne/chooseTwo, plus advancewinner is 1 or 2, phase is 2
phase is 2reportresults printed, simulation ends
ObservationMeaning
compatible=1,2Both rules writing different registers fired in the same cycle (section 2)
a single winner= valueOnly one of the two conflicting writes took effect (this section)
compile warnings G0010 and G0021One 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.

WhereText in this buildWhat it tells you
first line of G0010Rule "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.readthe read/write cycle of this section, in both directions
G0021According 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 reportBlocking rules: chooseOnechooseTwo is suppressed in every cycle in which chooseOne is enabled
every other rule in the reportBlocking 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, reportthe 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?

Your answer

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?

Your answer

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.

SourceWarningsBlocking rules: in mkTop.schedRules that fire in the phase-1 cycleRun prints
originalG0010, G0021 (chooseTwo never fires)chooseOne under chooseTwochooseOne, advancewinner=1
reads of winner removedG0036, G0117 (shadowing)(none) for bothchooseOne, chooseTwo, advancewinner=2
descending_urgency = "chooseTwo, chooseOne"G0021 (chooseOne never fires)chooseTwo under chooseOnechooseTwo, advancewinner=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?

Your answer

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
WhenWhat count reads as
Any read inside the ruleThe value at the start of the cycle
The next cyclecount + 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 !swapped on swap and its three assignments
  • The guard swapped on show

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?

Your answer

AtomicUpdate.bsv
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

endpackage

Practice this concept

Use this concept in a short BSV problem as your next step.

Check the result.

StepState
Start of cycle 0left = 1, right = 9, swapped = False
Next values computed by swapleft' = 9, right' = 1, swapped' = True; all three right-hand sides read the cycle-start state
Start of cycle 1All 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.

  1. Swap the two assignment lines so right <= left; comes first. The output is still left = 9, right = 1; assignment order does not change what is read.
  2. Change the initial values to mkReg(3) and mkReg(12). The output becomes left = 12, right = 3.
  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
endrule

This 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?

Your answer

RuleConflict.bsv
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

endpackage

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

OutputWhat it tells you
Warning G0010bsc 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 G0021According to the generated schedule, the losing rule can never fire
The ... fired lineOnly 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:

StepAnalysis or transition
1In cycle 0, phase == 0, so both conditions are true
2Each rule requests next values for phase and value
3Each 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
4Both writes of the chosen rule land together at the edge; phase becomes 1
5In 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.

  1. Change chooseTwo's condition to phase == 2. The two conditions can no longer be true together, so both warnings disappear, and the output shows chooseOne fired and value = 1.
  2. Add a second line value <= 2; inside chooseOne. Now you get the G0004 compile 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:

SituationFix
State already identifies which operation is meantGive the two rules mutually exclusive conditions
Priority is genuinely part of the specificationState it with a scheduling attribute, and check whether the lower-priority rule can starve
Both operations must happen, in a fixed orderExpress 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;  endrule

When 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; endrule

Even 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

SituationWhat happens
two ready rules, different registersboth fire; writes land together
two ready rules write one register, neither reads itboth fire; the later write shadows; warnings G0036, G0117
each reads what the other writesconflict; one is suppressed; warnings G0010, G0021
two writes to one register inside one rulecompile error G0004
inside one firingreads see the entry state; writes land together; the body is one transition
atomicityno half-applied firing is observable; not a promise of firing (liveness) or of taking turns (fairness)
enabled versus firedonly a subset of the enabled rules fires, one that has a legal one-at-a-time order
Reg relationsread/read CF, read/write SB, write/write SBR
a conflict warningfix the cause: exclusive conditions, a stated priority, or phase state
-show-schedule reportBlocking 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 reporta run shows one trace; whether a rule can ever fire is stated only by the warnings and the report

13. Check your understanding

  1. Why can updateLeft, updateRight, and advance all fire in the phase-0 cycle?

    Show answer

    They write left, right, and phase respectively, so no two write the same register. The two update rules read phase, and those reads can be ordered before advance writes it, so a legal one-at-a-time order exists.

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

  3. If the simulation output looks right, why should you still fix a G0021 warning?

    Show answer

    G0021 says 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.

  4. Two rules both write count and 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. bsc warns 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?

Your answer

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?

Your answer

The next chapter gives rules something other than registers to call: methods of other modules, which bring conditions of their own.

Checkpoint

Submit the Practice problem for grading

Grading runs on the server against hidden tests; the result is recorded in this browser only.

Sources

Licences and notices for the sources this site follows