1. A register is a module instance that remembers
Everything so far was combinational: a function's result followed its arguments within one
cycle and nothing was kept. State in BSV comes from instantiating a module that stores a value,
and the basic one is the register. You create one by instantiating the mkReg module and use
it through the Reg#(t) interface.
1.1 Declaring a register
Form
Reg#(valueType) name <- mkReg(initialValue);Reg#(Bit#(4)) count <- mkReg(0); // four bits, reset to 0
Reg#(Bool) done <- mkReg(False); // a flag, reset to False| Part | Meaning |
|---|---|
Reg#(Bit#(4)) | The interface type: a register holding a 4-bit value |
count | The instance name, which must start with a lowercase letter |
<- | Instantiate the module on the right and bind its interface to the name |
mkReg(0) | A register module whose reset value is 0 |
<- builds the instance, once, while the hardware structure is being made. This course
assumes one clock, a positive-edge design and a reset that puts every mkReg at its initial
value before cycle 0. Using count in an expression reads the register. The value read is the
one the register holds at the start of the current cycle: the current value from the
fundamentals, Q.
Note. <- instantiates; <= writes a register. They look alike but mean very different
things. Instantiation happens once, while the hardware structure is being built. A register
write happens every time its rule fires.
1.2 mkReg and mkRegU
mkReg provides a register with a reset value; mkRegU provides one without reset logic.
Form
Reg#(type) name <- mkReg(resetValue); // defined value after reset
Reg#(type) name <- mkRegU; // value after reset is unspecifiedReg#(UInt#(4)) count <- mkReg(0);
rule tick;
count <= count + 1; // read the old value, write the new one
endrule| Code | Meaning |
|---|---|
Reg#(UInt#(4)) | an interface for a register that stores a UInt#(4) |
<- mkReg(0) | instantiates the register with reset value 0 |
count in an expression | calls the register's _read method |
count <= ... | calls its _write method |
The stored type needs a Bits instance, because a register ultimately stores a fixed number of
bits.
Note. Use mkRegU only when other logic is guaranteed to write the register before anything
reads it. If the value after reset is part of the design's behaviour, use mkReg.
2. Three statements that look like assignment
2.1 Bind, instantiate, write
BSV has three statements that look like assignments. They mean different things and take effect at different times.
Form
name = expression; // bind a value
name <- moduleOrAction; // bind the result of a module or ActionValue
register <= expression; // request a register write| Form | What it does | When it takes effect |
|---|---|---|
value = expression; | gives a name a value | immediately, for the following statements; no state changes |
result <- context; | instantiates a module, or executes an ActionValue and binds its result | during module construction, or when the action fires |
register <= expression; | schedules a write to state | at the clock edge after the rule fires; every cycle the enclosing rule fires |
Reg#(UInt#(8)) counter <- mkReg(0); // <- : instantiate a module
UInt#(8) next = counter + 1; // = : name a value
counter <= next; // <= : schedule the register writeCaution. count = count + 1; is not a register write. count is an interface, not a local
variable, and bsc stops with P0039, suggesting <= in its message.
2.2 The write operator sets the next value; reads see the start of the cycle
Using a register's name in an expression reads its current value. Writing uses <=.
Form
registerName <= expression;count <= count + 1; // the next value is the current value plus oneA <= write does not take effect immediately. count <= count + 1; asks the register to store
count + 1 at the clock edge that ends the current cycle, and the new value is readable from
the next cycle on. Every read of count in the same rule, before or after that line, still
gives the start-of-cycle value. The right-hand side is the next value, D in the fundamentals,
and <= is the wire into the register's input.
rule step;
r <= 5;
$display("%0d", r); // still prints the old value of r
endruleIf r started at 1, this rule prints 1, and the next cycle reads 5. This is not sequential
software mutation: all writes of one rule commit together.
_read and _write are ordinary methods with the schedule _read before _write, which is
what gives mkReg its behaviour; other state elements declare other schedules. In tick above,
count + 1 always uses the old count, no matter how many statements come before or after the
write.
2.3 Type inference with let
let declares a name and lets the compiler infer its type from the initializer.
Form
let name = expression;
let name <- moduleOrAction;let encoded = pack(message); // type follows from message
let hi = word[15:8]; // Bit#(8)Use let when the initializer already fixes one type. Write the type out when it documents a
hardware width or when the expression needs it. An expression whose type nothing determines
remains an error: let w = unpack(r); followed only by pack(w) leaves the type of w open,
and bsc reports T0033, "There is not enough explicit type information to deduce the types
of all expressions."
3. A rule says when the write happens
3.1 Rule declarations and explicit conditions
A rule has a name, a condition, and a body of actions. The Boolean expression in parentheses after the name is the rule's explicit condition, often called its guard. In every cycle where the condition is true, the rule may fire; when it fires, all the writes in its body are stored together at the edge, as one state transition. A register that no firing rule writes in a cycle holds its value.
Form
rule name (condition);
actions;
endrulerule step (count < 5);
count <= count + 1;
$display("count = %0d", count);
endrule
rule always_on; // no condition means (True)
$display("tick");
endrule| Part | Meaning |
|---|---|
step | The rule name, starting with a lowercase letter |
(count < 5) | The explicit condition; the rule may fire only in cycles where it is true |
count <= count + 1; | The state change performed when the rule fires |
In a design whose only state is mkReg registers, the condition alone decides whether the rule
is ready: a register's read and write are always ready. Conditions that come from called
methods are added in Guards: hold and stop.
3.2 What happens in one cycle
The scheduler that bsc generates follows the same pattern every cycle.
| Moment | What happens |
|---|---|
| Start of the cycle | Every condition and expression reads the current Reg values (wires, mkCReg ports and bypass FIFOs can see same-cycle writes; see FIFOs, split, and the library primitives) |
| During the cycle | The scheduler picks a set of ready rules that can fire together and computes next values |
| Clock edge | The writes of the selected rules are stored |
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; what happens when two rules touch the same state is the subject of Rules that share state.
4. Predict two rules, then run
Intro.bsv has two registers and two rules. While reading, look for three things:
countis a 4-bit register starting at 0;doneis a Boolean starting atFalse.stepfires whilecount < 5. It writescount + 1and then displayscount.finishfires whencount == 5 && !done. It setsdone, displayscountand ends the simulation. The two conditions are never true in the same cycle, so in every cycle at most one of them fires.
package Intro;
// A register holding a 4-bit value, starting at 0.
// Reg is an interface; mkReg is the module that provides one.
(* synthesize *)
module mkTop(Empty);
Reg#(Bit#(4)) count <- mkReg(0);
Reg#(Bool) done <- mkReg(False);
// Rules may fire when their explicit and implicit conditions hold and scheduling permits.
rule step (count < 5);
count <= count + 1;
$display("count = %0d", count);
endrule
// Two rules whose conditions overlap on no cycle: count reaches 5
// exactly once, and only then does this one become ready.
rule finish (count == 5 && !done);
done <= True;
$display("done at count = %0d", count);
$finish(0);
endrule
endmodule
endpackagePractice this concept
Use this concept in a short BSV problem as your next step.
Trace it on paper before running: a column for count at the start of each cycle, a column for
the rule that fires, a column for the value displayed.
Check
step writes count + 1 and then displays count. What is the first number displayed?
Check
How many count = lines are printed?
Check
What number does the done at count = line show?
Press Run and compare the lines with your trace.
count at cycle start | Rule that fires | Value shown | State after the edge |
|---|---|---|---|
| 0 | step | 0 | count is 1 |
| 1 | step | 1 | count is 2 |
| 2 | step | 2 | count is 3 |
| 3 | step | 3 | count is 4 |
| 4 | step | 4 | count is 5 |
| 5 | finish | 5 | done is True; simulation ends |
The count = lines run from 0 to 4. step displays after it requests a write, but that write
is stored at the edge, so the display shows the old value. Values 1 to 4 are printed by step
in the cycles after they were stored. The written value 5 is not printed by step, because
its guard is then false. Instead, finish reads 5 in the cycle after step's fifth write
was stored and prints it on the done at count = line.
5. Change one thing
In the lab, swap the two lines inside step so that $display comes first and the write
second. Decide what changes before you run.
Check
With the display above the write, what does the output look like?
| Change | Expected result |
|---|---|
Swap the write and the $display inside step | Same output: both are part of one atomic transition, and the write lands at the edge |
| Change both 5s to 3 | Three count = lines (0, 1, 2), and done at count = shows 3 |
Change only step's condition to count < 3 | count stops at 3 and finish never becomes ready; Guards: hold and stop studies exactly this |
6. A selected write replaces part of a register
6.1 A selected write is a read-modify-write
A register used where a value is expected is read implicitly. A <= writes it, and the left
side may name part of the register.
Form
type name = register; // read
register <= expression; // whole-register write
register[hi:lo] <= value; // selected write
register.field <= value; // field write, for a struct registerBit#(16) snapshot = word; // read
word <= snapshot + 1; // write the whole register
word[7:0] <= 8'h34; // replace only the low byteA selected write is a read-modify-write of the whole register. The unselected bits are copied
from the current value, and the complete register is written at the clock edge.
word[7:0] <= 8'h34; means the same as word <= { word[15:8], 8'h34 };.
Caution. Because the primitive operation writes the whole register, two rules that write different slices of the same register still conflict with each other. Selection does not split the register into independently owned parts.
6.2 Example: replace the low byte
RegisterSelection.bsv starts word at 16'hABCD and updated at False. Read both initial
values and the guards of updateLowerByte and show. Predict before you run: which rule can
fire in the first cycle, and can show ever see a word that is only half updated?
Check
What hexadecimal value does show print?
package RegisterSelection;
(* synthesize *)
module mkTop(Empty);
Reg#(Bit#(16)) word <- mkReg(16'hABCD);
Reg#(Bool) updated <- mkReg(False);
rule updateLowerByte (!updated);
// A slice write preserves bits outside the selected range.
word[7:0] <= 8'h34;
// The update is still one write to the complete register at the
// clock edge.
updated <= True;
endrule
rule show (updated);
$display("word = %h", word);
$finish(0);
endrule
endmodule
endpackagePractice this concept
Use this concept in a short BSV problem as your next step.
| Observation point | word | updated | What happens |
|---|---|---|---|
| cycle 0, rule entry | 16'hABCD | False | only updateLowerByte is enabled |
| cycle 0, action | old value | old value | writes of 16'hAB34 and True are scheduled |
| first clock edge | 16'hAB34 | True | both writes commit together |
| cycle 1, rule entry | 16'hAB34 | True | show prints ab34 |
The upper byte ab survived because the selected write copied it. show can never see a
half-updated word: both writes of the first rule commit at the same edge.
Try it yourself.
- Replace
word[7:0] <= 8'h34;withword <= zeroExtend(8'h34);. The program still compiles, butshowprints0034: the upper byte is no longer copied. - Replace it instead with
word <= { word[15:8], 8'h34 };. The output isab34again, because this is exactly the read-modify-write that the selected form writes more briefly.
7. Apply it: make the counter move, then keep the upper byte
The first graded problem below is a counter whose rule writes count <= count;. The rule is
ready and fires every cycle, but the next value equals the current value, so the testbench
prints 0 on every line. Change that one assignment so each firing schedules the value after the
current one. The testbench's own rule prints value and counting once per cycle, reading the
start-of-cycle state, so the expected trace is 0, 1, 2, 3, 4, 5 and then 5 held. Predict the line
on which counting turns false before you run.
The second problem is the mistake of section 6 in a module: patchLow must replace only the low
byte of a stored 16-bit word, but it writes wordReg <= zeroExtend(replacement);, so the loaded
upper byte ab is lost and the run prints 0034 instead of ab34. Make the destination of the
existing write more specific with a selection on bits 7 to 0; no temporary register or
concatenation is needed.
8. Common mistakes
8.1 Writing a register with =
// Wrong: = cannot change a register
rule step;
count = count + 1;
endrule
// Right: schedule a register write
rule step;
count <= count + 1;
endrule= binds a local name, and count is not a local name inside a rule. bsc reports P0039,
Assignment of `count' without declaration, or non-local assignment in possibly wrong context,
and the message itself suggests perhaps you meant "<=" instead.
8.2 Using <= on a local variable
// Wrong: x is a plain value, not a register
UInt#(8) x = 0;
x <= 5;
// Right: rebind the value
UInt#(8) x = 0;
x = 5;<= needs a register or another interface with a write method. For a plain UInt#(8), bsc
reports T0066, "The left-hand side of an assignment must be a register or interface type."
8.3 Expecting a write to be visible in the same rule
// Wrong expectation: this displays the old value, not the new one
rule step;
count <= count + 1;
$display("%0d", count);
endrule
// Right: compute the new value once and use it
rule step;
let next = count + 1;
count <= next;
$display("%0d", next);
endruleA written value is stored at the clock edge, and register reads in a rule always see the entry
state. If you need the new value inside the same rule, give it a local name with = or let
and use that name.
8.4 Reading an mkRegU register before writing it
// Wrong: the first cycles read an unspecified value
Reg#(UInt#(8)) total <- mkRegU;
rule accumulate;
total <= total + 1;
endrule
// Right: the starting value is part of the behaviour
Reg#(UInt#(8)) total <- mkReg(0);mkRegU saves reset logic only when the value is always written before it is read. A running
total reads itself in the very first cycle.
9. Summary
| Element | Meaning |
|---|---|
Reg#(t) r <- mkReg(init); | a register holding a t; init after reset; mkRegU leaves the reset value unspecified |
r in an expression | the current value, as held at the start of the cycle; the _read method |
r <= expr; | the next value, stored at the edge that ends the cycle; the _write method |
=, <-, <= | bind a value; bind a module or ActionValue result; schedule a register write |
let | infers the type from the initializer; an undetermined type is error T0033 |
rule name (cond); … endrule | may fire when cond is true and readiness and scheduling allow; its writes land together; no written condition means True |
| one cycle | conditions read start-of-cycle state, the scheduler picks ready rules, the edge stores their writes |
r[hi:lo] <= v; | a selected write copies the unselected bits and still writes the whole register |
$display inside a rule | prints start-of-cycle values |
10. Check your understanding
-
In
Intro.bsv,stepschedulescount <= count + 1and then displayscount. Which value is displayed, and why is the first value shown 0?Show answer
The value
counthad when the rule started. A<=write is stored at the clock edge, so the$displayin the same cycle reads the value stored at the start of the cycle, which is 0 in the first firing; only the next cycle reads the incremented value. -
What does
showprint if the selected write becomesword <= zeroExtend(8'h34);, and why does it still compile?Show answer
It prints
0034. The types are correct, so nothing fails to compile; only the next state is wrong, because the upper byte is no longer copied from the current value.