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:
din) is stored in the memory array at the specified address (addr) only on the rising edge of the clock when the Write Enable (we) signal is high.dout is not tied to the clock. It uses an assign statement (continuous assignment), meaning as soon as the addr changes, the data at that location appears at the output after a small propagation delay.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