Tutorial 03 · Verilog Series

Verilog Data Types

Verilog has two families of types: net types (wire) that model connections, and variable types (reg, integer, real) that model storage. Understanding which to use — and why — is the foundation of synthesizable RTL.

wire reg integer / real / time parameter / localparam 4-value logic (0,1,X,Z) Vectors & Arrays
0 Logic Low driven to GND 1 Logic High driven to VDD X Unknown uninitialized / conflict Z High Impedance floating / no driver
Verilog 4-value logic system — X and Z exist only in simulation; real silicon only has 0 and 1.

1. The 4-Value Logic System

Unlike software languages where a variable is simply 0 or 1 (or any integer), Verilog models real hardware signals with four possible values per bit:

0Logic Low — driven to GND / false
1Logic High — driven to VDD / true
XUnknown — uninitialized reg, conflicting drivers
ZHigh-Z — no driver, floating wire

X (unknown) propagates through logic — an X input to an AND gate can produce X output. This is intentional: it forces you to initialize signals before use and write complete sensitivity lists.

Z (high-impedance) is what you get when a wire has no driver. It is used intentionally in tri-state buffers: assign bus = en ? data : 8'bz;

Synthesis note: X and Z are simulation-only concepts. Your synthesis tool maps X to don't-care (for optimization) and any unresolved Z to a pull-up or pull-down depending on technology. Never rely on X or Z behavior in synthesizable RTL logic.

2. Net Types (wire, tri, wand, wor)

Net types model physical wires. They must have a driver at all times — if no driver is connected, they default to Z.

verilog
wire       clk;           // 1-bit wire
wire [7:0]  data_bus;      // 8-bit bus
wire        a, b, y;       // multiple wires in one declaration

assign y = a & b;           // wire driven by assign

Other net types (rarely needed in modern RTL):

Net TypeResolution (multiple drivers)Use Case
wireX if multiple drivers conflictSingle-driver connections — 99% of RTL
triSame as wireExplicit tri-state buses (same as wire, different intent)
wandAND of all driversWired-AND buses (open-drain, e.g. I2C)
worOR of all driversWired-OR buses

3. Variable Types (reg, integer, real, time)

Variable types hold their value until explicitly changed in a procedural block (always or initial).

verilog
reg         flag;           // 1-bit register
reg [7:0]   count;          // 8-bit register
reg [31:0]  accumulator;    // 32-bit register

integer     loop_var;       // 32-bit signed — use in for loops
real        voltage;        // double-precision float — simulation only
time        t_start;        // 64-bit unsigned — simulation timestamps
TypeWidthSigned?Synthesizable?Common Use
reg1 to 2³²−1 bitsNo (unless signed)✅ YesAll RTL variables driven by always
integer32 bitsYes⚠️ CautionFor-loop counters in simulation; avoid in RTL
realIEEE 754 doubleYes❌ NoBehavioral models, ADC/DAC models
time64 bitsNo❌ NoSimulation timestamps ($time)
The reg ≠ register rule: reg is a simulation type, not a hardware promise. In a combinational always @(*) block, a reg synthesizes to combinational logic (no flip-flop). In a clocked always @(posedge clk) block, it synthesizes to a flip-flop. The synthesis tool decides — not the keyword.

4. Parameters & localparams

Parameters are named constants. They make your design configurable and readable.

verilog
module shift_reg #(
    parameter WIDTH  = 8,   // overridable from parent
    parameter DEPTH  = 4
) (
    input              clk, rst_n,
    input  [WIDTH-1:0] d_in,
    output [WIDTH-1:0] d_out
);
    localparam STAGES = DEPTH - 1;  // derived, cannot be overridden
    reg [WIDTH-1:0] sr [0:STAGES];
    // ... logic ...
endmodule

// Instantiate with custom parameters:
shift_reg #(.WIDTH(16), .DEPTH(8)) u_sr (...);
parameterlocalparam
Overridable at instantiation✅ Yes❌ No
Visible outside module✅ Yes❌ No
Use forConfigurable constants (bus width, depth)Internal constants, computed from other params

5. Vectors (Multi-bit Signals)

Vectors group multiple bits into a single named signal. The [MSB:LSB] range is declared before the signal name.

verilog
wire [7:0] byte_bus;         // 8 bits
reg  [15:0] word;             // 16 bits
reg  [31:0] dword;            // 32 bits

// Bit-select: pick one bit
wire msb = byte_bus[7];

// Part-select: pick a range of bits
wire [3:0] upper_nibble = byte_bus[7:4];
wire [3:0] lower_nibble = byte_bus[3:0];

// Concatenation operator { } — join two 4-bit values into 8-bit
wire [7:0] combined = {upper_nibble, lower_nibble};

// Replication: {3{4'b1010}} → 12'b101010101010
wire [11:0] rep = {3{4'b1010}};

6. Arrays & Memories

Arrays declare multiple instances of a type, accessed by index. They are commonly used to model register files and memories.

verilog
// reg array: 256 entries of 8-bit words (256-byte memory)
reg [7:0] mem [0:255];

// 32-entry 32-bit register file
reg [31:0] regfile [0:31];

// Read and write
always @(posedge clk) begin
    if (wr_en) regfile[wr_addr] <= wr_data;
end
assign rd_data = regfile[rd_addr];  // async read

// Initialize memory from file (simulation)
initial $readmemh("program.hex", mem);
Packed vs Unpacked: The dimension before the name ([7:0] mem) is the packed (bit-width) dimension. The dimension after the name (mem [0:255]) is the unpacked (address) dimension. You can only bit-select from the packed dimension, not the unpacked one.

7. Signed vs Unsigned

By default, all Verilog wire and reg types are unsigned. Adding signed tells the simulator and synthesizer to treat the MSB as a sign bit (two's complement).

verilog
reg        [7:0] u_val;    // unsigned: 0 to 255
reg signed [7:0] s_val;    // signed: -128 to +127

initial begin
    u_val = 8'hFF;      // unsigned: 255
    s_val = 8'hFF;      // signed:   -1

    // Comparison difference
    if (u_val > 8'd127) $display("u: yes"); // 255 > 127: true
    if (s_val > 8'sd0)  $display("s: yes"); // -1 > 0:  false
end
Mixing signed and unsigned in the same expression silently converts both to unsigned. Always use $signed() cast when mixing: $signed(a) + $signed(b). Arithmetic right shift (>>>) respects the signed attribute; logical right shift (>>) does not.

8. Type Summary Table

TypeFamilyDefault ValueDriverSynthesizable
wireNetZassign / module output
triNetZMultiple drivers (tri-state)
regVariableXalways / initial block
integerVariableX (32-bit)always / initial block⚠️ Avoid in RTL
realVariable0.0always / initial block
timeVariable0always / initial block
parameterConstantDeclared valueDeclaration
localparamConstantDeclared valueDeclaration

9. Practical Examples

Parameterizable N-bit Counter

verilog
module counter #(parameter N = 8) (
    input         clk, rst_n,
    output reg [N-1:0] count
);
    always @(posedge clk or negedge rst_n)
        if (!rst_n) count <= {N{1'b0}};
        else        count <= count + 1;
endmodule

Tri-state Bus with wire (Z)

verilog
module tri_driver (
    input      [7:0] data_in,
    input             oe,      // output enable
    output     [7:0] bus
);
    // When oe=0: bus floats to Z (another device can drive it)
    assign bus = oe ? data_in : 8'bz;
endmodule

X Propagation — What to Watch in Simulation

verilog — simulation output
// If rst_n is not driven in the testbench:
// count = 8'hXX (X propagates everywhere)
// Fix: assert reset at t=0 in the testbench
initial begin
    rst_n = 0;   // assert reset
    #20;
    rst_n = 1;   // release reset → count starts at 0
end