Skip to content

VHDL · Chapter 7.4 · Sequential Logic Design

Registers and Register Banks

A flip-flop stores one bit, while a register stores a word. It is simply N D flip-flops sharing the same clock, capturing a whole standard logic vector on each edge. Stack many registers into an array and you have a register bank, also called a register file, which is addressable storage written by selecting one register and read by multiplexing one out. This is the heart of every CPU datapath and many controllers. This lesson shows how to model both, how a register file becomes an array of flip-flops with write-address decoding and read multiplexing, and the patterns that keep them clean and synthesizable, built directly on the D flip-flop.

Foundation14 min readVHDLRegisterRegister FileFlip-FlopStorageRTL

1. Engineering intuition — a word-wide flip-flop, then many

If a D flip-flop is one bit of memory, a register is a row of them clocked together — present a word on the input, and at the edge the whole word is captured and held. Nothing new happens per bit; the bits just share a clock. Go one step further and arrange many registers in an array, and you get a register bank: storage you select by address. That is how a CPU keeps its working registers, how a controller holds a set of configuration values, and how small fast memories are built — all from the same flip-flop repeated.

2. Formal explanation — register and register bank

register_forms.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all;
 
-- A REGISTER: N D flip-flops sharing a clock (a vector assigned on the edge).
reg : process (clk)
begin
  if rising_edge(clk) then
    if rst = '1' then q <= (others => '0');
    else              q <= d;                 -- capture the whole word
    end if;
  end if;
end process;
 
-- A REGISTER BANK / FILE: an array of registers; write by address, read combinationally.
type regfile_t is array (0 to 15) of std_logic_vector(31 downto 0);
signal rf : regfile_t;
 
wr : process (clk)
begin
  if rising_edge(clk) then
    if we = '1' then rf(to_integer(unsigned(waddr))) <= wdata; end if;  -- write one register
  end if;
end process;
 
rdata <= rf(to_integer(unsigned(raddr)));     -- combinational read (a mux over the array)

A register is a vector signal assigned under rising_edge(clk) — N flip-flops with a shared clock. A register bank is an array of such vectors (lesson 2.10): a clocked write selects one element by address (we enables it), and a read indexes the array combinationally — effectively a mux selecting one register's contents.

3. Production RTL — a two-read, one-write register file

regfile_2r1w.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all;
-- Classic CPU-style register file: two combinational read ports, one clocked write port.
type rf_t is array (0 to 31) of std_logic_vector(31 downto 0);
signal rf : rf_t := (others => (others => '0'));
 
write_port : process (clk)
begin
  if rising_edge(clk) then
    if we = '1' then rf(to_integer(unsigned(waddr))) <= wdata; end if;
  end if;
end process;
 
rdata_a <= rf(to_integer(unsigned(raddr_a)));   -- read port A (mux)
rdata_b <= rf(to_integer(unsigned(raddr_b)));   -- read port B (mux)

What hardware does this become? An array of 32 thirty-two-bit registers (flip-flops), a write-address decoder enabling exactly one register on a write, and two read multiplexers selecting any two registers combinationally. This is the canonical RISC register file. (Large memories are usually inferred as block RAM instead — a later FPGA topic — but the modeling pattern is the same array-of-registers idea.)

4. Hardware interpretation — N flip-flops, addressed

register as N flip-flops and a register file as an addressed array with write decode and read muxwrite onewrite data + waddrwe enables one registerwrite decodeselects one register(clocked)array of registersN words of flip-flopsread mux (raddr)selects one word (comb)read dataselected register contents12
A register is N D flip-flops sharing one clock, capturing a word together. A register bank/file arranges many such registers in an array: a write selects one register by decoding the write address and enabling its flip-flops; a read selects one register's contents through a multiplexer driven by the read address. The flip-flops are clocked storage; the address decode (write) and read mux are combinational logic around them. This is the same flip-flop repeated and surrounded by selection logic.

5. Simulation interpretation — write on edge, read continuously

Register bank: a clocked write lands on the edge; reads are combinational

8 cycles
Register bank: a clocked write lands on the edge; reads are combinationalwe=1 on the edge → register 3 captures AAwe=1 on the edge → reg…raddr=5 → read mux returns reg 5 (BB) combinationallyraddr=5 → read mux ret…clkwe11001100waddr33335555wdataAAAAAAAABBBBBBBBraddr33333355rdata00AAAAAAAAAABBBBt0t1t2t3t4t5t6t7
A write commits on the rising edge when we=1 (register 3 takes AA), exactly like a flip-flop. Reads are combinational: rdata continuously reflects the addressed register, so changing raddr immediately switches the output. Clocked write, combinational read is the standard register-file behaviour.

6. Debugging example — the write/read race and the missing enable

Expected: a value written this cycle is reliably stored and readable. Observed: an unexpected old/new value on a same-cycle read, or every register changes on a write. Root cause: for the read/write timing, a combinational read of the just-written address returns the old value (the write lands on the edge, the read is combinational) — a read-during-write hazard to define deliberately; for "every register changed," the write lacked an address-selected enable, so the assignment hit the whole array instead of one element. Fix: decide and document read-during-write behaviour (often forward the write data if needed), and gate the write with we plus an address so exactly one register updates. Engineering takeaway: a register file writes one addressed register on the edge and reads combinationally — guard the write with an enable and address, and define read-during-write explicitly.

write_enable_and_address.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- Write exactly ONE register, only when enabled.
if rising_edge(clk) then
  if we = '1' then rf(to_integer(unsigned(waddr))) <= wdata; end if;  -- one element, enabled
end if;

7. Common mistakes & what to watch for

  • Writing the whole array instead of one element. Index by the write address and gate with we.
  • Ignoring read-during-write. A combinational read of the address being written returns the old value on that edge; define the intended behaviour.
  • Index out of range. to_integer(unsigned(addr)) must stay within the array bounds; size the address to the array.
  • Resetting a large register file every cycle. Often unnecessary and costly; reset only what needs a defined initial value (or rely on write-before-read).
  • Expecting a huge array of registers to be cheap. Large storage should usually be inferred block RAM (later); flip-flop arrays suit small, multi-port register files.

8. Engineering insight & continuity

Registers and register files are the D flip-flop scaled up: a register is flip-flops sharing a clock, a register file is an addressable array of them with write decoding and read multiplexing. Keep the clocked write narrow (one enabled, addressed element) and the read combinational, and the structure synthesises to exactly the storage you intend. Most registers, though, should update only sometimes — on a condition, not every clock — which is the subject of the next lesson, Clock Enables: how to make a register hold or update under control, the gateway to counters, accumulators, and pipelines with stalls.