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.
- Available only in Slice M — approximately 25% of all slices in 7-series devices
- Write: synchronous — data is latched on the rising clock edge via a dedicated write port
- Read: asynchronous by default — purely combinational, no clock required
- Smallest configuration: 32×1-bit using one LUT. Largest common configuration: 256×8-bit using 16 LUTs across 2 Slice M
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:
- A WE (Write Enable) input gates writes to the SRAM cells
- A WCLK input captures the write address and data on the rising edge
- The address inputs double as the LUT inputs for reads — purely combinational, just like a normal LUT lookup
- Multiple LUTs are combined in parallel to achieve wider data widths (e.g., 8 LUTs for 8-bit-wide data)
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.
- SRL32: 32-bit shift register in 1 LUT with synchronous shift-in on every clock
- A 5-bit address input selects which stage's output to observe — delay from 1 to 32 cycles
- Vivado infers SRL automatically from shift-register RTL patterns
- Example: a 28-tap audio delay line uses just 1 SRL32 versus 28 individual flip-flops
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:
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
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
Q3. How many LUTs does the RAM64X1D primitive use?
- A 1
- B 2
- C 4
- D 8
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" *)
(* 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
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:
- Without attributes — note whether Vivado chooses BRAM or Distributed RAM
-
With
(* ram_style = "distributed" *)on thememdeclaration — verify 8 LUTs, 0 BRAMs in the utilization report -
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.