Friday, May 1, 2020

Memory in Verilog

Memory is basically a storage area that can be modelled using Verilog. As we know, a single flip-flop holds a single bit of data. When a number of these flip-flops are combined, we can get a large storage area.

Memory Parameters:
A memory is characterized by its addr_widthdata_width and depth. The data_width of a memory decides how many bits can be present in a single data. The depth of a memory decides how many of these data can be written to the memory. 
The addr_width indicates the width of the address. The address width must be selected such that it is able to access all the data in memory and is related to depth. 
For an address width of N, we can access 2^N data (in other words, depth will be 2^N)

In Verilog, a memory can be realized by using this statement:
                      reg [DATA_WIDTH-1 : 0] mem [DEPTH : 0]
where DATA_WIDTH and DEPTH can be inserted according to requirement.

Operations:
The basic operations performed on memory are Memory Read and Memory Write.
From the Verilog code given below, the memory works as explained:

Memory Write:
Place write address, write data and write enable on the first clock edge. The data will be immediately written to the write address.

Memory Read:
Place read address and read enable on the first clock edge. The read data will appear from the second clock edge.

Verilog Logic:
Here I have coded a simple dual port memory. This means the memory can be accessed by write and read ports independently. A true dual port memory consists of two sets of write and read ports.
I have used different clocks to control reads and writes to the memory.

Verilog Code:


module Memory (
    wclk,  //write clock
    waddr, //write address
    wen,   //write enable
    wdata, //write data
    rclk,  //read clock
    raddr, //read address
    ren,   //read enable
    rdata  //read data
);


parameter ADDR_WIDTH = 3;
parameter DATA_WIDTH = 16;
parameter DEPTH      = 7;

input wclk;
input [ADDR_WIDTH -1:0] waddr;
input wen;
input [DATA_WIDTH-1:0] wdata;

input rclk;
input [ADDR_WIDTH-1:0]  raddr;
input ren;
output[DATA_WIDTH-1:0] rdata;

reg   [DATA_WIDTH-1:0] rdata, next_rdata; 
reg   [DATA_WIDTH-1:0] mem [DEPTH:0]; //memory

always @ (posedge rclk)
begin
  rdata <= next_rdata;
end
  
always @ (*)
begin
  if (wen)
 mem[waddr] = wdata;
  else if (ren)
 next_rdata = mem[raddr];
end

endmodule

Testbench:


module Memory_tb;

 // Inputs
 reg wclk;
 reg [2:0] waddr;
 reg wen;
 reg [15:0] wdata;
 reg rclk;
 reg [2:0] raddr;
 reg ren;

 // Outputs
 wire [15:0] rdata;

 // Instantiate the Unit Under Test (UUT)
 Memory uut (
  .wclk(wclk), 
  .waddr(waddr), 
  .wen(wen), 
  .wdata(wdata), 
  .rclk(rclk), 
  .raddr(raddr), 
  .ren(ren), 
  .rdata(rdata)
 );

 always #5 wclk = ~wclk;
 always #10 rclk = ~rclk;
 
  task read_mem([2:0]addr);
  begin
  raddr = addr;
  ren = 1'b1;
  #20;
  ren = 1'b0;
  end
  endtask
      
  task write_mem([2:0]addr,[15:0]data);
  begin
  waddr = addr;
  wdata = data;
  wen = 1'b1;
  #10;
  wen = 1'b0;
  end
  endtask
 
 initial begin
  wclk  = 1'b0;
  waddr = 3'd0;
  wen   = 1'b0;
  wdata = 15'd0;
  rclk  = 1'b0;
  ren   = 1'b0;
  raddr = 3'd0;
  #20 write_mem(3'h001,16'hAABB);  //writes
  #10 write_mem(3'h002,16'hCCDD);
  #20 waddr = 3'd0; wdata = 16'd0;
  #20 read_mem(3'h001);            //reads
  #40 read_mem(3'h002);
  #40 read_mem(3'h003);
  #80 $finish;
 end
      
endmodule

Simulation Result:

Verilog memory simulation waveform









Here we see two writes happening at addresses 001 and 010. While providing the same addresses during read, the corresponding data is returned by the memory after one cycle.

Note:
If you see the code, the output rdata appears only after one clock cycle delay. In practical designs, it is always recommended to take the output after one flop delay, as otherwise purely combinational circuits can cause glitches.

Memory Initialization using built-in function
Consider the case in which you have a large memory and you wish to initialize the memory with pre-defined data without manually writing to each location.
  1. This can be done in Verilog using the $readmemh function.
  2. Memory initialization values are placed in a text file and saved with extension '.mem'
Contents of memory_file.mem:







Verilog code:

module Trial();

reg [15:0] test_memory [0:3];
    initial begin
        $display("Loading memory with pre-defined values.");
        $readmemh("memory_file.mem", test_memory);
    end
endmodule

Result:

Memory initialized using $readmemh







As seen in the above waveform, test_memory has been initialized with the following data: 15de, ca23, 0a0a, 1234.

No comments:

Post a Comment