Draft preview. Lessons are being written and reviewed.

Digital Design / Module 7: Memories and FIFOs

40. Using SRAM in RTL

Work with synchronous memory correctly.

Lecture coming soon

See it move

The idea

Large storage uses SRAM macros, generated for a specific size and process rather than written in RTL. An SRAM is far denser than flops, but it behaves differently, and your control logic must allow for that.

The key difference: read latency

A typical synchronous SRAM captures the address on a clock edge, and the data appears after that edge. If you request address A in cycle 1, you get the data in cycle 2. In a flop-based memory with a combinational read, you'd get it in cycle 1.

Any valid signal travelling with the request must be delayed by the same amount, or the control logic will pair data with the wrong request.

always_ff @(posedge clk) begin
  rd_valid <= rd_en;            // aligned with the SRAM output
end

sram_1p u_mem (
  .clk(clk), .cs(rd_en | wr_en), .we(wr_en),
  .addr(addr), .wdata(wdata), .rdata(rdata)
);

Port types

TypePer cycleCost
Single-portOne read or one writeSmallest
Two-portOne read and one writeLarger
True dual-portTwo accesses, each read or writeLargest

If a single-port memory is enough, or if you can organise accesses into banks, you can save considerable area.

Wrap your macros

Never instantiate a memory macro directly throughout a design. Put it in a small wrapper with your own interface. Then it can be replaced by a behavioural model in simulation, an FPGA block RAM, or a different size, without touching the rest of the design. The wrapper is also where test (BIST) and power pins connect.

Common mistake

Changing a flop array to SRAM late in a project and assuming only the storage changes. The added read cycle moves through all the control logic. Decide on your memory style early, and always design control logic with the read in mind.

Key ideas

  • SRAM reads usually have one cycle of latency.
  • Single-port and dual-port differ in cost.
  • Macros are generated, so wrap them.

Your exercise

Add read-latency handling to a small controller.

Find the bug

This code has one deliberate bug. Read the symptom, then click the line you think is wrong.

What goes wrong: rd_valid goes high one cycle before the data arrives, so the next block captures stale data.

Interview questions

coreWhat changes moving from flops to SRAM?

basicWhy are large on-chip memories built as SRAM macros rather than arrays of flops?

basicWhat is the difference between a single-port, a two-port and a true dual-port SRAM?

coreA controller reads from a synchronous SRAM and sets rd_valid directly from rd_en. What goes wrong?

assign rd_valid = rd_en;   // rdata comes from the SRAM
sram_1p u_mem (.clk(clk), .cs(rd_en | wr_en), .we(wr_en),
  .addr(addr), .wdata(wdata), .rdata(rdata));

coreA block issues 64 back-to-back reads, one address per cycle, to an SRAM with one cycle of read latency. How many cycles from the first address to the last data?

coreThis behavioural single-port SRAM model is written to the same address it is read from in one cycle. What does rdata show afterwards, and can you trust that the real macro does the same?

always @(posedge clk) begin
  if (we) mem[addr] <= wdata;
  rdata <= mem[addr];
end

coreWhy should every SRAM macro sit inside a wrapper module of your own?

advancedYou need one read and one write every cycle on average, but only single-port SRAM is available. What can you do?

advancedYour SRAM's rdata still shows the last word read, during cycles with no read. Is it safe for downstream logic to use rdata in those cycles?

Practise these 9 questions with animations