Draft preview. Lessons are being written and reviewed.

Digital Design / Module 7: Memories and FIFOs

39. Register files

Build multi-port storage.

Lecture coming soon

See it move

The idea

A register file is a small, fast memory built from flops, with independent read and write ports. CPUs use one to hold operands: read two registers and write one, all in a single cycle.

module regfile #(parameter W = 32, parameter N = 16) (
  input                    clk, we,
  input  [$clog2(N)-1:0]   ra1, ra2, wa,
  input  [W-1:0]           wd,
  output [W-1:0]           rd1, rd2
);
  reg [W-1:0] mem [0:N-1];

  always_ff @(posedge clk)
    if (we) mem[wa] <= wd;

  assign rd1 = (we && wa == ra1) ? wd : mem[ra1];
  assign rd2 = (we && wa == ra2) ? wd : mem[ra2];
endmodule

Read during write

What should a read return when it targets the address being written in that same cycle? There are two common answers:

  • Old data: the read sees the value before the write.
  • New data (bypass or write-through): a mux forwards the data being written, as in the code above.

Neither is universally right. Choose one, document it, and test it. The bypass adds a path from write data to read data, which also costs timing.

Cost

Each bit is a flop plus read-mux logic. That is far larger per bit than an cell, so register files are normally small: tens of entries, not thousands. Every additional read port adds another full set of read muxes.

Common mistake

Leaving reset out of your thinking. A large register file usually has no reset, to save area, so it holds x in simulation until it has been written. A read of an unwritten register then shows up as x. That is usually a useful warning of a real bug, so don't hide it by initialising everything in the testbench.

Key ideas

  • Array of flops; reads are often combinational.
  • Define same-cycle read and write behaviour.
  • Flop arrays cost much more area than SRAM per bit.

Your exercise

Write a 2-read 1-write register file with bypass.

Find the bug

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

What goes wrong: The specification says a read of the address being written returns the new data. This one returns the old value for that cycle.

Interview questions

coreWhat's write-through bypass?

basicIn a typical register file, writes happen on the clock edge but reads are combinational. What does that look like in hardware?

basicEstimate the storage and read-mux cost of a 32-entry, 32-bit register file with two read ports.

coreIn this register file, a test writes 60 to r1 (which holds 25) and reads r1 in the same cycle. What does rd return in that cycle and in the next, and is that a bug?

reg [7:0] mem [0:3];
always @(posedge clk)
  if (we) mem[wa] <= wd;
assign rd = mem[ra];

coreWhat does a write-through bypass cost, apart from the extra mux?

coreIn a RISC-style register file, register 0 always reads as zero. What is wrong with this bypass?

always @(posedge clk)
  if (we && wa != 0) mem[wa] <= wd;
wire [7:0] arr = (ra == 0) ? 8'd0 : mem[ra];
assign rd = (we && wa == ra) ? wd : arr;

coreA large register file has no reset. A testbench check reads a register that was never written. Why might this check report nothing?

if (rd != 8'd0)
  $display("FAIL: got %b", rd);

advancedYou need a register file with two write ports. What new problems do you have to solve?

advancedIn a classic five-stage CPU, write-back and decode access the register file in the same cycle. Which read-during-write behaviour is wanted there, and why?

Practise these 9 questions with animations