An array declaration of a net or variable can be either scalar or vector. Any number of dimensions can be created by specifying an address range after the identifier name and is called a multi-dimensional array. Arrays are allowed in Verilog for reg, wire, integer and real data types.

  
  
	reg        y1 [11:0];        // y is an scalar reg array of depth=12, each 1-bit wide
	wire [0:7] y2 [3:0]          // y is an 8-bit vector net with a depth of 4
	reg  [7:0] y3 [0:1][0:3];    // y is a 2D array rows=2,cols=4 each 8-bit wide

  

An index for every dimension has to be specified to access a particular element of an array and can be an expression of other variables. An array can be formed for any of the different data-types supported in Verilog.

Note that a memory of n 1-bit reg is not the same as an n-bit vector reg.

Assignment

  
  
	y1 = 0; 						// Illegal - All elements can't be assigned in a single go
	
	y2[0] = 8'ha2; 			// Assign 0xa2 to index=0 
	y2[2] = 8'h1c; 			// Assign 0x1c to index=2
	y3[1][2] = 8'hdd; 	// Assign 0xdd to rows=1 cols=2
	y3[0][0] = 8'haa; 	// Assign 0xaa to rows=0 cols=0

  

Example

The code shown below simply shows how different arrays can be modeled, assigned and accessed. mem1 is an 8-bit vector, mem2 is an 8-bit array with a depth of 4 (specified by the range [0:3]) and mem3 is a 16-bit vector 2D array with 4 rows and 2 columns. These variables are assigned different values and printed.

  
  
module des ();
  reg [7:0]  mem1; 							// reg vector 8-bit wide
  reg [7:0]  mem2 [0:3]; 				// 8-bit wide vector array with depth=4
  reg [15:0] mem3 [0:3][0:1]; 	// 16-bit wide vector 2D array with rows=4,cols=2
  
  initial begin
    int i;
    
    mem1 = 8'ha9;
    $display ("mem1 = 0x%0h", mem1);
    
    mem2[0] = 8'haa;
    mem2[1] = 8'hbb;
    mem2[2] = 8'hcc;
    mem2[3] = 8'hdd;
    for(i = 0; i < 4; i = i+1) begin
      $display("mem2[%0d] = 0x%0h", i, mem2[i]);
    end
    
    for(int i = 0; i < 4; i += 1) begin
      for(int j = 0; j < 2; j += 1) begin
        mem3[i][j] = i + j;
        $display("mem3[%0d][%0d] = 0x%0h", i, j, mem3[i][j]);
      end
    end
  end
endmodule

  
Simulation Log

ncsim> run
mem1 = 0xa9
mem2[0] = 0xaa
mem2[1] = 0xbb
mem2[2] = 0xcc
mem2[3] = 0xdd
mem3[0][0] = 0x0
mem3[0][1] = 0x1
mem3[1][0] = 0x1
mem3[1][1] = 0x2
mem3[2][0] = 0x2
mem3[2][1] = 0x3
mem3[3][0] = 0x3
mem3[3][1] = 0x4
ncsim: *W,RNQUIE: Simulation is complete.

Memories

Memories are digital storage elements that help store a data and information in digital circuits. RAMs and ROMs are good examples of such memory elements. Storage elements can be modeled using one-dimensional arrays of type reg and is called a memory. Each element in the memory may represent a word and is referenced using a single array index.

memory array in verilog

Register Vector

Verilog vectors are declared using a size range on the left side of the variable name and these get realized into flops that match the size of the variable. In the code shown below, the design module accepts clock, reset and some control signals to read and write into the block.

It contains a 16-bit storage element called register which simply gets updated during writes and returns the current value during reads. The register is written when sel and wr are high on the same clock edge. It returns the current data when sel is high and wr is low.

  
  
module des (    input           clk,
                input           rstn,
                input           wr,
                input           sel,
                input [15:0]    wdata,
                output [15:0]   rdata);

	reg [15:0] register;

	always @ (posedge clk) begin
    if (!rstn)
    	register <= 0;
    else begin
    	if (sel & wr) 
      	register <= wdata;
    	else
      	register <= register;
    end
	end

	assign rdata = (sel & ~wr) ? register : 0;
endmodule

  

The hardware schematic shows that a 16-bit flop is updated when control logic for writes are active and the current value is returned when control logic is configured for reads.

Array

In this example, register is an array that has four locations with each having a width of 16-bits. The design module accepts an additional input signal which is called addr to access a particular index in the array.

  
  
module des (    input           clk,
                input           rstn,
                input  [1:0]    addr,
                input           wr,
                input           sel,
                input [15:0]    wdata,
                output [15:0]   rdata);

reg [15:0] register [0:3];
integer i;

always @ (posedge clk) begin
    if (!rstn) begin
        for (i = 0; i < 4; i = i+1) begin 
            register[i] <= 0;
        end
    end else begin
        if (sel & wr) 
            register[addr] <= wdata;
        else
            register[addr] <= register[addr];
    end
end
 
assign rdata = (sel & ~wr) ? register[addr] : 0;
endmodule

  

It can be seen in the hardware schematic that each index of the array is a 16-bit flop and the input address is used to access a particular set of flops.

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

0 represents a logic zero, or a false condition
1 represents a logic one, or a true condition
x represents an unknown logic value (can be zero or one)
z represents 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

A structure can contain elements of different data types which can be referenced as a whole or individually by their names. This is quite different from arrays where the elements are of the same data-type.

  
  
	// Normal arrays -> a collection of variables of same data type
	int array [10];         // all elements are of int type
	bit [7:0] mem [256];    // all elements are of bit type
	
	// Structures -> a collection of variables of different data types
	struct {
		byte    val1;
		int     val2;
		string  val3;
	} struct_name;

  

Syntax

  
  
	struct { 
		[list of variables]
	} struct_name;

  

Unpacked Structures

A structure is unpacked by default and can be defined using the struct keyword and a list of member declarations can be provided within the curly brackets followed by the name of the structure.

Structure Example

  
  
module tb;  
  	// Create a structure called "st_fruit"
	// which to store the fruit's name, count and expiry date in days.
  	// Note: this structure declaration can also be placed outside the module
	struct {
  		string fruit;
  		int    count;
  		byte 	 expiry;  
	} st_fruit;
  
  initial begin
    // st_fruit is a structure variable, so let's initialize it
    st_fruit = '{"apple", 4, 15};
    
    // Display the structure variable
    $display ("st_fruit = %p", st_fruit);
    
    // Change fruit to pineapple, and expiry to 7 
    st_fruit.fruit = "pineapple";
    st_fruit.expiry = 7;
    $display ("st_fruit = %p", st_fruit);
  end
endmodule

  
Simulation Log

ncsim> run
st_fruit = '{fruit:"apple", count:4, expiry:'hf}
st_fruit = '{fruit:"pineapple", count:4, expiry:'h7}
ncsim: *W,RNQUIE: Simulation is complete.

What is the need to typedef a structure ?

Only one variable was created in the example above, but if there's a need to create multiple structure variables with the same constituents, it'll be better to create a user defined data type of the structure by typedef. Then st_fruit will become a data-type which can then be used to create variables of that type.

  
  
module tb;  
  	// Create a structure called "st_fruit"
	// which to store the fruit's name, count and expiry date in days.
  	// Note: this structure declaration can also be placed outside the module
	typedef struct {
  		string fruit;
  		int    count;
  		byte 	 expiry;  
	} st_fruit;
  
  initial begin
    // st_fruit is a data type, so we need to declare a variable of this data type
    st_fruit fruit1 = '{"apple", 4, 15};
    st_fruit fruit2;
    
    // Display the structure variable
    $display ("fruit1 = %p fruit2 = %p", fruit1, fruit2);
    
    // Assign one structure variable to another and print
    // Note that contents of this variable is copied into the other
   	fruit2 = fruit1;
    $display ("fruit1 = %p fruit2 = %p", fruit1, fruit2);
    
    // Change fruit1 to see if fruit2 is affected
    fruit1.fruit = "orange";
    $display ("fruit1 = %p fruit2 = %p", fruit1, fruit2);
  end
endmodule

  
Simulation Log

ncsim> run
fruit1 = '{fruit:"apple", count:4, expiry:'hf} fruit2 = '{fruit:"", count:0, expiry:'h0}
fruit1 = '{fruit:"apple", count:4, expiry:'hf} fruit2 = '{fruit:"apple", count:4, expiry:'hf}
fruit1 = '{fruit:"orange", count:4, expiry:'hf} fruit2 = '{fruit:"apple", count:4, expiry:'hf}
ncsim: *W,RNQUIE: Simulation is complete.

Packed Structures

A packed structure is a mechanism for subdividing a vector into fields that can be accessed as members and are packed together in memory without gaps. The first member in the structure is the most significant and subsequent members follow in decreasing order of significance.

A structure is declared packed using the packed keyword which by default is unsigned.

Example

  
  
// Create a "packed" structure data type which is similar to creating 
// bit [7:0]  ctrl_reg;
// ctrl_reg [0]   represents en
// ctrl_reg [3:1] represents cfg
// ctrl_reg [7:4] represents mode
typedef struct packed {
  bit [3:0] mode;
  bit [2:0] cfg;
  bit       en;
} st_ctrl;

module tb;
  st_ctrl    ctrl_reg;
  
  initial begin 
    // Initialize packed structure variable
    ctrl_reg = '{4'ha, 3'h5, 1};
    $display ("ctrl_reg = %p", ctrl_reg);
    
    // Change packed structure member to something else
    ctrl_reg.mode = 4'h3;
    $display ("ctrl_reg = %p", ctrl_reg);
   
    // Assign a packed value to the structure variable
    ctrl_reg = 8'hfa;
    $display ("ctrl_reg = %p", ctrl_reg);
  end  
endmodule	

  
Simulation Log

ncsim> run
ctrl_reg = '{mode:'ha, cfg:'h5, en:'h1}
ctrl_reg = '{mode:'h3, cfg:'h5, en:'h1}
ctrl_reg = '{mode:'hf, cfg:'h5, en:'h0}
ncsim: *W,RNQUIE: Simulation is complete.

In complex testbenches some variable declarations might have a longer data-type specification or require to be used in multiple places in the testbench.

In such cases we can use a typedef to give a user-defined name to an existing data type. The new data-type can then be used throughout the code and hence avoids the need to edit in multiple places if required.

  
  
	// Normal declaration may turn out to be quite long
	unsigned shortint  			my_data;
	enum {RED, YELLOW, GREEN} 	e_light;
	bit [7:0]  					my_byte;
	
	// Declare an alias for this long definition
	typedef unsigned shortint 			u_shorti;
	typedef enum {RED, YELLOW, GREEN} 	e_light;
	typedef bit [7:0]  					ubyte;

	// Use these new data-types to create variables
	u_shorti    my_data;
	e_light     light1;
	ubyte       my_byte;

  

Syntax

  
  
	typedef data_type type_name [range];

  

Example

  
  
module tb;
  typedef shortint unsigned u_shorti;
  typedef enum {RED, YELLOW, GREEN} e_light;
  typedef bit [7:0] ubyte;
 
  initial begin
    u_shorti 	data = 32'hface_cafe;
    e_light 	light = GREEN;
    ubyte 		cnt = 8'hFF;
    
    $display ("light=%s data=0x%0h cnt=%0d", light.name(), data, cnt);
  end
endmodule

  
Simulation Log

ncsim> run
light=GREEN data=0xcafe cnt=255
ncsim: *W,RNQUIE: Simulation is complete.

An event is a static object handle to synchronize between two or more concurrently active processes. One process will trigger the event, and another process waits for the event.

  • Can be assigned or compared to other event variables
    • Can be assigned to null
    • When assigned to another event, both variables point to same synchronization object
  • Can be passed to queues, functions and tasks