Types, Widths, and Signedness
Use logic, 2-state and 4-state types, sized literals, and conversions without surprises.
Four-state or two-state types
logic is a 4-state type. In addition to 0 and 1, it can hold X for an unknown value and Z for high impedance. These states help simulation expose missing initialization, conflicting drivers, or an undriven bus.
bit is limited to 0 and 1. The atomic integer types byte, shortint, int, and longint are also 2-state types; they are respectively 8, 16, 32, and 64 bits wide and are signed by default. The legacy integer and time types are 4-state. For a vector with an explicit width, bit [N-1:0] is 2-state while logic [N-1:0] is 4-state.
In RTL, logic is usually the clearest choice for ports and internal signals because an X value can expose incomplete reset or driving logic.
logic valid;
logic [15:0] data;
bit enable_model;
int unsigned transaction_count;A 2-state type can speed up some test models, but it converts an unknown value to 0. Do not use it where that conversion could hide an initialization error.
When a DUT input or output must remain 4-state, explicitly checking for unknown values prevents a conversion from masking them:
known_bus_a: assert (!$isunknown({valid, data}))
else $error("valid or data contains X/Z");Width is part of the calculation
The width of an expression depends on its operands, but also on the context in which it is evaluated. In an assignment, a wider destination can cause an addition to be evaluated with greater precision. That context may differ inside a nested expression, a function argument, or when a result is first stored in a narrow variable.
When a carry bit is part of the contract, explicit extension remains the most robust form: it makes the intended precision independent of the surrounding expression.
module add_with_carry (
input logic [7:0] i_a,
input logic [7:0] i_b,
output logic [8:0] o_sum
);
always_comb begin
o_sum = {1'b0, i_a} + {1'b0, i_b};
end
endmoduleThe explicit extension makes the carry bit visible and documents the design choice. Literals such as 8'hA5, 5'd17, or -16'sd3 state their width, base, and sign.
'0 fills the entire destination with zeros, while '1 fills it with ones. This notation follows the destination width and works well for generic resets.
Signed and unsigned
A logic [7:0] vector is unsigned by default. The signed keyword changes comparisons, extensions, and arithmetic shifts.
logic signed [11:0] sample;
logic signed [12:0] extended;
assign extended = $signed({sample[11], sample});Mixing signed and unsigned operands in one expression can produce an unexpected interpretation. Give signals a type that matches their meaning, then use $signed, $unsigned, or a type cast at the exact conversion point.
Casts and checks
A static cast uses type'(expression). It makes a conversion visible during review.
typedef logic signed [15:0] sample_t;
sample_t filtered;
assign filtered = sample_t'(raw_value);A cast does not prevent truncation. If the destination is narrower, upper bits are discarded. Check bounds, compiler warnings, and boundary values in the testbench.
Key takeaways
logicpreservesXandZ, which helps debugging.bit,byte,shortint,int, andlongintare 2-state types and can hide an unknown value.- Operand widths must be chosen before the operation.
- Signedness affects comparisons, extensions, and shifts.
- An explicit cast documents a conversion but cannot prevent truncation.
📝 Test your knowledge - Chapter quiz