1. States are an enum
A state machine is a register that holds "where we are" plus the logic that decides where to go next. In BSV the natural type for "where we are" is an enum: a new type whose only values are the labels you list.
Form
typedef enum { Idle, Count, Done } State deriving (Bits, Eq, FShow);
Reg#(State) state <- mkReg(Idle);deriving (Bits) gives the type a bit pattern, which is what lets it live in a register; Eq
gives ==; FShow lets fshow(state) print the label's name. Without explicit values the
first label is 0 and each next one adds 1, in the fewest bits that hold the largest code. You
compare states by label, never by number: state == Count, not state == 1.
The encoding, the comparison and the width of an enum value are the subject of the enum section of user-defined types, with a runnable example. This chapter uses enums as state labels and takes those rules as read.
Check
Stage lists Fetch, Decode, Execute, with no explicit values. What encoding does pack(Decode) have?
2. One rule per state
Write the machine as one rule for each state. The guard names the state with ==; the body
does that state's work and writes the next state into the register. Because a register holds
exactly one label, guards on different labels are never true together, so the rules never
compete and never conflict.
rule start (state == Idle);
state <= Count;
endrule
rule count (state == Count && n < 3);
n <= n + 1;
endrule
rule leave (state == Count && n == 3);
state <= Done;
endruleRead state as the current state and state <= … as the next state. A transition written in
cycle k is the state the guards see in cycle k + 1. A state that needs more than one rule,
like Count above, splits its condition so the two rules are still exclusive.
Check
Two rules are guarded by state == Idle and state == Count. Can both fire in one cycle?
3. From a state table to rules
Design the machine as a table first, then write one rule per row.
| Current state | Condition | Action | Next state |
|---|---|---|---|
Idle | always | none | Count |
Count | n < 3 | n <= n + 1 | Count |
Count | n == 3 | none | Done |
Done | always | print, stop | Done |
The same table can become a single rule with a case (state) whose arms hold the conditions
and writes. Both forms describe the same hardware; per-state rules keep each state's work
local, the single rule keeps every transition in one place.
4. The phase register: ordering what one rule cannot do
A state machine is not only for protocols with named modes. It is also the answer when two operations must both happen but cannot share one atomic transition, because a firing is one transition in which every read sees the entry state. Keep a register that records the current step, and make exactly one rule enabled in each step.
Form
typedef enum { Step1, Step2, ... } Phase deriving (Bits, Eq);
Reg#(Phase) phase <- mkReg(Step1);
rule firstWork (phase == Step1);
...
phase <= Step2;
endruletypedef enum { Load, Shift, Done } Step deriving (Bits, Eq);
Reg#(Step) step <- mkReg(Load);
rule load (step == Load);
data <= input_value;
step <= Shift;
endrule
rule shift (step == Shift);
data <= data << 1;
step <= Done;
endruleThe conditions of load and shift can never be true together, so the scheduler never has to
choose between them.
Note. Phase state costs a register and a cycle of latency. In exchange you get an observable busy interval, an unambiguous order, and an easy place to add steps to the protocol. Giving two conflicting rules a priority does not solve an ordering problem: priority chooses one operation per cycle; it does not guarantee that both eventually happen in the required order.
5. Ordering two transitions
OrderedRuleTransitions.bsv starts with value = 10, increment = 3, and phase = AddReady.
addFirst is enabled only in AddReady, doubleSecond only in DoubleReady, and show only
in Idle.
Predict before you run. Work out value and phase after each edge.
package OrderedRuleTransitions;
typedef enum { Idle, AddReady, DoubleReady } Phase
deriving (Bits, Eq, FShow);
(* synthesize *)
module mkTop(Empty);
Reg#(Bit#(8)) value <- mkReg(10);
Reg#(Bit#(8)) increment <- mkReg(3);
Reg#(Phase) phase <- mkReg(AddReady);
// Each phase enables exactly one rule, so scheduling does not need
// an arbitrary priority.
rule addFirst (phase == AddReady);
value <= value + increment;
phase <= DoubleReady;
endrule
// DoubleReady is an observable intermediate state between the two
// atomic transitions.
rule doubleSecond (phase == DoubleReady);
value <= value << 1;
phase <= Idle;
endrule
rule show (phase == Idle);
$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.
| Cycle | phase at start | Rule that fires | value after edge | phase after edge |
|---|---|---|---|---|
| 0 | AddReady | addFirst | 10 + 3 = 13 | DoubleReady |
| 1 | DoubleReady | doubleSecond | 13 × 2 = 26 | Idle |
| 2 | Idle | show | prints 26 |
A printed 26 means the addition happened before the doubling; the reverse order would give 10 × 2 + 3 = 23. The intermediate 13 is not a temporary expression inside one rule but real state held throughout cycle 1. This example compiles without warnings.
Try it yourself.
- Set the initial
valueto 250 andincrementto 10. The 8-bit sum 250 + 10 = 260 wraps past 256, so the intermediate value is 4 and the output is 8. This checks ordering and fixed-width arithmetic together. - Merge both steps into one rule as
value <= (value + increment) << 1;. One cycle and theDoubleReadystep disappear. If nothing outside needs to see the intermediate step, this is the simpler design.
6. Predict a two-state machine
A simple machine has two states and one input. A toggle holds q, 0 or 1, and an input
t: when t is 1 the next state is the other one, when t is 0 the machine holds. As rules
that is one rule guarded by t writing q <= ~q; a false guard is the hold.
Fill in the table. Cycle 0 starts from the reset state q = 0. The t column is the input the
rule sees during that cycle, and the column you fill is q after the edge that ends it.
Predict, then compare · toggle
Fill in each cell before comparing. Empty cells count as a mismatch.
| Cycle | tinputs sampled at the edge | qafter the edge |
|---|---|---|
| 0 | 1 | |
| 1 | 1 | |
| 2 | 0 | |
| 3 | 1 | |
| 4 | 0 | |
| 5 | 0 |
This is a browser model of the state machine, not a compiled design; the example above is the real toolchain.
7. A later option: sequential statements
Some processes read better as a plan than as a table: do this, then that. The StmtFSM library
takes a seq … endseq of actions and compiles it into the state register and rules you would
otherwise write by hand, one step per action, with the clock edge between steps. A seq that
writes 3 to a register and then adds 4 ends at 7, because the second step runs in a later cycle
and reads what the first committed; one rule holding both writes would read the entry value for
both and could not express the order at all.
Check
A seq block writes 3 to value and then writes value + 4. value starts at 0. What does it hold when the block ends?
Nothing in this course requires StmtFSM; the
finite state machines chapter covers it when you
want it, including start, done and the started flag its testbench needs.
8. Apply it: replace a conflict with two transitions
The Practice problem below the example starts from two rules enabled in AddReady that both
read and write the same registers, which is the conflict of
rules that share state. Your job is to turn it into
the machine of section 4 using the supplied Phase enum. Inspect the reset state and the
transition made by start, then correct the next phase after addition and the guard on
doubling. Check that the printed result could only come from the required order.
Check
A design prints 26 from value = 10 and increment = 3. Which order of operations does that rule out?
9. Summary
| Element | Meaning |
|---|---|
typedef enum { … } T deriving (Bits, Eq, FShow); | a type whose values are the labels |
Reg#(T) state <- mkReg(First); | the current state; its reset value is the initial state |
rule r (state == Label); … state <= Next; endrule | one rule per state; exclusive guards |
| state table | current state, condition, action, next state, one row per rule |
case (state) in one rule | the same machine with every transition in one place |
| phase register | the way to order two operations that cannot share one atomic transition |
StmtFSM | a later option: steps written in order, compiled into rules |
10. Check your understanding
-
Why do per-state rules never produce a conflict warning?
Show answer
Their guards compare one register against different labels. At the start of a cycle that register holds exactly one label, so at most one guard is true and the rules can never want the same cycle.
-
Two rules must both run, in a fixed order, but each needs to see what the other wrote. Why does giving one of them priority not solve it?
Show answer
Priority decides which rule fires when both want the same cycle; it does not sequence them. The fix is a phase register, which makes exactly one of them enabled per cycle and writes the next phase as part of the same atomic transition.
-
In the ordered-transitions example, what is held in
valueduring cycle 1, and what would be lost by merging the two rules?Show answer
valueholds the intermediate 13 for the whole of cycle 1: it is real state, observable from outside. Merging the rules intovalue <= (value + increment) << 1;removes that observable step and the cycle it takes, which is the right trade only when nothing outside needs to see it.
Check
Rule ra (state == A) writes count <= count + 1 and state <= B; rule rb (state == B) writes count <= count + 5 and state <= C. Both read and write count and state. What does bsc 2026.01 report?