The fixed-width 2-state integers byte, shortint, int, and longint are all signed by default. The legacy 4-state types integer (32-bit signed) and time (64-bit unsigned) complete the family, while bit and logic vectors are unsigned.
The signed default is the part that bites. One unsigned operand makes a whole comparison unsigned, so a negative value silently becomes huge and the comparison flips.
Type | Bits | State | Signed by default |
|---|---|---|---|
| 8 | 2 | Yes |
| 16 | 2 | Yes |
| 32 | 2 | Yes |
| 64 | 2 | Yes |
| 32 | 4 | Yes, legacy Verilog |
| 64 | 4 | No, unsigned |
Declare int unsigned or byte unsigned whenever the value is a count, an address, or a bit pattern rather than a true signed number. Expression-level signedness and shift rules belong to the operators topic; this lesson covers declarations and conversions.
A static cast (int'(expr), unsigned'(expr), 16'(expr)) converts between compatible types: width, signedness, or numeric type. It always succeeds, and data may truncate or round, since real-to-int rounds rather than truncates.
A dynamic cast ($cast(dst, src)) is a runtime-checked downcast for stuffing an integer back into an enum. Called as a function it returns 0 on failure, so always check the result. Class-handle downcasts use the same call and are covered in the OOP patterns topic.
A bit-stream cast reinterprets raw bits between packed structs, vectors, and queues, which is the standard trick for serializing a header into bytes. The next lesson proves the struct-to-vector form in simulation.
Plain assignment across widths needs no cast. A narrower target silently truncates from the MSB side, and a wider target extends: zero-extension for unsigned sources, sign-extension for signed ones.
The rule to internalize is that the signedness of the source decides extension while the width of the target decides truncation. Most lint tools flag implicit truncation precisely because the language stays silent.
byte holding -2 is assigned to a 16-bit vector, and int'(3.7) is evaluated. What are the two results?16'hFFFE and 4. The byte is signed, so the assignment copies its top bit into the new upper bits. A cast from real to int rounds to the nearest integer; it does not truncate.
Which conversion to use depends on what is changing:
Changing width, signedness, or numeric type of values: static cast, or plain assignment where lint allows.
Converting an integer to an enum: $cast, checking the function result.
Serializing packed data: bit-stream cast or the streaming operator.
Real to integer with a chosen direction: $floor or $ceil first, since plain cast rounds.
Truncation, both extension rules, the mask idiom, and three static casts with exact expected values.
module tb_casts;
logic [3:0] nib;
logic [15:0] wide;
byte sb;
int errors = 0;
initial begin
nib = 16'hABCD; // silent truncation from the MSB side
if (nib !== 4'hD) errors++;
wide = nib; // unsigned source: zero-extend
if (wide !== 16'h000D) errors++;
sb = -2; // 8'hFE
wide = sb; // signed source: sign-extend
if (wide !== 16'hFFFE) errors++;
wide = byte'(sb) & 8'hFF; // mask idiom recovers 16'h00FE
if (wide !== 16'h00FE) errors++;
if (unsigned'(-1) !== 32'hFFFF_FFFF) errors++;
if (int'(3.7) !== 4) errors++; // real to int rounds, not truncates
if (byte'(16'h1234) !== 8'h34) errors++;
if (errors != 0) $fatal(1, "FAIL: cast checks saw %0d unexpected results", errors);
$display("PASS: truncation kept low bits, signed sources sign-extended, casts converted");
$finish;
end
endmodulePASS: truncation kept low bits, signed sources sign-extended, casts convertednib is logic [3:0] and the code says nib = 16'hABCD. Does the compiler stop you, and what does nib hold?No error; at most a lint warning. nib holds 4'hD, because a narrower target keeps the low bits and drops the rest silently.
$cast instead of a static cast?When the conversion needs a runtime legality check, such as stuffing an integer into an enum. A static cast always succeeds, so it cannot reject an illegal encoding.
Run with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown, with no warnings. Icarus 13 (-g2012) runs them too, with the same output.