FPGA design art (18) how to use arrays in Verilog to model memory?

preface

The two-dimensional array in Verilog is very useful. You can use for and generate for together with the two-dimensional array to replace a large number of registers. In fact, a large number of similar registers can be replaced by memory. In Verilog, you can use the two-dimensional array to model the memory.

What resources of FPGA make up the memory can be set by comprehensive attributes or automatically set by comprehensive tools. This is not the focus of this article. For details, see: Vivado Essays (1) ram of comprehensive attributes_ style & rom_ style?

Let's look at arrays in Verilog and how to use arrays to model memory.

storage

Memory is a digital storage element that helps store data and information in digital circuits. The models of RAM, ROM and other memories in FPGA are probably as follows. In this way, we can design different bit widths and depths:

In FPGA, we can customize the IP check storage components provided by EDA tools, and of course, we can also use the array in Verilog for modeling. It is described below:

Arrays in Verilog

We can also create and use array types in verilog. These are particularly useful in memory modeling.

In order to declare an array in verilog, we only need to add an additional field after the variable name to declare how many elements are in the array.

The following code snippet shows the general syntax for declaring array types in Verilog. We use fields to declare the size of the array.

// General syntax to declare an array type

<type> <size> <variable_name> <elements>;

For example, suppose we want to create an array of 4-bit wire types. We want a total of 16 elements in the array. The following Verilog code shows how to create this array.

wire [3:0] example [15:0];

We can use square brackets to access each element in the array type. For example, the following verilog code shows how to assign the value of Fh to the first element in the sample array.

assign example[0] = 4'hF;

Arrays allow Verilog as 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 [7:0] y2 [3:0]          // y is an 8-bit vector net with a depth of 4

The index of each dimension must be specified to access specific elements of the array and can be expressions of other variables. You can form an array for any different data types supported by Verilog.

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

Multidimensional array

In the verilog 1995 standard, we can only create one-dimensional arrays, such as those used in the previous section.

However, when we use the verilog 2001 standard, we can also create arrays with multiple dimensions.

To do this, we simply add another field to define the number of elements required.

The following code snippet shows the general syntax for creating 2D arrays in verilog.

// General syntax to declare an array type

<type> <size> <variable_name> <elements> <elements>;

For example, let's consider the case where we want to change the size of the array from the previous example.

Now, we want to create a variable that can store two elements, both of which have 16 4-bit wire elements.

To do this, we just need to add an additional field at the end of the declaration. The following code snippet shows how we will do this.

wire [3:0] example [15:0][1:0];

reg  [7:0] y3 [0:1][0:3];    // y is a 2D array rows=2,cols=4 each 8-bit wide

We also assign values to multidimensional arrays in the same way as one-dimensional arrays. However, we now use square brackets to define elements on both dimensions of the array.

For example, suppose we want to assign the value of Ah to the last element in two dimensions. The following verilog code shows how to allocate data to this element in the array.

The same principle as the array above:

The index of each dimension must be specified to access specific elements of the array and can be expressions of other variables. You can form an array for any different data types supported by Verilog.

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 [15][1] = 4'ha;

example [15][0] = 4'ha;

Array modeling and initialization example

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

Finally, the printing results can be obtained:

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

Give an example of memory logic design:

In this example, the memory is an array with a depth of 4 and a width of 16 bits per depth. The design module accepts an additional input signal called addr to access a specific index in the memory.

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

As can be seen in the hardware diagram, each index of the memory is a 16 bit trigger, and the input address is used to access a specific set of triggers.

Tags: FPGA

Posted on Tue, 21 Sep 2021 07:20:07 -0400 by ThEMakeR