Skip to content
code0.HDL

Bool, Bit and pure functions

Tell a Bool from a Bit#(n), build and take apart bit vectors with concatenation, selection and reduction, and write a pure function that becomes combinational logic where it is called.

In this chapter

  • Classify an expression by its type and tell a Bool from a Bit#(n)
  • Compute the width of a concatenation, a bit selection and a reduction, and predict their values
  • Write a pure function and explain why calling it builds logic rather than state
  • Use the operator table to choose the right bitwise or logical operator for a contract
  • Predict how the operand type changes wrap-around, shifts, comparison and division, and add parentheses where precedence differs from intent
Concepts in this chapter
  • Bool, Bit, expressions and pure functions
Prerequisite concepts

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.

TypeValuesOperatorsTypical use
BoolTrue, 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
GroupOperatorsOperand typesResult
Arithmetic+, -, *, /, %Bit#(n), UInt#(n), Int#(n)same type as the operands
Comparison==, !=, <, <=, >, >=two operands of one typeBool
Logical&&, ||, !Bool onlyBool
Bitwise&, |, ^, ~bit types, never Boolsame width as the operands
Shift<<, >>a bit type on the left, an amount on the rightsame 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.

OperatorOperandHardware
~vBit#(n)n inverters, one per wire; the result is n bits
a & b, a | b, a ^ btwo Bit#(n)n gates side by side, bit 0 with bit 0, bit 1 with bit 1
!cBoolone inverter on a condition
c && d, c || dtwo Boolone 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)PrintedWhy
200 + 10044300 does not fit in 8 bits; the ninth bit is dropped
0 - 1255unsigned subtraction below zero wraps to the top
200 * 10032only the low 8 bits of 20000 survive
unsignedMul(200, 100)20000the 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 with8'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:

LevelOperators
1unary !, ~, 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?

Your answer

The traps are the places where the C order differs from how the line reads:

WrittenParsed asWrite instead
v & mask == 0v & (mask == 0), a type error(v & mask) == 0
a + b << 1(a + b) << 1a + (b << 1) if the shift was meant for b
x << n + 1x << (n + 1)(x << n) + 1 if the add comes after the shift
a | b ^ ca | (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?

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

endpackage

Press Run and read the five lines.

LineWhat to look atWhat it shows
wrapa+b=44, 0-1=255, a*b=32 next to unsignedMul=200008-bit arithmetic drops the carry; only the widening multiply keeps the full product
shiftu>>k=60 against s>>k=-4the same bits shift in zeros as UInt and the sign bit as Int
cmpu>100=1 against s>100=0one bit pattern, one literal, two answers decided by the type
divides/7=-2 and s%7=-2signed division truncates toward zero and the remainder keeps the dividend's sign
preca+b<<1=88 against a+(b<<1)=144without 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 : valueIfFalse
UInt#(8) magnitude = negative ? 0 - value : value;
PartMeaning
negativea Bool condition
0 - valuethe result when the condition is true
valuethe 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.

FormMeaningWidth of the result
{ a, b, c }concatenation; the first operand lands in the high bitssum of the operand widths
v[6:4]bit selection; both ends included6 - 4 + 1 = 3
^vreduction: XOR of all bits of v; &v and |v likewise1

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];   // 101

The 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'b1010

The 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
OperatorResult is 1 when
&vevery bit of v is 1
|vat least one bit is 1
^van 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;
endfunction
function UInt#(8) increment(UInt#(8) value);
  return value + 1;
endfunction
PartMeaning
UInt#(8) before the namethe 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 kindUsed asExample
value methodan ordinary valueBit#(8) x = fifo.first;
Action methoda statement in an action contextfifo.deq;
ActionValue methodexecuted 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 typeWhat it stands forWhen it matters
Bit#(n), UInt#(n), a struct, or another value typedata computed from operandswhenever the enclosing rule or method evaluates it
Actionatomic state change with no resultwhen an action context executes it
ActionValue#(t)atomic state change plus one result of type twhen executed with <- in an action context
Rulesa collection of rule definitionsduring 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?

Your answer

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.

  1. pack(ready) contributes the bit 1.
  2. Concatenation forms the seven-bit body 1_101_011, 1 + 3 + 3 = 7 bits.
  3. The body has five 1 bits, so ^body is 1.
  4. { body, ^body } appends parity at the right: 1_101_011_1, 7 + 1 = 8 bits.
  5. 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)?

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

endpackage

Practice 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 fieldValueWhat it proves
framed7the concatenation order and total width are as traced
ready1bit 7 holds the first operand
code5the inclusive range 6:4 is three bits wide
count3the range 3:1 matches the third operand
parity1the 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?

Your answer

ChangeExpected result
return { ^body, body };, decoder unchangedThe 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

ElementMeaning
expression typedecides whether an expression computes a value, performs an action, or builds structure
Bool, True, Falsea truth value; &&, ||, !
Bit#(n)n raw bits; &, |, ^, ~
operatorsoperands 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
precedenceVerilog and C order; parenthesize mixes of shift, arithmetic and bitwise operators
pack(b)a Bool as one bit
c ? a : bselects 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, |vreduction to one bit
function … endfunctionlogic placed where it is called; no state; a method call is used according to its kind

11. Check your understanding

  1. Why is { body, ^body } exactly eight bits when body is Bit#(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.

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