|
|||||
for 6.111 Introduction to Digital Systems |
|||||
6.111 home → Labkit home → Advanced ISE Concepts Advanced ISE ConceptsInferring Memories on the FPGAMemories 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 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 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: data <= 16'hXXXX; |
|||||
MIT 6.111 Introduction to Digital Systems, Updated April 12, 2007 |