8 min read | Beginner

The primary intent of data-types in the Verilog language is to represent data storage elements like bits in a flip-flop and transmission elements like wires that connect between logic gates and sequential structures.

What You'll Learn

  • Understand Verilog's 4-value logic system (0, 1, X, Z) and its hardware implications
  • Master the difference between nets (wire) and variables (reg) for proper hardware modeling
  • Use integer, time, real, and string data types for testbench and modeling tasks
  • Recognize when each data type is appropriate for synthesis vs simulation

What values do variables hold ?

Almost all data-types can only have one of the four different values as given below except for real and event data types.

0represents a logic zero, or a false condition
1represents a logic one, or a true condition
xrepresents an unknown logic value (can be zero or one)
zrepresents a high-impedance state

The following image shows how these values are represented in timing diagrams and simulation waveforms. Most simulators use this convention where red stands for X and orange in the middle stands for high-impedance or Z.

verilog value system

What does the verilog value-set imply ?

Since Verilog is essentially used to describe hardware elements like flip-flops and combinational logic like NAND and NOR, it has to model the value system found in hardware. A logic one would represent the voltage supply Vdd which can range anywhere between 0.8V to more than 3V based on the fabrication technology node. A logic zero would represent ground and hence a value of 0V.

X or x means that the value is simply unknown at the time, and could be either 0 or 1. This is quite different from the way X is treated in boolean logic, where it means "don't care".

As with any incomplete electric circuit, the wire that is not connected to anything will have a high-impedance at that node and is represented by Z or z. Even in verilog, any unconnected wire will result in a high impedance.

Nets and Variables

Nets and variables are the two main groups of data types which represent different hardware structures and differ in the way they are assigned and retain values.

Nets

Nets are used to connect between hardware entities like logic gates and hence do not store any value on its own. In the image shown below, a net called net_11 is used to connect between the output of the AND gate to the first input of the flip-flop called data_0. In a similar way, the two inputs of the AND gate are connected to nets net_45 and net_67.

nets and variables

There are different types of nets each with different characteristics, but the most popular and widely used net in digital designs is of type wire. A wire is a Verilog data-type used to connect elements and to connect nets that are driven by a single gate or continuous assignment. The wire is similar to the electrical wire that is used to connect two components on a breadboard.

Breadboard wires

When there is a requirement for mulitple nets, they can be bunched together to form a single wire. In the image shown below, we have a 4-bit wire that can send 4 separate values on each one of the wires. Such entities with a width more than 1 are called vectors as we shall see in the next article.


wire [3:0] 	n0; 		// 4-bit wire -> this is a vector
wire type net

It is illegal to redeclare a name already declared by a net, parameter or variable as shown in the code below.


module design;
	wire abc;
	wire 	a;
	wire 	b;
	wire 	c;
	
	wire abc; // Error: Identifier "abc" previously declared
	
	assign abc = a & b | c;
endmodule

Variables

reg type variable

A variable on the other hand is an abstraction of a data storage element and can hold values. A flip-flop is a good example of a storage element.

Verilog data-type reg can be used to model hardware registers since it can hold values between assignments. Note that a reg need not always represent a flip-flop because it can also be used to represent combinational logic.

In the image shown on the left, we have a flip-flop that can store 1 bit and the flip-flop on the right can store 4-bits.

flip-flops with different widths

Other data-types

integer

An integer is a general purpose variable of 32-bits wide that can be used for other purposes while modeling hardware and stores integer values.


 // integer: 32-bit signed data type for general purpose use
 integer count; // Can hold values from -2,147,483,648 to 2,147,483,647

time

A time variable is unsigned, 64-bits wide and can be used to store simulation time quantities for debugging purposes. A realtime variable simply stores time as a floating point quantity.


 // time: 64-bit unsigned for storing simulation time (integer representation)
 time end_time; // Example: end_time = 50 (represents 50 time units)

 // realtime: 64-bit floating point for high-precision time measurements
 realtime rtime; // Example: rtime = 40.25 (can represent fractional time units)

real

A real variable can store floating point values and can be assigned the same way as integer and reg.


 // real: 64-bit floating point for mathematical calculations
 real float; // Example: float = 12.344 (double-precision IEEE 754)

Datatypes Example


module testbench;
 integer 	int_a; 				// Integer variable (32-bit signed)
 real 		real_b; 			// Real variable (64-bit floating point)
 time 		time_c; 			// Time variable (64-bit unsigned)

 initial begin
 int_a 	= 32'hcafe_1234; 	// Assign hexadecimal value to integer
 real_b 	= 0.1234567; 		// Assign floating point value to real

 #20; 						// Advance simulation time by 20 units
 time_c 	= $time; 			// Capture current simulation time ($time returns time type)

 // Print all variables using $display with appropriate format specifiers
 $display ("int_a 	= 0x%0h", int_a);   // %0h = hexadecimal format
 $display ("real_b 	= %0.5f", real_b);  // %0.5f = 5 decimal places
 $display ("time_c 	= %0t", time_c);    // %0t = time format
 end
endmodule
Output
ncsim> run
int_a 	= 0xcafe1234
real_b 	= 0.12346
time_c 	= 20
ncsim: *W,RNQUIE: Simulation is complete.

Verilog Strings

Strings are stored in reg, and the width of the reg variable has to be large enough to hold the string. Each character in a string represents an ASCII value and requires 1 byte. If the size of the variable is smaller than the string, then Verilog truncates the leftmost bits of the string. If the size of the variable is larger than the string, then Verilog adds zeros to the left of the string.


// String storage: Each ASCII character requires 8 bits (1 byte)
// "Hello World" = 11 characters = 11 bytes = 88 bits

// Exact fit: 11 characters * 8 bits = 88 bits [8*11:1]
reg [8*11:1] str = "Hello World"; // Stores full string: "Hello World"

// Too small: Only 5 bytes (40 bits), leftmost 6 bytes truncated
reg [8*5:1] str = "Hello World";  // Stores rightmost 5 chars: "World"

// Too large: 20 bytes (160 bits), leftmost bits padded with zero bytes (ASCII NUL, not actual spaces)
reg [8*20:1] str = "Hello World"; // Stores "Hello World" with 9 leading NUL bytes, which print as blank

Here is a full example showing how the three variables given above can be simulated.


module testbench;
 reg [8*11:1] str1;
 reg [8*5:1] str2;
 reg [8*20:1] str3;
 
 initial begin
 str1 = "Hello World";
 str2 = "Hello World";
 str3 = "Hello World";
 
 $display ("str1 = %s", str1);
 $display ("str2 = %s", str2);
 $display ("str3 = %s", str3);
 end
endmodule

Note that str1 has the right size to store all 11 bytes of the string "Hello World" and hence the whole string gets printed. However str2 can store only 5 bytes and hence the upper 6 bytes get truncated and end up with storing only "World". The third variable str3 is larger than 11 bytes and pads zero bytes (ASCII NUL, not actual space characters) to the left, which print as blank, so the value stored in it appears as "Hello World" preceded by 9 non-printing NUL bytes.

Output
ncsim> run
str1 = Hello World
str2 = World
str3 =  Hello World
ncsim: *W,RNQUIE: Simulation is complete.

Data Type Comparison

Data TypeWidthSigned/UnsignedPrimary UseSynthesizable
wireUser-defined (default 1-bit)Unsigned (4-state: 0,1,X,Z)Connecting hardware elements (nets)Yes - represents physical wires
regUser-defined (default 1-bit)Unsigned (4-state: 0,1,X,Z)Procedural assignments, storageYes - may infer flip-flops or combinational logic
integer32 bitsSignedLoop counters, arithmetic operationsPartial - primarily for testbenches
time64 bitsUnsignedStoring simulation timeNo - simulation only
real64 bits (IEEE 754 double)Signed floating pointFloating point calculations, delaysNo - simulation only
realtime64 bits (IEEE 754 double)Signed floating pointHigh-precision time measurementsNo - simulation only

Nets vs Variables

AspectNets (wire)Variables (reg)
Hardware ModelPhysical wires connecting componentsStorage elements (flip-flops) or combinational logic
Value RetentionNo storage - value driven by sourceRetains value until reassigned
AssignmentContinuous assignment (assign)Procedural assignment (always, initial)
Default ValueZ (high-impedance)X (unknown)
Multiple DriversAllowed (resolves to X if conflicting)Not allowed - last assignment wins
Example UseModule interconnections, combinational outputsSequential logic outputs, procedural blocks
Synthesis ResultPhysical wires in netlistFlip-flops (if clocked) or combinational gates

Real-World Application: Simulation vs Synthesis Data Types

In verification testbenches, integer, time, real, and string types are extensively used for stimulus generation, scoreboarding, and performance measurement. For example, integer counters track transaction counts, real variables calculate bandwidth (Gbps), and time variables measure latency. However, synthesis tools ignore these types during RTL synthesis - only wire and reg (with their vectors) synthesize to actual hardware. Understanding this distinction prevents common mistakes where testbench code accidentally includes non-synthesizable types in design modules.

Real-World Application: X-Propagation in Pre-Silicon Verification

The 4-value logic system (0, 1, X, Z) is critical for catching hardware bugs before fabrication. When a flip-flop reset is not properly initialized, it holds X, which propagates through combinational logic. This X-propagation reveals uninitialized paths that would cause unpredictable behavior in silicon. RTL simulation with X-values catches reset tree bugs, clock domain crossing issues, and undriven signals that 2-state logic (the SystemVerilog bit type has no X) would miss. ASIC tapeout flows mandate X-pessimism analysis to ensure no X-dependent paths exist.

Common Beginner Mistakes

Mistake #1: Confusing reg with Hardware Registers


// WRONG: Thinking 'reg' always means flip-flop
always @(*) begin
  sum_reg = a + b;  // Combinational logic, NOT a register!
end

// CORRECT: Understanding 'reg' is just a variable type
// This reg synthesizes to combinational gates (no flip-flop)
always @(*) begin
  sum = a + b;  // Combinational: reg assigned in always @(*)
end

// This reg synthesizes to flip-flop (storage element)
always @(posedge clk) begin
  q <= d;  // Sequential: reg assigned on clock edge
end

Why it's problematic: The name reg is misleading - it does NOT always synthesize to a hardware register (flip-flop). A reg assigned in always @(*) or always @(a or b) blocks synthesizes to combinational logic (AND, OR, MUX gates). Only reg variables assigned on clock edges (always @(posedge clk)) synthesize to flip-flops. This naming confusion causes beginners to incorrectly assume all reg declarations create storage.

Best Practice: Use naming conventions to clarify intent: suffix _r or _reg for true registers (clocked), and _c or _comb for combinational logic. For example: reg data_r (flip-flop) vs reg sum_c (combinational). Better yet, use SystemVerilog logic type which replaces both wire and reg without the misleading naming.

Mistake #2: Using integer/real in Synthesizable RTL


// WRONG: Using non-synthesizable types in design module
module counter (input clk, rst, output reg [7:0] count);
  integer i;  // Non-synthesizable type in RTL design!
  always @(posedge clk) begin
    i = i + 1;
    count <= i[7:0];
  end
endmodule

// CORRECT: Use synthesizable types (reg with explicit width)
module counter (input clk, rst, output reg [7:0] count);
  always @(posedge clk) begin
    if (rst)
      count <= 8'b0;
    else
      count <= count + 1;  // count is reg [7:0], fully synthesizable
  end
endmodule

Why it's problematic: Synthesis tools either reject integer, real, time types or synthesize them unpredictably. integer is 32-bit signed, but you may only need 8 bits, wasting area. real cannot synthesize at all - no FPGA/ASIC has floating point hardware unless explicitly instantiated. Using these types in RTL causes synthesis errors or massive resource usage. Testbenches can freely use these types, but design modules must stick to wire and reg with explicit bit widths.

Best Practice: Reserve integer, real, time for testbench-only code. In RTL, always use explicit bit-width declarations: reg [15:0] counter instead of integer counter. If you need signed arithmetic, use reg signed [15:0] or SystemVerilog int. This ensures predictable synthesis and optimal area.

Mistake #3: Incorrectly Sizing Strings Leading to Truncation


// WRONG: Undersized string variable (silent truncation)
reg [8*4:1] msg;
initial begin
  msg = "Hello World";  // Only stores "orld" (rightmost 4 chars)!
  $display("Message: %s", msg);  // Prints "orld" - rest silently lost
end

// CORRECT: Size string variable to match content
reg [8*11:1] msg;  // 11 characters * 8 bits = 88 bits
initial begin
  msg = "Hello World";  // Stores full string
  $display("Message: %s", msg);  // Prints "Hello World" correctly
end

Why it's problematic: Verilog silently truncates strings that don't fit in the declared width, keeping only the rightmost characters. This causes cryptic debug output where messages are missing critical information. Unlike C, Verilog has no runtime error for buffer overflow - truncation happens during compilation/elaboration without warning. Long diagnostic messages or file paths get truncated, hiding valuable debug data.

Best Practice: Always calculate string size as [8*LENGTH:1] where LENGTH is the exact character count. For variable-length strings, over-allocate: reg [8*256:1] msg for messages up to 256 chars (extra bits are padded with zero (NUL) bytes). Use `define MAX_STR_LEN 256 for consistency. In SystemVerilog, use the string type which is dynamic and avoids sizing issues entirely.

Mistake #4: Misunderstanding X and Z Values


// WRONG: Treating X as "don't care" like in Karnaugh maps
reg [1:0] sel = 2'bxx;  // Thinking X means "any value is fine"
if (sel == 2'b00) ...   // This comparison FAILS - X never equals anything!

// CORRECT: X means "unknown", not "don't care"
reg [1:0] sel = 2'b00;  // Initialize to known value
if (sel == 2'b00) ...   // Now comparison works

// CORRECT: Understand Z means high-impedance (tri-state)
wire bus;
assign bus = enable ? data : 1'bz;  // Z allows multiple drivers
// When enable=0, bus is high-impedance (not driven)

Why it's problematic: X (unknown) is often confused with "don't care" from digital design theory. In simulation, X == anything always returns false (or X). Comparisons with X never match expected values, causing conditional statements to take unexpected paths. Z (high-impedance) is not the same as 0 or X - it represents a tri-state buffer output in the off state. Misusing Z in non-tri-state contexts causes multi-driver conflicts.

Best Practice: Initialize all registers to known values (0 or specific reset value) to avoid X-propagation. Use X deliberately in testbenches to verify design robustness (X-injection testing). Only use Z on nets that explicitly need tri-state capability (buses with multiple drivers). For "don't care" optimization in synthesis, use casez with ? instead of relying on X. See our tutorial on Casex and Casez for don't-care handling.

Summary

In this tutorial, you learned:

  • Verilog uses a 4-value logic system (0, 1, X, Z) to accurately model hardware behavior including unknown and high-impedance states
  • Nets (wire) represent physical connections and are driven by continuous assignments; variables (reg) represent storage or procedural logic
  • integer, time, real, realtime are primarily simulation-only types for testbenches and do not synthesize to hardware
  • String storage requires explicit sizing ([8*N:1] for N characters) with truncation/padding rules based on declared width
  • The reg data type name is misleading - it synthesizes to combinational logic or flip-flops depending on always block sensitivity
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5