Skip to content
VLSI Mentor

DDR · Module 1

Registers

Registers are storage moved inside the execution path and named by the instruction encoding rather than addressed. The architectural, microarchitectural and RTL views of that, a synthesizable 2R1W register file with its clocked behaviour and reset argument, and the port and scaling limits that make a denser tier unavoidable.

Chapter 1.1 ended with a claim it did not prove: registers sit at the top of the memory hierarchy because they are the fastest and closest storage a processor has, and they cannot supply system-scale capacity. This chapter proves the second half.

The question worth the chapter is not what is a register. It is this: if registers are the fastest and closest storage available to computation, why can a processor not simply use registers for all of its working data? Answer that properly and you have derived the need for every tier below — caches, main memory, and eventually the DDR interface that makes main memory usable. Answer it with "registers are small because they are expensive" and you have learned a slogan.

So this chapter does three things. It fixes what the word register means at three different abstraction levels, because engineers routinely talk past each other on exactly that point. It builds a small register file in synthesizable SystemVerilog and reasons about its clocked behaviour, its reset strategy and its failure modes. And it identifies precisely which properties of that structure stop scaling — which is the argument that makes the next tier necessary.

1. Where Are the Operands When the ALU Needs Them?

Start with an arithmetic operation rather than with a storage technology:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
R3 = R1 + R2

An adder cannot act on names. Before it computes anything, two 32-bit values must be physically present at its inputs, and a cycle later the result must be physically somewhere that the next instruction can reach. So the interesting question about this one line is not what the adder does. It is: where were R1 and R2 an instant before the addition, and where does R3 go?

Suppose the answer were "in main memory". Then every arithmetic instruction would require two memory reads and one memory write, each travelling the full path Chapter 1.1 laid out — through the cache hierarchy, the memory controller, the PHY, to the DRAM device and back. A machine built that way does not merely run slowly; it stops being a pipelined processor at all, because the execution units would spend almost all of their time waiting for operands rather than computing.

The way out is not a faster memory. It is a different kind of storage, defined by its relationship to execution rather than by its capacity: a small set of locations that the instruction encoding can name directly, physically adjacent to the execution units, readable and writable within the rhythm of the pipeline itself. Those locations are registers.

2. Three Meanings of One Word

Most confusion about registers is a collision between abstraction levels. Three engineers can use the word correctly and mean three different things.

The architectural view — state the instruction set defines. At this level a register is a storage location named by the architecture, so software can rely on it existing. General-purpose registers are the obvious case; a program counter, a stack pointer, and status or control state are registers in exactly the same architectural sense. What makes them architectural is that the instruction encoding refers to them and their behaviour is part of the contract between hardware and software. This is the level at which R1 in §1 is meaningful: it is a name in an agreement, not a location on a die.

The microarchitectural view — structures that hold values near execution. At this level a register is a physical thing: an entry in a storage array placed close to the execution units that read and write it. The architectural contract does not dictate the implementation, and modern processors exploit that gap. An implementation may hold more physical entries than the architecture names, and may map architectural names onto them dynamically — the machinery of out-of-order execution. That mapping is a genuine subject and it is not this chapter's; what matters here is the weaker and more useful point: the architectural register count and the physical entry count are not required to be the same number.

The RTL view — state that persists across a clock event. At this level a register is neither a name nor an entry but a behaviour: a value that must still be there on the next cycle, described in an HDL as sequential state. This is the level RTL and verification engineers work at, and it is where the most expensive misunderstanding lives.

3. The Register File as a Structure

Individually declared registers stop being a workable model almost immediately. If an instruction can name any of several architectural registers as either of its two sources, the hardware needs to select among them by index — and a set of storage entries plus index-driven selection is not a pile of separate registers. It is one structure: a register file.

An instruction supplies read indices to a register file. The register file supplies two operands to an execution unit. The execution unit's result goes to a write-back stage, which returns a destination index and data to the register file.Instructionnames source + destinationRegister fileDEPTH entries of WIDTH bitsExecution unitneeds both operands nowWrite-backresult + destination indexindicesoperandsresultwrite12
Figure 1 — the operand loop: the instruction names indices, the file supplies two operands, the result returns as an index plus data.

Read the loop rather than the boxes. The instruction supplies indices — small numbers selecting which entries to use. The file presents the selected entries' contents as operands, in time for the execution unit to consume them. The result travels back as a destination index plus data, and is written into the entry that index selects. Nothing in that loop involves a memory address, a bus transaction, or an acknowledgement. The file is inside the pipeline, not attached to it.

Two observations that shape everything afterwards.

Selection is the whole mechanism. A register file is an addressed array whose address happens to arrive from the instruction encoding rather than from an address bus. That is why the architectural register count and the instruction format are linked: the index has to fit in the encoding.

The loop has a fixed traffic pattern per cycle. One instruction of the shape in §1 needs two entries read and one entry written — and if the machine is to sustain one such instruction per cycle, it needs all three of those accesses in the same cycle. That requirement has a name, and it is where the cost lives.

4. Ports, and Why They Are the Expensive Part

A port is an independent access path into the array: a set of wires and selection logic that can perform one access per cycle, concurrently with the other ports. The traffic pattern of §3 therefore demands a file with two read ports and one write port — a 2R1W file — because two operands must emerge and one result must be accepted within one cycle.

The phrase "just make the register file bigger" hides the fact that two very different things can grow.

Growing depth adds entries. The index widens, the selection logic grows, and the array occupies more area — a real cost, but a comparatively gentle one, and one that runs into the instruction encoding long before it runs into physics.

Growing ports adds simultaneous access capability, and this is the expensive direction. Every additional read port needs its own index distribution to every entry, its own selection network, and its own data path out of the array. Every additional write port needs its own index and data distribution in, plus a defined answer to what happens when two write ports target the same entry in the same cycle. Qualitatively, adding ports increases the circuitry inside the array, the routing between the array and the units it feeds, the electrical load that each entry's output must drive, the total area, the difficulty of closing timing on the access path, and the power spent per access. How much, in any specific design, depends on the implementation style, the array dimensions, the process technology and the target frequency — which is exactly why this chapter states the direction of each effect and not a ratio.

Two consequences matter for the rest of the DDR curriculum:

Port count is driven by how much work the machine wants to issue per cycle, not by how much data it wants to hold. A wider execution engine — more operands consumed per cycle — pushes port count up even if the number of architectural registers never changes.

Capacity and concurrency are separate axes, and only one of them scales cheaply. A storage structure that must serve several independent accesses every cycle is fundamentally a different engineering problem from one that must merely hold a great many bits. Main memory is the second problem. A register file is the first.

5. RTL — A 2R1W Register File

Here is the structure as synthesizable SystemVerilog. It is deliberately small: eight entries of 32 bits, two read ports, one write port. Everything in it is load-bearing, and §6 walks through it line by line.

What it does. It holds DEPTH values of WIDTH bits. Two indices select two entries whose contents appear on the read data outputs in the same cycle; when we is asserted, the entry selected by waddr takes the value on wdata at the next rising clock edge.

How to simulate it. With a Questa-style flow, vlog regfile_2r1w.sv tb_regfile_2r1w.sv then vsim -c tb_regfile_2r1w -do "run -all"; with VCS, vcs -sverilog regfile_2r1w.sv tb_regfile_2r1w.sv && ./simv; with Xcelium, xrun -sv regfile_2r1w.sv tb_regfile_2r1w.sv. Every later code block in this chapter simulates the same way.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. A two-read / one-write register file — the storage an
// execution unit needs to sustain one "Rd = Rs1 op Rs2" per cycle.
//
// READ SEMANTICS: combinational. The indices arrive and the selected
// entries' contents are available in the SAME cycle, because the execution
// unit downstream needs them in that cycle (§3). A registered-read variant
// and the reason to choose it are in §6.
//
// RESET: there is deliberately no reset on the storage array. §9 is the
// argument for that, and the conditions that would change it.
module regfile_2r1w #(
  parameter int WIDTH = 32,
  parameter int DEPTH = 8,
  // DERIVED — not intended to be overridden by the instantiator. The guard
  // keeps a DEPTH of 1 legal instead of producing a zero-width index.
  parameter int ADDR_W = (DEPTH <= 1) ? 1 : $clog2(DEPTH)
) (
  input  logic              clk,

  // Read port A — index in, data out, same cycle.
  input  logic [ADDR_W-1:0] raddr_a,
  output logic [WIDTH-1:0]  rdata_a,

  // Read port B — independent of port A in every respect.
  input  logic [ADDR_W-1:0] raddr_b,
  output logic [WIDTH-1:0]  rdata_b,

  // Write port — takes effect at the next rising edge of clk when we is high.
  input  logic              we,
  input  logic [ADDR_W-1:0] waddr,
  input  logic [WIDTH-1:0]  wdata
);

  // The storage itself: DEPTH entries, each WIDTH bits wide. This one
  // declaration is the whole array; the ports below are views onto it.
  logic [WIDTH-1:0] regs [DEPTH];

  // THE ONLY WRITER of regs. State changes on a clock event and nowhere
  // else, which is what makes this structure sequential (§8).
  always_ff @(posedge clk) begin
    if (we) begin
      regs[waddr] <= wdata;
    end
  end

  // The two read ports. Continuous assignment, so each output tracks the
  // selected entry within the cycle rather than a cycle later. The ports are
  // independent: either index may select any entry, including the same one.
  assign rdata_a = regs[raddr_a];
  assign rdata_b = regs[raddr_b];

endmodule

Expected output. The module produces no messages of its own; a directed stimulus sequence writing 32'h11 to entry 3 and then reading entry 3 should report, from the testbench, rdata_a = 00000011 on the cycle after the write and not before it. §7's figure is that sequence in full.

Waveform expectation. At a rising edge where we is high, the entry selected by waddr changes value; on the following cycle a read port presenting that same index shows the new data. A read port presenting the index during the write cycle shows the old data — the figure in §7 makes this the central event.

Synthesis implication. The always_ff block describes DEPTH × WIDTH bits of clocked storage with a write-enable term, and the two continuous assignments describe two independent selection networks reading that storage. What a tool actually builds from it depends on the target technology, the array dimensions, the coding style, the constraints and the tool itself: at these dimensions a flip-flop array with multiplexed read paths is a reasonable expectation, while a much larger array with the same combinational read requirement may be mapped very differently or may not be implementable as a single macro at all. Treat that as a question to answer per project from the synthesis report, not as a property of the code.

Debugging notes. If reads return the value written on an earlier cycle rather than the latest one, check we and waddr at the edge in question before suspecting the array. If reads return x in simulation, the entry has not been written yet — §9 explains why that is a design decision here rather than a bug. If read port B ever agrees with port A when their indices differ, suspect the index wiring at the instantiation rather than inside this module.

6. Reading the RTL

The storage array. logic [WIDTH-1:0] regs [DEPTH]; is one unpacked array of packed vectors: DEPTH entries, each WIDTH bits. Everything else in the module is a view onto this declaration. Note what the declaration alone does not settle — it does not make these bits flip-flops. They become sequential storage because of the always_ff block and because the read ports observe values written on earlier cycles.

The write port. Three signals decide one write: we says whether a write happens at all, waddr says which entry, wdata says what value. The if (we) is the write-enable term, and its absence would mean the array was written every cycle — a common and expensive slip, because it destroys the entry named by whatever happens to be on waddr.

The clocked update. always_ff @(posedge clk) states that the block's state changes at a rising clock edge and at no other time. The nonblocking assignment <= is what makes the update behave like hardware — §8 is the fuller argument.

The read ports. Two continuous assignments, each selecting an entry by its own index. Three properties are worth naming explicitly because verification will check each one. The ports are independent — nothing couples port A's index to port B's. They are pure functions of the index and the array — the same index over the same array contents gives the same data, so a read has no side effect and reading twice is not different from reading once. And they are combinational — the data is available in the cycle the index is presented, which is the property §3 said the execution unit needs.

Read semantics are a choice, and this is the other one. Registering the read moves the data one cycle later:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// A REGISTERED-READ variant of one port. NOT a drop-in replacement: the
// data now arrives one cycle after the index, so the consumer must be
// pipelined to match. rdata_a_q is the port's output, declared
// logic [WIDTH-1:0] in place of the combinational rdata_a.
always_ff @(posedge clk) begin
  rdata_a_q <= regs[raddr_a];
end

Neither form is the correct one in general. A combinational read puts the array's selection path into the same cycle as whatever consumes the operand, which is simple to reason about but lengthens that cycle's timing path. A registered read shortens the path by cutting it with a flip-flop, at the cost of a pipeline stage the rest of the design must accommodate, and it is also the interface that dense memory arrays typically present. The choice belongs to the surrounding microarchitecture — and this is the first appearance of a trade-off that reappears throughout the DDR curriculum, where the storage device's own access latency is likewise something the consumer must be built to tolerate rather than something it can wish away.

Read-during-write becomes a question you must answer. Because the read is combinational and the write lands at the edge, a read of the entry being written in that same cycle returns the value the entry held before the write. That is a consequence of this implementation, not a universal law, which is why §13 treats it as a specification obligation rather than a fact.

7. When the State Actually Changes

The whole point of sequential storage is that the value changes at a defined moment. This figure is that moment, eight cycles of it.

regfile_2r1w — writes, visibility, and a same-cycle collision

8 cycles
Eight cycles of the register file. Entry 3 is read before it has ever been written and returns x. A write of 0x11 to entry 3 is in progress during that cycle and becomes visible on the next. A write of 0x22 to entry 5 follows. A later write of 0x33 to entry 3 collides with a read of entry 3, which returns the old value 0x11 in that cycle and 0x33 on the next. A final read of entry 5 shows it still holds 0x22.never written yet — x, not zeronever written yet — x, notzeroread collides: returns OLD 0x11read collides: returns OLD0x110x33 visible from here0x33 visible from hereentry 5 still holds 0x22entry 5 still holds 0x22clkwewaddr--35--3------wdata--0x110x22--0x33------raddr_a33333355rdata_aXX0x110x110x110x330x220x22t0t1t2t3t4t5t6t7
Figure 2 — one write lands, one read collides with a write, and an unrelated entry keeps its value.

Four things to read out of it, in order.

Cycle 0 and 1 — an unwritten entry reads as x. Read port A presents index 3 before anything has ever been written there. In simulation the four-state array has no defined value, so the read returns x. This is not a bug in the module; it is the visible consequence of the reset decision in §9, and seeing it here is the reason that decision deserves argument rather than reflex.

Cycle 1 into 2 — a write becomes visible one cycle after it is ordered. During cycle 1, we is high with waddr = 3 and wdata = 0x11. The read of index 3 in cycle 1 still shows x: the write has been presented, not yet applied. The rising edge that ends cycle 1 applies it, and from cycle 2 the read port shows 0x11. The distinction between presenting and applying is the entire content of the word "clocked".

Cycle 4 — the collision. A write of 0x33 to entry 3 is presented in the same cycle as a read of entry 3. The read returns 0x11 — the value before the write. One cycle later it returns 0x33. Under this implementation the same-cycle read is a read of the old value, and §13 explains why a design must state which of the possible answers it intends rather than discover it.

Cycle 6 — an unrelated entry is untouched. Three writes have happened, two of them to entry 3. Entry 5 still holds the 0x22 written back in cycle 2. Storage that quietly loses unrelated entries is one of the failure modes §14 asks a verification plan to rule out, and this is what ruling it out looks like on a trace.

8. always_ff and Sequential Intent

The block that creates the storage is four lines:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always_ff @(posedge clk) begin
  if (we) begin
    regs[waddr] <= wdata;
  end
end

Three deliberate choices are packed into it.

always_ff states the intent. It tells a reader, and a lint or synthesis tool, that this block is meant to describe sequential storage. A tool may report it if the block's contents would not in fact produce flip-flops, which converts a class of silent mistakes into a message at elaboration. always @(posedge clk) would describe the same behaviour to a simulator while asserting nothing, and the value of always_ff is precisely the assertion.

The sensitivity list is the whole timing model. @(posedge clk) says state changes on a rising edge of clk and at no other time. There is nothing else in the list — no we, no waddr — because those are conditions evaluated at the edge, not events that cause an update.

The nonblocking assignment orders the update correctly. <= schedules the new value so it takes effect after every block in this time step has evaluated, which reproduces what real flip-flops do: they all sample their inputs from the state that existed before the edge, so one flop's new output cannot race into another flop's input within the same edge. Writing = here would let the update be visible to other code in the same time step depending on evaluation order — a race whose symptoms depend on the simulator, and one of the reasons the repository's convention, and the industry's, is nonblocking assignment for clocked state. The mechanics of that choice are their own topic: Blocking vs Nonblocking Assignments.

The educational point is smaller than the syntax: this block says that a value survives a clock event. That is the entire definition of sequential state, and it is what makes the structure a register file rather than a network of gates.

9. Reset Is an Engineering Decision

Beginner material resets everything, because resetting everything is never wrong in simulation. It is often wrong in a chip, and a register file is the clearest place to learn why.

Ask the question properly: does every bit of this array actually need a defined value before the first write? For the data entries of a register file, frequently not — and the reason is architectural rather than electrical. Nothing is entitled to read an entry before something has written it. A program that reads an architectural register before initialising it is reading a value the architecture never promised; a pipeline that reads a file entry before writing it has a control bug, not a storage bug. Where that guarantee genuinely holds, resetting the data bits adds no correctness and costs something real: a reset signal distributed to every bit of the array, extra circuitry inside each storage element, routing and load on a net that fans out very widely, and an area and power cost paid permanently for a value that is overwritten before it is ever legitimately observed.

That is why the module in §5 has no reset on regs — and why Figure 2 shows an x at cycle 0. The x is the cost of the decision, made visible.

Now the other side, because the decision is not free.

Control and validity state usually does need reset, even when bulk data does not. The distinction that matters is not "small versus large" but "is this bit read before it is written?" A state machine's current state, a counter, a valid bit, a pointer — all of these are read on the first cycle after reset by definition, so all of them need a defined initial value. The general pattern is therefore a reset on the control state that governs access, and no reset on the data the control state protects:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The pattern: control state is reset because it is READ before it is
// written; the data it guards is not, because it is WRITTEN before it is
// read. Both live in the same design; only one needs the reset net.
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    entry_valid <= '0;        // read on the first cycle — must be defined
  end else if (we) begin
    entry_valid[waddr] <= 1'b1;
  end
end

And several real requirements can overturn the conclusion for the data too. A verification or bring-up flow that requires simulation to be free of x propagation may mandate a defined initial value. A safety, security or DFT requirement may mandate a known state after reset — for instance so that a scan or self-test sequence starts from a defined point, or so that no residual data survives a reset boundary. A design may also get determinism without a reset net at all, by initialising the array through its ordinary write port during a defined start-up sequence, which trades cycles for area. Which of these applies is a project decision, so the honest statement is the conditional one: reset the bits that are read before they are written, reset the bits a requirement obliges you to reset, and justify the rest rather than resetting them by habit.

10. Four Words That Are Not Synonyms

The abstraction levels of §2 produce a vocabulary that is routinely flattened. Keeping these apart is what lets a design discussion stay precise.

TermWhat it actually denotes
Architectural registerState the instruction set defines and software may rely on — named by the encoding, part of the hardware/software contract.
Register-file entryOne addressable slot in a physical storage array. An implementation's entries need not correspond one-to-one with the architectural names.
RTL sequential variableAn HDL variable whose value survives a clock event because of how it is assigned and read. A description of behaviour, not yet a structure.
Flip-flopA physical sequential storage element — the primitive a clocked single-bit variable is typically realised as.
Memory macroA dense, purpose-built array block. It may implement storage that behaves like a register file, but its ports, access timing and internal organisation are the macro's, not the RTL's to assume.

The relationships between the rows are the point. An architectural register is implemented by one or more file entries. An RTL sequential variable is realised as flip-flops or, for a large enough array, as something denser. A flip-flop and a memory macro are both storage, but they are not interchangeable choices at a given size: the first is the natural unit for a handful of bits near logic, and the second exists because the first stops being appropriate — which is the whole subject of §11.

11. Why Not Build Everything From Flip-Flops?

Here is where the chapter's central question gets answered, and where the DDR curriculum's motivation appears.

A handful of clocked storage elements next to an execution unit is an excellent engineering answer. The same construction applied to a system's entire working data set is not, and it fails on several independent axes at once.

Area per bit. A flip-flop is built to be individually clocked and individually readable, and in a register file it is individually enabled as well. That generality costs devices per bit — devices the design pays for at every single bit. A structure whose purpose is to hold an enormous number of bits cannot afford per-bit generality it does not use.

Wiring, which is usually the real limit. Registers are cheap partly because their wires are short and few. A file with per-entry access needs index distribution to every entry and a data path out of every entry; grow the entry count and the wiring grows with it, in length and in count. Past some size the structure is dominated not by the storage but by the interconnect reaching it — and interconnect is a primary limiter of both achievable frequency and area.

Clocking and power. Every clocked element is a load on a clock network that switches continuously. A very large array of individually clocked elements therefore spends power in proportion to how much storage exists rather than to how much of it is being used, which is exactly the wrong scaling for a capacity tier. Techniques exist to reduce it; none of them make the underlying scaling attractive.

Access organisation. This is the deepest difference and the easiest to miss. A register file is organised for arbitrary simultaneous access to a few entries: any index on any port, every cycle. A capacity tier is organised for the opposite — sharing decode, selection and data paths across a very large number of bits, so that the per-bit cost of those shared resources becomes small. Those two organisations are not points on one continuum. They are different structures, arising from different requirements, and a design that wants capacity must adopt the second and accept what it costs: an access that is no longer a same-cycle selection but a sequence of operations with rules.

Scaling all of the above together. Each axis alone might be survivable. Together they mean that "a very large register file" is not a large version of a register file at all — it is a different device, and it will have acquired the properties of a memory in the process.

So the answer to the chapter's question is not that registers are slow or expensive. It is that the properties that make registers excellent are exactly the properties that do not scale: per-entry access, per-bit generality, and physical adjacency to the execution units. Keep those and you cannot have capacity. Want capacity and you must give them up — and the only question left is what you give up next, which is the sequence the rest of Module 1 walks.

12. Naming Versus Addressing

One last distinction, because it is the cleanest way to hold the difference between a register access and a memory access.

A register operand is named. The index is part of the instruction, resolved when the instruction is decoded, and the storage it selects is inside the pipeline. There is no address to translate, no structure to search, nothing that can miss, and nothing to wait for beyond the selection path itself.

A main-memory operand is addressed. The address must be computed, possibly translated, then presented to a hierarchy that may or may not hold the data, and — if it does not — to a memory controller that turns the request into a sequence of device operations and eventually returns a value. Chapter 1.1 walked that path; the point of repeating its shape here is only to line the two up:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
execution         ← operands are NAMED here

registers         ← selection, in-cycle, no possibility of a miss

cache hierarchy   ← ADDRESSED from here down; may or may not hold the data

memory controller ← a request becomes a sequence of device operations

DDR PHY

DRAM

Everything below the second line is addressed, can miss, and takes a variable amount of time. That is the price of capacity, and the reason "closest to computation" is worth as much as it is.

13. Common RTL Mistakes

Each of these is a wrong mental model first and a bug second, which is why the fix is rarely a line of code.

Expecting storage from a declaration. Wrong model: declaring a variable creates a register. Resulting risk: a value the designer believes is held is actually recomputed, or a value believed to be combinational is inferred as a latch because some path through a block leaves it unassigned. Either way the synthesized hardware differs from the mental model, and the symptom is usually an unexpected cycle of delay or a latch the tool reports. Prevention: decide whether each signal is combinational or sequential before writing it, use always_comb and always_ff to declare that decision, and read the tool's inference report rather than assuming.

Blocking assignment for clocked state. Wrong model: = and <= differ only in style. Resulting risk: within one clock edge, an update becomes visible to other code depending on evaluation order, so the design simulates as a chain of updates where the hardware behaves as a set of simultaneous ones. The symptom is a value that arrives a cycle early, results that change when unrelated code is reordered, or a simulation-versus-synthesis mismatch. Prevention: nonblocking assignment for every clocked update, blocking assignment inside combinational blocks, and never both to the same variable.

Leaving read-during-write undefined. Wrong model: whatever the RTL happens to do is the behaviour. Resulting risk: the design has a policy nobody wrote down. Reading and writing one entry in a single cycle can legitimately yield the old value, the new value, or something the implementation technology decides — and a dense memory array need not offer the same answer the flop-based version did. Consumers built around an unstated assumption break when the implementation changes. Prevention: state the intended policy in the specification, assert it, and — if the consumer needs the new value in the same cycle — implement the forwarding explicitly rather than relying on an inferred behaviour. The implementation options are covered in Read Timing and Read-During-Write.

Resetting a large array because resetting is a habit. Wrong model: reset is always correct, so reset everything. Resulting risk: a reset net fanning out to every bit of an array, paid for in area, routing and power, for values that are overwritten before any legitimate read. On a large enough structure the reset distribution becomes a timing and physical-design problem of its own. Prevention: §9's test — reset what is read before it is written, plus whatever a requirement obliges, and justify the rest.

Assuming the index cannot exceed the array. Wrong model: the address width matches the depth, so every index is valid. Resulting risk: it matches only when the depth is a power of two. Parameterise the same module to a depth of, say, six and the three-bit index can select entries six and seven, which do not exist. An out-of-range access to an unpacked array is not a case the design intended, and simulation and synthesis need not agree about what it does — so the failure is both silent and inconsistent. Prevention: constrain depth to a power of two, or handle the out-of-range case explicitly, and assert that the index is in range so the condition is caught rather than interpreted.

More than one procedural writer. Wrong model: two blocks can each update the array when convenient. Resulting risk: with two always_ff blocks assigning the same variable the result is a tool error or a race, and with a subtler split — one block writing the array, another writing the same entry through a different path — the design has no single place that decides what an entry holds. Prevention: exactly one procedural block owns each piece of state. Anything else that wants to change it presents a request to that block's inputs, which is what we, waddr and wdata are for.

14. Verification — What There Is to Check

The module is twenty lines. The list of behaviours a verification plan should pin down is longer than the module, which is normal and is the point.

Write enable is honoured in both directions. A write occurs when we is high, and no entry changes when we is low. The negative half is the one that gets skipped and the one that catches a missing enable term.

A write lands on the selected entry. The entry named by waddr takes wdata, and does so at the intended edge rather than a cycle early or late.

Unrelated entries are undisturbed. A write to one entry leaves every other entry as it was. This is the property that catches an index decoded too widely, and it cannot be established from the read ports alone in one cycle — it needs either a reference model of the whole array or a systematic read-back.

Each read port selects the entry its own index names. Separately for A and B, because a swapped or shared index is easy to introduce at integration and invisible whenever the two indices happen to be equal.

The read ports are independent. Any combination of indices, including both ports on the same entry, and including one port reading the entry the other is nowhere near. Independence is a claim about the absence of coupling, so it is tested by combinations, not by a single case.

Read-during-write follows the stated policy. Not "whatever it does" — the policy §13 insists on writing down. Both orders matter: read port A colliding, read port B colliding, and both at once.

Reset behaviour matches the specification. For this module the specification says the data array has no reset and must not be read before it is written, so the check is on the consumer's discipline. For a variant with reset, the check is that the reset value is what the specification claims.

Out-of-range indices behave as the contract says. If the contract is that they cannot occur, that is an assertion on the environment; if it is that they are ignored, that is a behaviour to test. Silence is not a contract.

A full environment for this is not a foundation-chapter subject. What belongs here is the habit: enumerate the behaviours before writing the checks, and notice which of them a black-box view cannot reach.

15. Three Assertions Worth Writing

Assertions earn their place when they state a behaviour precisely enough that a later "simplification" of the design cannot quietly violate it. These three are chosen because each is provable from the module's own ports — no hierarchical peeking into regs — and each maps to a specific item in §14.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// VERIFICATION-ONLY. Inside regfile_2r1w, so WIDTH and ADDR_W are in scope.
// There is no `disable iff` clause because this module HAS no reset (§9);
// adding one would imply a signal that does not exist.

// P1 -- the read ports are index-pure and mutually consistent. If both
// indices name the same entry, both ports must present the same data. This
// is what catches a port wired to the wrong index, or to waddr.
property p_same_index_same_data;
  @(posedge clk)
    (raddr_a == raddr_b) |-> (rdata_a == rdata_b);
endproperty
assert property (p_same_index_same_data);

// P2 -- a disabled write changes nothing observable. If we was low at an
// edge, then any read port still presenting the same index afterwards must
// still see the same data. This is the negative half of the write-enable
// check from §14 -- the half that catches a missing `if (we)`.
property p_no_write_no_change;
  @(posedge clk)
    !we |=> ($stable(raddr_a) -> $stable(rdata_a));
endproperty
assert property (p_no_write_no_change);

// P3 -- a write lands on the selected entry and is visible from the NEXT
// cycle. The local variables capture the address and data AT the write
// edge, so a later change of waddr/wdata cannot weaken the check. This is
// the read-during-write policy of §6 stated as a requirement: not visible
// in the write cycle, visible in the one after it.
property p_write_visible_next_cycle;
  logic [ADDR_W-1:0] wa;
  logic [WIDTH-1:0]  wd;
  @(posedge clk)
    (we, wa = waddr, wd = wdata) |=> ((raddr_a == wa) |-> (rdata_a == wd));
endproperty
assert property (p_write_visible_next_cycle);

What each one buys. P1 is a consistency claim that holds in every cycle and costs nothing to evaluate; it fails loudly the moment a read path is mis-wired, which is the most common integration error on this block. P2 turns "the enable works" into a checkable statement without needing to know the array's contents — it only asserts that nothing changed, which is precisely the weaker and more provable form of the property. P3 is the policy assertion: it pins the write's destination, its data, and the cycle from which it is observable, and because it captures waddr and wdata in local variables at the write edge it remains exact even when the write bus changes immediately afterwards.

What they deliberately do not claim. None of the three proves that a write left other entries untouched — §14's third item. From the ports alone, in general, that property needs a reference model of the array holding a shadow copy and comparing on every read, which is a scoreboard rather than an assertion. Writing a property that appeared to prove it from two read ports would be the worse outcome: a check that passes while the behaviour it names goes untested. Three assertions that are exactly true beat ten that are approximately about the right thing.

16. Debugging — A Destination Register Holding Yesterday's Value

The symptom. An execution unit occasionally computes from a source register that contains the value from before the instruction that was supposed to have written it. Not always — intermittently, and more often when particular instruction sequences appear close together.

Work the path in the order that eliminates the most causes per step.

First, establish which cycle is wrong. Find the write that should have supplied the value and the read that missed it, and look at the edge between them. The whole question is whether the write was applied before the read observed it, so the two events and the edge that separates them are the only things in scope. A symptom that looks like bad data is usually a disagreement about when.

Then check whether the write was presented at all. Was we high in the cycle you expect? An enable that is qualified by a condition — a stall, a flush, an exception, a predicate — is the most common reason a write silently does not happen. A write that never occurred leaves the old value in place, which is exactly the symptom.

Then check where it was presented. Was waddr the destination index you expect at that edge, and did it hold steady through it? A destination index that arrives a cycle late relative to its data writes the right value into the wrong entry, which produces this symptom and a second corrupted register that nobody has noticed yet.

Then check what was presented. Was wdata the value you expect at that edge? A result that is still being computed when the write is ordered stores whatever was on the bus, and the stored value will often look like a previous result — indistinguishable from "the write did not happen" if you only inspect the reading end.

Then ask whether this is a collision. If the reading instruction is adjacent to the writing one, the read may be in the same cycle as the write. Under §6's semantics that returns the old value correctly — the module is doing what it was built to do, and the bug is in the consumer, which needed the new value in the cycle the implementation cannot supply it. This is the case where the fix is not in the register file: either the pipeline must wait, or the design must forward the result to the operand path explicitly. Intermittency that tracks instruction adjacency rather than instruction identity is the signature.

Then question the timing of the writeback stage itself. If the result is written a cycle later than the pipeline's model assumes, every dependent instruction within that window sees a stale operand. The distinguishing evidence is that the failure depends on the distance between the producing and consuming instructions, and disappears once they are far enough apart.

What makes this tractable. Each step above is a different signal at a defined edge, and each one, checked, removes a family of causes. The undisciplined version of this debug — staring at the corrupted value and reasoning backwards about what could produce it — has too many candidates to converge. The disciplined version has six, in an order, and the assertions in §15 would already have eliminated two of them before the debug started.

17. Interview Reasoning

These are asked because the answers separate an engineer who has built storage from one who has read about it. Each is a trade-off question wearing a factual disguise.

"Why can a processor not replace its cache and DRAM with one enormous register file?" Because the register file's defining properties are the ones that do not scale (§11). Per-entry access, per-bit generality and physical adjacency to execution make a small structure superb and a large one infeasible — area per bit, wiring, clock power and access organisation all degrade together, and the wiring usually binds first. A strong answer also makes the deeper point: scaled up far enough the structure would have to share decode, selection and data paths across many bits to be affordable, and at that moment it has stopped being a register file and become a memory, with a memory's access rules.

"What hardware capability is implied by a file that supplies two operands and accepts one result in the same cycle?" Two read ports and one write port — independent, concurrent access paths, not one path used three times (§4). The follow-up is the real question: what does adding ports cost? Circuitry inside the array, index and data distribution to and from every entry, electrical load, area, timing closure on the access path, and power per access. A candidate who answers "more area" has the least interesting third of it; one who mentions routing and timing has built something.

"Does every variable declared logic in SystemVerilog become a flip-flop?" No. logic is a data type and carries no implementation commitment (§2). Storage appears when a value is assigned on a clock event and read on a later cycle. The instructive addition is the failure mode in the other direction: a combinational block that leaves a variable unassigned on some path can infer a latch nobody wanted, so the question of whether storage exists is answered by assignment structure and the tool's inference report, never by the declaration.

"Should every bit in a large register array be reset?" Not by default, and the test is not size but order of use (§9). Bits read before they are written — control state, valid bits, pointers, state-machine state — need a defined initial value. Bulk data written before it is read usually does not, and resetting it costs a reset net fanning out to every bit, plus area and power, permanently. The mature answer names what would overturn the conclusion: a requirement for x-free simulation, a safety, security or DFT obligation to reach a known state, or a decision to initialise through the write port instead.

"What happens if the same register is read and written in one cycle?" It depends on the implementation, which is why the design must decide and document it (§13). Possible answers include the old value, the new value, and a behaviour the storage technology imposes — a dense array need not match what a flop-based version did. The best answer inverts the question: the correct engineering move is not to discover the behaviour but to specify it, assert it, and if the consumer requires the new value in the same cycle, implement that forwarding explicitly rather than depending on inference.

18. Engineering Check

Reason this through before reading the discussion.

A simple execution unit must sustain, every cycle: two operand reads, one result write, 32-bit values, across eight architecturally visible registers. Then a later proposal asks for the same structure scaled to millions of entries.

How many read ports and how many write ports are required? Two read and one write, because all three accesses must occur in the same cycle for the unit to sustain one operation per cycle. The count follows from the concurrency requirement, not from the number of registers — which is why the answer would not change if there were sixteen architectural registers instead of eight, and would change immediately if the unit consumed three operands per cycle.

What state must persist across cycles, and what must not? The eight entries must persist: a value written by one instruction has to be there for a later one, which is the definition of sequential state. The indices, the write enable and the operand values on the wires must not persist — they are per-cycle control and data, recomputed every cycle, and making them sequential would add a pipeline stage nobody asked for. Sorting a design's signals into these two categories before writing any RTL is most of what §13's first mistake is about.

What happens on a read/write collision? Under the implementation in §5, the same-cycle read returns the old value and the new value is visible from the next cycle (§7, cycle 4). But the answer a specification should give is not "the old value" — it is that the design states a policy, asserts it (P3 in §15), and if the consumer needs the new value in the collision cycle, implements forwarding explicitly. A design whose collision behaviour is whatever the code happened to produce has a latent dependency on its own implementation.

Why does scaling to millions of entries change the problem rather than the parameters? Because the properties being scaled are the ones that do not survive it (§11). Per-entry index distribution and per-entry data paths grow with the entry count in both length and number, so the structure becomes dominated by interconnect; per-bit generality multiplies area by a number that is paid at every bit; every element loads a clock network that switches whether the data is used or not. Making it affordable means sharing decode, selection and data paths across many bits — and that changes the access model from a same-cycle selection into a sequenced operation with rules about what may be done when. The parameters did not get bigger. The device became a different device, and the rest of this curriculum is about what that device demands in exchange for its capacity.

19. Summary

A register is not fast memory; it is storage relocated into the execution path and named by the instruction encoding rather than addressed. That single decision produces everything else. It explains why registers are the fastest storage a machine has, why they are counted in tens rather than millions, and why an access to one cannot miss.

The word means three different things at three levels, and keeping them apart is a working skill: architectural state the instruction set defines, microarchitectural entries in a physical array that need not correspond one-to-one with those names, and RTL state that survives a clock event because of how it is assigned and read. A declaration creates no storage; clocked assignment and later reading do.

A register file is the structure that makes a set of named registers usable: an array selected by index, with ports for concurrent access. Depth grows cheaply and runs into the instruction encoding; ports grow expensively and are driven by how much work the machine issues per cycle. Capacity and concurrency are separate axes.

The engineering judgement is in the details rather than the definitions. Read semantics are a choice between a longer combinational path and an extra pipeline stage. Reset belongs on state that is read before it is written, not on everything. Read-during-write is a policy the design must state, assert, and implement rather than discover.

And the answer to the question the chapter opened with: registers cannot become main memory because the properties that make them excellent are exactly the properties that do not scale. Per-entry access, per-bit generality and adjacency to execution are what deliver the speed — and area, wiring, clock power and access organisation all degrade together when the entry count grows. A structure large enough to hold a system's working data must share its decode, selection and data paths across many bits, and in doing so it stops being a register file and becomes a memory, with a memory's rules.

20. What Comes Next

The pressure this chapter creates is precise. Registers are the right answer for a few tens of values next to an execution unit, and the wrong answer for everything larger. But the step from "a handful of entries in the pipeline" to "the whole working data set in DRAM" is far too big to take in one move — the machine would stall on nearly every access.

So the next question is the intermediate one: is there a denser structure that gives up per-entry adjacency to compute, but stays much faster and much closer than main memory? There is, and it is what every cache in every processor is built from. Chapter 1.3 takes it apart: the static cell, why it needs no refresh, what its density costs, and why it holds the tier between the register file and DRAM rather than either of them.

Return to The Memory Hierarchy for the tier map this chapter filled in, or the DDR tutorials index for the full path. To build the structure rather than reason about it, Multi-Port Memories and Register Files is the implementation pattern, The Register Pattern is the single-bit foundation underneath it, and Cache Hierarchy Review is the tier immediately below, seen from a coherent system's point of view.

Continue learning

Standards & specifications

Governing standard
JEDEC JESD79 (DDR SDRAM)(opens JEDEC Solid State Technology Association in a new tab)

Defines the DDR SDRAM device itself — signals, command encoding, mode registers, timing parameters and the initialisation sequence — one document per generation. Memory-controller microarchitecture, address-mapping policy, PHY training algorithms and board-level design are not specified by it.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the DDR curriculum.