Chapter 05: Memory, RAM & Block RAM

Storing data in your FPGA - the foundation of real systems

Types of Memory in FPGAs

FPGAs have THREE types of memory resources:

TypeSourceSizeUse Case
Flip-FlopsLogic cellsSmall (1 bit each)Registers, state machines
Distributed RAMLUTs as RAMSmall arraysFIFOs, small buffers
Block RAM (BRAM)Dedicated blocksLarge (KB)Frame buffers, data storage
Tang Primer 20K Resources: GW2A-18C has ~18K flip-flops and 828 Kbits of Block RAM (103.5 KB)

Simple Register Array (Using Flip-Flops)

For small amounts of data, use a register array:

// 16-entry x 8-bit register file
// Fast but uses lots of logic resources

module register_file (
    input  clk,
    input  [3:0] write_addr,
    input  [3:0] read_addr,
    input  [7:0] write_data,
    input  write_enable,
    output [7:0] read_data
);

    // Array of 16 registers, each 8 bits wide
    reg [7:0] registers [0:15];
    integer i;

    // Write operation
    always @(posedge clk) begin
        if (write_enable)
            registers[write_addr] <= write_data;
    end

    // Read operation (combinational)
    assign read_data = registers[read_addr];

endmodule

Block RAM (BRAM): The Real Deal

For larger memory, use Block RAM. It's free - dedicated hardware that doesn't consume logic resources.

Single-Port RAM

// 256-byte single-port RAM
// One port for both read and write

module single_port_ram (
    input  clk,
    input  [7:0] addr,        // 8-bit address = 256 locations
    input  [7:0] write_data,
    input  write_enable,
    output reg [7:0] read_data
);

    // Memory array - synthesis tool will infer BRAM
    reg [7:0] memory [0:255];

    always @(posedge clk) begin
        if (write_enable)
            memory[addr] <= write_data;
        
        read_data <= memory[addr];  // Registered read
    end

endmodule
Magic: The synthesis tool recognizes this pattern and automatically maps it to physical Block RAM! You get 256 bytes of memory using ZERO logic resources.

Dual-Port RAM

Read and write simultaneously on different ports:

// True dual-port RAM
// Can read from one address while writing to another

module dual_port_ram (
    input  clk,
    
    // Port A (write port)
    input  [9:0] addr_a,
    input  [7:0] data_a,
    input  we_a,
    
    // Port B (read port)
    input  [9:0] addr_b,
    output reg [7:0] data_b
);

    reg [7:0] memory [0:1023];  // 1KB RAM

    // Port A: Write
    always @(posedge clk) begin
        if (we_a)
            memory[addr_a] <= data_a;
    end

    // Port B: Read
    always @(posedge clk) begin
        data_b <= memory[addr_b];
    end

endmodule

Initializing Memory

You can pre-load memory with data from a file:

// RAM with initialization from file

module rom_with_init (
    input  clk,
    input  [7:0] addr,
    output reg [7:0] data
);

    reg [7:0] memory [0:255];

    // Initialize from file
    initial begin
        $readmemh("init_data.hex", memory);
    end

    always @(posedge clk) begin
        data <= memory[addr];
    end

endmodule
init_data.hex format:
Each line is one byte in hexadecimal:
FF
A5
00
42

FIFO: First-In-First-Out Buffer

FIFOs are essential for data streaming between clock domains:

// Simple synchronous FIFO
// 16-entry deep, 8-bit wide

module fifo_sync (
    input  clk,
    input  rst,
    
    // Write interface
    input  [7:0] write_data,
    input  write_en,
    output full,
    
    // Read interface
    output [7:0] read_data,
    input  read_en,
    output empty
);

    reg [7:0] memory [0:15];
    reg [4:0] write_ptr, read_ptr;
    reg [4:0] count;

    assign full = (count == 16);
    assign empty = (count == 0);
    assign read_data = memory[read_ptr[3:0]];

    // Write operation
    always @(posedge clk) begin
        if (rst) begin
            write_ptr <= 0;
        end
        else if (write_en && !full) begin
            memory[write_ptr[3:0]] <= write_data;
            write_ptr <= write_ptr + 1;
        end
    end

    // Read operation
    always @(posedge clk) begin
        if (rst) begin
            read_ptr <= 0;
        end
        else if (read_en && !empty) begin
            read_ptr <= read_ptr + 1;
        end
    end

    // Count tracking
    always @(posedge clk) begin
        if (rst)
            count <= 0;
        else begin
            case({write_en && !full, read_en && !empty})
                2'b10: count <= count + 1;      // Write only
                2'b01: count <= count - 1;      // Read only
                default: count <= count;       // Both or neither
            endcase
        end
    end

endmodule

Using Gowin Block RAM IP

For production, use the vendor IP Core Generator:

  1. Tools → IP Core Generator
  2. Select "Block RAM" (BSRAM_9K or BSRAM_18K)
  3. Configure size, ports, initialization
  4. Generate and instantiate
Vendor IP Benefits: Optimized for the specific FPGA, tested, and guaranteed to synthesize correctly. Use it for production code.

Memory Size Planning

ApplicationMemory SizeType
State machine variables< 32 bitsFlip-flops
Small lookup table< 256 bytesDistributed RAM
Video line buffer (640px RGB)1920 bytesBlock RAM
Audio buffer (1s @ 44.1kHz)88 KBBlock RAM
Frame buffer (640x480 RGB)900 KBExternal SDRAM

Key Takeaways