Skip to content

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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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).
sized-basics.v — the four bases
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 OK

All 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.

underscores.v — readability tricks
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 fine

Rules:

  • 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'_hAA is 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).

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
8'b0000_0001       // 1
8'b1111_1111       // 255
8'b1010_xxxx       // upper nibble = 0xA, lower nibble = unknown
4'bzzzz            // four high-Z bits

Octal (o)

Each digit represents 3 bits. Value characters 0–7, plus x, z, ?.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
9'o777             // 9-bit value = 511
6'o12              // 6-bit value = 10

Octal 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).

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
8'd170             // 170
16'd1000           // 1000

Hex (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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
32'h_DEAD_BEEF     // 32-bit value 0xDEADBEEF
8'hFF              // 255
8'hx0              // upper nibble = unknown, lower nibble = 0

4-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-state.v — X and Z in literals
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 mix

One 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

unsized-trap.v — the 32-bit default
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

signed-literals.v — signed conversion
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 = 170

Without 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.

negative-numbers.v — operator vs signed
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

reals.v — testbench-only floating-point
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 ps

Rules:

  • A real literal contains a . or an e/E exponent (or both).
  • 1 is an integer; 1.0 is a real.
  • Real numbers cannot mix with sized integer literals directly — implicit conversion happens but loses precision.
  • $realtime, $realtobits, $bitstoreal exist 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:

LiteralDestination width > literal widthExtension
Unsigned (8'hFF)16 bitsZero-extends → 16'h00FF
Signed (8'sh_FF)16 bitsSign-extends → 16'sh_FFFF (= -1)
x valueanyExtends with x
z valueanyExtends 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.

extension.v — what extends to what
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 unsigned

Common Mistakes

Five number-related production bugs.

  1. Unsized literal truncation. reg [7:0] x = 1 << 8;x = 0. Always size literals in RTL.
  2. Signed/unsigned mix in comparisons. wire signed [7:0] s = -1; if (s < 0) works. But if (s < 8'd0) reads 8'd0 as unsigned, promotes s to unsigned (255), then 255 < 0 → false. Silent bug.
  3. Decimal literals with X. 8'dx is illegal — decimal base doesn't support X/Z. Use 8'hx or 8'bx.
  4. Padding surprise. 8'h1 is 8'h01, but 8'hx is 8'bxxxxxxxx. Always write the full width explicitly to avoid this.
  5. Real literals in synthesisable RTL. Synthesisers ignore real-typed declarations or reject them. Keep real in testbenches only.

Worked Example — Parameterised Reset Value

A small RTL block showing the literal-sizing discipline.

rtl/scratch_reg.v — parameter-driven literal sizing
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
 
endmodule

What's right about this:

  • parameter [WIDTH-1:0] RESET_VAL makes 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 any WIDTH.
  • 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.