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.
Testbench:
Simulation Result:
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.
Result:
Memory Parameters:
A memory is characterized by its addr_width, data_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:
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:
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.
- This can be done in Verilog using the $readmemh function.
- 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:
As seen in the above waveform, test_memory has been initialized with the following data: 15de, ca23, 0a0a, 1234.
No comments:
Post a Comment