DDR · Module 13
Why Timing Parameters Exist
A DDR command can be perfectly meaningful and target a bank in exactly the right state and still be illegal right now. That third refusal is what timing parameters are, and it is a different question from the first two.
Six modules have built a command interface that works. Module 7 gave commands their meanings, Module 8 gave them operands, Module 9 gave banks a state model, and Modules 10, 11 and 12 moved data.
Every one of those chapters quietly assumed something this module now has to withdraw:
That a command you are entitled to issue is a command you may issue now.
It is not. A DDR controller routinely holds a command that is completely correct — right opcode, right operands, right bank, right row, nothing wrong with it at all — because not enough time has passed since something else happened. That refusal is not a bug, not a performance heuristic, and not a queueing decision. It is a third kind of illegality, and it is the reason the timing parameters exist.
1. The Command That Is Correct and Still Refused
Here is the situation, built entirely from chapters you have already read.
A controller holds a read request for bank 3, row 912, column 64. It checks the bank-state table from Chapter 9.1: bank 3 is open, and the open row is row 912. So this is a row hit — Chapter 9.3's best case. The column operand is in range. READ is exactly the command the situation calls for.
And the controller does not issue it.
It does not issue it because the ACTIVATE that opened row 912 was accepted two cycles ago, and the device does not promise that column data is available two cycles after a row is selected. The row is open in the sense that matters to Module 9 — the bank's state is OPEN and the correct row is the one that is open — and it is not yet usable in the sense that matters here.
Chapter 3.5 §6 already told you this would happen. Its heading is “Selected Is Not Usable”, and it drew the gap between the two. What that chapter did not do — because it was a chapter about sense amplifiers — is say what a controller is supposed to do about it.
This is what a controller does about it: it asks a third question.
2. Three Questions, Not One
Before a controller issues any command it answers three questions, and they are genuinely independent.
Question 1 — is this command semantically meaningful?
Does this opcode, with these operands, mean anything? A READ with a column operand wider than the device's column space is not meaningful. A PRECHARGE ALL with a bank operand is not meaningful. Neither is a command the device does not implement. This question is about the command in isolation — you can answer it without knowing anything about the device's current condition or history.
Question 2 — does the target's state permit it?
A READ to a closed bank is semantically perfect and architecturally forbidden: there is nothing in the sensing circuitry to read. An ACTIVATE to a bank that is already open is a row conflict, not a command. This question is about the present condition of the resource the command names. Answering it requires the state model from Module 9 and nothing more.
Question 3 — has enough time passed?
Given the events that have already occurred, is this command permitted at this moment? This question is about history. It cannot be answered from the command, and it cannot be answered from the state. It requires knowing when things happened.
The three questions map onto three different kinds of information: the command, the state, and the history. That is why they do not collapse into each other, and it is why a controller needs three separate mechanisms rather than one.
Notice what the three rejections on the right-hand side have in common, and what they do not. A meaningless command is never legal — no amount of waiting or state change helps. A command the state forbids becomes legal after a state transition, which somebody has to cause. A command that is too early becomes legal on its own, with nothing changed — the controller need only wait. Three refusals, three completely different remedies, and a controller that reports only “blocked” has thrown away the distinction that tells you what to do next.
3. Why the Third Question Exists at All
It is worth being precise about the origin of timing constraints, because there is a tempting wrong answer.
The wrong answer is that timing constraints are a safety margin — that the device could really go faster, and the parameters are conservatism. The right answer is structural: commands do not perform operations, they start them.
When the device accepts an ACTIVATE, the command is over in one cycle. The operation it started is not. Chapter 2.5 established that reading a DRAM cell destroys the stored value, and Chapter 2.6 established that the row must be restored from the sensing circuitry. Chapter 3.5 established that resolving a small bitline difference into a logic level is a process with phases. Those processes take time. They are underway while the command interface sits idle, ready to accept the next command.
So the command interface and the array operate on different timescales, and the command interface is the faster of the two. That mismatch is the entire source of timing constraints. A controller that issued commands as fast as the interface could carry them would be asking the array to begin operations faster than it finishes them.
There is a second origin, and it is not physical at all. Some obligations exist because a shared resource can only do one thing at a time. The data bus carries one transfer at a time; Chapter 12.1 showed a single column command occupying it for many transfer positions. A second column command whose data would land on the same wires is not forbidden by physics — it is forbidden by arithmetic. Two things cannot occupy one resource.
That matters because the two origins behave differently. A physical obligation is a property of the device. A resource obligation is a property of the interface, and it depends on what else the controller chose to do. Chapter 13.3 §4 builds this into the scope taxonomy.
4. An Obligation Has Five Parts
If timing legality is a real question, it needs a precise object to reason about. Here is the one this module uses throughout. Every timing obligation has exactly five parts, and you cannot reason about an obligation with any of them missing.
1. The triggering event. What happened. ACTIVATE accepted on bank 3. Last transfer of a write burst completed. A specific, observable moment.
2. The constrained event. What is now restricted. “A column command to this bank.” “A precharge of this bank.” Usually a class of future command, not one particular command.
3. The resource. What the obligation is attached to. Bank 3. The data bus. The command bus. This is the part most often left implicit, and §22 of this module's third chapter shows that getting it wrong produces two distinct and opposite hardware bugs.
4. The magnitude. How much separation is required, in some unit, and — critically — which unit. Chapter 13.2 is entirely about this part.
5. The sense. Whether the requirement is a minimum separation (“not before”) or a fixed latency (“exactly then”). These are different relations and confusing them is a classic error. Chapter 13.3 §2 separates them properly.
Write an obligation out with all five parts and ambiguity mostly disappears:
triggering event : ACTIVATE accepted, bank 3, cycle 21
constrained event : any column command to bank 3
resource : bank 3 (bank-local)
magnitude : 4 cycles (EDUCATIONAL VALUE)
sense : minimum separation, not fixed latency
------------------------------------------------------------
consequence : column command to bank 3 not before cycle 25Compare that with how the same obligation usually gets stated in conversation: “you have to wait four cycles after activate.” Wait from when — the cycle the command was presented, or the cycle it was sampled? Four cycles meaning the fourth cycle is legal, or the fifth? Wait for what — any command, or column commands? On this bank, or all banks? The casual sentence has three ambiguities and the five-part form has none. Chapter 13.2 §6 nails down the sampling convention that makes the fourth-or-fifth question answerable.
5. Timing Is a Graph, Not a Table
Timing parameters are almost always presented as a table: a column of names, a column of values. That presentation is useful for looking things up and actively misleading about the structure, because a table suggests that each parameter is an independent fact you apply on its own.
They are not independent facts. They are edges.
triggering event constrained event
┌────────────────────┐ minimum ┌──────────────────────┐
│ ACTIVATE, bank 3 │ ────────────▶ │ column cmd, bank 3 │
└────────────────────┘ separation └──────────────────────┘Each parameter says: this event constrains that event, on this resource, by this much. A set of parameters is therefore a directed graph over event classes, and the interesting properties of the system are properties of the graph, not of any single edge.
Three consequences follow immediately, and they are the reason this module exists.
First, one candidate command can be the target of several edges at once. A column command might be constrained by the activate that opened its bank and by the previous column command on the shared data bus and by a grouping rule. Three edges, three deadlines, one command. Chapter 13.3 derives what to do with them, and the answer is not “apply them in turn.”
Second, legality is not a property of a command — it is a property of a command at an instant. The same command can be illegal at cycle 30 and legal at cycle 31, with nothing about the command changed.
Third, and least obvious: issuing a command adds edges. Every command is a triggering event for some future obligation. So the graph is not a fixed structure you analyse once — each issue decision modifies the constraints on subsequent decisions. A controller that computed legality once and cached it would be wrong the moment it issued anything.
6. Enumerating the Combinations
Three independent binary questions produce eight combinations, and enumerating them is not pedantry — it is how you discover that some demand opposite responses from the controller. The table collapses to five rows because when the semantic layer fails, the other two answers carry no meaning: the state and timing analysis of a malformed command is analysis of a command that does not exist. Those four combinations are therefore one row.
| # | Semantic | State | Timing | Reachable? | What it is |
|---|---|---|---|---|---|
| 1 | ✗ | — | — | yes | A malformed command. Never legal; no waiting helps. |
| 2 | ✓ | ✗ | ✗ | yes | Wrong state and too early. Both must clear. |
| 3 | ✓ | ✗ | ✓ | yes | Timing is fine; the bank is in the wrong state. Needs a transition, not patience. |
| 4 | ✓ | ✓ | ✗ | yes — the important one | Everything is right and it is too early. |
| 5 | ✓ | ✓ | ✓ | yes | Issue it. |
Rows 2 through 4 are the ones worth dwelling on, because they need different responses from the controller and they are easy to confuse in a log.
Row 3 — state-illegal, timing-legal — is a request that needs work done on its behalf. A read to a closed bank will never become legal by waiting; somebody has to issue an ACTIVATE. A controller that treats this as a timing stall waits forever. This is a deadlock, and it is a real one: the row miss path exists precisely to do that work.
Row 4 — everything legal except timing — is a request that needs nothing but time. A controller that treats this as a state problem and issues a helpful ACTIVATE has just made things worse: the bank is already open with the right row, so the activate is a row conflict that closes and reopens the row it was about to read.
7. RTL — Three Gates and a Named Refusal
Before writing any of this module's RTL, a question worth asking: does the repository already own this?
It partly does, and that shapes the design. Chapter 11.4's write_recovery_guard already implements a single countdown obligation from a data-completion event. Chapter 4.5's bank_group_spacer already implements two abstract separations with scope. Chapter 6.5's cas_to_data_window already implements a fixed latency and a resource window. The corpus is not short of timers.
What no existing block does is separate the three legality questions and name which one refused. That is this block's only job. It measures no time at all — the timing answer arrives as an input, produced by machinery the next two chapters build.
The engineering problem. A controller needs to decide whether to issue a candidate command, and when it declines, it needs to say why in a form its own logic can act on. A single-bit ready throws away the information §6 just showed is decision-critical.
Classification: controller-side combinational decision logic. Purely combinational, deliberately. This block is a decision function, and a decision function with state would raise the question of when its state updates relative to the decision — a question with no good answer here. The state lives in the blocks that produce its inputs.
What it models: the priority ordering of three legality questions, and the identification of the first one that fails.
What it does not model: any physical process, any duration, any measurement of time. It contains no counter. This matters for §15's rule: there is nothing in this block that could be mistaken for a model of device internals, because there is nothing temporal in it whatsoever.
// ─────────────────────────────────────────────────────────────────────
// command_legality_layers
//
// CLASSIFICATION
// Controller-side combinational decision logic. No clock, no state,
// no timekeeping. A pure function of its inputs.
//
// WHAT IT MODELS
// The three independent legality questions of §2, evaluated in
// priority order, plus identification of the first question that
// failed and -- when timing is the failing layer -- which specific
// obligation is responsible.
//
// WHAT IT DOES NOT MODEL
// Any physical process inside the DRAM. Any duration. Any elapsed
// time. It does not decide WHEN a deadline is met; it is TOLD, on
// the deadline_met input. Chapter 13.2 builds the block that
// resolves an obligation's magnitude, and Chapter 13.3 builds the
// block that tracks several obligations and reduces them. This
// block deliberately contains no counter so that it cannot be
// mistaken for a model of device timing.
//
// PRIORITY CONVENTION
// Semantic outranks state outranks timing. The ordering is a
// reporting convention, not a correctness requirement: all three
// must hold for issue. It is chosen so that the reported layer is
// the most fundamental problem, since a malformed command's state
// and timing analysis is meaningless anyway.
// ─────────────────────────────────────────────────────────────────────
module command_legality_layers #(
// How many independent timing obligations may constrain this
// candidate. Three is the working example of §5; the parameter
// exists because the number is a property of the constraint set,
// not of the decision logic.
parameter int CONSTRAINTS = 3,
// Width of an index into the obligation vector. Guarded so that a
// single-obligation configuration stays legal: $clog2(1) is 0, and a
// zero-width signal is not.
parameter int CST_W = (CONSTRAINTS <= 1) ? 1 : $clog2(CONSTRAINTS)
) (
// ── The candidate. Presented, not yet issued.
input logic cand_valid,
// ── Layer 1. Is the opcode meaningful with these operands? Computed
// by decode logic (Module 7) from the command alone.
input logic semantic_ok,
// ── Layer 2. Does the target's present state permit it? Computed
// from the bank-state model (Module 9).
input logic state_ok,
// ── Layer 3. One bit per obligation that constrains this candidate.
// A bit is high when that obligation is discharged. All high means
// timing permits the command NOW.
input logic [CONSTRAINTS-1:0] deadline_met,
// ── The decision.
output logic issue_allowed,
// ── Layer 3's verdict on its own, published because §11's debugging
// procedure needs to distinguish "timing refused" from "timing
// was never consulted because an earlier layer refused".
output logic timing_ok,
// ── WHICH layer refused. 0 = none (or no candidate), 1 = semantic,
// 2 = state, 3 = timing. §6 is the argument for this output
// existing: layers 2 and 3 demand opposite responses.
output logic [1:0] reject_layer,
// ── WHICH obligation is responsible, when timing is the failing
// layer. Lowest outstanding index wins -- an arbitrary but FIXED
// tie-break, so that a report is reproducible. Chapter 13.3
// replaces this with the genuinely controlling deadline, which
// requires magnitudes this block does not have.
output logic [CST_W-1:0] blocking_constraint,
output logic blocking_valid
);
// ── Elaboration guard. A zero-obligation configuration would make
// deadline_met zero-width, which is not a legal signal. One
// obligation is the minimum meaningful case and is supported.
if (CONSTRAINTS < 1) begin : g_bad_constraints
initial $fatal(1, "command_legality_layers: CONSTRAINTS must be >= 1");
end
// ── Layer 3 collapses to a single question: is every obligation
// discharged? A reduction AND, which is the RTL form of §5's
// "one command, several edges". Note that this says nothing about
// WHEN each bit rose -- an obligation met long ago and one met
// this cycle are indistinguishable here, and that is correct:
// legality is a property of the present instant.
always_comb begin
timing_ok = &deadline_met;
end
// ── The decision and the refusal report.
always_comb begin
issue_allowed = cand_valid && semantic_ok && state_ok && timing_ok;
// Priority encode the first failing layer. With no candidate there
// is no refusal to report, which is why cand_valid gates this and
// not merely issue_allowed.
if (!cand_valid) reject_layer = 2'd0;
else if (!semantic_ok) reject_layer = 2'd1;
else if (!state_ok) reject_layer = 2'd2;
else if (!timing_ok) reject_layer = 2'd3;
else reject_layer = 2'd0;
end
// ── Which obligation blocks. Scanned downward so the LOWEST
// outstanding index is the surviving assignment, giving a
// deterministic report. A descending for loop in always_comb is
// a priority encoder; it is unrolled at elaboration and infers
// no storage, because every output is assigned on every path.
always_comb begin
blocking_constraint = '0;
blocking_valid = 1'b0;
for (int unsigned i = CONSTRAINTS; i > 0; i--) begin
if (!deadline_met[i-1]) begin
blocking_constraint = CST_W'(i-1);
blocking_valid = 1'b1;
end
end
end
endmoduleInterface contract. cand_valid qualifies everything: with no candidate the block reports no refusal and no issue. The three layer inputs are independently computed elsewhere — this block does not know how, and that independence is the point. issue_allowed is a pure function with no registered delay, so a consumer that registers it sees the decision one cycle later; that is the consumer's convention to document, not this block's.
Parameter contract. CONSTRAINTS must be at least 1, enforced at elaboration. CST_W is derived and guarded: $clog2(1) is 0, and a zero-width port is illegal, so the ternary forces 1. At CONSTRAINTS == 1 the block still works — deadline_met is one bit, blocking_constraint is one bit and always 0, and blocking_valid simply mirrors the inverted obligation.
Why the cast is safe. CST_W'(i-1) narrows a 32-bit loop variable. The loop bounds i to the range 1 through CONSTRAINTS, so i-1 lies in 0 through CONSTRAINTS-1, and CST_W is by construction wide enough for CONSTRAINTS-1. There is a whole class of bug here that this module's RTL has to avoid repeatedly: a cast to $clog2(N) bits cannot hold the value N when N is a power of two. This cast holds N-1, not N, which is exactly the boundary that makes it safe. §9's fourth property checks it anyway.
Synthesis implications. A reduction AND, a four-way priority mux and a priority encoder over CONSTRAINTS bits. The encoder is the only part that grows, and it grows logarithmically in depth. Nothing here is on a critical path worth worrying about at the scale a real constraint set reaches.
Corner cases. No candidate: everything reports inactive, including blocking_valid, even if obligations are outstanding — the block reports refusals of candidates, and there is no candidate. All three layers failing: reject_layer reports 1, the most fundamental. A malformed command whose obligations are also outstanding: same answer, and timing_ok still publishes the truth about layer 3 independently, which is what lets §11's procedure ask “was timing even consulted?”
Failure modes to expect if you build this wrong. Gating reject_layer on issue_allowed instead of cand_valid reports layer 0 for every refused command — the report becomes useless exactly when you need it. Scanning the obligation vector upward makes blocking_constraint report the highest outstanding index, which is still deterministic but disagrees with the documented convention, and a checker written against the convention will fire. Omitting the cand_valid term from issue_allowed issues commands that were never requested.
8. The Decision, in Cycles
A trace of the case §6 called the important one: a candidate that is semantically and state legal throughout, refused only by timing, and issued on the first cycle that its last obligation clears.
command_legality_layers — refused by timing alone
10 cyclesThree things in this trace repay attention.
The refusal at cycles 3 through 5 is invisible to the first two layers. semantic_ok and state_ok are both high the whole time. A controller instrumented only on those two signals sees a perfectly legal command and no reason for the stall.
deadline_met filling in progressively is the normal case, not a special one. Obligations on a single candidate come from different triggering events at different times, so they discharge at different times. The intermediate value 011 — two of three met — is exactly the state in which a careless design concludes that timing is satisfied.
issue_allowed is high for one cycle only, because cand_valid drops after the issue. The block has no memory of having issued; the disappearance of the candidate is what ends the decision.
9. Four Assertions Worth Writing
Named by intent, with the bug each catches stated plainly.
Where these live. command_legality_layers is combinational and has no clk or rst_n port. A concurrent property needs a sampling event, so these belong in a testbench or a bind unit, sampled on the surrounding environment's clock — not inside the module. Chapter 13.2 §9 develops what that costs: the properties check once per sampling edge rather than on every input change, which is acceptable here because the decision is consumed on an edge anyway.
// ── Sampled on the ENVIRONMENT's clock: this block has none of its
// own. Place these in a testbench or bind unit, not in the module.
//
// ── P1. A command is never issued with any layer unsatisfied.
// THE bug this catches: an issue term that omits a layer. The most
// consequential version omits timing_ok, which produces a design
// that is functionally correct in simulation against a model with
// no timing checks and violates the device's contract on silicon.
property p_issue_requires_all_layers;
@(posedge clk) disable iff (!rst_n)
issue_allowed |-> (cand_valid && semantic_ok && state_ok && timing_ok);
endproperty
a_issue_requires_all_layers: assert property (p_issue_requires_all_layers);
// ── P2. Timing is satisfied only when EVERY obligation is discharged.
// Catches the single most likely arithmetic error in the whole
// module: a reduction OR where a reduction AND belongs, which makes
// the candidate legal as soon as ANY obligation clears. Note this is
// an equivalence, not an implication -- the converse half catches a
// design that refuses a candidate whose obligations are all met,
// which over-serialises silently and shows up only as lost
// bandwidth.
property p_timing_is_conjunction;
@(posedge clk) disable iff (!rst_n)
timing_ok == (deadline_met == '1);
endproperty
a_timing_is_conjunction: assert property (p_timing_is_conjunction);
// ── P3. The reported layer is the FIRST failing layer, not merely a
// failing one. Written as the three implications the priority
// convention actually promises, joined with the property operator
// `and` -- implications cannot be joined with && .
// Catches a report that names timing when the command was malformed,
// which sends a debugger to the timing state for a decode bug.
property p_reject_layer_is_first_failure;
@(posedge clk) disable iff (!rst_n)
( (cand_valid && !semantic_ok) |-> (reject_layer == 2'd1) )
and ( (cand_valid && semantic_ok && !state_ok) |-> (reject_layer == 2'd2) )
and ( (cand_valid && semantic_ok && state_ok && !timing_ok)
|-> (reject_layer == 2'd3) );
endproperty
a_reject_layer_is_first_failure: assert property (p_reject_layer_is_first_failure);
// ── P4. The reported blocking obligation is genuinely outstanding, and
// is reported exactly when one exists. Catches the truncating-cast
// bug class directly: if CST_W were too narrow, the reported index
// would alias onto a DIFFERENT obligation, and this property fires
// because the aliased index's bit is met.
property p_blocking_report_is_sound;
@(posedge clk) disable iff (!rst_n)
(blocking_valid == (deadline_met != '1))
and ( blocking_valid |-> !deadline_met[blocking_constraint] );
endproperty
a_blocking_report_is_sound: assert property (p_blocking_report_is_sound);What these prove. That the decision is the conjunction §2 claims it is, that the reduction is an AND, that the refusal report matches the documented priority convention, and that the blocking-obligation index is both sound and correctly bounded.
What these do not prove. Nothing about time. Not one of them checks that an obligation is outstanding for the right duration, because this block does not determine that — deadline_met is an input. A design in which every obligation is discharged one cycle early passes all four properties and violates the device's contract. That proof obligation belongs to Chapter 13.2's resolver and Chapter 13.3's scoreboard, and the fact that it cannot be discharged here is a reason to be suspicious of block-level assertion suites in general.
A vacuity note. P1 and the second half of P4 are implications, so they pass trivially in a simulation where the antecedent never occurs. A run in which issue_allowed never rises proves nothing about P1. Cover the antecedents — cover property (@(posedge clk) issue_allowed); and one for blocking_valid — or the suite reports success for a block that never ran.
10. Verification Perspective
The natural first testbench for this block is also the wrong one: drive the three layer inputs, check issue_allowed. That is a truth-table test, and since the block is a truth table, it passes immediately and finds nothing.
The interesting tests are about the report, because the report is the part with a convention and conventions are what get misread.
Enumerate exhaustively, since you can. With three obligations the input space is 2 × 2 × 2 × 8 = 64 cases plus the cand_valid axis. Enumerate all of it and compare against an independently written reference function — one that recomputes the priority ordering from the prose of §2 rather than from the RTL. If you translate the RTL into the reference you have proved only that you can copy.
Test the parameter extremes, not just the default. CONSTRAINTS == 1 collapses the obligation vector to a single bit and CST_W to its guarded minimum; this is where a $clog2 mistake surfaces. CONSTRAINTS == 4 makes CST_W exactly 2 with blocking_constraint reaching 3 — the power-of-two boundary where a truncating cast would alias. Run both.
Check the distinction §6 says is decision-critical. Construct the row-3 case (state illegal, timing legal) and the row-4 case (state legal, timing illegal) and assert that the reports differ. A design that reported a generic refusal would pass P1 and P2 and fail here, which is the whole argument for the report existing.
11. Debugging — State-Legal and Still Too Early
Symptom. A command sits in the controller for several cycles. The bank-state view shows the correct row open. Decode shows a well-formed command. Nothing looks wrong, and it is not issued.
This is §6's row 4, and it is the single most common confusing observation in DDR controller bring-up, because every debug view that exists shows a healthy system.
| Candidate mechanism | Evidence that supports it | Discriminator |
|---|---|---|
| An obligation is genuinely outstanding | timing_ok low, blocking_constraint names an index | Follow that index to its triggering event. Is the elapsed time short? Then the design is correct and this is just latency. |
| An obligation's magnitude is programmed too large | timing_ok low, elapsed time already generous | Compare the configured magnitude against the device's requirement. A mis-set configuration register looks exactly like a timing bug. |
| The obligation is attached to the wrong resource | Another bank is also stalled, with no shared cause | Chapter 13.3 §4's scope test. A bank-local obligation held globally stalls banks that have no obligation. |
| An off-by-one in the obligation's own logic | Stall is exactly one cycle longer than the magnitude, every time | Chapter 13.2 §6's convention check. One extra cycle everywhere is the signature. |
| The candidate is not actually being presented | cand_valid low; reject_layer reports 0 | Upstream problem. The command was never offered, so nothing refused it. |
The discriminator that resolves this fastest is the pair timing_ok and reject_layer. If reject_layer reports 3, timing refused and blocking_constraint names the obligation to investigate — a one-step path from symptom to cause. If it reports 0 while the command is visibly waiting, the command was never presented, and every cycle spent looking at timing state is wasted. That is the most valuable thing this block does, and it is worth noting that it costs a two-bit output.
Responsible layer. A refusal reported as layer 3 with a plausible elapsed time is not a bug at all — it is the device's contract being honoured, and the correct response is to check whether the scheduler could have had something else to do, which is Module 17's question and not this one's.
12. Common Misconceptions
“If the bank state is legal, the command is legal.” Tempting because Module 9 built a state model that answers “can I read this?” convincingly, and it does answer it — for the state layer. Why it is wrong: state describes the present condition; timing describes history, and no state model records when the state was entered. Consequence: a controller that issues on state alone violates the device contract in a way that functional simulation against an untimed model will not catch. Replacement model: state and timing are independent questions over different information. Debugging clue: failures appear only against a timing-checked model or on silicon, never in early functional simulation.
“DDR timing is just memorising tRCD, tRP and tRAS.” Tempting because those names appear on every datasheet and in every interview. Why it is wrong: the names are edge labels. Without the graph, three memorised numbers cannot answer when a command is legal, because the answer depends on which edges apply and what else has happened. Consequence: you can recite parameters and be unable to analyse a trace — a gap that shows up immediately in Chapter 13.4's worked example. Replacement model: learn the structure, then populate it in Module 14. Debugging clue: if you can name a parameter but not say what event triggers it and what resource owns it, you have the label without the edge.
“Timing constraints only matter for performance.” Tempting because stalls are visible as lost bandwidth, and timing conversations usually happen in performance reviews. Why it is wrong: violating a minimum separation is not slow, it is incorrect — the device's guarantees do not hold and returned data may be wrong. Consequence: the constraint gets treated as tunable, and a bring-up team “optimises” a parameter downward and produces intermittent data corruption that looks like a signal-integrity problem. Replacement model: timing legality is a correctness property; only the slack above the minimum is a performance question. Debugging clue: rare, data-dependent corruption that moves when frequency changes.
“The controller models how long the DRAM's internal circuitry takes.” Tempting because the obligations originate in physical processes, as §3 said. Why it is wrong: the controller holds a configured number and counts. It has no visibility into the array and needs none — the published obligation is precisely the abstraction that makes controllers portable across devices. Consequence: engineers look for an analog explanation of a digital off-by-one, or worse, write RTL that claims to model sensing. Replacement model: physics sets the magnitude; the controller counts. Chapter 13.2 is about the boundary between those sentences. Debugging clue: if a proposed explanation requires knowing what the array is doing right now, it is the wrong explanation — the controller does not know either.
“A command that is legal at cycle N is legal at N+1.” Tempting because obligations expire and never un-expire, so legality feels monotonic. Why it is wrong: §5's third consequence — issuing other commands creates new obligations, and one of them may constrain this candidate. Consequence: a design that computes legality once and holds the result issues an illegal command after an intervening issue on a shared resource. Replacement model: legality is evaluated at an instant, against current history. Debugging clue: the violated command is always preceded by a different command that the analysis did not account for.
“Semantic legality is a decode detail not worth a separate layer.” Tempting because a correct controller never generates a malformed command, so the layer looks like dead logic. Why it is wrong: the layer's value is diagnostic. When a malformed command does appear — from a misconfigured operand field or a bad address map — the report must say “meaningless” rather than “too early.” Consequence: a decode bug presents as a timing stall and the investigation starts in the wrong place, sometimes for days. Replacement model: the layer exists to route the investigation. Debugging clue: a refusal that never clears no matter how long you wait is not a timing refusal.
13. Interview Reasoning
“Can a DDR command be valid and still illegal? Explain.” Yes, and the answer should reach three levels. Architecture: commands start operations rather than performing them, and the command interface is faster than the array, so a correct command can arrive before the previous operation's obligations are discharged. Temporal dependency: the obligation is an edge from a prior triggering event to this command class on a particular resource, with a magnitude and a sense. RTL consequence: the issue term needs a third conjunct that neither decode nor the state model can supply. DV consequence: a functional model without timing checks will not catch its absence, so the timing layer needs its own independent checking — which is Chapter 13.4's subject.
“A read to an open bank with the right row is not issuing. Walk me through your debug.” Establish first which layer refused, because that decides everything after. If the report says timing, name the blocking obligation and find its triggering event; if elapsed time is already generous, suspect a programmed magnitude or a scope error rather than the obligation itself. If the report says state despite the bank view looking right, the controller's state model and your debug view disagree — and one of them is stale. If nothing refused, the command was never presented and the problem is upstream. Naming the three branches before investigating any of them is the answer; jumping straight to timing state is the common mistake.
“Why report which layer refused instead of a single ready bit?” Because state-illegal and timing-illegal demand opposite responses. One needs an action taken on the request's behalf — an activate; the other needs the absence of action, since the bank is already correct and an activate would close the row. Collapse them and the controller either hangs waiting for time to fix a state problem or thrashes issuing activates against a bank that needed only patience. §6's rows 3 and 4 are the concrete cases.
“Where does a timing obligation come from?” Two places, and distinguishing them is the substance of the answer. Some obligations arise because a physical process started by a command takes time to complete, and those are properties of the device. Others arise because a shared resource cannot carry two things at once, and those are properties of the interface and depend on what else the controller chose to issue. The second kind can be reduced by scheduling differently; the first cannot be reduced at all. Chapter 13.3 builds the taxonomy.
“Three obligations constrain a command and two are discharged. Is it legal?” No. Timing legality is a conjunction over every applicable obligation, so the candidate becomes legal when the last one clears, not the first. The wrong answer here is usually a reduction OR in disguise, and it is the exact bug §9's second property exists to catch.
14. Engineering Exercises
1. Classify five refusals. For each, name the layer and the remedy: (a) a READ whose column operand exceeds the device's column space; (b) a PRECHARGE of a bank that is already closed; (c) a READ to the correct open row, three cycles after the ACTIVATE, where the obligation's magnitude is four; (d) an ACTIVATE to a bank holding a different row; (e) a READ whose cand_valid is low.
Worked: (a) semantic — never legal, no remedy but fixing the operand; (b) state — legal after a transition, though here the transition is one nobody needs; (c) timing — legal at the next cycle with nothing changed; (d) state — this is a row conflict, legal after a precharge and re-activate, and note the remedy costs two commands; (e) none — nothing refused it, because nothing asked. The instructive pairing is (c) against (d): both involve an open bank, and one needs patience while the other needs two commands.
2. Write an obligation in five parts. Take the sentence “you have to wait before precharging after a write” and express it in §4's five-part form. Which parts does the sentence leave unspecified, and what would you have to consult to fill each one in? Then check your answer against Chapter 11.4, which owns this obligation — particularly the part about which event the separation is measured from, which the casual sentence gets wrong.
3. Find the reduction bug. A colleague's issue logic reads timing_ok = |deadline_met;. Describe the first symptom in simulation against a timing-checked model, explain why it may not appear immediately, and say which of §9's properties fires.
Worked: the candidate is issued as soon as any obligation is discharged, so violations occur only when obligations discharge at different times — with one obligation, or with several that happen to discharge together, behaviour is correct. That is why it may pass early testing: a simple trace has one edge. P2 fires immediately and unconditionally, because it is an equivalence over the present cycle and needs no particular traffic to expose the mismatch.
4. Reason about the report convention. blocking_constraint reports the lowest outstanding index. Construct a case where this report is true but unhelpful — the named obligation is outstanding yet is not the one that will clear last. What extra information would be needed to report the genuinely controlling obligation, and which chapter supplies it?
Worked: obligation 0 has one cycle remaining and obligation 2 has nine; the report names 0, which will clear almost immediately and is not what the command is waiting for. Reporting the controlling obligation requires knowing each one's remaining time, not merely whether it is met — a magnitude, not a bit. Chapter 13.3's scoreboard carries magnitudes and can therefore report the true maximum.
5. Test the power-of-two boundary. Set CONSTRAINTS to 4 and confirm CST_W is 2 and blocking_constraint can represent 3. Now suppose someone had written parameter int CST_W = $clog2(CONSTRAINTS + 1); instead. Is that wrong? Is it wasteful? Justify which sizing is correct for a vector index as opposed to a count.
Worked: it is not wrong, it is one bit wide for an index — $clog2(5) is 3, enough for 0 through 4, and the index never reaches 4. The distinction generalises and is worth internalising: an index into N items needs $clog2(N) bits, while a count of up to N items needs $clog2(N+1), because the count can equal N and the index cannot. Conflating them is the source of both truncating casts and wasted flops, and both errors appear in this module's later RTL if you are not deliberate.
6. Argue about cost. reject_layer and the blocking report cost logic and wires that a single ready bit would not. Make the strongest case against including them, then rebut it.
15. Summary
A DDR command must pass three independent tests. Semantic legality asks whether the command means anything and is answerable from the command alone. State legality asks whether the target's present condition permits it and is answerable from Module 9's state model. Timing legality asks whether enough time has passed since the events that constrain it, and is answerable only from history — which is why it needs its own machinery and its own chapter.
Timing obligations exist because commands start operations rather than performing them, and because shared resources carry one thing at a time. Each obligation has five parts: triggering event, constrained event, resource, magnitude and sense. Omit any one and the obligation cannot be reasoned about.
The obligations form a directed graph over event classes, not a table of independent facts. One candidate can be the target of several edges, so timing legality is a conjunction over every applicable obligation — the command becomes legal when the last clears, not the first. And because every issued command is itself a triggering event, issuing changes the graph, which is why legality must be re-evaluated rather than cached.
command_legality_layers separates the three questions and reports which one refused. It measures nothing, on purpose: the distinction between a refusal that needs an action and one that needs only patience is worth a two-bit output, and no timer is required to make it.
16. What Comes Next
This chapter established that an obligation has a magnitude and said nothing about where the magnitude comes from or what unit it is in. That gap is larger than it looks.
The physical processes of Modules 2 and 3 take time measured in absolute time — they know nothing about any clock. A controller counts cycles. Those are different quantities, the conversion between them depends on the clock frequency, and it does not divide evenly. Chapter 13.2 works out how a duration becomes an integer cycle obligation, why the same device needs more cycles for the same physical delay at a higher clock, and why specifications state some requirements two ways at once and take the greater.
Continue learning
Related tutorials
- Related topic
tRCD Question
Earliest legal time is a maximum over every applicable rule, so one parameter yields a lower bound. The same timing sentence has three readings differing by one cycle, and a conservative checker violates nothing while costing bandwidth.
- Related topic
Row Opening
ACTIVATE transfers no data. It changes a bank from closed to holding one row, and it does so over an interval rather than instantly — which is why a bank has transitional states and why a controller must track them.
- Related topic
Row Hits
A row hit is not “the row number matches”. It needs the same bank, the same row, and a bank that is actually usable — and getting any of the three wrong gives a hit rate that looks excellent and means nothing.
- Related topic
Device Physics Behind Timing
The array's processes take absolute time and know nothing about any clock. A controller counts cycles. Converting between them is where a physical duration becomes a digital obligation — and where the same device needs more cycles the faster you run it.
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.
