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;| Code | Meaning |
|---|---|
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 chainThese 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;
endruleCheck
r is a register that starts at 0, and step fires every cycle. What does r hold after step has fired three times?
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| Part | Meaning |
|---|---|
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 -4For 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?
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.
Percent utilization = 37;calls which function, with which argument, and what is stored inbasisPoints?- What does
Percent full = 100;store? - What do
inLiteralRange(probe, 100)andinLiteralRange(probe, 101)return, given that the ten-bit field could hold values up to 1023?
Check
Percent utilization = 37; — what does basisPoints hold afterwards?
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
endpackagePractice this concept
Use this concept in a short BSV problem as your next step.
Check the result.
| Output line | What 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 valid | 1: 100 is inside the domain the instance describes |
101 valid | 0: 101 is outside it, even though the field has room for it |
Try it yourself.
- Change
Percent full = 100;toPercent full = 101;. UnlikeUInt#(8), the build still succeeds, and the second line prints the stored value 1010. Currentbscdoes not call a user-definedinLiteralRangeautomatically; the function documents the domain and can be called explicitly, but it does not stop a bad literal by itself. Restore 100 afterwards. - Change
Percent utilization = 37;toUInt#(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) statementBit#(4) reversed = 0;
for (Integer i = 0; i < 4; i = i + 1)
reversed[i] = data[3 - i];| Part | Meaning |
|---|---|
Integer i = 0 | an elaboration-time index |
i < 4 | a 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.
- For the input
8'b10110101, what iscountafter each test? - 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?
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
endpackagePractice 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:
| Index | Selected bit | count after the if |
|---|---|---|
| start | — | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 2 | 1 | 2 |
| 3 | 0 | 2 |
| 4 | 1 | 3 |
| 5 | 1 | 4 |
| 6 | 0 | 4 |
| 7 | 1 | 5 |
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.
- Change the bound from
i < 8toi < 4. Only bits 3 to 0,0101, are tested, and the result is 2. The input did not change; the bound changed the hardware. - Rewrite the loop as
Integer i = 0; while (i < 8) begin ... i = i + 1; endwith the sameifinside. 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?
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
| Topic | Key point |
|---|---|
| Variable | a name for a value; reassigning with = builds logic, not storage |
| Array | a constant-size group of values or instances, not a memory |
| Declared type | chooses the Literal instance; built-in types range-check literals with T0051 |
| Unassigned path | warning P0042, then error G0028 once the value is used; initialize before branching |
| Loops | unfolded during elaboration; runtime repetition needs registers and rules |
6. Check your understanding
-
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. -
Which part of
countOnesis 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. -
What happens when you write
Percent full = 101;, and how does that differ fromUInt#(8) c = 300;?Show answer
Percent full = 101;compiles and stores 1010 inbasisPoints, becausebscdoes not call a user-definedinLiteralRangeautomatically.UInt#(8) c = 300;stops during elaboration withT0051, 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.