Verilog · Chapter 4.4 · Lexical Conventions
Number Representation in Verilog
Every Verilog numeric literal carries three pieces of information: a size, meaning how many bits it occupies, a base such as binary, octal, decimal, or hexadecimal, and the value itself. Get any one of these wrong and the synthesized hardware can do something the simulator quietly hid. This lesson is the working engineer's reference for the full literal syntax, the difference between sized and unsized numbers, and how the unknown and high-impedance values extend across a vector. It also covers signed versus unsigned interpretation, the underscore separator that makes long numbers readable, and the single most common Verilog bug, the silent truncation that happens when an unsized literal defaults to 32 bits and loses your intended value.
Foundation16 min readVerilogLiteralsNumbersSyntax
Chapter 4 · Page 4.4 · Lexical Conventions
The Sized Literal Syntax
Per IEEE 1364-2005 §3.5, the full form of a Verilog numeric literal is:
<size>'<base><value>where:
<size>— the literal's width in bits (decimal, no underscores).'— a single apostrophe; no whitespace allowed before or after.<base>— one letter, case-insensitive:b/B(binary),o/O(octal),d/D(decimal),h/H(hex).<value>— the digits, in the named base. Underscores anywhere inside are ignored (readability only).
8'b1010_1010 // 8-bit binary = 0xAA = 170
8'o252 // 8-bit octal = 0xAA = 170
8'd170 // 8-bit decimal = 0xAA = 170
8'hAA // 8-bit hex = 0xAA = 170
8'h_aa // same — case-insensitive base + value; leading underscore is OKAll four forms above produce identical 8-bit values. Pick the base that makes the intent clearest to a human reader — 8'b1010_1010 if the bit pattern matters, 8'hAA if the value's hex representation is the natural form, 8'd170 if it's a decimal count.
Underscores as Digit Separators
Underscores anywhere inside <value> are stripped by the lexer. Their only purpose is readability.
32'h_DEAD_BEEF // 32-bit hex — group hex by nibbles
32'b1010_1010_1010_1010_1010_1010_1010_1010 // group binary by nibbles
16'd_12_345 // legal but unusual — decimals normally aren't grouped
8'h_F // leading underscore right after base is fineRules:
- Underscores cannot start a
<value>(must follow a base specifier or a digit). - Underscores cannot appear in
<size>. - Underscores are not allowed inside the base specifier itself (
8'_hAAis illegal).
The leading underscore right after the base (8'h_AA) is a popular convention because it makes the boundary between base and value visible at a glance — but it's not required.
The Four Bases
Binary (b)
Each digit is one bit. Value characters are 0, 1, x, X, z, Z, ? (same as z).
8'b0000_0001 // 1
8'b1111_1111 // 255
8'b1010_xxxx // upper nibble = 0xA, lower nibble = unknown
4'bzzzz // four high-Z bitsOctal (o)
Each digit represents 3 bits. Value characters 0–7, plus x, z, ?.
9'o777 // 9-bit value = 511
6'o12 // 6-bit value = 10Octal is rarely used in modern Verilog — hex is almost always more readable.
Decimal (d)
Plain decimal. Value characters 0–9. Cannot use X or Z (use hex / binary if you need 4-state). Decimal is the default base when the literal omits the apostrophe entirely — 42 is the same as 'd42 (unsized — see below).
8'd170 // 170
16'd1000 // 1000Hex (h)
Each digit represents 4 bits. Value characters 0–9, a–f, A–F, plus x, X, z, Z, ?. Hex is the workhorse for register values, addresses, and bit-mask constants.
32'h_DEAD_BEEF // 32-bit value 0xDEADBEEF
8'hFF // 255
8'hx0 // upper nibble = unknown, lower nibble = 04-State Values
Verilog signals carry four states: 0, 1, x (unknown), z (high-Z). Literals can include x and z in any digit position.
4'b0000 // four zeros
4'b1111 // four ones
4'bxxxx // four unknowns — what an uninitialised reg starts as
4'bzzzz // four high-Z — what an unconnected output drives
4'b1x0z // mixed (every bit is independent)
8'hx // upper nibble = unknown, lower nibble = ? (lower defaults to 0)
8'hxx // both nibbles unknown
8'b10xx_zz01 // explicit bit-by-bit mixOne quirk: when fewer digits are supplied than the size requires, Verilog pads with the leftmost digit's character — except x and z, which pad with themselves.
8'h1 // pads with 0 → 8'h01
8'hx // pads with x → 8'bxxxxxxxx
8'hz // pads with z → 8'bzzzzzzzz
8'h1x // pads the missing upper nibble with 0 → 8'h1x ? no — actually padded to 8'h01x but wait — 8 bits = 2 hex digits exactly. Let me reword: 8'h1 is 1 hex digit for an 8-bit value; the upper nibble pads with 0 → 8'h01. 8'hx is 1 hex digit (the x) for 8 bits; padding rule says "leftmost is x" → 8'bxxxxxxxx.In practice you rarely rely on this padding rule. Write the full width explicitly — 8'h01 and 8'h00 — to avoid surprise.
Unsized Literals — The 32-bit Default Trap
If you omit the size, Verilog uses 32 bits. This is the single most common Verilog bug source.
parameter WIDTH = 8;
// ❌ Subtle bugs
reg [WIDTH-1:0] mask = 1; // 1 is 32-bit `0...0001` → fits 8-bit `0000_0001` ✅
reg [WIDTH-1:0] all_ones = -1; // -1 is 32-bit `1...1111` → fits 8-bit `1111_1111` ✅
reg [WIDTH-1:0] high_bit = 1 << WIDTH; // 1 is 32-bit; 1 << 8 = 32'h100; truncated → 8'h00 ❌
// Expected: high bit set. Got: zero.
// ✅ Always size your literals in RTL
reg [WIDTH-1:0] mask_ok = {WIDTH{1'b0}} | 1'b1;
reg [WIDTH-1:0] all_ones_ok = {WIDTH{1'b1}};
reg [WIDTH-1:0] high_bit_ok = 1'b1 << (WIDTH - 1);Signed Literals
Adding s to the base specifier marks the literal as signed for the purposes of sign-extension during width-changing operations.
8'sb_1010_1010 // signed 8-bit binary = -86 (two's complement)
8'sd170 // signed 8-bit decimal — but 170 > 127 → reads as -86 (wraparound)
8'sh_AA // signed 8-bit hex = -86
// Signed literals matter at WIDTH-CHANGING boundaries
wire signed [7:0] a = 8'sb1010_1010; // -86 as signed
wire signed [15:0] a_ext = a; // sign-extends → 16'sb1111_1111_1010_1010 = -86
wire [7:0] b = 8'b1010_1010; // 170 as unsigned
wire [15:0] b_ext = b; // zero-extends → 16'h00AA = 170Without the s, the literal is unsigned regardless of context. To get sign-extension behaviour you need both the literal and the destination to be signed, or use $signed() to cast.
Negative Numbers
-8'd5 is the negation operator applied to the unsigned 8-bit literal 5. The result is 8'b1111_1011 (-5 in two's complement). This is not the same as a signed literal.
wire [7:0] neg_lit = -8'd5; // unary minus on unsigned 5 → 8'b1111_1011 (251 as unsigned)
wire signed [7:0] neg_signed = -8'sd5; // unary minus on signed 5 → -5 as signed
// Use cases:
// In an unsigned context, -8'd5 is 251. Probably not what you wanted.
// In a signed context, -8'sd5 is -5 and arithmetic operations preserve the sign.Best practice: declare the destination signed and use the s literal form when you mean signed arithmetic.
Real Numbers — Simulation Only
Verilog supports IEEE 754 double-precision real literals. They are not synthesisable and live only in testbenches and simulation models.
real pi = 3.14159;
real freq = 100e6; // 100 million → 1e8
real period = 1.0e-9; // 1 ns
real db = 12.5;
real tiny = 1.5e-12; // 1.5 psRules:
- A real literal contains a
.or ane/Eexponent (or both). 1is an integer;1.0is a real.- Real numbers cannot mix with sized integer literals directly — implicit conversion happens but loses precision.
$realtime,$realtobits,$bitstorealexist for real-domain testbench work.
Two-State vs Four-State
Plain Verilog literals are 4-state (can contain X / Z). SystemVerilog adds 2-state literals — declarations like bit [7:0] x = 8'hAA use the 2-state type system. In pure Verilog (.v files, IEEE 1364), every literal lives in the 4-state system; assigning an X to a reg is a normal operation in simulation.
Width-Extension Rules
When a literal is assigned to a wider destination, Verilog extends it based on the literal's signed-ness:
| Literal | Destination width > literal width | Extension |
|---|---|---|
Unsigned (8'hFF) | 16 bits | Zero-extends → 16'h00FF |
Signed (8'sh_FF) | 16 bits | Sign-extends → 16'sh_FFFF (= -1) |
x value | any | Extends with x |
z value | any | Extends with z |
The sign-extension rule fires only when the literal itself is signed (the s in the base). Assigning an unsigned literal to a signed reg does not trigger sign extension — the literal stays zero-extended.
wire [7:0] a_u = 8'hFF;
wire signed [7:0] a_s = 8'sh_FF; // signed -1
wire [15:0] b_u_to_u = a_u; // 16'h00FF — zero-extend
wire signed [15:0] b_s_to_s = a_s; // 16'sh_FFFF (-1) — sign-extend
wire signed [15:0] b_u_to_s = a_u; // 16'sh_00FF (255) — NO sign-extend
// because a_u is unsignedCommon Mistakes
Five number-related production bugs.
- Unsized literal truncation.
reg [7:0] x = 1 << 8;→x = 0. Always size literals in RTL. - Signed/unsigned mix in comparisons.
wire signed [7:0] s = -1; if (s < 0)works. Butif (s < 8'd0)reads8'd0as unsigned, promotessto unsigned (255), then 255 < 0 → false. Silent bug. - Decimal literals with X.
8'dxis illegal — decimal base doesn't support X/Z. Use8'hxor8'bx. - Padding surprise.
8'h1is8'h01, but8'hxis8'bxxxxxxxx. Always write the full width explicitly to avoid this. - Real literals in synthesisable RTL. Synthesisers ignore real-typed declarations or reject them. Keep
realin testbenches only.
Worked Example — Parameterised Reset Value
A small RTL block showing the literal-sizing discipline.
module scratch_reg #(
parameter WIDTH = 16,
parameter [WIDTH-1:0] RESET_VAL = {WIDTH{1'b0}} // parametric all-zero literal
) (
input wire clk,
input wire rst_n,
input wire we,
input wire [WIDTH-1:0] data_in,
output reg [WIDTH-1:0] data_q
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
data_q <= RESET_VAL; // ✅ parametric — works for any WIDTH
else if (we)
data_q <= data_in;
end
endmoduleWhat's right about this:
parameter [WIDTH-1:0] RESET_VALmakes the reset value parametric — instantiator overrides without touching the RTL.- The default
{WIDTH{1'b0}}uses replication of a sized 1-bit literal. Works for anyWIDTH. - If we'd written
parameter RESET_VAL = 0, the literal would be 32-bit-unsized, and a 64-bit instantiation would silently zero-truncate the upper 32 bits. The sized form is the correct pattern.
Instantiating with a non-default reset:
scratch_reg #(
.WIDTH (8),
.RESET_VAL (8'hA5) // ✅ sized literal matches WIDTH
) u_scratch (
.clk (clk),
.rst_n (rst_n),
.we (load_en),
.data_in(data_in),
.data_q (data_q)
);Interview Insights
Summary
Every Verilog numeric literal carries three pieces of information — size, base, and value. The sized form (8'b1010_1010, 32'h_DEAD_BEEF) is universal in synthesisable RTL because the unsized 32-bit default produces silent truncation bugs every working engineer has shipped at least once. The base specifier (b / o / d / h) is a readability choice; underscores inside the value are a separator for human eyes only; the s modifier marks signed-ness for width-changing operations. Real literals exist for testbench timing setup but never synthesise. The next sub-topic covers string handling — Verilog's ASCII-packed-into-reg model and where it differs from SystemVerilog's proper string type.
Beat coverage notes: all required beats present. ⏱ Timing Observation, 🧠 Simulator Scheduling Insight omitted — numbers are pure lexical/value rules; no timing or scheduling angle. The 🏗 Synthesis Concern is the canonical 32-bit-default truncation trap.
Related Tutorials
- Lexical Conventions — Chapter 4 overview; the parent layer.
- Operator Usage — Chapter 4.3; the operators that consume these literals.
- RTL Designing — Chapter 3; the parametric-RTL patterns that need correct literal sizing.