1. The width is part of the type
Bit#(4) and Bit#(8) are two different types, not one type with two sizes. BSV never changes
a sized value's width on its own, in either direction. Assigning a 4-bit value to an 8-bit name
is a compile error, and so is assigning an 8-bit value to a 4-bit name. There is no silent zero
fill and no silent dropping of high bits; the compiler stops with T0020 (expected one type,
inferred another) or T0060 (bit-size mismatch), and no circuit is built.
This is the opposite of Verilog, where an expression is extended or truncated to fit its context. Keep the two rules apart: in BSV width changes must be explicit.
1.1 When type checking happens
Compiling BSV runs through several stages, and type checking comes before elaboration, the stage that builds the hardware structure.
| Stage | What it does |
|---|---|
| Parsing | Groups tokens into declarations, expressions and statements |
| Type checking | Confirms that every expression's type and width agree |
| Elaboration | Turns modules and instances into a hardware structure |
| Simulation | Runs that hardware cycle by cycle |
If type checking fails, no circuit is generated. A type error therefore never describes a misbehaving circuit; it tells you that no circuit was built.
1.2 Four questions to ask of a type
| Question | What to look for | Example |
|---|---|---|
| Representation | How many bits exist? | Bit#(4) has four |
| Interpretation | What number do the bits mean? | UInt#(4) and Int#(4) read the same bits as different numbers |
| Capability | Which operations are allowed? | With Eq#(t), you may use == on t |
| Stage | Is this a type or a value? | The 8 in Bit#(8) is a numeric type, not a value |
All four are settled at compile time and never change from cycle to cycle. The traces in this chapter therefore follow how types and values are worked out, not how registers change. The capability question, and the provisos that answer it for a type variable, belong to Typeclasses and provisos.
1.3 Parameterized types
A parameterized type writes its parameters after #.
Form
TypeConstructor#(param1, param2, ...)Bit#(8) // eight raw bits
Vector#(4, Bit#(8)) // four 8-bit elements
Bit#(n) // width given by numeric type variable n| Part | Rule |
|---|---|
| Type constructor | Starts uppercase, like Bit and Vector; the Verilog-compatible int and bit are the exceptions |
| A fixed number | The 8 in Bit#(8) is a fixed numeric type |
| A type variable | Starts lowercase, like the n in Bit#(n), and is settled by the context where it is used |
The Verilog-compatible spelling bit[m:0] also means a vector of m + 1 bits; its second index
must currently be 0.
1.4 Width changes must be explicit
Narrowing and widening assignments are both compile errors; only an unsized integer literal takes
the width of its target, so Bit#(8) x = 5; is fine.
Bit#(4) nibble = 4'b1100;
Bit#(8) wide = nibble; // error T0020: expected Bit#(8), inferred Bit#(4)
Bit#(4) narrow = 5'b10101; // error T0060: bit-size mismatchTo change a width, call the function that states your intent; section 5 lists them.
1.5 Example: implicit narrowing
This example fails to compile on purpose. narrow is declared with four bits, but the
literal 5'b10101 has five.
package Narrowing;
(* synthesize *)
module mkTop(Empty);
rule show;
// Five bits into a four-bit variable: rejected before any circuit exists.
Bit#(4) narrow = 5'b10101;
$display("%b", narrow);
$finish(0);
endrule
endmodule
endpackagePractice this concept
Use this concept in a short BSV problem as your next step.
Predict before you run: will bsc drop the extra high bit and start the simulation, or stop
during compilation?
Check
What happens when you run Narrowing.bsv?
Press Run and read the message.
| Item | Detail |
|---|---|
| Faulty code | A 5-bit literal assigned to a Bit#(4) variable |
| Failing stage | Type checking in bsc; elaboration and simulation never start |
| Message | T0060 with bit-size mismatch |
| Fix | Widen the target to Bit#(5) to keep every bit; write 4'b0101 if the source should be four bits; use truncate if dropping the high bit is the intent |
The type derivation is short:
- The sized literal
5'b10101has typeBit#(5). - The declaration requires
Bit#(4). - Those types match only if 5 equals 4, so type checking fails.
Whether the value would fit in four bits is irrelevant. 5'b00101 also has type Bit#(5) and
fails the same way. A type error never describes a misbehaving circuit. It says no circuit
exists.
Try it yourself. If the target were Bit#(3), making the literal's number smaller would not
help. You would need a 3-bit literal such as 3'b101, or an explicit truncate.
2. An unsized literal takes the width of its target
A literal without a size, such as 12, has no width of its own. The type of the place it is
used supplies one: in Bit#(4) a = 12; the literal becomes four bits. The value must fit,
though. Bit#(4) a = 16; is rejected with T0051, Literal 16 is not a valid Bit#(4), because
four bits hold 0 to 15.
| Literal | Width | Rule |
|---|---|---|
5'b10101 | 5, fixed by the literal | must equal the target width exactly |
12 | taken from the target | must be representable in that width |
So the two kinds of literal fail differently: a sized literal of the wrong width is a type
mismatch, an unsized literal that is too large is a range error. Both are compile-time. The
spelling rules for literals, including bases, underscores and the fill literals '0 and '1,
are in Lexical elements.
Check
Bit#(4) x = 16; — what does bsc do?
3. Widths add in a concatenation; the declaration only checks
BitWidth.bsv joins a 4-bit nibble and an 8-bit octet with { nibble, octet } and stores
the result in a Bit#(12). In a concatenation, the first operand takes the high bits. The 12 is
not chosen by the declaration; it is 4 + 8, the width the expression already has. The
declaration agrees with it, which is why the program compiles.
package BitWidth;
(* synthesize *)
module mkTop(Empty);
rule show;
// The number in Bit#(n) is the width, and it is part of the type.
Bit#(4) nibble = 4'b1100;
Bit#(8) octet = 8'h3f;
// Concatenation adds the widths: 4 + 8 fixes this type, not the declaration.
Bit#(12) joined = { nibble, octet };
$display("nibble = %b", nibble);
$display("octet = %b", octet);
$display("joined = %b", joined);
$finish(0);
endrule
endmodule
endpackagePractice this concept
Use this concept in a short BSV problem as your next step.
Predict before you run: how wide is the concatenation, and what are the top four bits of
joined?
Check
If nibble were declared Bit#(5), how wide would { nibble, octet } be?
Run it and read the three printed vectors.
| Output line | What it shows |
|---|---|
nibble | 1100, four bits |
octet | 00111111, eight bits |
joined | 110000111111, twelve bits: nibble on top, octet below |
The width 12 comes from 4 + 8. The Bit#(12) declaration checks that result; it does not create
it.
Try it yourself.
| Change | Expected result |
|---|---|
{ nibble, octet } to { octet, nibble } | Still twelve bits, so it compiles, and joined becomes 001111111100 |
Declare nibble as Bit#(5) nibble = 5'b11000; | To keep the result at twelve bits, octet would have to be seven bits, so compilation fails with T0020, reporting that octet was expected to be Bit#(7) |
Types check the total width; the order inside the braces decides the layout.
4. Four ways to hold a number
BSV separates how many bits a value has from how those bits are read. The width is always part of the type, so a width mistake is a type error rather than a silent change in the circuit.
Form
Bit#(width) name = value; // raw bits; arithmetic and comparison treat them as unsigned
UInt#(width) name = value; // unsigned number
Int#(width) name = value; // two's-complement signed number
Integer name = value; // unbounded, elaboration time onlyBit#(8) raw = 8'hA5;
UInt#(8) count = 200;
Int#(8) offset = -3;
Integer depth = 16;| Type | Width | Meaning | Becomes hardware? |
|---|---|---|---|
Bit#(n) | exactly n | a bit pattern; layout is the point | yes |
UInt#(n) | exactly n | unsigned arithmetic and comparison | yes |
Int#(n) | exactly n | signed arithmetic and comparison | yes |
int | 32 | a synonym for Int#(32) | yes |
Integer | none | a mathematical integer used while the design is elaborated | no |
Pick Bit#(n) when you care where each bit sits, such as fields in a frame, and UInt#(n) or
Int#(n) when you care what the number means. How each type changes the result of >>, <,
/ and % is shown with a runnable example in
Bool, Bit and pure functions.
Note. Integer values are used for loop bounds, depths and other parameters. They are gone
by the time hardware is generated, so an Integer can never be stored in a register.
5. Changing a width is a function call
When a width really must change, call the function that says how. The destination type tells each one how wide the result is.
Form
narrower = truncate(wider); // drop the high bits
wider = zeroExtend(narrower); // fill the new high bits with 0
wider = signExtend(narrower); // copy the old top bit into the new high bits
wider = extend(narrower); // zero for Bit/UInt, sign for IntBit#(8) a = 8'hA5; // 1010_0101
Bit#(4) t = truncate(a); // 4'h5
Bit#(12) z = zeroExtend(a); // 12'h0a5
Bit#(12) s = signExtend(a); // 12'hfa5
Bit#(12) e = extend(a); // 12'h0a5, because a is a Bit| Call | What happens to 8'hA5 | Result |
|---|---|---|
truncate to 4 bits | bits 7..4 are dropped | 5 |
zeroExtend to 12 bits | four 0s are added on top | 0a5 |
signExtend to 12 bits | the top bit, 1, is copied four times | fa5 |
extend on a Bit | behaves like zeroExtend | 0a5 |
| Function | Effect | Example |
|---|---|---|
zeroExtend(x) | widen; new high bits are 0 | Bit#(8) w = zeroExtend(4'hC); gives 8'h0C |
signExtend(x) | widen; new high bits copy the top bit | Int#(8) w = signExtend(Int#(4)'(-3)); gives -3 |
truncate(x) | narrow; keep the low bits | Bit#(4) t = truncate(8'hA5); gives 4'h5 |
extend(x) | widen the way the type says | on an Int#(8) holding -91, extend to Int#(12) keeps -91 |
None of these takes a bit count. The target type, usually the declaration on the left, tells
the function how many bits to produce. The same truncate call yields three bits when the target
is Bit#(3).
Note. Writing Bit#(4) b = a; with an 8-bit a is a compile error, T0020, with expected
type Bit#(4) and inferred type Bit#(8). The fix is to say which bits you want with
truncate, not to change a.
6. The same bits, read two ways
Bit#(n), UInt#(n) and Int#(n) all occupy n bits, and they are three types. Bit is a
raw pattern, UInt reads it as an unsigned number, Int as two's complement.
| Type | Interpretation | Range at four bits |
|---|---|---|
Bit#(n) | raw bit vector | 0000 to 1111 |
UInt#(n) | unsigned integer | 0 to 15 |
Int#(n) | two's-complement signed integer | -8 to 7 |
6.1 pack and unpack
pack turns a value into a Bit#(n) of the same width, and unpack turns a Bit#(n) into a
value of the target type. Neither moves any bits; they change only how the compiler reads them.
Form
Bit#(n) bits = pack(value);
TargetType v = unpack(bits);Bit#(4) raw = 4'b1101;
UInt#(4) u = unpack(raw); // 13
Int#(4) s = unpack(raw); // -3
Bit#(4) b = pack(s); // 1101 again6.2 Example: one pattern, two types
Signedness.bsv unpacks raw = 4'b1101 once as UInt#(4) and once as Int#(4) and displays
both.
package Signedness;
(* synthesize *)
module mkTop(Empty);
rule show;
Bit#(4) raw = 4'b1101;
// The same four bits under two types. unpack reinterprets, it moves no wires.
UInt#(4) asUnsigned = unpack(raw);
Int#(4) asSigned = unpack(raw);
$display("raw = %b", raw);
$display("as UInt#(4) = %0d", asUnsigned);
$display("as Int#(4) = %0d", asSigned);
$finish(0);
endrule
endmodule
endpackagePractice this concept
Use this concept in a short BSV problem as your next step.
Predict before you run: work out 1101 as an unsigned number and as a two's-complement number.
Check
What does 1101 print as an Int#(4)?
| Output line | What it shows |
|---|---|
raw | 1101 |
as UInt#(4) | 8 + 4 + 1 = 13 |
as Int#(4) | the top bit weighs -8, so -8 + 4 + 1 = -3 |
Both variables hold the same four bits; only their types differ. unpack alone adds no
conversion hardware.
Try it yourself.
raw value | as UInt#(4) | as Int#(4) |
|---|---|---|
4'b1000 | 8 | -8, the smallest Int#(4) |
4'b0111 | 7 | 7, the largest Int#(4) |
6.3 Widening a signed value
To keep a signed 4-bit value's meaning in eight bits, order matters: interpret it as Int#(4)
first, then widen with signExtend.
Bit#(4) raw = 4'b1101;
Int#(8) good = signExtend(unpack(raw)); // -3: the sign bit is copied
Int#(8) bad = unpack(zeroExtend(raw)); // 13: the new top bit is 0Caution. Zero-extending the raw bits first makes the new top bit 0, so the signed reading is
positive. Both lines compile. The mistake shows up only for inputs whose top bit is 1; a positive
input such as 0111 looks fine. Testing 1000 and 0111 together exposes it.
7. Conversions in one table, and the type assertion that is not one
7.1 Converting between types
Changing how bits are interpreted, or how many there are, needs an explicit function.
| Function | What it does | Example | Result |
|---|---|---|---|
pack(x) | turns a value into its Bit#(n) representation | pack(True) | 1'b1 |
unpack(b) | turns bits into a value of the expected type | UInt#(8) u = unpack(8'hff); | 255 |
zeroExtend(x) | widens by adding zeros at the top | Bit#(8) w = zeroExtend(4'b1101); | 8'h0d |
signExtend(x) | widens by copying the sign bit | Int#(8) w = signExtend(Int#(4)'(-3)); | -3 |
truncate(x) | keeps the low bits | Bit#(4) t = truncate(8'hab); | 4'hb |
The target width comes from the expected type, usually the declaration on the left.
7.2 A static type assertion is not a conversion
Syntax that looks like a C cast is a static type assertion.
Form
Type'(expression)UInt#(8) ok = UInt#(8)'(255); // tells the literal its type
UInt#(8) no = UInt#(8)'(bits); // error if bits is Bit#(8)The assertion only tells type checking which type the expression must have. It works for an
overloaded expression such as a literal, which is why Int#(4)'(-3) appeared in the conversion
table. It does not change a Bit#(8) into a UInt#(8): the second line fails with T0020,
expected type UInt#(8), inferred type Bit#(8). Use unpack for that.
8. A map of common types
The Prelude and Foundation libraries provide types that fall into a few families.
| Family | Representative types | Purpose |
|---|---|---|
| Packed values | Bit#(n), UInt#(n), Int#(n), Bool, Maybe#(t), Tuple2#(a, b) | Values with a fixed bit representation through a Bits instance |
| Elaboration or simulation values | Integer, Real, String, Char, Fmt | Compile-time calculation and display; not all are synthesizable |
| Hardware interfaces | Reg#(t), FIFO#(t), Clock, Reset, Inout#(t) | State elements and hardware connections |
| Program structure | Action, ActionValue#(t), Rules, Module#(ifc) | State changes, rule sets and module construction |
Action and ActionValue#(t) are ordinary types, not special method syntax. Their role in
interfaces and scheduling is covered in
Methods: value, Action, ActionValue.
9. Apply it: make every conversion explicit
Three graded problems follow, one per rule of this chapter.
| Problem | What is wrong | The one edit |
|---|---|---|
| Narrow a byte explicitly | lowNibble must return the low four bits of a Bit#(8) but returns the whole input, so it does not compile, for the reason section 1 showed | Replace the return expression with the one conversion whose meaning is "drop the high bits"; the return type tells it how many to keep |
| Zero-extend a nibble | zeroExtendNibble returns { upper, lower }, already eight bits wide, but both locals are ? | Give each local its role: a four-bit zero for upper, the input for lower; the total width was never the problem, the field order is |
| Sign-extend a four-bit integer | asSignedByte widens with zeroExtend first and reads the bits as Int#(8) second, so 1101 prints 13 instead of -3 | Reverse the order from section 6.3: unpack while the value is still four bits, then signExtend to the return width |
For the third problem, test both sides of the 0111/1000 boundary before you decide the repair
is complete; a zero extension looks correct on every positive input.
10. Common mistakes
10.1 Assigning across widths
Bit#(8) octet = 8'hA5;
Bit#(4) nibble = 4'hC;
// Wrong
Bit#(4) low = octet; // 8 bits into 4: T0020, expected Bit#(4), inferred Bit#(8)
Bit#(8) wide = nibble; // 4 bits into 8: T0020
// Right
Bit#(4) low = truncate(octet);
Bit#(8) wide = zeroExtend(nibble);BSV adjusts neither direction automatically and refuses to drop bits silently. Name the
conversion, truncate, zeroExtend, signExtend or extend, so the reader of the code can see
the width change too.
10.2 Zero-extending a signed value
// Wrong: 1101 (-3) becomes 13
Int#(8) v = unpack(zeroExtend(raw));
// Right: interpret as signed first, then sign-extend
Int#(8) v = signExtend(unpack(raw));Both versions compile. Only an input whose top bit is 1 reveals the difference.
10.3 Using a type assertion as a conversion
// Wrong: Bit#(8) is not UInt#(8)
UInt#(8) x = UInt#(8)'(bits);
// Right: reinterpret the bits
UInt#(8) x = unpack(bits);A type assertion never changes a value. Use unpack, pack, zeroExtend, signExtend or
truncate when the type or width must really change.
10.4 Expecting a declaration to set an expression's width
// Wrong expectation: declaring Bit#(12) trims the concatenation to 12 bits
Bit#(12) joined = { nibble5, octet }; // 5 + 8 = 13 bits: T0020
// Right: the expression's width comes from its operands; the declaration checks it
Bit#(13) joined = { nibble5, octet };11. Summary
| Rule | Consequence |
|---|---|
| type checking runs before elaboration | any error means no circuit is generated |
| width is part of the type | Bit#(4) and Bit#(8) never mix without a conversion |
| sized literal | its width must equal the target's, else T0060 or T0020 |
| unsized literal | takes the target width; out of range is T0051 |
| concatenation | width is the sum of the operands; the declaration only checks |
zeroExtend, signExtend, truncate, extend | standard width-conversion functions; target width from context |
pack, unpack | change interpretation, not bits; Bit, UInt and Int read the same bits differently |
| sign extension | interpret as Int first, then signExtend |
T'(e) | asserts a type for an overloaded expression such as a literal; it converts nothing |
Integer | exists only during elaboration and never becomes hardware |
12. Check your understanding
-
Why does assigning a 5-bit value to
Bit#(4)cause a compile error instead of an overflow during simulation?Show answer
Width is part of the type, and type checking runs before elaboration.
Bit#(5)andBit#(4)are different types, so compilation stops withT0060before any circuit exists. -
What changes and what stays the same when
1101is unpacked asUInt#(4)and asInt#(4)?Show answer
The four bits
1101stay the same. Only the interpretation changes: 13 asUInt#(4), -3 asInt#(4). -
What is
extend(x)whenxis anInt#(8)holding-91, and the result is anInt#(12)?Show answer
-91. On a signed type,extendperforms sign extension, so the value is preserved. On aBitorUIntit would add zeros instead.