1. Bool is a truth value, Bit#(n) is n wires
This chapter is about values: the data a circuit computes from its inputs in the same cycle, with no memory. Every expression here becomes combinational logic, gates and wires, and nothing in it stores anything between cycles. Storing values is the job of registers and rules, two chapters on.
1.1 Two types that Verilog folds into one
BSV keeps two things apart that Verilog folds into one. A Bool is True or False: the
result of a comparison, the operand of &&, || and !, the thing a condition tests. A
Bit#(n) is a vector of n raw bits: the operand of the bitwise operators &, |, ^ and
~, and the type of anything that travels on wires.
| Type | Values | Operators | Typical use |
|---|---|---|---|
Bool | True, False | &&, ||, !, == | conditions |
Bit#(n) | all n-bit patterns | &, |, ^, ~, { }, [ ] | data on wires |
The two do not mix by themselves. A Bool is not a one-bit vector; to put it into a vector you
call pack, which turns True into 1'b1 and False into 1'b0. The reverse, making a
Bool from a bit, is a comparison such as b == 1. A sized literal spells its width and base:
3'b101 is three bits, 8'h3f eight.
1.2 Operators and their result types
BSV has the usual C-like binary operators. The operand types decide width and interpretation. The table is the map; the subsections after it say what each group builds in hardware and what bsc 2026.01 does when you use an operator on the wrong type.
Form
operand1 operator operand2| Group | Operators | Operand types | Result |
|---|---|---|---|
| Arithmetic | +, -, *, /, % | Bit#(n), UInt#(n), Int#(n) | same type as the operands |
| Comparison | ==, !=, <, <=, >, >= | two operands of one type | Bool |
| Logical | &&, ||, ! | Bool only | Bool |
| Bitwise | &, |, ^, ~ | bit types, never Bool | same width as the operands |
| Shift | <<, >> | a bit type on the left, an amount on the right | same type as the left operand |
UInt#(8) a = 200;
UInt#(8) b = 100;
UInt#(8) sum = a + b; // 44: the result keeps 8 bits and wraps
Int#(8) s = -1;
UInt#(8) u = unpack(pack(s));
Bool sNeg = s < 0; // True: Int#(8) compares as signed
Bool uNeg = u < 0; // False: the same bits are 255 as UInt#(8)UInt#(8), Int#(8) and Bit#(8) all pack to eight bits, but they are different types.
Signed arithmetic, unsigned arithmetic and plain bit vectors are separate contracts, and the
type, not the bit pattern, decides which one applies. The next chapter,
Types, widths and explicit conversion, is
about reading one pattern under each of the three types.
1.3 Bitwise or logical: ~ is n inverters, ! is one
The two families look alike and build different circuits.
| Operator | Operand | Hardware |
|---|---|---|
~v | Bit#(n) | n inverters, one per wire; the result is n bits |
a & b, a | b, a ^ b | two Bit#(n) | n gates side by side, bit 0 with bit 0, bit 1 with bit 1 |
!c | Bool | one inverter on a condition |
c && d, c || d | two Bool | one gate that combines two conditions |
bsc does not convert between them. A bitwise operator on a Bool stops with T0061:
Bitwise operators are not defined for Boolean values. Consider using the Boolean operators && and || instead.
A logical operator on a bit vector fails the other way. Bit#(8) y = bx && 8'h0f; and
Bit#(8) y = !bx; both stop with T0080, because && and ! can only produce a Bool:
Type error at the use of the following function: (&&) The expected return type of the function: Bit#(8) The return type according to the use: Bool
To ask a condition of a vector, write the comparison that states it: v != 0 for "any bit
set", (v & mask) == mask for "all masked bits set". Each yields a Bool that &&, || and
! accept.
1.4 Arithmetic keeps the operand width
+, - and * produce a result of the operands' own type, so an 8-bit sum or product is an
8-bit adder or multiplier whose carry out of bit 7 is dropped. Nothing is reported at compile
time or at run time; the value simply wraps modulo 2^n.
Expression, all UInt#(8) | Printed | Why |
|---|---|---|
200 + 100 | 44 | 300 does not fit in 8 bits; the ninth bit is dropped |
0 - 1 | 255 | unsigned subtraction below zero wraps to the top |
200 * 100 | 32 | only the low 8 bits of 20000 survive |
unsignedMul(200, 100) | 20000 | the product is a UInt#(16), wide enough for every result |
unsignedMul and signedMul take two n-bit operands and return the full 2n-bit product,
UInt#(16) or Int#(16) for 8-bit inputs. signedMul(-16, 7) gives -112. When the full
result matters, use them, or widen the operands first with the conversion functions of the
next chapter.
Both operands must have the same type. Adding a UInt#(16) to a UInt#(8) stops with
T0020:
Type error at: w Expected type: UInt#(8) Inferred type: UInt#(16)
Bit#(n) takes the same arithmetic and treats its bits as unsigned: 8'hf0 + 8'h20 is
8'h10. Prefer UInt#(n) when the value is a number, so that the type says so.
1.5 Division and remainder
/ and % are defined for Bit#(n), UInt#(n) and Int#(n). / truncates toward zero and
% takes the sign of the dividend, so for Int#(8) the expressions -16 / 7 and -16 % 7 are
-2 and -2.
bsc 2026.01 accepts a division whose operands are both registers. With ra and rb as
Reg#(UInt#(8)) it emits Verilog that leaves the operator to the synthesis tool:
assign ra_QUOT_rb___d3 = ra / rb ; assign ra_REM_rb___d5 = ra % rb ;
That line is a complete divider that must settle within one clock cycle. It is large and slow
compared with an adder, so in a design you choose it on purpose. Dividing by a power of two
needs no divider: x / 8 on a UInt is x >> 3, which is wiring.
1.6 Shifts
<< moves bits toward the top and fills the bottom with zeros. What >> fills the top with
depends on the type of the left operand.
| Left operand | >> fills with | 8'hf0 >> 2 read as that type |
|---|---|---|
Bit#(8) | zeros (logical shift) | 00111100 |
UInt#(8) | zeros (logical shift) | 60 |
Int#(8) | copies of the sign bit (arithmetic shift) | -4, from -16 |
The result has the left operand's type and width; bits shifted out are lost, so UInt#(8) 240
shifted left by 2 is 192. The shift amount does not need the same type or width as the value:
bsc accepts an Integer, a Bit#(2), a UInt#(3) or a UInt#(8) there.
A constant amount is only wiring, since every output bit is connected to a fixed input bit or to a constant. An amount that comes from a register or an input is a shifter, a layer of multiplexers that chooses the connection every cycle. Both give the same results; the example below shifts by a register.
The left operand must be a bit type. 1 << 2 + 1 with no declared width is an Integer
expression, and bsc rejects it with T0062, "Bitwise operators are not defined for the type:
Integer". Give the value a type, for example UInt#(8) one = 1;, before shifting it.
1.7 Comparisons follow the type
A comparison returns a Bool, and the operand type decides how the bits are read: unsigned for
Bit#(n) and UInt#(n), two's-complement signed for Int#(n). The bits 8'hf0 are 240 as a
UInt#(8) and -16 as an Int#(8), so u > 100 is True while s > 100 is False.
Comparing two different types is a type error, even when both are eight bits. ra < rm with a
UInt#(8) and an Int#(8) stops with T0020:
Type error at: rm Expected type: UInt#(8) Inferred type: Int#(8)
Decide which reading you mean and convert one side explicitly, as described in The same bits, read two ways.
1.8 Precedence
BSV binds operators in the Verilog and C order. From tightest to loosest:
| Level | Operators |
|---|---|
| 1 | unary !, ~, unary -, reduction &, |, ^ |
| 2 | *, /, % |
| 3 | +, - |
| 4 | <<, >> |
| 5 | <, <=, >, >= |
| 6 | ==, != |
| 7 | & |
| 8 | ^ |
| 9 | | |
| 10 | && |
| 11 | || |
Runs with bsc 2026.01 confirm each step: 2 + 3 * 4 is 14, 4 + 3 % 2 is 5, one << 2 < 5 is
(one << 2) < 5, ~x & y complements x alone, a | b ^ c is a | (b ^ c),
a ^ b & c is a ^ (b & c), and t || f && f is t || (f && f).
Check
You want to test whether the low four bits of a Bit#(8) bx are all zero and write Bool y = bx & 8'h0f == 0;. What does bsc do?
The traps are the places where the C order differs from how the line reads:
| Written | Parsed as | Write instead |
|---|---|---|
v & mask == 0 | v & (mask == 0), a type error | (v & mask) == 0 |
a + b << 1 | (a + b) << 1 | a + (b << 1) if the shift was meant for b |
x << n + 1 | x << (n + 1) | (x << n) + 1 if the add comes after the shift |
a | b ^ c | a | (b ^ c) | (a | b) ^ c if the OR comes first |
The first trap is caught by the type checker. The other three compile and give a different number, so put parentheses around any mix of shift, arithmetic and bitwise operators, even where the default order happens to be the one you want.
1.9 Example: the operators on registers
Operators.bsv keeps its operands in registers, so bsc builds every operator as logic and
Icarus computes the values. Before you run, decide for each line which way it will go: will
s>>k print a negative number, will u>100 and s>100 agree, and which of the two prec
values is the sum that wrapped before the shift?
package Operators;
(* synthesize *)
module mkTop(Empty);
// Registers hold the operands, so bsc builds the operators as logic instead of folding constants.
Reg#(UInt#(8)) a <- mkReg(200);
Reg#(UInt#(8)) b <- mkReg(100);
Reg#(UInt#(8)) zero <- mkReg(0);
Reg#(Bit#(8)) pattern <- mkReg(8'hf0);
Reg#(UInt#(3)) k <- mkReg(2);
rule show;
// The same eight bits read as an unsigned and as a signed number.
UInt#(8) u = unpack(pattern);
Int#(8) s = unpack(pattern);
// + - * keep the operand width and wrap; unsignedMul keeps the full product.
$display("wrap: a+b=%0d 0-1=%0d a*b=%0d unsignedMul=%0d",
a + b, zero - 1, a * b, unsignedMul(a, b));
// >> fills with zeros on UInt and copies the sign bit on Int; << always fills with zeros.
$display("shift: u=%0d u>>k=%0d u<<k=%0d s=%0d s>>k=%0d s<<k=%0d",
u, u >> k, u << k, s, s >> k, s << k);
// The same bits compared with the same literal: unsigned on UInt, signed on Int.
$display("cmp: u>100=%b s>100=%b s<0=%b", u > 100, s > 100, s < 0);
// / truncates toward zero and % takes the sign of the dividend.
$display("divide: u/7=%0d u%%7=%0d s/7=%0d s%%7=%0d", u / 7, u % 7, s / 7, s % 7);
// + binds tighter than <<, so the wrapped sum is shifted.
$display("prec: a+b<<1=%0d a+(b<<1)=%0d", a + b << 1, a + (b << 1));
$finish(0);
endrule
endmodule
endpackagePress Run and read the five lines.
| Line | What to look at | What it shows |
|---|---|---|
wrap | a+b=44, 0-1=255, a*b=32 next to unsignedMul=20000 | 8-bit arithmetic drops the carry; only the widening multiply keeps the full product |
shift | u>>k=60 against s>>k=-4 | the same bits shift in zeros as UInt and the sign bit as Int |
cmp | u>100=1 against s>100=0 | one bit pattern, one literal, two answers decided by the type |
divide | s/7=-2 and s%7=-2 | signed division truncates toward zero and the remainder keeps the dividend's sign |
prec | a+b<<1=88 against a+(b<<1)=144 | without parentheses the wrapped sum 44 is shifted; with them b is shifted first |
Then change one thing at a time in the lab. Make pattern 8'h70: with the top bit clear, u
and s both print 112, and >>, >100 and /7 give the same answers for both. Only s<<k
still differs, at -64 against 192, because the shift moves a 1 into the sign bit. Or remove the
parentheses from the second prec expression and see both values become 88.
2. The conditional operator
? : selects one of two values of the same type.
Form
condition ? valueIfTrue : valueIfFalseUInt#(8) magnitude = negative ? 0 - value : value;| Part | Meaning |
|---|---|
negative | a Bool condition |
0 - value | the result when the condition is true |
value | the result when it is false |
Only the selected branch contributes the value, but both branches must type-check and have the
same type. Mixing Bit#(8) and Bit#(4) branches is type error T0020. What this operator is
as hardware, and how case and functions build larger selections on it, is the subject of
Selection, case, and the don't-care value.
3. Vectors are built with braces and taken apart with brackets
Concatenation and selection rearrange bits; reduction combines their values.
| Form | Meaning | Width of the result |
|---|---|---|
{ a, b, c } | concatenation; the first operand lands in the high bits | sum of the operand widths |
v[6:4] | bit selection; both ends included | 6 - 4 + 1 = 3 |
^v | reduction: XOR of all bits of v; &v and |v likewise | 1 |
Concatenation and constant-index selection are wiring. A reduction computes a result with a tree of gates over one vector.
3.1 Bit selection
Square brackets select bits from a bit vector. A range includes both endpoints.
Form
vector[index]
vector[high:low]Bit#(8) frame = 8'b1101_0111;
Bit#(1) top = frame[7]; // 1
Bit#(3) code = frame[6:4]; // 101The width of frame[6:4] is 6 - 4 + 1 = 3.
3.2 Concatenation
Braces join bit vectors into a wider one. The leftmost operand goes to the most significant end.
Form
{ operand1, operand2, ... }Bit#(8) joined = { 4'ha, 4'h5 }; // 8'ha5
Bool ready = True;
Bit#(4) withFlag = { pack(ready), 3'b010 }; // 4'b1010The result width is the sum of the operand widths. If it does not equal the declared width,
compilation stops with T0060, "bit-size mismatch". A Bool must be turned into a bit with
pack before it can be concatenated.
3.3 Reduction operators
A unary reduction combines all bits of one vector into a single bit.
Form
&vector |vector ^vector| Operator | Result is 1 when |
|---|---|
&v | every bit of v is 1 |
|v | at least one bit is 1 |
^v | an odd number of bits are 1 (parity) |
Bit#(8) payload = 8'b0010_0110;
// &payload is 0, |payload is 1, ^payload is 1 (three 1 bits)^v is 1 when v has an odd number of ones, which is why it is the parity operator.
4. A function is logic built where it is called
4.1 Defining a function
A pure value-returning function takes arguments, has a result type, and computes its result in a statement body. It
has no state and no clock. Calling it in a rule or a method places a copy of its logic at that
point in the circuit, evaluated in the same cycle as its caller.
Form
function resultType name(argType1 arg1, argType2 arg2);
statements
return expression;
endfunctionfunction UInt#(8) increment(UInt#(8) value);
return value + 1;
endfunction| Part | Meaning |
|---|---|
UInt#(8) before the name | the result type |
(UInt#(8) value) | one argument |
return value + 1; | the result |
A short function can also be written as one assignment:
function UInt#(8) increment(UInt#(8) value) = value + 1;A local declared with = inside the function is a name for a value, not storage. The result
type is checked: an eight-bit function must return exactly eight bits. Calling a function builds
logic at the call; it does not create state. A function can be passed to another function as an
argument, and it can carry provisos, the type constraints of
Typeclasses and provisos. Work that writes
registers belongs in an Action or ActionValue, executed by a rule or method. A function may
return such an action description, but the pure value-returning functions here do not.
4.2 Function calls and method calls
A function call applies a function to arguments: increment(x). Dot notation selects a method
of an interface: dispenser.issue, fifo.first. The method's type decides how you use it.
| Method kind | Used as | Example |
|---|---|---|
| value method | an ordinary value | Bit#(8) x = fifo.first; |
Action method | a statement in an action context | fifo.deq; |
ActionValue method | executed with <- | let t <- dispenser.issue; |
Struct, tagged-union and interface expressions also build values. A struct expression fills named fields; a tagged-union expression picks one member; an interface expression supplies method implementations, which may carry conditions and actions. These types are introduced in User-defined types.
5. An expression's type decides its role
Every expression so far was a value. BSV has three other kinds of expression, and you will meet them in later chapters; knowing now that they exist explains why a function cannot do everything. Every expression in BSV has a type, and the type decides what the expression stands for: a value computed from operands, an atomic state change, a state change that also returns a value, or a set of rules built during elaboration. Before reading the inside of an expression, ask which of these four kinds it is.
| Expression type | What it stands for | When it matters |
|---|---|---|
Bit#(n), UInt#(n), a struct, or another value type | data computed from operands | whenever the enclosing rule or method evaluates it |
Action | atomic state change with no result | when an action context executes it |
ActionValue#(t) | atomic state change plus one result of type t | when executed with <- in an action context |
Rules | a collection of rule definitions | during elaboration, when addRules installs it |
A value expression is combinational logic. An Action is a description of work, so naming it
with = does not perform the work. An ActionValue adds a result to the same atomic work. A
Rules value is design structure: it exists before simulation starts rather than happening on a
clock edge.
Check
You want the pure UInt#(8)-returning function increment(x) to also add one to a counter register every time it is called. Can this function do that?
This chapter stays with the first kind. Action and ActionValue expressions are the subject
of Methods: value, Action, ActionValue,
and Rules expressions of Rules as values.
6. Example: compose and decode a parity frame
BitComposition.bsv builds an eight-bit frame from three fields and a parity bit, then decodes
the frame again. Read makeFrame first and add up the widths of pack(ready), code and
count. Then find where show prints both the whole frame and the selected fields.
The call passes ready = True, code = 3'b101 and count = 3'b011.
pack(ready)contributes the bit1.- Concatenation forms the seven-bit body
1_101_011,1 + 3 + 3 = 7bits. - The body has five 1 bits, so
^bodyis1. { body, ^body }appends parity at the right:1_101_011_1,7 + 1 = 8bits.- Bits 7, 6:4, 3:1 and 0 then recover the three fields and the parity bit.
Predict before you run: work the body out on paper as one 7-bit value, then decide which values
ready, code, count and parity will print.
Check
The frame is the 7-bit body followed by its parity bit. What is it in hexadecimal (two digits)?
Check
How many bits wide is { body, ^body } when body is Bit#(7)?
package BitComposition;
function Bit#(8) makeFrame(Bool ready, Bit#(3) code, Bit#(3) count);
// Concatenation fixes the field positions in the frame.
Bit#(7) body = { pack(ready), code, count };
// Reduction XOR computes one parity bit from the seven body bits.
return { body, ^body };
endfunction
(* synthesize *)
module mkTop(Empty);
rule show;
Bit#(8) frame = makeFrame(True, 3'b101, 3'b011);
$display("frame=%02h ready=%0d code=%0d count=%0d parity=%0d",
frame, frame[7], frame[6:4], frame[3:1], frame[0]);
$finish(0);
endrule
endmodule
endpackagePractice this concept
Use this concept in a short BSV problem as your next step.
Press Run. The single output line shows the frame and the four decoded fields.
| Printed field | Value | What it proves |
|---|---|---|
frame | d7 | the concatenation order and total width are as traced |
ready | 1 | bit 7 holds the first operand |
code | 5 | the inclusive range 6:4 is three bits wide |
count | 3 | the range 3:1 matches the third operand |
parity | 1 | the reduction produced exactly one bit at position 0 |
The byte on its own proves little. Decoding it back to the values that went in shows that the encoder and the decoder agree on every field position.
7. Change one thing
In the lab, change the return line to return { ^body, body }; and leave the decoder alone.
Before you run, decide what will and will not change.
Check
With the parity bit moved to the top and the decoder left alone, what happens?
| Change | Expected result |
|---|---|
return { ^body, body };, decoder unchanged | The frame becomes eb, and the fields print as ready 1, code 6, count 5, parity 1. Every selection still has a legal width, so the program compiles; only the meaning is wrong |
Also move every selection: frame[6], frame[5:3], frame[2:0] and frame[7] | The fields decode to 1, 5, 3 and 1 again |
Caution. A Bool cannot be concatenated directly. Writing { ready, code, count } with a
Bool operand is a type error; pack(ready) is what makes it one bit.
8. Apply it: finish the parity frame, then select an operation
The first graded problem below is the same frame with the parity bit left out. encodeFrame
builds the seven-bit body correctly and then appends 1'b0, so even bodies look right and odd
bodies are one bit off. Replace that constant with the one-bit reduction of the complete body,
keep the field order, and run. Grading checks the whole contract: bits 7 to 1 are the fields for
every input, and bit 0 is 1 exactly when the body has an odd number of ones.
The second problem is a pure function selectOp that must return the AND, OR, XOR or NOR of two
4-bit operands according to a 2-bit selector, and currently returns ?. Choose among four
expressions with a case on the selector or a chain of conditional operators, build each arm
from the operator table in section 1.2, and remember that there is no single NOR operator: it is
the complement ~ of the whole OR.
9. Common mistakes
9.1 A concatenation that does not fill the declared width
// Wrong: 3 + 4 = 7 bits for an 8-bit name
Bit#(8) f = { code, 4'h3 };
// Right: add the missing bit explicitly
Bit#(8) f = { 1'b0, code, 4'h3 };Concatenation never pads or truncates on its own. The wrong line stops with T0060, a bit-size
mismatch.
10. Summary
| Element | Meaning |
|---|---|
| expression type | decides whether an expression computes a value, performs an action, or builds structure |
Bool, True, False | a truth value; &&, ||, ! |
Bit#(n) | n raw bits; &, |, ^, ~ |
| operators | operands share one type; the type decides signed or unsigned behaviour and width |
+ - * | wrap at the operand width; unsignedMul, signedMul keep the full product |
>> | logical on Bit and UInt, arithmetic on Int |
| precedence | Verilog and C order; parenthesize mixes of shift, arithmetic and bitwise operators |
pack(b) | a Bool as one bit |
c ? a : b | selects one of two values of the same type |
{ a, b } | concatenation, widths add, first operand on top; a mismatch with the declaration is T0060 |
v[h:l] | bit selection, both ends included |
^v, &v, |v | reduction to one bit |
function … endfunction | logic placed where it is called; no state; a method call is used according to its kind |
11. Check your understanding
-
Why is
{ body, ^body }exactly eight bits whenbodyisBit#(7)?Show answer
Concatenation adds the widths of its operands, and a reduction XOR always yields one bit, so the result is 7 + 1 = 8 bits.