Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • RTL Foundations36
  • Combinational Design25
  • Sequential Design27
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
  • Memory & Buses57
    • SRAM and ROM
      • Inference Starts from Coding Style
      • Single-Port Synchronous RAM Template
      • Read-First vs Write-First
      • Dual-Port RAM Patterns
      • True Dual-Port RAM and Collisions
      • ROM Inference
    • Register Files
    • Valid/Ready Handshake
    • AXI-Lite
    • AXI-Stream
    • Bus Integration
    • Synchronous FIFO
    • Register-Map and CSR RTL
    • Memory Reliability
    • Peripheral RTL Integration
    • Full AXI4: RTL Roadmap
  • Advanced RTL28
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

←SRAM and ROM
  1. Topics
  2. RTL Design
  3. Memory & Buses
  4. SRAM and ROM
  5. Dual-Port RAM Patterns
On this page

    Part 7 of 9, lesson 4 of 57Part 7 of 9/Memory & Buses/Lesson 4 of 57Intermediate

    Dual-Port RAM Patterns

    Simple and true dual-port RAMs with independent clocks, tested on unrelated clocks.

    A RAM written on one clock and read on another is the heart of an asynchronous FIFO. If both sides touch the same address at once, the read returns old data, new data or garbage, depending on the device.

    After this lesson you can: choose between simple and true dual-port RAM, and explain why a same-address access across two clocks must be prevented by design.

    Two flavours

    A dual-port RAM serves two sides at once. The two flavours differ in how many sides may write.

    Type

    Ports

    Typical use

    Simple dual-port

    One write port, one read port, each with its own clock

    FIFOs (including the async FIFO), line buffers, producer/consumer buffers

    True dual-port

    Two ports, each can read or write, each with its own clock

    Shared buffers between two masters, frame stores

    The ports of the two listings below: ram_sdp has a write port and a read port, ram_tdp has two ports that each read and write, and every port has its own clock

    Count the arrows on each side. In the simple dual-port RAM the left side can only write and the right side can only read. In the true dual-port RAM both sides have the full set: clock, write enable, address, write data and read data.

    • Simple dual-port: one writer, so two ports can never write the same word at once. The only collision is a read of the word being written.

    • True dual-port: two writers. Both can target the same word in the same cycle, which is the collision the next lesson handles.

    Use a simple dual-port RAM whenever one side only writes and the other only reads. It is smaller and faster than a true dual-port RAM and has fewer collision cases.

    Clock-domain rules

    In a simple dual-port RAM with two clocks, reading an address being written at the same time returns old data, new data or garbage, depending on the device. The async FIFO avoids it by design: the read pointer never reaches an address the write pointer is still writing.

    FPGA block RAMs have fixed aspect ratios and port-width options. A very wide RAM or an unusual ratio between port widths may need several blocks, and non-matching read and write widths may need a vendor template. Check the library's guidance for your device.

    Check yourself When would you choose a true dual-port RAM instead of a simple dual-port one?

    When both sides need to read and write the same memory (for example two processors sharing a buffer, or a frame store updated by one master and read-modify-written by another). If one side only writes and the other only reads, a simple dual-port RAM is enough.

    Testbench

    Both listings plus a testbench that writes and reads back the full array on unrelated clocks.

    The two-writer ram_tdp listing uses plain clocked always processes as a dual-port inference idiom. Two always_ff writers to one shared array violate strict single-writer intent checks in some tools, even with partitioned traffic. Plain always avoids that intent promise but does not guarantee inference. This is an unverified source template: select the device/tool's approved independent-clock dual-port template, then inspect mapped primitive type, depth/width, both clocks, enables and same-/opposite-port read modes. Reject unexpected flop/mux expansion. RTL readback cannot establish macro collision windows, CDC safety or physical timing signoff.

    ram_sdp.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // Simple dual-port RAM: one write port (clock wclk), one read port (clock rclk).
    module ram_sdp #(parameter int AW = 5, parameter int DW = 8) (
      input  logic          wclk, we,
      input  logic [AW-1:0] waddr,
      input  logic [DW-1:0] wdata,
      input  logic          rclk,
      input  logic [AW-1:0] raddr,
      output logic [DW-1:0] rdata
    );
      logic [DW-1:0] mem [2**AW];
      always_ff @(posedge wclk) if (we) mem[waddr] <= wdata;
      always_ff @(posedge rclk) rdata <= mem[raddr];             // read-during-write to the same address: old or new, undefined across clocks
    endmodule
    
    // True dual-port RAM: two independent read/write ports.
    module ram_tdp #(parameter int AW = 5, parameter int DW = 8) (
      input  logic          clka, wea, input logic [AW-1:0] addra, input logic [DW-1:0] dina, output logic [DW-1:0] douta,
      input  logic          clkb, web, input logic [AW-1:0] addrb, input logic [DW-1:0] dinb, output logic [DW-1:0] doutb
    );
      logic [DW-1:0] mem [2**AW];
      always @(posedge clka) begin
        if (wea) mem[addra] <= dina;
        douta <= mem[addra];
      end
      always @(posedge clkb) begin
        if (web) mem[addrb] <= dinb;
        doutb <= mem[addrb];
      end
    endmodule
    
    • Simple dual-port: the testbench writes all 32 words on wclk (10 ns) and reads them back on rclk (7.4 ns).

    • True dual-port: the testbench initializes each port's half, overwrites random words there using separate modelS and modelT references, then compares both ports' full-array readback.

    • The testbench stays away from the same address at the same time on purpose. That case is the subject of the next lesson.

    • Use a simple dual-port RAM when one side only writes and the other only reads: it is smaller, faster and has fewer collision cases.

    • Across two clocks, a read of an address being written is undefined. The async FIFO avoids it by keeping the pointers apart.

    • Wide RAMs and unusual port-width ratios can need several blocks or a vendor template.

    How this was checked

    Simple dual-port: source checker, run with Icarus Verilog 13.0 (2026-10-16): prints PASS. It writes all 32 words on wclk (10 ns) and reads them back on rclk (7.4 ns). The clocks have different periods, but this finite simulation does not cover every physical phase.

    True dual-port: a finite behavioural check using separate modelS and modelT references. It is not measured mapping or collision evidence.

    • Practice: Dual-port synchronous RAM design

    • Practice: Dual-port RAM with independent clocks

    • Code lab: Simple dual-port RAM

    • Interview: run a mock interview on this topic

    Related topics

    • True dual-port RAM

    Previous

    192 / 350

    Next: True Dual-Port RAM and Collisions

    Up next

    Up nextTrue Dual-Port RAM and Collisions

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Inference Starts from Coding Style
    • Single-Port Synchronous RAM Template
    • ROM Inference