Aim: To design, implement, and verify various configurations of a Single Port RAM in Verilog, exploring different data retrieval methods including Asynchronous Read, Synchronous Read-First, and Synchronous Write-First (Transparent) modes.

Theory:

A Single Port RAM is a memory element that uses a single address bus for both reading and writing. While writing is typically synchronous (occurring on the clock edge when Write Enable is high), the reading behavior varies by design:

Code for Single port Ram Async read

module single_port_ram_async_read #(
    parameter DATA_WIDTH = 32, // Number of bits per word
    parameter ADDR_WIDTH = 6   // 2^6 = 64 words in RAM
)(
    input clk,
    input we,                        // Write Enable
    input [ADDR_WIDTH-1:0] addr,     // Single shared address bus
    input [DATA_WIDTH-1:0] din,      // Data Input
    output [DATA_WIDTH-1:0] dout     // Data Output
);

    // Declare the memory array
    reg [DATA_WIDTH-1:0] ram [0:(1<<ADDR_WIDTH)-1];

    // Synchronous Write Logic
    always @(posedge clk) begin
        if (we) begin
            ram[addr] <= din;
        end
    end

    // Asynchronous Read Logic (Combinational)
    assign dout = ram[addr];

endmodule

Test Bench

module tb_single_port_ram_async_read;

    // Parameters
    parameter DATA_WIDTH = 32;
    parameter ADDR_WIDTH = 6;

    // Testbench Signals
    reg clk;
    reg we;
    reg [ADDR_WIDTH-1:0] addr;
    reg [DATA_WIDTH-1:0] din;
    wire [DATA_WIDTH-1:0] dout;

    // Instantiate the Unit Under Test (UUT)
    single_port_ram_async_read #(
        .DATA_WIDTH(DATA_WIDTH),
        .ADDR_WIDTH(ADDR_WIDTH)
    ) uut (
        .clk(clk),
        .we(we),
        .addr(addr),
        .din(din),
        .dout(dout)
    );

    // Clock Generation (10ns period)
    always #5 clk = ~clk;

    initial begin
        // Initialize Inputs
        clk = 0;
        we = 0;
        addr = 0;
        din = 0;

        $display("Time | WE | Address |    DIN     |    DOUT    ");
        $display("----------------------------------------------");
        $monitor("%4t |  %b |      %02d | %h | %h", $time, we, addr, din, dout);

        #15; // Wait for initial state to settle

        // --- Phase 1: Synchronous Writes ---
        @(negedge clk);
        we = 1;
        addr = 6'd5;
        din = 32'hDEADBEEF;

        @(negedge clk);
        we = 1;
        addr = 6'd12;
        din = 32'hCAFEBABE;

        @(negedge clk);
        we = 0; // Disable writing

        // --- Phase 2: Asynchronous Reads ---
        // Change address without waiting for a clock edge
        #7 addr = 6'd5;  // Output should immediately show DEADBEEF
        
        #7 addr = 6'd12; // Output should immediately show CAFEBABE
        
        #7 addr = 6'd1;  // Uninitialized address, output should be xxxxxxxx

        #10 $finish;
    end
endmodule

Code for Single port Ram Sync read

module single_port_ram #(
    parameter DATA_WIDTH = 8,  // Width of each data word
    parameter ADDR_WIDTH = 4   // Number of address bits (4 bits = 16 locations)
)(
    input wire clk,
    input wire we,                               // Write Enable
    input wire [ADDR_WIDTH-1:0] addr,            // Address bus
    input wire [DATA_WIDTH-1:0] din,             // Data input
    output reg [DATA_WIDTH-1:0] dout             // Data output
);

    // Calculate the depth of the RAM based on the address width
    localparam RAM_DEPTH = 1 << ADDR_WIDTH;

    // Memory array declaration
    reg [DATA_WIDTH-1:0] ram [0:RAM_DEPTH-1];

    // Synchronous Read/Write operation
    always @(posedge clk) begin
        if (we) begin
            ram[addr] <= din;    // Write operation
        end
        // Synchronous read: outputs the data at the given address.
        // If reading and writing to the same address simultaneously, 
        // this specific implementation yields "Read-First" behavior (reads old data).
        dout <= ram[addr];       
    end

endmodule

TestBench

module tb_single_port_ram;

    // Parameters
    parameter DATA_WIDTH = 8;
    parameter ADDR_WIDTH = 4;

    // Testbench signals
    reg clk;
    reg we;
    reg [ADDR_WIDTH-1:0] addr;
    reg [DATA_WIDTH-1:0] din;
    wire [DATA_WIDTH-1:0] dout;

    // Instantiate the Unit Under Test (UUT)
    single_port_ram #(
        .DATA_WIDTH(DATA_WIDTH),
        .ADDR_WIDTH(ADDR_WIDTH)
    ) uut (
        .clk(clk),
        .we(we),
        .addr(addr),
        .din(din),
        .dout(dout)
    );

    // Clock generation (10ns period -> 100MHz)
    initial begin
        clk = 0;
        forever #5 clk = ~clk;
    end

    // Stimulus process
    initial begin
        // Initialize Inputs
        we = 0;
        addr = 0;
        din = 0;

        // Wait 100 ns for global reset to finish
        #100;
        
        $display("--- Starting RAM Write Operations ---");
        
        // Write Data to Address 0x0
        @(negedge clk);
        we = 1; addr = 4'h0; din = 8'hAA;
        
        // Write Data to Address 0x1
        @(negedge clk);
        we = 1; addr = 4'h1; din = 8'hBB;
        
        // Write Data to Address 0x5
        @(negedge clk);
        we = 1; addr = 4'h5; din = 8'hCC;
        
        // Write Data to Address 0xF
        @(negedge clk);
        we = 1; addr = 4'hF; din = 8'hDD;

        $display("--- Starting RAM Read Operations ---");

        // Disable writing to start reading
        @(negedge clk);
        we = 0; addr = 4'h0; // Read from 0x0
        
        @(negedge clk);
        $display("Addr: %h, Data Out: %h (Expected: AA)", addr, dout);
        addr = 4'h1;         // Read from 0x1
        
        @(negedge clk);
        $display("Addr: %h, Data Out: %h (Expected: BB)", addr, dout);
        addr = 4'h5;         // Read from 0x5
        
        @(negedge clk);
        $display("Addr: %h, Data Out: %h (Expected: CC)", addr, dout);
        addr = 4'hF;         // Read from 0xF
        
        @(negedge clk);
        $display("Addr: %h, Data Out: %h (Expected: DD)", addr, dout);

        // Test simultaneous read/write on the same address (Read-First behavior)
        $display("--- Testing Simultaneous Read/Write ---");
        @(negedge clk);
        we = 1; addr = 4'h1; din = 8'hEE; // Overwrite 0x1 with EE
        
        @(negedge clk);
        we = 0;
        $display("Addr: %h, Data Out: %h (Read old data during write, Expected: BB)", addr, dout);
        
        @(negedge clk);
        $display("Addr: %h, Data Out: %h (Read new data after write, Expected: EE)", addr, dout);

        // End simulation
        #20;
        $display("--- Simulation Complete ---");
        $finish;
    end
end module

Single-port RAM with synchronous read--read first mode

module single_port_ram_read_first #(
    parameter DATA_WIDTH = 8,  // Width of each data word
    parameter ADDR_WIDTH = 4   // Number of address bits (16 locations)
)(
    input  wire                  clk,
    input  wire                  we,     // Write Enable
    input  wire [ADDR_WIDTH-1:0] addr,   // Address bus
    input  wire [DATA_WIDTH-1:0] din,    // Data input
    output reg  [DATA_WIDTH-1:0] dout    // Data output
);

    // Calculate the depth of the RAM
    localparam RAM_DEPTH = 1 << ADDR_WIDTH;

    // Memory array declaration
    reg [DATA_WIDTH-1:0] ram [0:RAM_DEPTH-1];

    // Synchronous block for Read-First RAM
    always @(posedge clk) begin
        if (we) begin
            ram[addr] <= din;    // Write the new data
        end
        
        // Because we use non-blocking assignments (<=), this reads the 
        // *old* value of ram[addr] even if 'we' is high.
        dout <= ram[addr];       
    end

endmodule

Test Bench