⚡ Digital Design Blog

Part 6 of 10

Memory Design

SRAM, ROM, FIFO, and Register Files

By Praveen Kumar Vagala

1,031 views

Memory Types Overview

TypeVolatile?Read/WriteSpeedUse Case
SRAMYesR/WFastCache, registers
DRAMYesR/WSlowerMain memory
ROMNoRead onlyFastBoot code, constants
FlashNoR/W (slow)MediumStorage
Register FileYesR/WFastestCPU registers

SRAM (Static RAM)

Each bit stored in a flip-flop/latch circuit. No refresh needed.

SRAM Array Structure: Word Line 0 │ ┌─────────┼─────────┐ │ ┌────┴────┐ │ │ │ 6T Cell │ │ │ └────┬────┘ │ Bit │ │ │ Bit' Line│ ┌────┴────┐ │ Line │ │ 6T Cell │ │ │ └────┬────┘ │ └─────────┼─────────┘ │ Word Line 1

Simple SRAM Model

// Single-port SRAM
module sram #(
    parameter DEPTH = 256,
    parameter WIDTH = 8,
    parameter ADDR_W = 8
)(
    input                clk,
    input                we,      // Write enable
    input  [ADDR_W-1:0]  addr,
    input  [WIDTH-1:0]   din,
    output reg [WIDTH-1:0] dout
);
    reg [WIDTH-1:0] mem [0:DEPTH-1];
    
    always @(posedge clk) begin
        if (we)
            mem[addr] <= din;
        dout <= mem[addr];  // Read (1 cycle latency)
    end
endmodule

Dual-Port SRAM

// True dual-port SRAM (both ports can read and write)
module dual_port_sram #(
    parameter DEPTH = 256,
    parameter WIDTH = 8
)(
    input                clk,
    // Port A
    input                we_a,
    input  [7:0]         addr_a,
    input  [WIDTH-1:0]   din_a,
    output reg [WIDTH-1:0] dout_a,
    // Port B
    input                we_b,
    input  [7:0]         addr_b,
    input  [WIDTH-1:0]   din_b,
    output reg [WIDTH-1:0] dout_b
);
    reg [WIDTH-1:0] mem [0:DEPTH-1];
    
    // Port A
    always @(posedge clk) begin
        if (we_a)
            mem[addr_a] <= din_a;
        dout_a <= mem[addr_a];
    end
    
    // Port B
    always @(posedge clk) begin
        if (we_b)
            mem[addr_b] <= din_b;
        dout_b <= mem[addr_b];
    end
endmodule

ROM (Read-Only Memory)

Contents fixed at design time. Used for lookup tables, boot code.

// ROM using case statement
module rom_16x8 (
    input  [3:0] addr,
    output reg [7:0] data
);
    always @(*) begin
        case (addr)
            4'h0: data = 8'h00;
            4'h1: data = 8'h17;
            4'h2: data = 8'h2E;
            4'h3: data = 8'h44;
            4'h4: data = 8'h58;
            4'h5: data = 8'h6A;
            4'h6: data = 8'h79;
            4'h7: data = 8'h84;
            4'h8: data = 8'h8C;
            4'h9: data = 8'h90;
            4'hA: data = 8'h91;
            4'hB: data = 8'h8F;
            4'hC: data = 8'h8A;
            4'hD: data = 8'h81;
            4'hE: data = 8'h76;
            4'hF: data = 8'h69;
        endcase
    end
endmodule

// ROM using initialization file
module rom_file #(
    parameter DEPTH = 256,
    parameter WIDTH = 8
)(
    input  [7:0] addr,
    output [WIDTH-1:0] data
);
    reg [WIDTH-1:0] mem [0:DEPTH-1];
    
    initial begin
        $readmemh("rom_data.hex", mem);
    end
    
    assign data = mem[addr];
endmodule

Register File

Small, fast memory with multiple read/write ports. Core of CPU.

// Register File: 32 registers, 2 read ports, 1 write port
module reg_file #(
    parameter WIDTH = 32,
    parameter DEPTH = 32,
    parameter ADDR_W = 5
)(
    input                clk,
    input                we,
    input  [ADDR_W-1:0]  waddr,
    input  [WIDTH-1:0]   wdata,
    input  [ADDR_W-1:0]  raddr1, raddr2,
    output [WIDTH-1:0]   rdata1, rdata2
);
    reg [WIDTH-1:0] regs [0:DEPTH-1];
    
    // Write (synchronous)
    always @(posedge clk) begin
        if (we && waddr != 0)  // Register 0 often hardwired to 0
            regs[waddr] <= wdata;
    end
    
    // Read (asynchronous - combinational)
    assign rdata1 = (raddr1 == 0) ? 0 : regs[raddr1];
    assign rdata2 = (raddr2 == 0) ? 0 : regs[raddr2];
endmodule

FIFO (First-In First-Out)

Queue memory - data comes out in the order it went in.

Write ──►┌─────────────────────┐──► Read │ [D1][D2][D3][D4][ ] │ └─────────────────────┘ ↑ ↑ Write Read Pointer Pointer Write: Add data at write pointer, increment Read: Get data at read pointer, increment

Synchronous FIFO

module sync_fifo #(
    parameter WIDTH = 8,
    parameter DEPTH = 16
)(
    input                clk, rst_n,
    input                wr_en, rd_en,
    input  [WIDTH-1:0]   din,
    output [WIDTH-1:0]   dout,
    output               full, empty,
    output [4:0]         count
);
    localparam PTR_W = $clog2(DEPTH);
    
    reg [WIDTH-1:0] mem [0:DEPTH-1];
    reg [PTR_W:0]   wr_ptr, rd_ptr;  // Extra bit for full/empty
    
    wire [PTR_W-1:0] wr_addr = wr_ptr[PTR_W-1:0];
    wire [PTR_W-1:0] rd_addr = rd_ptr[PTR_W-1:0];
    
    // Write
    always @(posedge clk) begin
        if (wr_en && !full)
            mem[wr_addr] <= din;
    end
    
    // Read
    assign dout = mem[rd_addr];
    
    // Pointers
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            wr_ptr <= 0;
            rd_ptr <= 0;
        end else begin
            if (wr_en && !full)
                wr_ptr <= wr_ptr + 1;
            if (rd_en && !empty)
                rd_ptr <= rd_ptr + 1;
        end
    end
    
    // Status flags
    assign empty = (wr_ptr == rd_ptr);
    assign full  = (wr_ptr[PTR_W] != rd_ptr[PTR_W]) && 
                   (wr_ptr[PTR_W-1:0] == rd_ptr[PTR_W-1:0]);
    assign count = wr_ptr - rd_ptr;
endmodule

Memory Initialization

// Method 1: Initial block
reg [7:0] mem [0:255];
initial begin
    mem[0] = 8'hAB;
    mem[1] = 8'hCD;
    // ...
end

// Method 2: Read from file
initial begin
    $readmemh("data.hex", mem);     // Hex format
    // or
    $readmemb("data.bin", mem);     // Binary format
end

// data.hex file format:
// AB
// CD
// 12
// 34

Memory Access Patterns

Read-First vs Write-First

// Read-First: Read returns OLD value
always @(posedge clk) begin
    dout <= mem[addr];     // Read first
    if (we)
        mem[addr] <= din;  // Then write
end

// Write-First: Read returns NEW value  
always @(posedge clk) begin
    if (we)
        mem[addr] <= din;  // Write first
    dout <= mem[addr];     // Then read (new value)
end

// No-Change: Output unchanged during write
always @(posedge clk) begin
    if (we)
        mem[addr] <= din;
    else
        dout <= mem[addr];
end

Summary

Memory TypePortsTypical Use
Single-port SRAM1 R/WGeneral storage
Dual-port SRAM2 R/WBuffers, caches
ROM1 RConstants, LUTs
Register FileMulti-portCPU registers
FIFO1W, 1RRate matching, buffering