DDR · Module 1
DRAM
The smallest practical way to store a bit, and what a system must accept in exchange. The one-transistor one-capacitor cell, why its charge leaks, why reading it destroys it, and why an access becomes a sequence rather than an operation — the three consequences the whole DDR standard exists to manage.
Chapter 1.3 made the density trade once. The cell shrank from a flip-flop to six transistors, the decoder and sense amplifiers became shared property, and the read stopped answering in the cycle it was asked. The result was dense enough for a cache and nowhere near dense enough for a system's working memory.
So this chapter asks the question in its most aggressive form: what is the smallest structure that can hold a bit at all, and what does a system have to accept in exchange?
The answer is one transistor and one capacitor. That is the whole cell, and its smallness is why the tier exists — it is what makes a system's working memory affordable. But the reduction is not free, and it is not free in an unusually interesting way. Removing everything except the bare minimum removes the parts of the SRAM cell that were doing work: the loop that held the value and the drive that made a read harmless. Three consequences follow, and they are not details. They are the reason this curriculum has thirty-three more modules.
The stored charge leaks away, so the contents must be periodically rewritten. Reading the cell destroys what it stored, so every read must be followed by a restoration. And because the access now has parts that must happen in order with minimum times between them, an access stops being an operation and becomes a sequence.
Everything in the DDR standard — commands, timing parameters, a scheduler, a controller, a training procedure — is machinery for living with those three sentences.
1. Subtract Until Only Storage Remains
Start from the static cell and take things away, asking at each step what was lost.
Remove the drive. The SRAM cell's two inverters actively hold its value against leakage. Delete them and the cell no longer maintains itself. What remains has to store charge passively — and a structure that stores charge passively is a capacitor.
Remove one side of the pair. The static cell is differential: two internal nodes, two bitlines, complementary values. Keep one node and one wire, and the cell halves its connections at the cost of making sensing harder, because there is no longer a complementary reference sitting right next to it.
Keep exactly one switch. Something must connect the storage to the outside world when, and only when, this cell is selected. One access transistor does that, gated by a wordline exactly as in SRAM.
What is left is one transistor and one capacitor — the 1T1C cell. A bit is present as charge on the capacitor: charged for one logic value, discharged for the other. The transistor is the gate between that capacitor and the bitline. There is nothing else, and that is the point — there is nothing else to pay for, at every single bit, across billions of bits.
2. Consequence One — Charge Leaks, So Refresh Exists
A capacitor holding charge in a real semiconductor is not a sealed container. Charge escapes through paths that cannot be eliminated — the access transistor is never perfectly off, and the surrounding silicon is never a perfect insulator. Over time, a charged cell drifts toward being indistinguishable from a discharged one, and once the difference is too small to resolve, the bit is lost.
The fix is unavoidable and it is structural, not a workaround: the contents must be read and rewritten periodically, often enough that no cell drifts past the point of recovery. That operation is refresh.
Three properties of refresh matter now; the mechanism is Module 15's.
It is mandatory, not an optimisation. A DRAM device that is not refreshed loses data. This is the first storage technology in the curriculum whose correctness depends on something happening continuously, which is a genuinely new obligation for a system to carry.
It is not free. While part of the device is being refreshed, that part is not available for the requests a processor is waiting on. Refresh therefore consumes a share of the device's capability — a share the memory controller has to plan around rather than discover.
It is temperature-sensitive in direction. Leakage increases with temperature, so a hotter device needs its cells refreshed more often. The exact retention behaviour of a real part is a specified characteristic of that part, and this chapter quotes no interval and no temperature figure; the directional claim is the one to hold, and it is the reason real controllers care about device temperature at all.
And notice what refresh is, mechanically. It is a read followed by a rewrite of the same data. Which means refresh is not a separate feature bolted on — it is the same machinery consequence two is about, run deliberately.
3. Consequence Two — Reading Destroys, So Restoring Is Part of Reading
This is the consequence engineers most often know as a phrase and least often understand as a mechanism, and it is worth being precise about.
To read the cell, the access transistor connects the capacitor to the bitline. The bitline is a long wire shared by every cell in the column, and it is physically much larger than one tiny cell capacitor. When the two are connected, the cell's charge redistributes onto that much larger wire. The result is a very small change in the bitline's voltage — a change in a direction that reveals what the cell held, but small, because a small charge has been shared with something large.
Two things follow from that single act.
The signal is small, so sensing is an amplification problem. A circuit must decide which way the bitline moved, from a small difference, reliably, across manufacturing variation and temperature and noise. That circuit is the sense amplifier, and it is why DRAM arrays are organised the way they are. Module 3 builds it.
The cell no longer holds what it held. The charge left it. Whatever the cell contained before the read, afterwards it holds something closer to the middle — which is to say, nothing usable. The read has consumed the data.
So the access cannot end there. Once the sense amplifier has decided what the value was, that value must be written back into the cell — driven to a full level again so the cell holds a recoverable bit. This is the restore, and the essential point for this chapter is that it is not an extra feature: it is part of what reading a DRAM cell means. A DRAM read that is not followed by a restore is a DRAM read that has thrown away data.
Compare the two technologies in one line. An SRAM read leaves the cell driving its own value, unchanged. A DRAM read takes the cell's charge, decides what it was, and puts it back. The first is one event; the second is a small process with an order. That difference is where consequence three comes from.
4. Consequence Three — An Access Becomes a Sequence
Put consequences two and three together with the array organisation Chapter 1.3 introduced, and the shape of a DRAM access falls out.
Selecting a row connects every cell in that row to its column's bitline, all at once. Every one of those cells dumps its charge, every column's sense amplifier resolves its own small signal, and every one of those cells now needs restoring. That is not a per-bit event; it is a whole-row event, and it takes a defined amount of time to complete.
Which means a DRAM access has at least three distinguishable parts:
- Select a row and resolve it. The row's cells share their charge with the bitlines, the sense amplifiers decide, and the row's data is now held in those amplifiers rather than only in the cells.
- Access the data. With the row resolved, particular columns within it can be read or written. Several such accesses can be made to the same resolved row.
- Return the array to a state where another row can be selected. The bitlines have been disturbed and must be brought back to a defined starting condition before a different row can be resolved on them.
Three implications, each of which is a later module's entire subject.
Order is mandatory. Columns cannot be accessed before the row is resolved, and a different row cannot be resolved before the array is returned to a ready state. These are not conventions; they are physical prerequisites. Module 9 gives them their names.
Time separates the parts. Charge sharing, amplification and restoration each take a real, specified amount of time, so a minimum interval separates one part of the sequence from the next. This is the origin of every DDR timing parameter, and Modules 13 and 14 are nothing but the catalogue of these intervals and their consequences.
The cost of an access now depends on the state the device is already in. An access to a row that is already resolved skips step one. An access to a different row in the same part of the array must pay for step three and then step one before it can even begin. The same device answers two identical-looking requests in very different amounts of time, depending on history — and that single sentence is why a memory controller is a scheduler rather than a bridge.
5. What DRAM Buys
Having spent four sections on the costs, state the benefit precisely, because the benefit is why any of it is tolerated.
Density, and therefore capacity at a price a system can pay. A cell of one transistor and one capacitor is the smallest practical way to store a bit in a random-access memory, and a smaller cell means more bits per unit of silicon, which means more capacity per unit of cost. The tier that has to hold a system's entire working set can only be built out of the cheapest bits available.
Rewritability without limit or penalty. A DRAM cell can be written as often as needed, and a write costs no more than a read. That sounds unremarkable until Chapter 1.5, where the persistent tier turns out to have a very different and much more awkward relationship with writing.
Fine-grained random access. Any location can be reached without disturbing others — subject to the sequence in §4, which constrains order and timing, not reachability.
This chapter deliberately gives no numbers for any of that. Cell areas, capacities per die, cost per bit and access times are all real, specified, product-dependent quantities, and inventing plausible values would make the argument look more precise while making it less true. Chapter 1.6 is where the cost structure is analysed properly — in terms of what cost per bit is made of, which is a durable analysis, rather than in terms of figures that change every generation.
6. RTL — A Behavioural Model With an Open Row
Here is the shape of consequence three as something you can simulate. It is the first model in this curriculum where the state the device is in changes what a request costs.
What this is and is not — read this before the code. It is a behavioural teaching model of row-oriented access: a storage array, a notion of one resolved row, and a rule that a column access requires the right row to be resolved. It is not a DRAM cell, not a DRAM device model, not JEDEC-compliant, and not a memory controller. It has no real timing parameters, no commands in any standard encoding, no refresh scheduling and no banks. Those belong to Modules 2, 3, 9, 15 and 17, and a model that pretended to them here would teach the shapes of things the learner cannot yet evaluate.
What it does faithfully reproduce is the one property that matters now: a request's cost depends on whether the row it needs is already open.
What it does. It holds ROWS × COLS words. A column access is legal only when that column's row is the currently open one. Opening a row takes OPEN_CYCLES; closing one takes CLOSE_CYCLES. The model reports, per request, whether it was a hit on the open row or required a row change — which is the observable this whole chapter has been building toward.
How to simulate it. vlog dram_row_model.sv tb_dram_row_model.sv then vsim -c tb_dram_row_model -do "run -all"; with VCS vcs -sverilog dram_row_model.sv tb_dram_row_model.sv && ./simv; with Xcelium xrun -sv dram_row_model.sv tb_dram_row_model.sv.
// BEHAVIOURAL TEACHING MODEL of row-oriented access. See the prose above for
// what this is NOT. The cycle counts are parameters chosen to be visible in a
// waveform -- they are not DDR timing parameters and no real device value is
// implied.
module dram_row_model #(
parameter int WIDTH = 32,
parameter int ROWS = 8,
parameter int COLS = 4,
// Representative costs, not device figures. Must be >= 1: the model's
// control flow assumes a counted wait of at least one cycle.
parameter int OPEN_CYCLES = 2,
parameter int CLOSE_CYCLES = 2,
// DERIVED.
parameter int ROW_W = (ROWS <= 1) ? 1 : $clog2(ROWS),
parameter int COL_W = (COLS <= 1) ? 1 : $clog2(COLS)
) (
input logic clk,
input logic rst_n,
// One request at a time. `req` is held until `ack`.
input logic req,
input logic req_we,
input logic [ROW_W-1:0] req_row,
input logic [COL_W-1:0] req_col,
input logic [WIDTH-1:0] req_wdata,
output logic ack,
output logic [WIDTH-1:0] rdata,
// The observable this chapter is about: was the needed row already open?
output logic row_hit,
output logic row_miss
);
// ── Storage. Behavioural: charge, leakage and sensing are Module 2's. ────
logic [WIDTH-1:0] cells [ROWS][COLS];
// ── The device's state. This is what makes cost history-dependent. ───────
typedef enum logic [2:0] {
S_IDLE, // no row open
S_CLOSING, // returning the array to a ready state
S_OPENING, // resolving a row
S_OPEN // a row is resolved; column accesses are legal
} state_e;
state_e state, state_n;
logic [ROW_W-1:0] open_row;
logic open_valid;
// Wait counter. Width covers the larger of the two waits.
localparam int WAIT_MAX = (OPEN_CYCLES > CLOSE_CYCLES) ? OPEN_CYCLES
: CLOSE_CYCLES;
localparam int WAIT_W = (WAIT_MAX <= 1) ? 1 : $clog2(WAIT_MAX + 1);
logic [WAIT_W-1:0] wait_cnt;
// A request needs a row change when no row is open, or a different one is.
logic needs_change;
assign needs_change = !open_valid || (open_row != req_row);
// ── Next-state logic. Kept separate from the datapath so the ORDER of the
// sequence is readable in one place (§4). ──────────────────────────────
always_comb begin
state_n = state;
unique case (state)
S_IDLE: if (req) state_n = needs_change ? S_OPENING : S_OPEN;
S_CLOSING: if (wait_cnt == 1) state_n = S_OPENING;
S_OPENING: if (wait_cnt == 1) state_n = S_OPEN;
S_OPEN: if (req && needs_change) state_n = S_CLOSING;
default: state_n = S_IDLE;
endcase
end
// ── Sequential state. ───────────────────────────────────────────────────
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= S_IDLE;
open_valid <= 1'b0;
open_row <= '0;
wait_cnt <= '0;
ack <= 1'b0;
row_hit <= 1'b0;
row_miss <= 1'b0;
end else begin
state <= state_n;
ack <= 1'b0;
row_hit <= 1'b0;
row_miss <= 1'b0;
// Load the wait counter on entry to a counted state; otherwise count.
if (state_n != state) begin
if (state_n == S_OPENING) wait_cnt <= WAIT_W'(OPEN_CYCLES);
else if (state_n == S_CLOSING) wait_cnt <= WAIT_W'(CLOSE_CYCLES);
end else if (wait_cnt != '0) begin
wait_cnt <= wait_cnt - 1'b1;
end
// A row becomes open when the opening wait completes.
if (state == S_OPENING && wait_cnt == 1) begin
open_row <= req_row;
open_valid <= 1'b1;
end
// Closing invalidates the open row -- the array is being made ready.
if (state_n == S_CLOSING) begin
open_valid <= 1'b0;
end
// The column access itself, legal only against a resolved row.
if (state == S_OPEN && req && !needs_change) begin
if (req_we) cells[req_row][req_col] <= req_wdata;
else rdata <= cells[req_row][req_col];
ack <= 1'b1;
// Classify the request for the requester's benefit. Reaching here
// means the row was ALREADY open (needs_change is false), so this is
// a hit by construction -- the miss case is reported below.
row_hit <= 1'b1;
row_miss <= 1'b0;
end
// A request that forces a row change is reported as a miss when it is
// recognised, so a requester can count misses without inferring them.
if (state == S_OPEN && req && needs_change) begin
row_miss <= 1'b1;
end
end
end
endmoduleInterface and cycle behaviour. The requester holds req with a row, a column and a direction until ack. If the requested row is already open, the access completes and ack rises with row_hit. If a different row is needed, row_miss rises, the model spends CLOSE_CYCLES returning the array to a ready state and OPEN_CYCLES resolving the new row, and only then completes the access. The requester's code is identical in both cases; only the elapsed time differs, which is exactly the property real memory has and the property a naive model hides.
Design decisions worth naming. The state machine is written with combinational next-state logic separated from the sequential update so that the order of the sequence — close, then open, then access — is legible in one place rather than spread through a counter. open_valid is separate from open_row because "no row is open" is a distinct state from "row zero is open", and conflating them is a classic source of a spurious first-access hit. The wait counter is sized from the larger of the two waits by a localparam rather than a hand-written width, so changing a parameter cannot silently truncate it.
Expected behaviour. Request row 3 column 0, then row 3 column 1, then row 5 column 0. The first pays an open; the second completes with row_hit in the next cycle; the third asserts row_miss and then pays a close and an open before completing. A testbench that prints the cycle count of each ack should show the second request completing far sooner than the first and third.
Expected waveform. §7 is that sequence: two requests that look identical at the interface and take very different numbers of cycles.
Synthesis implication. The state machine, the counter and the row tracking are synthesizable. The cells array is a behavioural store; at these toy dimensions a tool will build flip-flops, which is fine for a teaching model and wrong for anything real — the point of the model is the control, not the storage. Nothing here should be read as describing how a DRAM device is implemented internally; a real device is not an RTL state machine.
Limitations, stated plainly. No banks, so no parallelism between rows — a real device's ability to have several rows open in different banks is Module 5's subject and is one of the most important performance mechanisms in the whole curriculum. No refresh. No real timing parameters. One outstanding request. No burst. No standard command encoding. Every one of those absences is a later module.
Debugging observations. If every request reports row_hit, check open_valid — a model or design that treats "nothing open" as "row 0 open" gives a free hit on the first access to row 0. If the model hangs, check that OPEN_CYCLES and CLOSE_CYCLES are at least 1; the counted-wait control flow has no meaning at zero, which is why the parameter comment says so. If ack appears a cycle later than expected, note that it is registered — the access happens at the edge and ack is observed in the following cycle.
7. Two Identical Requests, Two Different Costs
This is the figure the chapter exists to produce.
dram_row_model — a row hit and a row miss, side by side
10 cyclesCycle 0 into 1 — the row hit. Row 3 is already the open row, so the column access is legal immediately. The access happens at the edge ending cycle 0, and ack with row_hit is observed in cycle 1. From the requester's point of view this is a fast memory.
Cycle 1 into 2 — the miss is recognised. In cycle 1 the requester presents row 5 while row 3 is open. The model reports row_miss in cycle 2 and enters the closing state. Nothing is wrong: this is the sequence of §4 being paid.
Cycles 2 to 5 — the sequence. The array is returned to a ready state, then row 5 is resolved. Throughout, req stays asserted and the requester waits. No part of this is overhead that a better interface could remove; it is the physical prerequisite for touching a different row.
Cycle 7 — the same request completes. ack finally rises, six cycles after the request was presented. Compare it with cycle 1, where an identically shaped request was answered in one: the requester did the same thing and waited six times longer. The device is not broken, not slow, and not badly designed. It is a device whose access cost depends on its state — and a requester with no idea of that state cannot predict its own performance.
8. Verification — What Becomes Checkable Once State Matters
The model is the first in Module 1 whose correctness is temporal, so the verification perspective changes shape.
Ordering is now a property to prove, not a thing to observe. A column access must never happen unless the right row is resolved. This is the central invariant, and it is the model's analogue of the real rule Module 9 formalises.
Classification must be trustworthy. row_hit and row_miss are the observables a performance analysis would be built on, so they must be mutually exclusive and must actually correspond to what happened. A model or design whose hit counter over-reports makes every subsequent performance conclusion wrong, and nothing else in the system will flag it.
The handshake must be complete. Every held request must eventually be acknowledged; no acknowledgement may appear without a request. Liveness matters here in a way it did not in Chapter 1.3, because the model can now make a requester wait for an unbounded-looking time.
Row tracking must be exact. After a completed row change, the open row must be the requested one. An off-by-one or a stale tracker produces accesses to the wrong row — data corruption that presents as a functional bug far from memory.
The distinction between "nothing open" and "row zero open" must hold. This is worth its own line because it is the specific bug the open_valid bit exists to prevent, and because a state-coverage plan that never starts from reset with a request to row 0 will not find it.
Coverage worth asking for. Hit and miss on every row; a miss immediately following a hit and vice versa; a request arriving in each state of the machine including mid-wait; back-to-back accesses to different columns of one open row (the case that makes the open row worth having); and reset asserted during each counted wait.
9. Four Assertions Worth Writing
These state the invariants of §8 against the model's own signals.
// VERIFICATION-ONLY, inside dram_row_model.
// P1 -- THE ordering invariant, and the one that corresponds to a real
// device rule: an access completes only from the OPEN state with the
// requested row actually resolved. This is §4's "order is mandatory" made
// checkable.
property p_access_requires_open_row;
@(posedge clk) disable iff (!rst_n)
ack |-> $past(state == S_OPEN && open_valid && (open_row == req_row));
endproperty
assert property (p_access_requires_open_row);
// P2 -- hit and miss are mutually exclusive. Both high in one cycle would
// make every performance count derived from them meaningless, and nothing
// downstream would notice.
property p_hit_miss_exclusive;
@(posedge clk) disable iff (!rst_n)
!(row_hit && row_miss);
endproperty
assert property (p_hit_miss_exclusive);
// P3 -- no acknowledgement without a request. The safety half of the
// handshake; the liveness half is P4.
property p_ack_requires_req;
@(posedge clk) disable iff (!rst_n)
ack |-> $past(req);
endproperty
assert property (p_ack_requires_req);
// P4 -- LIVENESS, bounded deliberately rather than left open. A held
// request must be acknowledged within the worst case the parameters allow:
// a close, an open, and the access itself. An unbounded `s_eventually`
// would pass on a design that is merely slow; a bound turns "it finishes"
// into "it finishes in time".
localparam int WORST_CASE = CLOSE_CYCLES + OPEN_CYCLES + 2;
property p_held_request_completes;
@(posedge clk) disable iff (!rst_n)
$rose(req) |-> ##[1:WORST_CASE] ack;
endproperty
assert property (p_held_request_completes);What each buys. P1 is the chapter's physics expressed as a temporal contract: it fails the moment someone "optimises" the state machine into letting an access through early, which is the mistake that would silently return data from the wrong row. P2 protects the measurement rather than the function — an unusual and underrated category of assertion, and exactly the kind that saves a performance investigation from chasing a phantom. P3 and P4 are the two halves of the handshake, and P4 is bounded on purpose: WORST_CASE is derived from the parameters, so the bound moves correctly if the parameters do, and the property distinguishes "eventually completes" from "completes within the time the design promises".
What they do not claim. Nothing here proves the data is right — that needs a reference model holding a shadow array, compared on every ack. And P4 assumes the requester holds req, which is the interface contract; a requester that drops its request mid-wait is a different bug, and asserting the requester's obligation belongs in the environment rather than the device.
10. Common Mistakes
Treating refresh as an implementation detail. Wrong mental model: refresh is background housekeeping that does not concern the design above the controller. What the engineer does: sizes buffers, models latency or budgets bandwidth as though the device were always available. Resulting bug: a system that meets its requirement in analysis and misses it in silicon, because a share of the device's capability was never accounted for. The symptom is a latency distribution with a tail nobody predicted, or a real-time deadline that is missed rarely and unreproducibly. How to detect it: look for a latency or bandwidth model with no term for periodic unavailability. If the analysis has no such term, it is not modelling DRAM. How to prevent it: treat device availability as a first-class input to any memory-system budget. Module 15 quantifies it.
Believing a read is passive. Wrong mental model: reading memory observes it without changing it, the way reading a register does. What the engineer does: reasons about DRAM accesses as though a read costs nothing to the array's state, and is then baffled by why reads and writes interact, why a read has a recovery cost, or why "just reading" a row has consequences. Resulting bug: usually not a functional bug but a modelling bug — performance predictions that are wrong in a direction the engineer cannot explain, and a mental model that cannot accommodate the real timing rules when they arrive. How to prevent it: hold §3's mechanism. Reading takes the charge; the value must be put back; that is why the array needs time after being read.
Assuming uniform access cost. Wrong mental model: one memory access costs one memory access. What the engineer does: writes traffic generators, performance models or test plans in which every access is equivalent. Resulting bug: a design verified against traffic that never exercises the expensive case. A test suite of sequential accesses hits the open row almost every time and can pass a design whose row-change handling is broken or catastrophically slow. How to detect it: count hits and misses in your own stimulus. If the miss rate is near zero, the stimulus is not testing the mechanism that dominates real behaviour. How to prevent it: make row locality an explicit, controlled dimension of stimulus, with cases at both extremes.
Confusing the abstraction levels of a model. Wrong mental model: the RTL model of a memory is the memory. What the engineer does: draws conclusions about device behaviour, timing or implementation from a behavioural model's structure — for instance concluding that a DRAM device contains a state machine like §6's, or that its internal timing is a small number of cycles. Resulting bug: confident, wrong reasoning that survives review because it sounds specific. Downstream decisions get made on it. How to prevent it: label every model's abstraction level where it is introduced, as §6 does, and say explicitly what it does not represent. A model with an unstated abstraction level is a liability regardless of how correct it is.
11. Debugging — Throughput Collapses on One Access Pattern
Symptom. A block that streams through memory achieves good throughput on one workload and a small fraction of it on another. The code path is the same, the data volume is the same, and no functional error is reported.
Hypotheses, ordered by discriminating power.
Hypothesis 1 — the access pattern changed row locality. Evidence to look for: the hit and miss classification per request, aggregated over a window. Discriminator: if the slow workload's miss rate is high and the fast one's is low, this is the cause and everything else is a symptom. This hypothesis goes first precisely because it is cheap to test and explains the largest observed differences in real systems.
Hypothesis 2 — the traffic interleaves two streams that fight for the same resource. Evidence to look for: whether requests alternate between two distant address regions. Discriminator: two streams each with perfect internal locality can still produce a miss on nearly every access when interleaved, because each one keeps closing the other's row. The signature is that each stream alone is fast and the two together are far slower than either — a strongly diagnostic pattern.
Hypothesis 3 — the requester cannot keep enough work in flight. Evidence to look for: cycles where the memory is idle while the requester is waiting. Discriminator: if the device is idle and the requester is blocked, the limit is the requester's own concurrency, not the memory. Chapter 1.8 is about this case specifically.
Hypothesis 4 — the memory is saturated. Evidence to look for: the fraction of cycles the memory is busy. Discriminator: near-total busy means the resource is the limit and the fix is structural rather than a matter of ordering.
Hypothesis 5 — the device is genuinely unavailable part of the time. Evidence to look for: periodic gaps unrelated to the request stream. Discriminator: periodicity. Request-driven stalls correlate with the traffic; maintenance-driven ones do not.
Root-cause discrimination in one measurement. Instrument three counters — requests, misses, and cycles the memory was busy — and the space collapses. High miss rate points at hypotheses 1 or 2, distinguished by whether the addresses alternate between regions. Low miss rate with low busy fraction points at hypothesis 3. High busy fraction points at hypothesis 4. Periodic gaps in an otherwise explained trace point at hypothesis 5.
And note the answer that is not on the list. "DRAM is too slow" appears nowhere, because it explains nothing: the same device delivered the good result on the other workload. The useful question is never how fast the device is but how often the request stream forces it to pay for the sequence — which is the question a memory controller exists to make the answer to as favourable as possible.
12. Interview Reasoning
"Why does DRAM need refreshing when SRAM does not?" Because their storage mechanisms differ in kind. An SRAM cell is a bistable loop actively driven from the supply, so it restores its own value continuously. A DRAM cell is charge held passively on a capacitor, and charge escapes through paths that cannot be eliminated, so the value degrades until it can no longer be resolved. Refresh is the periodic read-and-rewrite that prevents that. The strong version of the answer connects it to density: the drive circuitry is exactly what was removed to make the cell small, so refresh is the bill for the density rather than a defect.
"Why does the physical storage mechanism force operations SRAM does not need?" Two reasons, both from §3. The cell does not drive, so reading it means sharing its small charge with a much larger bitline — which produces a tiny signal needing amplification, and which removes the charge from the cell. Therefore the value must be written back as part of the read. And because that happens for a whole row at once and each stage takes real time, the access acquires an order and minimum intervals — it becomes a sequence rather than an operation. Neither requirement exists for a cell that holds and drives its own value.
"Two memory requests look identical at the interface and take very different times. Is the memory faulty?" No — this is normal and it is the defining behaviour of the tier. Cost depends on whether the needed row is already resolved. A request to an open row skips the expensive stages; a request to a different row must first return the array to a ready state and then resolve the new row. A candidate who reaches this without prompting understands why a memory controller is a scheduler, and why address mapping and request ordering are performance levers rather than plumbing.
"Is the DRAM access sequence overhead that a better design could eliminate?" No, and this is the most important misconception to be free of. Each stage does something physically required by a cell that stores charge passively: share the charge, amplify the result, write it back, return the bitlines to a defined state. There is no version of the technology with this density and without the sequence. The engineering target is not to remove the sequence but to organise requests so that it is paid as rarely as possible.
"Why is DRAM the main-memory tier rather than SRAM?" Density, and therefore cost per bit at the capacity a working set requires — combined with unlimited, symmetric rewritability and fine-grained random access. SRAM cannot reach the capacity affordably; persistent storage, as Chapter 1.5 shows, cannot offer the write behaviour. The complete argument, including the parts that are economic rather than physical, is Chapter 1.7.
13. Engineering Check
Two request streams, each of one thousand accesses, run against the model in §6 with
OPEN_CYCLESandCLOSE_CYCLESboth greater than one. Stream A walks columns within one row, then moves to the next row, and repeats. Stream B alternates between two rows on every access.
1. Which stream completes sooner, and what is the mechanism? Stream A. It pays a row change once per row and then reuses the resolved row for every subsequent column in it, so the expensive stages are amortised over many accesses. Stream B forces a row change on every access, so every access pays a close and an open. The mechanism is entirely §4's: the cost of an access depends on the state the device is already in.
2. Which one's performance would improve most if the model had more than one row open at a time? Stream B, dramatically — if the two rows it alternates between could be resolved simultaneously in independent parts of the array, its accesses would stop conflicting and it would behave like stream A. Stream A gains little, because it already reuses one row. This is the single most valuable performance mechanism in the device, and Module 5 is where it is introduced properly; noticing why it would help is the point of the question.
3. Does the requester's code differ between the two streams? No. Both hold req with a row, a column and a direction and wait for ack. Nothing in the interface reveals the cost difference in advance — which is exactly why performance counters like row_hit and row_miss exist. A requester cannot infer what it was charged.
4. Would doubling the requester's clock frequency halve the total time? No, and this is the reasoning to practise. The requester would issue and retire faster, but the time the model spends closing and opening rows is set by the model's own parameters, not by the requester's clock. Stream A, dominated by accesses to an open row, would benefit noticeably; stream B, dominated by waiting, would benefit far less. Whether a frequency increase helps depends on what fraction of the time is spent waiting on memory — the question Chapter 1.8 makes central.
5. What stimulus would you add to be confident the model's row handling is correct? Cases at both extremes — all accesses to one row, and a different row on every access — plus a request arriving in each state of the machine including part-way through a counted wait, plus reset during each wait, plus a first access to row 0 immediately after reset. That last one is not arbitrary: it is the specific case that catches "nothing open" being mistaken for "row 0 open".
6. Which counters would settle an argument about whether the memory or the requester is the limit? Requests, misses, and cycles the memory was busy. High misses explain a slow stream by the access pattern; a low busy fraction with a blocked requester moves the blame to the requester's concurrency; a high busy fraction means the resource itself is the limit. Three counters, three different engineering responses.
14. Summary
DRAM's cell is one transistor and one capacitor: the smallest practical way to store a bit in a random-access memory. It is dense because the cell does no work — it neither holds its value actively nor drives it during a read, which is exactly what the static cell spent transistors on.
That single reduction produces the three facts the rest of this curriculum is built around. Charge leaks, so the contents must be periodically read and rewritten: refresh is mandatory, consumes device capability, and worsens with temperature. A read shares the cell's small charge with a much larger bitline, which both makes sensing an amplification problem and removes the value from the cell — so the read must restore what it consumed. And because that happens for a whole row at once, with each stage taking real time in a required order, an access is no longer an operation but a sequence: resolve a row, access columns within it, return the array to a ready state before a different row can be resolved.
The consequence that matters most in practice: the same device answers identical-looking requests in very different amounts of time, depending on which row is already resolved. That is not a defect and not overhead to be engineered away — it is what a passively stored bit costs. The engineering response is to organise requests so the sequence is paid as rarely as possible, which is why a memory controller is a scheduler and why access patterns are a performance variable.
In exchange the tier gets what nothing else offers together: capacity at an affordable cost per bit, unlimited symmetric rewritability, and fine-grained random access. That combination is why DRAM holds main memory.
15. What Comes Next
Two tiers now have their mechanisms, and the pattern is clear: each step down trades a property away for density. The static cell gave up self-maintenance and harmless reads to get small. But both tiers still share one limitation that no amount of density fixes — remove power and everything is gone.
Chapter 1.5 is the tier that solves that, and its interest is not that it is persistent but what persistence costs: a write that is nothing like a DRAM write, an erase granularity that is nothing like a word, and a wear mechanism that means the storage does not last forever. Understanding those costs is what makes it obvious why persistent memory cannot simply replace DRAM — and why the main-memory tier has to exist as its own thing.
Return to SRAM for the tier above, or The Memory Hierarchy for the map. The full path is on the DDR tutorials index.
Continue learning
Related tutorials
- Related topic
Memory Chiplets
DRAM and HBM as standalone chiplets — why a memory die is an endpoint with scheduled unavailability rather than a passive target, what controller placement actually moves, refresh as a maintenance obligation that is not an error, why HBM bandwidth comes from channel parallelism, read-return identity when the scheduler reorders, where ECC lives and which errors each placement catches, post-package repair state, and the timeouts that mistake a busy memory for a broken one.
- Related topic
CXL Memory Controllers
Where the pipeline's ordering meets a scheduler that reorders. This chapter builds the row buffer, bank parallelism, scheduling against ordering, refresh, bus turnaround, ECC, interleaving, queue depth, delivered bandwidth and the assembled model.
- Related topic
RGMII — Double Data Rate, and Half the Margin
Four bits on both edges of 125 MHz halves GMII's pins and halves the bit time. The clock must be delayed into the eye by about 2 ns, nothing says who does it, and both ways to get it wrong look identical.
- Related topic
The Memory Hierarchy
Why a processor needs several storage technologies instead of one. Speed, capacity, cost per bit, power and physical distance from compute cannot all be maximised at once, and the hierarchy is what that conflict forces — with DRAM holding the working-memory tier that DDR exists to drive.
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.
