Lesson 17 of 25 · 10 MIN · 68% complete

Distributed RAM

Distributed RAM repurposes the SRAM cells already present in Slice M LUTs to create fast, small memories distributed throughout the logic fabric — no dedicated BRAM columns needed and zero read latency by default.

What is Distributed RAM?

Every LUT in a 7-series FPGA contains 64 SRAM bits that normally store the truth-table function. In Slice M (the memory-capable slice type), these same SRAM cells can be repurposed as small, writable RAM arrays. Because the SRAM is already present in the silicon, using it as RAM does not "cost" extra area — it changes how an existing LUT is configured.

Resource perspective Distributed RAM is "free" in terms of dedicated FPGA resources — it uses LUTs you may already have available in the design. Only consider it a limiting resource when overall LUT utilization is already high.

Available Primitives

Xilinx provides a range of distributed RAM primitives that can be instantiated directly or inferred automatically by Vivado synthesis. Common primitives for 7-series devices:

Primitive Depth × Width LUTs Used Read Mode
RAM32X1S 32×1 1 Async
RAM64X1S 64×1 1 Async
RAM128X1S 128×1 2 Async
RAM256X1S 256×1 4 Async
RAM32X1D 32×1 (dual port) 2 Async
RAM64X1D 64×1 (dual port) 2 Async
RAM32M 32×8 (multi-port) 8 Async
SRL16E 16-bit shift register 1 Dynamic tap
SRL32 32-bit shift register 1 Dynamic tap

How Distributed RAM Works

The internal SRAM cells of a LUT normally store the 64-entry truth table for the programmed logic function. In Slice M, additional write-port circuitry exists alongside each LUT:

flowchart LR WE([WE]) --> CTRL[Write\nControl] WCLK([WCLK]) --> CTRL DIN([Data In]) --> CTRL CTRL -->|"sync write\n(rising edge)"| SRAM["LUT SRAM\n64 bits"] ADDR([Address]) --> SRAM SRAM -->|"async read\n(combinational)"| DOUT([Data Out]) style SRAM fill:#1e3a5f,stroke:#3b82f6,color:#93c5fd style CTRL fill:#1e293b,stroke:#475569,color:#94a3b8

BRAM vs Distributed RAM: Decision Guide

Choosing between Block RAM and Distributed RAM depends on size, latency requirements, and physical placement needs:

Factor Distributed RAM Block RAM
Size <2 Kbits (ideal) ≥18 Kbits (efficient)
Read latency 0 cycles (async, combinational) 1 cycle (synchronous)
Power Low (reuses existing LUTs) Higher (dedicated block)
Physical location Throughout entire fabric Fixed BRAM columns only
Dual-port capability Limited (RAM32X1D, RAM64X1D) Full true dual-port
ECC support No Yes (in RAMB36E1)
Typical use case Register files, small FIFOs, LUTs Packet buffers, frame buffers, large queues

SRL — Shift Register LUT (the hidden gem)

The most underutilized capability of Slice M LUTs is the SRL (Shift Register LUT). A single LUT implements a 32-bit synchronous shift register with a dynamically selectable output tap — something that would otherwise consume 32 flip-flops.

flowchart LR SIN([Serial In]) --> SRL32["SRL32\n32-bit shift register\n1 LUT"] CLK([CLK]) --> SRL32 TAP(["tap[4:0]"]) --> SRL32 SRL32 --> OUT([Output at selected tap]) style SRL32 fill:#1e3a5f,stroke:#3b82f6,color:#93c5fd

Synthesis Inference and Attributes

Vivado automatically infers distributed RAM from standard Verilog array syntax. The synthesis tool decides between distributed and block RAM based on size heuristics, but you can override this behavior with attributes:

// Standard inference — Vivado chooses BRAM or Distributed RAM based on size
reg [7:0] mem [0:63];
always @(posedge clk) if (we) mem[addr] <= din;
assign dout = mem[addr];  // async read

// Force Distributed RAM regardless of size
(* ram_style = "distributed" *)
reg [7:0] mem [0:63];

// Force Block RAM
(* ram_style = "block" *)
reg [7:0] mem [0:63];

// Force SRL inference for a shift register
(* srl_style = "srl" *)
reg [31:0] shift_reg;
always @(posedge clk) shift_reg <= {shift_reg[30:0], data_in};

After synthesis, inspect Reports → Utilization → RAM Summary to see each inferred RAM with its primitive type, LUT count, and port configuration.

Same Silicon, Different Function

Both a logic LUT and a distributed RAM LUT use exactly the same 64 SRAM bits. The difference is purely configuration — whether those bits store a truth table or writable data:

Resource perspective Distributed RAM is free in terms of dedicated FPGA resources — it uses LUTs you may already have available. Only consider it a resource constraint when LUT utilization is already high across the design.
Register file design tip Use Distributed RAM for CPU register files (e.g., 32×32-bit). The asynchronous read lets you read two source registers in the same clock cycle as a write, simplifying forwarding logic in pipelines.
Timing hazard — async reads Distributed RAM read is asynchronous — any glitch on the read address causes an immediate glitch on the data output. Pipeline the read address if used in timing-critical paths to avoid setup/hold violations downstream.
Interview question Q: When would you choose distributed RAM over block RAM?
A: When memory size is less than ~2 Kbits, when you need zero-latency (asynchronous) reads without adding pipeline stages, or when no BRAM columns are physically located near the logic that needs the memory.

Knowledge Check

Q1. Which Slice type supports Distributed RAM?

  • A Slice L only
  • B Slice M only
  • C Both Slice L and Slice M
  • D Neither — Distributed RAM uses dedicated columns separate from slices
Correct! Only Slice M contains the additional write-port circuitry needed for Distributed RAM. Slice L LUTs can only implement combinational logic or register storage.

Q2. What is the default read mode of Distributed RAM?

  • A Synchronous — registered on rising clock edge
  • B Asynchronous — combinational output with no clock required
  • C Pipelined — 2-cycle latency
  • D Registered with optional combinational bypass
Correct! Reads are purely combinational — the address inputs drive the LUT just like a normal lookup, producing output immediately with no clock edge. This gives zero read latency.

Q3. How many LUTs does the RAM64X1D primitive use?

  • A 1
  • B 2
  • C 4
  • D 8
Correct! RAM64X1D is a 64×1-bit dual-port distributed RAM using 2 LUTs. The "D" suffix denotes dual-port — the second LUT provides the additional independent read port.

Q4. Which RTL attribute forces a Verilog memory to be inferred as Distributed RAM?

  • A (* ram_style = "block" *)
  • B (* ram_style = "distributed" *)
  • C (* use_dsp = "distributed" *)
  • D (* mem_type = "lut" *)
Correct! The (* ram_style = "distributed" *) attribute forces Vivado to infer Slice M LUT-based RAM regardless of the memory's size or depth.

Q5. SRL32 implements what function in a single LUT?

  • A 32×1-bit synchronous RAM with one write port
  • B 32-bit barrel shifter
  • C 32-bit shift register with a dynamically selectable output tap
  • D 5-to-32 priority decoder
Correct! SRL32 implements a 32-bit synchronous shift register in a single LUT. The 5-bit address input selects which delay stage to observe, enabling variable-length delay lines with minimal resources.

Practical Exercise

In Vivado, create a Verilog module with a 64×8-bit memory array:

module mem_test (
  input  wire       clk,
  input  wire       we,
  input  wire [5:0] addr,
  input  wire [7:0] din,
  output wire [7:0] dout
);
  reg [7:0] mem [0:63];
  always @(posedge clk) if (we) mem[addr] <= din;
  assign dout = mem[addr];   // async read
endmodule

Synthesize three times targeting an Artix-7 device:

  1. Without attributes — note whether Vivado chooses BRAM or Distributed RAM
  2. With (* ram_style = "distributed" *) on the mem declaration — verify 8 LUTs, 0 BRAMs in the utilization report
  3. With (* ram_style = "block" *) — verify 0 LUTs for RAM, 1 BRAM used

Compare the Synthesis Report → Utilization Summary and note LUT count, BRAM count, and simulated read latency differences. Inspect the "RAM Summary" section of the synthesis log for inferred primitive names.