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.
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:
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;
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.
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 Type | Resolution (multiple drivers) | Use Case |
|---|---|---|
wire | X if multiple drivers conflict | Single-driver connections — 99% of RTL |
tri | Same as wire | Explicit tri-state buses (same as wire, different intent) |
wand | AND of all drivers | Wired-AND buses (open-drain, e.g. I2C) |
wor | OR of all drivers | Wired-OR buses |
3. Variable Types (reg, integer, real, time)
Variable types hold their value until explicitly changed in a procedural block (always or initial).
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
| Type | Width | Signed? | Synthesizable? | Common Use |
|---|---|---|---|---|
reg | 1 to 2³²−1 bits | No (unless signed) | ✅ Yes | All RTL variables driven by always |
integer | 32 bits | Yes | ⚠️ Caution | For-loop counters in simulation; avoid in RTL |
real | IEEE 754 double | Yes | ❌ No | Behavioral models, ADC/DAC models |
time | 64 bits | No | ❌ No | Simulation timestamps ($time) |
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.
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 (...);
parameter | localparam | |
|---|---|---|
| Overridable at instantiation | ✅ Yes | ❌ No |
| Visible outside module | ✅ Yes | ❌ No |
| Use for | Configurable 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.
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.
// 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);
[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).
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
$signed() cast when mixing: $signed(a) + $signed(b). Arithmetic right shift (>>>) respects the signed attribute; logical right shift (>>) does not.8. Type Summary Table
| Type | Family | Default Value | Driver | Synthesizable |
|---|---|---|---|---|
wire | Net | Z | assign / module output | ✅ |
tri | Net | Z | Multiple drivers (tri-state) | ✅ |
reg | Variable | X | always / initial block | ✅ |
integer | Variable | X (32-bit) | always / initial block | ⚠️ Avoid in RTL |
real | Variable | 0.0 | always / initial block | ❌ |
time | Variable | 0 | always / initial block | ❌ |
parameter | Constant | Declared value | Declaration | ✅ |
localparam | Constant | Declared value | Declaration | ✅ |
9. Practical Examples
Parameterizable N-bit Counter
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)
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
// 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