Skip to content
code0.HDL

Variables, loops, and the context of a literal

A variable names a value rather than storing it, the declared type decides what an unsized literal means, and a loop with a constant bound unfolds into a fixed amount of hardware during elaboration.

In this chapter

  • Read a chain of = assignments as wires and operators evaluated in one cycle, not as storage
  • Predict what an unsized literal becomes from the declared type on its left, including a user-defined Literal instance
  • Separate the elaboration-time structure of a for loop from the runtime values its body tests
  • Give every variable a value on every path and instantiate modules with <- rather than =
Concepts in this chapter
  • Variables, loops, and the context of a literal
Prerequisite concepts

A BSV variable is a name for a value, not a box that keeps the value from one clock cycle to the next. Statements give those names their values, and a loop over such names describes how much hardware to build. Knowing which statement does which job is what keeps a design from silently losing state or doing work in the wrong cycle. This chapter covers the declarations, literal context, and loops of §9 "Variable declarations and statements" of the BSV Language Reference Guide. The register statements <- and <= are in Registers and one rule, if and case in Selection, case, and the don't-care value, and function definitions in Bool, Bit and pure functions.

Keep one question in mind for every statement in this chapter: does it name a value, construct hardware, or request a state change? The answer tells you when the statement takes effect.

1. Declaring variables

1.1 Declarations and initial values

A declaration gives one or more identifiers a type and, optionally, an initial value. Several names of the same type can share one declaration.

Syntax

type name;
type name = expression;
type name1 = expression1, name2 = expression2;
UInt#(8) count = 0;
Integer x = 16, y = 32;
Bool ready;
CodeMeaning
UInt#(8) count = 0;an 8-bit unsigned value named count, starting at 0
Integer x = 16, y = 32;two Integer values in one declaration
Bool ready;a name with a type but no value yet; a later = must give it one

Integer is an unbounded integer that exists only during elaboration. It is useful for loop indices and constants, but it never becomes a hardware signal.

1.2 Variables are values, not storage

A variable can be assigned more than once with =, but each assignment only gives the name a new value for the statements that follow. The compiler turns the chain of values into wires, operators, and multiplexers. Nothing is remembered when the clock ticks.

UInt#(8) total = a;
total = total + b;   // total now names a + b
total = total + c;   // total now names a + b + c: an adder chain

These three lines describe two adders in a row, evaluated in the same cycle. If you need a value that survives into the next cycle, use a state element such as Reg.

rule step;
  UInt#(8) acc = 0;
  acc = acc + 1;
  r <= acc;
endrule

Check

r is a register that starts at 0, and step fires every cycle. What does r hold after step has fired three times?

Your answer

Note. "Variable" in BSV means what a local let means in many functional languages. Treat each = as "from here on, this name means...", not as a memory write.

1.3 Arrays

An array declaration adds dimensions in brackets after the name. Each dimension must be a constant that elaboration can compute.

Syntax

type name[size];
type name[size] = { value0, value1, ... };
Int#(5) samples[3] = { 14, 12, 9 };
Int#(5) middle = samples[1];   // 12
PartMeaning
Int#(5)the element type
[3]three elements, indexed 0 to 2
{ 14, 12, 9 }the initial elements in index order

A BSV array groups values or module instances. It is not a memory with read and write ports; for that, use a primitive such as RegFile.

2. A declared type supplies expression context

2.1 The left side chooses the literal's meaning

An unsized integer literal such as 37 has no type of its own. The declaration it is assigned to supplies the type, and that type's Literal instance converts the number. How bsc finds that instance is the subject of Typeclasses and provisos; this section only uses the result.

Syntax

type name = integerLiteral;   // uses Literal#(type)
UInt#(8) count = 37;   // Literal#(UInt#(8)): the 8-bit value 37
Int#(16) delta = -4;   // Literal#(Int#(16)): the 16-bit value -4

For built-in types, the compiler also checks the literal's range during elaboration. UInt#(8) c = 300; stops with error T0051, Literal 300 is not a valid UInt#(8). No simulation runs.

Check

Which of these declarations stops with T0051?

Your answer

A sized literal such as 8'd37 already carries its width, so it is handled by the SizedLiteral typeclass instead of Literal.

2.2 Example: a literal with a domain meaning

A user-defined type can give a literal its own meaning. In OverloadedLiterals.bsv, a Percent stores each whole percentage multiplied by ten in a ten-bit field. Despite the name basisPoints and the printed unit, this scale represents tenths of a percentage point, not basis points. Read the struct first, then the two instance functions: fromInteger converts a source integer into the stored unit, and inLiteralRange answers whether a source integer belongs to the type's domain.

Predict before you run.

  1. Percent utilization = 37; calls which function, with which argument, and what is stored in basisPoints?
  2. What does Percent full = 100; store?
  3. What do inLiteralRange(probe, 100) and inLiteralRange(probe, 101) return, given that the ten-bit field could hold values up to 1023?

Check

Percent utilization = 37; — what does basisPoints hold afterwards?

Your answer

OverloadedLiterals.bsv
package OverloadedLiterals;

typedef struct {
  UInt#(10) basisPoints;
} Percent deriving (Bits, Eq);

instance Literal#(Percent);
  function Percent fromInteger(Integer value);
    // Store tenths of a percentage point; basisPoints is a legacy name.
    return Percent { basisPoints: fromInteger(value * 10) };
  endfunction

  // The semantic range is narrower than every value the ten-bit
  // field can represent.
  function Bool inLiteralRange(Percent target, Integer value);
    return value >= 0 && value <= 100;
  endfunction
endinstance

(* synthesize *)
module mkTop(Empty);
  rule show;
    // The declared Percent type selects this Literal instance for an
    // unsized integer literal.
    Percent utilization = 37;
    Percent full = 100;
    Percent probe = unpack(10'd0);
    $display("37%% = %0d basis points", utilization.basisPoints);
    $display("100%% = %0d basis points", full.basisPoints);
    $display("100 valid = %0d", inLiteralRange(probe, 100));
    $display("101 valid = %0d", inLiteralRange(probe, 101));
    $finish(0);
  endrule
endmodule

endpackage

Practice this concept

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

Check the result.

Output lineWhat it shows
37%the declaration selected Literal#(Percent), and fromInteger(37) stored 370
100%the same conversion stored 1000; the token 37 or 100 does not decide this, the declared type does
100 valid1: 100 is inside the domain the instance describes
101 valid0: 101 is outside it, even though the field has room for it

Try it yourself.

  1. Change Percent full = 100; to Percent full = 101;. Unlike UInt#(8), the build still succeeds, and the second line prints the stored value 1010. Current bsc does not call a user-defined inLiteralRange automatically; the function documents the domain and can be called explicitly, but it does not stop a bad literal by itself. Restore 100 afterwards.
  2. Change Percent utilization = 37; to UInt#(8) utilization = 37; in your head: the same token would now mean the plain value 37, because a different instance is selected.

3. Loops describe finite structure

3.1 for and while

for and while repeat a statement. During elaboration, bsc unfolds the loop into a fixed number of copies of the body. The loop therefore describes how much hardware to build, not a process that runs over several cycles.

Syntax

for (init; condition; update) statement
while (condition) statement
Bit#(4) reversed = 0;
for (Integer i = 0; i < 4; i = i + 1)
  reversed[i] = data[3 - i];
PartMeaning
Integer i = 0an elaboration-time index
i < 4a constant bound: four copies of the body
reversed[i] = data[3 - i];each copy wires one bit

A loop with an Integer index and a constant bound always unfolds. If the condition depends on a runtime value that elaboration cannot settle, unfolding never finishes. For example, while (c != n) c = c + 1; with n read from a register makes bsc stop with a stack-space overflow instead of producing hardware. Repetition that really spans several cycles belongs in registers and rules, or in the FSM notation of Finite state machines with StmtFSM.

3.2 Example: count the set bits

ControlFlowFunction.bsv counts the 1 bits of a byte with a loop and an if. Read the return type and the initial value of count first. Then separate the static index values 0 to 7 from the runtime bit value[i] tested at each index.

Predict before you run.

  1. For the input 8'b10110101, what is count after each test?
  2. Does the function take one cycle or eight?

Check

countOnes tests eight bits in a for loop. How many clock cycles does one call take, and what does it print for 8'b10110101?

Your answer

ControlFlowFunction.bsv
package ControlFlowFunction;

function UInt#(4) countOnes(Bit#(8) value);
  UInt#(4) count = 0;
  // The Integer loop variable and constant bound let elaboration
  // unfold all eight tests.
  for (Integer i = 0; i < 8; i = i + 1) begin
    if (value[i] == 1) begin
      // Variable assignment accumulates a combinational result; it
      // does not create state.
      count = count + 1;
    end
  end
  return count;
endfunction

(* synthesize *)
module mkTop(Empty);
  rule show;
    $display("ones in b5 = %0d", countOnes(8'b10110101));
    $finish(0);
  endrule
endmodule

endpackage

Practice this concept

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

Check the result. Elaboration unfolds eight tests. Starting from the least significant bit:

IndexSelected bitcount after the if
start—0
011
101
212
302
413
514
604
715

The printed count is 5. The eight count = count + 1 assignments form one combinational function, evaluated in the single cycle in which show fires. No loop-counter register is created. The bit tests and adders are synthesizable; $display and $finish are simulation-only.

Try it yourself.

  1. Change the bound from i < 8 to i < 4. Only bits 3 to 0, 0101, are tested, and the result is 2. The input did not change; the bound changed the hardware.
  2. Rewrite the loop as Integer i = 0; while (i < 8) begin ... i = i + 1; end with the same if inside. The result is still 5, because the condition is still settled during elaboration.

4. Common mistakes

4.1 Instantiating a module with =

// Wrong: binds the module constructor, not the register
Reg#(UInt#(8)) r = mkReg(0);

// Right: run the module and bind its interface
Reg#(UInt#(8)) r <- mkReg(0);

mkReg(0) is a module, so its value is a construction step that returns a Reg interface. Only <- runs that step. With =, the types do not line up and bsc reports type error T0080 at the use of mkReg. The three assignment forms =, <-, and <= are compared in Registers and one rule.

4.2 Leaving a path unassigned

// Wrong: result has no value when sel is False
UInt#(8) result;
if (sel) result = 5;
return result;

// Right: every path assigns
UInt#(8) result = 0;
if (sel) result = 5;
return result;

Check

The wrong version sits in a function that a rule calls. What does bsc report?

Your answer

The wrong version first gets warning P0042, Assignment to `result' missing from some paths. As soon as the function is used, elaboration needs a value on the path where sel is False and stops with error G0028, `result' uses uninitialized value. A function that is never called gets only the warning, so the mistake can sit unnoticed until the first caller appears. No latch is created to remember an older value. Initialize the variable before the if, or assign it in every branch.

5. Summary

TopicKey point
Variablea name for a value; reassigning with = builds logic, not storage
Arraya constant-size group of values or instances, not a memory
Declared typechooses the Literal instance; built-in types range-check literals with T0051
Unassigned pathwarning P0042, then error G0028 once the value is used; initialize before branching
Loopsunfolded during elaboration; runtime repetition needs registers and rules

6. Check your understanding

  1. Why can a function-local accumulator be assigned eight times without becoming an eight-cycle register?

    Show answer

    Each = only gives the name a new value for the following statements. Elaboration unfolds the loop into eight adders in a chain, which together form one combinational function evaluated in a single cycle.

  2. Which part of countOnes is fixed during elaboration, and which part depends on runtime input?

    Show answer

    The index values 0 to 7 and the number of tests are fixed during elaboration. The bit value[i] and therefore whether each adder adds 1 are runtime values.

  3. What happens when you write Percent full = 101;, and how does that differ from UInt#(8) c = 300;?

    Show answer

    Percent full = 101; compiles and stores 1010 in basisPoints, because bsc does not call a user-defined inLiteralRange automatically. UInt#(8) c = 300; stops during elaboration with T0051, because built-in types check their literal range.

The Practice problems of this chapter follow the two examples. Below the percent example, a Percent instance applies the wrong scaling factor and accepts values outside its domain. Below the bit-count example, you build a counting function that currently ignores its input.

Checkpoint

Run the example and compare it with your prediction

Sources

Licences and notices for the sources this site follows