MIT
6.111 Labkit  

 FPGA Labkit

for 6.111 Introduction to Digital Systems

6.111 homeLabkit home → Advanced ISE Concepts

Advanced ISE Concepts

Inferring Memories on the FPGA

Memories are implemented on the FPGA in one of two ways. The most efficient mechanism, referred to by Xilinx as "block RAM", utilizes many small (18 kbit) dedicated memory blocks distributed throughout the FPGA. Each block is a synchronous, dual port memory, and can be configured with various aspect ratios, from 16k x 1 bit to 512k x 36 bits. Larger memories are synthesized automatically by combining multiple blocks. The XC2V6000 device used in the labkit has 144 of these blocks, for a total storage of just over 2.5Mbits.

If a design requires more on-chip memory than the block RAMs provide, or needs a memory style that cannot be synthesized from block RAMs (for example, an asynchronous RAM), then memories can also be synthesized using the same look-up tables (LUTs) that are normally used to implement combinational logic functions. This implementation is very inefficient, compared to block RAMs, because each LUT can only store 16 bits, and significant routing resources are required to implement distributed RAMs of any significant size. Xilinx specifies the distributed RAM capacity of the XC2V6000 as a bit over 1Mbit, but it's unlikely that anywhere near that amount could be utilized in practice.

ROMs are implemented on an FPGA in exactly the same manner as RAMs, except that ROMs of course, have no write ports. When the FPGA is configured, the contents of a ROM are specified by the .bit or .ace file used to configure the FPGA. (In fact, the .bit/.ace file always specified the initial contents of every RAM, and every flip-flop on the FPGA.)

Memories (RAMs and ROMs) can be incorporated into a design in one of two ways: by instantiating macros generated using CoreGen (which is pretty easy, but won't be covered here, because the second method is even easier and perhaps even more flexible) or by inferrence from behavioral Verilog code.

RAMs are inferred using arrays of registers in Verilog. For example, the following code defines an array of 128 16-bit registers, numbered 0 through 127.

reg [15:0] memdata[127:0];

The n-th of these registers can be refered to using the syntax memdata[n] (which is, unfortunately, identical to the syntax for referring to the n-th bit of a register).

The following code illustrates how an array of registers can be used to model a 1024 x 16-bit RAM. When synthesized, this memory would be implemented in one block RAM.

module ram_16x1024 (clock, en, we, addr, rdata, wdata);
  input clock;
  input en;            // device enable: 1=enabled, 0=disabled
  input we;            // write enable: 1=write, 0=read
  input [9:0] addr;    // address
  output [15:0] rdata; // read data port
  input [15:0] wdata;  // write data port

  reg [15:0] memory[1023:0];

  always @(posedge clock)
    if (en)
      begin
        if (we)
          memory[addr] <= wdata;
        rdata <= memory[addr];
      end
	
endmodule

The initial contents of the RAM will be all zeros, by default. If for some reason you need to specify the initial contents of a RAM, then using a CoreGen macro (instead of the above Verilog code) is pretty much the only option. In general, it's not a good idea to write code that depends on the contents of a RAM being initialized on startup.

ROMs are inferred from case statements, like this:

module rom_16x128 (clock, addr, data);
  input clock;
  input [6:0] addr;
  output [15:0] data;

  always @(posedge clock)
    case (addr)
      7'h00: data <= 16'h234A;
      7'h01: data <= 16'h1344;
      7'h02: data <= 16'h4646;
      // etc...
      7'h7F: data <= 16'h4532
    endcase
endmodule

Note, that according to the XST documentation, it is important to specify the bit width of each option in the case statement. That is, don't write code like this:

    case (addr)
      0: data <= 16'h234A;
      1: data <= 16'h1344;
      // ...
    endcase

And, of course, there should be a default option at the end of the case statement, unless you have written a specific case for every possible address. If you don't care what data is stored at unused addresses, use

      default: data <= 16'hXXXX;

MIT 6.111 Introduction to Digital Systems, Updated April 12, 2007