Applications 8

De la WikiLabs

A random access memory is an address-based data storage block, with a highly regular structure, which may be logically defined either as a bi-dimensional array of bits, or a uni-dimensional list of data words. Each row of the array, or each item of the list has a unique address. The addresses form a contiguous set of integers, starting from zero and running up to the highest address which defines the size of the address space, usually a power of 2. The memory capacity is therefore the number of address locations, N, times the width of the memory location (or of the memory word), W. These two numbers are the main parameters of any memory:

  • N - the number of address locations, also the number of words;
  • W - the number of bits for each location, or the data word width;

The storage capacity is N x W bits, but the memory capacity may also be given in bytes.

Being a vector of words or a bidimensional array of bits, the memory could be described using a single bidimensional variable, usually declared as a vector of multibit reg items. A memory array of N locations, with W bits per location is declared as:

reg [W-1: 0] memory [0: N-1];

Attention!

  • the width of the word is declared as usual, with msb as the left index, [msb:0]
  • the length of the memory is declared after the variable name, and starts with 0, the right index being the highest index of the vector, [0:endindex]


There are two kinds of memories:

  • read-only memories
  • writable memories

Memories can also be classified by their synchronous or asynchronous character:

  • asynchronous - read data is immediately available at the output, write is performed as into a latch
  • synchronous - all operations are synchronized by one of the clock edges.


Exercise 1

ROM - Read-Only Memory

It may be described as a purely combinational logic circuit, whose output (data) is a logic function of its input (address). The transcoder from Applications 4 is nothing else than a ROM, with value input acting as an address that selects a particular location from where data is read and sent out as seg. There it was described behaviorally with a case statement that had a branch for each combination of input value bits.

Appl8 rom.png

A more flexible description employs a single memory array, declared as a vector of reg variables, and a statement that assigns the selected (addressed) element of the memory vector to the memory output.

assign dout = memory[addr];

Memory initialization task

Memory arrays may be initialized with predefined values from a text file using verilog system tasks $readmemb and $readmemh. $readmemb reads predefined values given in binary, while $readmemh is used if values are given in hexadecimal. These verilog system tasks have two mandatory arguments and two optional arguments, in the following order:

  1. initialization filename, given as a string (between double quotes)
  2. the memory array variable name
  3. the start address (optional)
  4. the end address (optional)

If the start and end addresses are not given, the memory array is initialized from the first address (address zero) until the last address or until the end of initialization file is reached.

If the memory initialization file, meminit.txt, resides in the project's folder, and the memory array to be initialized is mem inside the memory instance dut, the testbench may initialize the memory right at the beginning of the simulation:

initial $readmemb("../../meminit.txt", dut.mem);

If the initialization is intended to be done for the implemented memory, the initialization process must be placed inside the memory module, in which case the memory array name is just the variable array to be initialized:

initial $readmemb("../../meminit.txt", mem);

The filename, the first argument of $readmemb and $readmemh tasks, may be given either relative to the simulation folder (as in the previous two examples), or as the complete absolute filename, with the pathname starting from the linux root, /, for example "/home/student/rom/meminit.txt" if meminit.txt file resides in the project's folder rom, created in the student's home directory /home/student.

Memory initialization file

The initialization file is a text file with values written in binary (using 0 and 1 symbols) or in hexadecimal (using digits and letters a to f, either uppercase of lowercase). Values are separated by white spaces, tabs or newlines. Comments (started with // as in verilog) are ignored. An example of a binary initialization file:

// four values in binary text format
00000011
00001111
00111111
11111111

The same initialization sequence, but given as a hexadecimal text file:

// four values in hexadecimal text format
03
0f
3f
ff
  • Start a new project, rom, in a newly created folder rom
  • Create a new verilog file rom.v that describes a 16x8 ROM using a vector variable and a continuous assignment.
  • Create a testbench, rom_tb, that drives the input of rom with a sequence of addresses from 0 to highest one. Run simulation.
  • Open a text editor, write some values in binary format on successive lines, and save the file into the rom folder with the name meminit.txt
  • In the testbench module add an initial process with a single statement that calls the system task for memory initialization. Run simulation.
  • Assign the segments of Digit0 from DE1-SOC display to dout, and SW[3] ... SW[0] switches to addr.
  • In the rom module add an initial process with a single statement that calls the system task for memory initialization. Pay atention how its second argument is given - the array variable is now directly accesible. Compile the project and program the FPGA.


Exercise 2

RAM - Read-Write Memory

A synchronous memory reacts only on clock edges. The addresses are sampled at clock edges, the output changes also on clock edges, and the write is performed on clock edges too.

Appl8 ram.png

The FPGA has dedicated blocks for memory implementation, the so called ram blocks, or ramblocks. In order for the synthesis to map a memory description to a ramblock, the sequential description must conform to a template:

always @(posedge clk) begin
    if(~we)                   // if write is enabled,
        mem[addr] <= wdata;   //     update the addressed location to wdata value
    rdata <= mem[addr];       // data read from the addressed location is transferred to the output
end
  • Start a new project, ram, in a new folder ram
  • Create a new verilog file ram.v that describes a 16x4 RAM using a vector variable and a sequential always process that assigns to rdata output the value of the memory location selected by addr. In the same process update the same location with wdata value if we input is active (active at 0).
  • Create a testbench, ram_tb, which writes some data to four addresses, and then reads the same addresses in the same order, as indicated in the waveforms below. Don't forget the clock! Run simulation and check that the written data are correctly read afterwards.
  • Assign LEDs to the output, switches to the addr and wdata inputs, a push button (KEY0) to the write enable input (the push button generates 0 if pressed), and a 50 MHz source clock to clk input.
  • Compile, program the FPGA and test the RAM. Set switches to an address and a data value, then briefly push the write enable button. The LEDs will change accordingly. Change the address and data and push again the write enable button. Change back the address switches and see if the previously written configuration reappears on LED.

Appl8 ram wave.png