Skip to content
code0.HDL

Registers and one rule

Declare a register with mkReg, write it from one rule with <=, and see that a read inside the rule gives the start-of-cycle value while the written value appears in the next cycle.

In this chapter

  • Declare a register with mkReg or mkRegU and tell instantiation (<-), binding (=) and a register write (<=) apart
  • Predict that every read inside a rule sees the start-of-cycle value and that writes land together at the edge
  • Trace two rules cycle by cycle from their explicit conditions
  • Replace part of a register with a selected write and explain why it still writes the whole register
Concepts in this chapter
  • Reg, rule, current and next state
Prerequisite concepts

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
PartMeaning
Reg#(Bit#(4))The interface type: a register holding a 4-bit value
countThe 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 unspecified
Reg#(UInt#(4)) count <- mkReg(0);

rule tick;
  count <= count + 1;   // read the old value, write the new one
endrule
CodeMeaning
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 expressioncalls 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
FormWhat it doesWhen it takes effect
value = expression;gives a name a valueimmediately, for the following statements; no state changes
result <- context;instantiates a module, or executes an ActionValue and binds its resultduring module construction, or when the action fires
register <= expression;schedules a write to stateat 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 write

Caution. 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 one

A <= 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
endrule

If 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;
endrule
rule step (count < 5);
  count <= count + 1;
  $display("count = %0d", count);
endrule

rule always_on;          // no condition means (True)
  $display("tick");
endrule
PartMeaning
stepThe 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.

MomentWhat happens
Start of the cycleEvery 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 cycleThe scheduler picks a set of ready rules that can fire together and computes next values
Clock edgeThe 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:

  • count is a 4-bit register starting at 0; done is a Boolean starting at False.
  • step fires while count < 5. It writes count + 1 and then displays count.
  • finish fires when count == 5 && !done. It sets done, displays count and ends the simulation. The two conditions are never true in the same cycle, so in every cycle at most one of them fires.
Intro.bsv
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

endpackage

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

Your answer

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 startRule that firesValue shownState after the edge
0step0count is 1
1step1count is 2
2step2count is 3
3step3count is 4
4step4count is 5
5finish5done 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?

Your answer

ChangeExpected result
Swap the write and the $display inside stepSame output: both are part of one atomic transition, and the write lands at the edge
Change both 5s to 3Three count = lines (0, 1, 2), and done at count = shows 3
Change only step's condition to count < 3count 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 register
Bit#(16) snapshot = word;     // read
word <= snapshot + 1;         // write the whole register
word[7:0] <= 8'h34;           // replace only the low byte

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

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

endpackage

Practice this concept

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

Observation pointwordupdatedWhat happens
cycle 0, rule entry16'hABCDFalseonly updateLowerByte is enabled
cycle 0, actionold valueold valuewrites of 16'hAB34 and True are scheduled
first clock edge16'hAB34Trueboth writes commit together
cycle 1, rule entry16'hAB34Trueshow 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.

  1. Replace word[7:0] <= 8'h34; with word <= zeroExtend(8'h34);. The program still compiles, but show prints 0034: the upper byte is no longer copied.
  2. Replace it instead with word <= { word[15:8], 8'h34 };. The output is ab34 again, 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);
endrule

A 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

ElementMeaning
Reg#(t) r <- mkReg(init);a register holding a t; init after reset; mkRegU leaves the reset value unspecified
r in an expressionthe 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
letinfers the type from the initializer; an undetermined type is error T0033
rule name (cond); … endrulemay fire when cond is true and readiness and scheduling allow; its writes land together; no written condition means True
one cycleconditions 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 ruleprints start-of-cycle values

10. Check your understanding

  1. In Intro.bsv, step schedules count <= count + 1 and then displays count. Which value is displayed, and why is the first value shown 0?

    Show answer

    The value count had when the rule started. A <= write is stored at the clock edge, so the $display in 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.

  2. What does show print if the selected write becomes word <= 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.

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